luatex-basics-gen.lua /size: 12 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['luat-basics-gen'] = {
2    version   = 1.100,
3    comment   = "companion to luatex-*.tex",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9if context then
10    os.exit()
11end
12
13-- We could load a few more of the general context libraries but it would
14-- not make plain / latex users more happy I guess. So, we stick to some
15-- placeholders.
16
17local match, gmatch, gsub, lower = string.match, string.gmatch, string.gsub, string.lower
18local formatters, split, format, dump = string.formatters, string.split, string.format, string.dump
19local loadfile, type = loadfile, type
20local setmetatable, getmetatable, collectgarbage = setmetatable, getmetatable, collectgarbage
21local floor = math.floor
22
23local dummyfunction = function()
24end
25
26local dummyreporter = function(c)
27    return function(f,...)
28        local r = texio.reporter or texio.write_nl
29        if f then
30            r(c .. " : " .. (formatters or format)(f,...))
31        else
32            r("")
33        end
34    end
35end
36
37local dummyreport = function(c,f,...)
38    local r = texio.reporter or texio.write_nl
39    if f then
40        r(c .. " : " .. (formatters or format)(f,...))
41    else
42        r("")
43    end
44end
45
46statistics = {
47    register      = dummyfunction,
48    starttiming   = dummyfunction,
49    stoptiming    = dummyfunction,
50    elapsedtime   = nil,
51}
52
53directives = {
54    register      = dummyfunction,
55    enable        = dummyfunction,
56    disable       = dummyfunction,
57}
58
59trackers = {
60    register      = dummyfunction,
61    enable        = dummyfunction,
62    disable       = dummyfunction,
63}
64
65experiments = {
66    register      = dummyfunction,
67    enable        = dummyfunction,
68    disable       = dummyfunction,
69}
70
71storage = { -- probably no longer needed
72    register      = dummyfunction,
73    shared        = { },
74}
75
76logs = {
77    new           = dummyreporter,
78    reporter      = dummyreporter,
79    messenger     = dummyreporter,
80    report        = dummyreport,
81}
82
83callbacks = {
84    register = function(n,f)
85        return callback.register(n,f)
86    end,
87}
88
89utilities = utilities or { }
90
91utilities.storage = utilities.storage or {
92    allocate = function(t)
93        return t or { }
94    end,
95    mark     = function(t)
96        return t or { }
97    end,
98}
99
100utilities.parsers = utilities.parsers or {
101    -- these are less flexible than in context but ok
102    -- for generic purpose
103    settings_to_array = function(s)
104        return split(s,",")
105    end,
106    settings_to_hash  = function(s)
107        local t = { }
108        for k, v in gmatch((gsub(s,"^{(.*)}$", "%1")),"([^%s,=]+)=([^%s,]+)") do
109            t[k] = v
110        end
111        return t
112    end,
113    settings_to_hash_colon_too  = function(s)
114        local t = { }
115        for k, v in gmatch((gsub(s,"^{(.*)}$", "%1")),"([^%s,=:]+)[=:]([^%s,]+)") do
116            t[k] = v
117        end
118        return t
119    end,
120}
121
122characters = characters or {
123    data = { }
124}
125
126-- we need to cheat a bit here
127
128texconfig.kpse_init = true
129
130resolvers = resolvers or { } -- no fancy file helpers used
131
132local remapper = {
133    otf    = "opentype fonts",
134    ttf    = "truetype fonts",
135    ttc    = "truetype fonts",
136    cid    = "cid maps",
137    cidmap = "cid maps",
138 -- fea    = "font feature files", -- no longer supported
139    pfb    = "type1 fonts",        -- needed for vector loading
140    afm    = "afm",
141    enc    = "enc files",
142    lua    = "tex",
143}
144
145function resolvers.findfile(name,fileformat)
146    name = gsub(name,"\\","/")
147    if not fileformat or fileformat == "" then
148        fileformat = file.suffix(name)
149        if fileformat == "" then
150            fileformat = "tex"
151        end
152    end
153    fileformat = lower(fileformat)
154    fileformat = remapper[fileformat] or fileformat
155    local found = kpse.find_file(name,fileformat)
156    if not found or found == "" then
157        found = kpse.find_file(name,"other text files")
158    end
159    return found
160end
161
162resolvers.findbinfile = resolvers.findfile
163
164function resolvers.loadbinfile(filename,filetype)
165    local data = io.loaddata(filename)
166    return true, data, #data
167end
168
169function resolvers.resolve(s)
170    return s
171end
172
173function resolvers.unresolve(s)
174    return s
175end
176
177-- Caches ... I will make a real stupid version some day when I'm in the
178-- mood. After all, the generic code does not need the more advanced
179-- ConTeXt features. Cached data is not shared between ConTeXt and other
180-- usage as I don't want any dependency at all. Also, ConTeXt might have
181-- different needs and tricks added.
182
183--~ containers.usecache = true
184
185caches = { }
186
187local writable  = nil
188local readables = { }
189local usingjit  = jit
190
191if not caches.namespace or caches.namespace == "" or caches.namespace == "context" then
192    caches.namespace = 'generic'
193end
194
195do
196
197    -- standard context tree setup
198
199    local cachepaths = kpse.expand_var('$TEXMFCACHE') or ""
200
201    -- quite like tex live or so (the weird $TEXMFCACHE test seems to be needed on miktex)
202
203    if cachepaths == "" or cachepaths == "$TEXMFCACHE" then
204        cachepaths = kpse.expand_var('$TEXMFVAR') or ""
205    end
206
207    -- this also happened to be used (the weird $TEXMFVAR test seems to be needed on miktex)
208
209    if cachepaths == "" or cachepaths == "$TEXMFVAR" then
210        cachepaths = kpse.expand_var('$VARTEXMF') or ""
211    end
212
213    -- and this is a last resort (hm, we could use TEMP or TEMPDIR)
214
215    if cachepaths == "" then
216        local fallbacks = { "TMPDIR", "TEMPDIR", "TMP", "TEMP", "HOME", "HOMEPATH" }
217        for i=1,#fallbacks do
218            cachepaths = os.getenv(fallbacks[i]) or ""
219            if cachepath ~= "" and lfs.isdir(cachepath) then
220                break
221            end
222        end
223    end
224
225    if cachepaths == "" then
226        cachepaths = "."
227    end
228
229    cachepaths = split(cachepaths,os.type == "windows" and ";" or ":")
230
231    for i=1,#cachepaths do
232        local cachepath = cachepaths[i]
233        if not lfs.isdir(cachepath) then
234            lfs.mkdirs(cachepath) -- needed for texlive and latex
235            if lfs.isdir(cachepath) then
236                logs.report("system","creating cache path '%s'",cachepath)
237            end
238        end
239        if file.is_writable(cachepath) then
240            writable = file.join(cachepath,"luatex-cache")
241            lfs.mkdir(writable)
242            writable = file.join(writable,caches.namespace)
243            lfs.mkdir(writable)
244            break
245        end
246    end
247
248    for i=1,#cachepaths do
249        if file.is_readable(cachepaths[i]) then
250            readables[#readables+1] = file.join(cachepaths[i],"luatex-cache",caches.namespace)
251        end
252    end
253
254    if not writable then
255        logs.report("system","no writeable cache path, quiting")
256        os.exit()
257    elseif #readables == 0 then
258        logs.report("system","no readable cache path, quiting")
259        os.exit()
260    elseif #readables == 1 and readables[1] == writable then
261        logs.report("system","using cache '%s'",writable)
262    else
263        logs.report("system","using write cache '%s'",writable)
264        logs.report("system","using read cache '%s'",table.concat(readables," "))
265    end
266
267end
268
269function caches.getwritablepath(category,subcategory)
270    local path = file.join(writable,category)
271    lfs.mkdir(path)
272    path = file.join(path,subcategory)
273    lfs.mkdir(path)
274    return path
275end
276
277function caches.getreadablepaths(category,subcategory)
278    local t = { }
279    for i=1,#readables do
280        t[i] = file.join(readables[i],category,subcategory)
281    end
282    return t
283end
284
285local function makefullname(path,name)
286    if path and path ~= "" then
287        return file.addsuffix(file.join(path,name),"lua"), file.addsuffix(file.join(path,name),usingjit and "lub" or "luc")
288    end
289end
290
291function caches.is_writable(path,name)
292    local fullname = makefullname(path,name)
293    return fullname and file.is_writable(fullname)
294end
295
296function caches.loaddata(readables,name,writable)
297    for i=1,#readables do
298        local path   = readables[i]
299        local loader = false
300        local luaname, lucname = makefullname(path,name)
301        if lfs.isfile(lucname) then
302            logs.report("system","loading luc file '%s'",lucname)
303            loader = loadfile(lucname)
304        end
305        if not loader and lfs.isfile(luaname) then
306            -- can be different paths when we read a file database from disk
307            local luacrap, lucname = makefullname(writable,name)
308            logs.report("system","compiling luc file '%s'",lucname)
309            if lfs.isfile(lucname) then
310                loader = loadfile(lucname)
311            end
312            caches.compile(data,luaname,lucname)
313            if lfs.isfile(lucname) then
314                logs.report("system","loading luc file '%s'",lucname)
315                loader = loadfile(lucname)
316            else
317                logs.report("system","error in loading luc file '%s'",lucname)
318            end
319            if not loader then
320                logs.report("system","loading lua file '%s'",luaname)
321                loader = loadfile(luaname)
322            else
323                logs.report("system","error in loading lua file '%s'",luaname)
324            end
325        end
326        if loader then
327            loader = loader()
328            collectgarbage("step")
329            return loader
330        end
331    end
332    return false
333end
334
335function caches.savedata(path,name,data)
336    local luaname, lucname = makefullname(path,name)
337    if luaname then
338        logs.report("system","saving lua file '%s'",luaname)
339        table.tofile(luaname,data,true)
340        if lucname and type(caches.compile) == "function" then
341            os.remove(lucname) -- better be safe
342            logs.report("system","saving luc file '%s'",lucname)
343            caches.compile(data,luaname,lucname)
344        end
345    end
346end
347
348-- The method here is slightly different from the one we have in context. We
349-- also use different suffixes as we don't want any clashes (sharing cache
350-- files is not that handy as context moves on faster.)
351
352function caches.compile(data,luaname,lucname)
353    local d = io.loaddata(luaname)
354    if not d or d == "" then
355        d = table.serialize(data,true) -- slow
356    end
357    if d and d ~= "" then
358        local f = io.open(lucname,'wb')
359        if f then
360            local s = loadstring(d)
361            if s then
362                f:write(dump(s,true))
363            end
364            f:close()
365        end
366    end
367end
368
369-- simplfied version:
370
371function table.setmetatableindex(t,f)
372    if type(t) ~= "table" then
373        f, t = t, { }
374    end
375    local m = getmetatable(t)
376    if f == "table" then
377        f = function(t,k) local v = { } t[k] = v return v end
378    end
379    if m then
380        m.__index = f
381    else
382        setmetatable(t,{ __index = f })
383    end
384    return t
385end
386
387function table.makeweak(t)
388    local m = getmetatable(t)
389    if m then
390        m.__mode = "v"
391    else
392        setmetatable(t,{ __mode = "v" })
393    end
394    return t
395end
396
397-- helper for plain:
398
399arguments = { }
400
401if arg then
402    for i=1,#arg do
403        local k, v = match(arg[i],"^%-%-([^=]+)=?(.-)$")
404        if k and v then
405            arguments[k] = v
406        end
407    end
408end
409
410-- another one
411
412if not number.idiv then
413    function number.idiv(i,d)
414        return floor(i/d) -- i//d in 5.3
415    end
416end
417
418-- hook into unicode
419
420local u = unicode and unicode.utf8
421
422if u then
423
424    utf.lower = u.lower
425    utf.upper = u.upper
426    utf.char  = u.char
427    utf.byte  = u.byte
428    utf.len   = u.len
429
430    -- needed on font-*
431
432    if lpeg.setutfcasers then
433        lpeg.setutfcasers(u.lower,u.upper)
434    end
435
436    -- needed on font-otr
437
438    local bytepairs = string.bytepairs
439    local utfchar   = utf.char
440    local concat    = table.concat
441
442    function utf.utf16_to_utf8_be(s)
443        if not s then
444            return nil
445        elseif s == "" then
446            return ""
447        end
448        local result, r, more = { }, 0, 0
449        for left, right in bytepairs(s) do
450            if right then
451                local now = 256*left + right
452                if more > 0 then
453                    now = (more-0xD800)*0x400 + (now-0xDC00) + 0x10000
454                    more = 0
455                    r = r + 1
456                    result[r] = utfchar(now)
457                elseif now >= 0xD800 and now <= 0xDBFF then
458                    more = now
459                else
460                    r = r + 1
461                    result[r] = utfchar(now)
462                end
463            end
464        end
465        return concat(result)
466    end
467
468    local characters = string.utfcharacters
469
470    function utf.split(str)
471        local t, n = { }, 0
472        for s in characters(str) do
473            n = n + 1
474            t[n] = s
475        end
476        return t
477    end
478
479end
480