cldf-ini.lua /size: 54 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['cldf-ini'] = {
2    version   = 1.001,
3    comment   = "companion to cldf-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- todo: {token} -> 3 tokens
10
11-- This started as an experiment: generating context code at the lua end. After all
12-- it is surprisingly simple to implement due to metatables. I was wondering if
13-- there was a more natural way to deal with commands at the lua end. Of course it's
14-- a bit slower but often more readable when mixed with lua code. It can also be handy
15-- when generating documents from databases or when constructing large tables or so.
16--
17-- In cldf-tod.lua I have some code that might end up here. In cldf-old.lua the
18-- code that precedes more modern solutions (made possible by features in the engine),
19-- most noticeably function handling, which worked well for a decade, but by now the
20-- more efficient approach is stable enough to move the original code to the obsolete
21-- module.
22--
23-- maybe:  (escape) or 0x2061 (apply function) or 0x2394 (software function ⎔) (old)
24-- note : tex.print == line with endlinechar appended
25--
26-- During the crited project we ran into the situation that luajittex was 10-20 times
27-- slower that luatex ... after 3 days of testing and probing we finally figured out that
28-- the the differences between the lua and luajit hashers can lead to quite a slowdown
29-- in some cases.
30--
31-- context(lpeg.match(lpeg.patterns.texescape,"${}"))
32-- context(string.formatters["%!tex!"]("${}"))
33-- context("%!tex!","${}")
34--
35-- We try not to polute the context namespace too much. For that reason the commands are
36-- registered in the context.core namespace. You can call all context commands using
37-- context.foo etc. and pipe to context with context("foo"). Defining a local command
38-- foo being context.foo is okay, as is context.core.foo. We will have some definitions
39-- that are plugged into the core namespace (mostly for speedup) but otherwise specialized
40-- writers are in the context namespace only. In your documents you can best use the
41-- context.foo(...) and context(...) variant but in some core modules we use the faster
42-- ones in critical places (no one will notice of course). The standard syntax highlighter
43-- that I use knows how to visualize ctx related code.
44
45-- I cleaned this code up a bit when updating the cld manual but got distracted a bit by
46-- watching Trio Hiromi Uehara, Anthony Jackson & Simon Phillips (discovered via the
47-- later on YT). Hopefully the video breaks made the following better in the end.
48
49-- This module is also a test bed for experimental features so the content changes over
50-- time (for the better or worse). There have been no fundamental changes for many years
51-- and performance has not changed much either.
52
53-- The code here has evolved over time, and was already present in the early versions of
54-- mkiv. However, it got adapted when feature were added to luatex, like the ability to
55-- "print" nodes and later tokens. We use to have control over the method used (if only
56-- for testing) but the older code has been removed now. The history is in the archives.
57-- These were the controls:
58
59-- local tokenflushmode    = true
60-- local nodeflushmode     = true
61-- local scannerdefmode    = true
62-- local maxflushnodeindex = 0x10FFFF - 1
63
64-- In earlier experiments a function tables was referred to as lua.calls and the primitive
65-- \luafunctions was \luacall and we used our own implementation of a function table (more
66-- indirectness).
67
68local format, stripstring = string.format, string.strip
69local next, type, tostring, tonumber, unpack, select, rawset = next, type, tostring, tonumber, unpack, select, rawset
70local insert, remove, concat = table.insert, table.remove, table.concat
71local lpegmatch, lpegC, lpegS, lpegP, lpegV, lpegCc, lpegCs, patterns = lpeg.match, lpeg.C, lpeg.S, lpeg.P, lpeg.V, lpeg.Cc, lpeg.Cs, lpeg.patterns
72
73local formatters           = string.formatters -- using formatters is slower in this case
74local setmetatableindex    = table.setmetatableindex
75local setmetatablecall     = table.setmetatablecall
76local setmetatablenewindex = table.setmetatablenewindex
77
78context                 = context    or { }
79commands                = commands   or { }
80interfaces              = interfaces or { }
81
82local context           = context
83local commands          = commands
84local interfaces        = interfaces
85
86local loaddata          = io.loaddata
87
88local tex               = tex
89local texsprint         = tex.sprint    -- just appended (no space,eol treatment)
90local texprint          = tex.print     -- each arg a separate line (not last in directlua)
91----- texwrite          = tex.write     -- all 'space' and 'character'
92
93-- In this stage we don't yet have nodes populated so we access the library directly ...
94
95local isnode            = node.isnode   or node.is_node
96local copynodelist      = node.copylist or node.copy_list
97local writenode         = node.write
98local tonut             = node.direct.todirect
99local tonode            = node.direct.tonode
100
101local newtoken          = token.new
102local createtoken       = token.create
103
104local istoken           = token.istoken or token.is_token
105local setluatoken       = token.setlua  or token.set_lua
106
107-- ... till here.
108
109local isprintable       = tex.isprintable or function(n)
110    return n and (type(n) == "string" or isnode(n) or istoken(n))
111end
112
113if tex.runlocal then
114    -- obselete names
115    tex.runtoks  = tex.runlocal
116    tex.quittoks = tex.quitlocal
117else
118    tex.runlocal  = tex.runtoks
119    tex.quitlocal = tex.quittoks
120end
121
122local catcodenumbers    = catcodes.numbers
123
124local ctxcatcodes       = catcodenumbers.ctxcatcodes
125local prtcatcodes       = catcodenumbers.prtcatcodes
126local texcatcodes       = catcodenumbers.texcatcodes
127local txtcatcodes       = catcodenumbers.txtcatcodes
128local vrbcatcodes       = catcodenumbers.vrbcatcodes
129local xmlcatcodes       = catcodenumbers.xmlcatcodes
130
131local flush             = texsprint   -- snippets
132local flushdirect       = texprint    -- lines
133----- flushraw          = texwrite
134
135local report_context    = logs.reporter("cld","tex")
136local report_cld        = logs.reporter("cld","stack")
137----- report_template   = logs.reporter("cld","template")
138
139local processlines      = true -- experiments.register("context.processlines", function(v) processlines = v end)
140
141local trialtypesetting  = function() return false end
142
143function context.settrialtypesettingmethod(f)
144    trialtypesetting         = f
145    context.trialtypesetting = f
146end
147
148context.trialtypesetting = function() return trialtypesetting() end -- can be aliased !
149
150local knownfunctions = (lua.getfunctionstable or lua.get_functions_table)(true)
151local showstackusage = false
152
153trackers.register("context.stack",function(v) showstackusage = v end)
154
155local freed    = { }
156local nofused  = 0
157local noffreed = 0
158
159local usedstack = function()
160    return nofused, noffreed
161end
162
163local flushfunction = function(slot,arg)
164    if arg() or trialtypesetting() then
165        -- keep
166    else
167        noffreed = noffreed + 1
168        freed[noffreed] = slot
169        knownfunctions[slot] = false
170    end
171end
172
173local storefunction = function(arg)
174    local f = function(slot) flushfunction(slot,arg) end
175    if noffreed > 0 then
176        local n = freed[noffreed]
177        freed[noffreed] = nil
178        noffreed = noffreed - 1
179        knownfunctions[n] = f
180        return n
181    else
182        nofused = nofused + 1
183        knownfunctions[nofused] = f
184        return nofused
185    end
186end
187
188local flushnode = function(slot,arg)
189    if trialtypesetting() then
190        writenode(copynodelist(arg))
191    else
192        writenode(arg)
193        noffreed = noffreed + 1
194        freed[noffreed] = slot
195        knownfunctions[slot] = false
196    end
197end
198
199local storenode = function(arg)
200    local f = function(slot) flushnode(slot,arg) end
201    if noffreed > 0 then
202        local n = freed[noffreed]
203        freed[noffreed] = nil
204        noffreed = noffreed - 1
205        knownfunctions[n] = f
206        return n
207    else
208        nofused = nofused + 1
209        knownfunctions[nofused] = f
210        return nofused
211    end
212end
213
214storage.storedfunctions = storage.storedfunctions or { }
215local storedfunctions   = storage.storedfunctions
216local initex            = environment.initex
217
218storage.register("storage/storedfunctions", storedfunctions, "storage.storedfunctions")
219
220local f_resolve = nil
221local p_resolve = ((1-lpegP("."))^1 / function(s) f_resolve = f_resolve[s] end * lpegP(".")^0)^1
222
223local function resolvestoredfunction(str)
224    if type(str) == "string" then
225        f_resolve = global -- namespace
226        lpegmatch(p_resolve,str)
227        return f_resolve
228    else
229        return str
230    end
231end
232
233local function expose(slot,f,...) -- so we can register yet undefined functions
234    local func = resolvestoredfunction(f)
235    if not func then
236        func = function() report_cld("beware: unknown function %i called: %s",slot,f) end
237    end
238    knownfunctions[slot] = func
239    return func(...)
240end
241
242if initex then
243    -- todo: log stored functions
244else
245    local slots = table.sortedkeys(storedfunctions)
246    local last  = #slots
247    if last > 0 then
248        -- we restore the references
249        for i=1,last do
250            local slot = slots[i]
251            local data = storedfunctions[slot]
252            knownfunctions[slot] = function(...)
253                -- print(data) -- could be trace
254                return expose(slot,data,...)
255            end
256        end
257        -- we now know how many are defined
258        nofused = slots[last]
259        -- normally there are no holes in the list yet
260        for i=1,nofused do
261            if not knownfunctions[i] then
262                noffreed = noffreed + 1
263                freed[noffreed] = i
264            end
265        end
266     -- report_cld("%s registered functions, %s freed slots",last,noffreed)
267    end
268end
269
270local registerfunction = function(f,direct,slot) -- either f=code or f=namespace,direct=name
271    local func
272    if slot then
273        -- already used
274    elseif noffreed > 0 then
275        slot = freed[noffreed]
276        freed[noffreed] = nil
277        noffreed = noffreed - 1
278    else
279        nofused = nofused + 1
280        slot = nofused
281    end
282    if direct then
283        if initex then
284            func = function(...) expose(slot,f,...) end
285            storedfunctions[slot] = f
286        else
287            func = resolvestoredfunction(f)
288        end
289        if type(func) ~= "function" then
290            func = function() report_cld("invalid resolve %A, case %s",f,1) end
291        end
292    elseif type(f) == "string" then
293        func = loadstring(f)
294        if type(func) ~= "function" then
295            func = function() report_cld("invalid code %A, case %s",f,2) end
296        end
297    elseif type(f) == "function" then
298        func = f
299    else
300        func = function() report_cld("invalid function %A, case %s",f,3) end
301    end
302    knownfunctions[slot] = func
303    return slot
304end
305
306local unregisterfunction = function(slot)
307    if knownfunctions[slot] then
308        noffreed = noffreed + 1
309        freed[noffreed] = slot
310        knownfunctions[slot] = false
311    else
312        report_cld("invalid function slot %A",slot)
313    end
314end
315
316local reservefunction = function()
317    if noffreed > 0 then
318        local n = freed[noffreed]
319        freed[noffreed] = nil
320        noffreed = noffreed - 1
321        return n
322    else
323        nofused = nofused + 1
324        return nofused
325    end
326end
327
328local callfunctiononce = function(slot)
329    knownfunctions[slot](slot)
330    noffreed = noffreed + 1
331    freed[noffreed] = slot
332    knownfunctions[slot] = false
333end
334
335setmetatablecall(knownfunctions,function(t,n) return knownfunctions[n](n) end)
336
337-- some protection
338
339do
340
341    local stub    = { }
342    local done    = false
343    local message = function()
344        -- one time message
345        if not done then
346            report_cld("")
347            report_cld("use : slot = context.functions.register(f)")
348            report_cld("and : context.functions.unregister(slot)")
349            report_cld("")
350            done = true
351        end
352    end
353
354    setmetatable(stub, {
355        __index    = message,
356        __newindex = message,
357    })
358
359    function lua.getfunctionstable()
360        message()
361        return stub
362    end
363
364    lua.get_functions_table = lua.getfunctionstable
365
366end
367
368-- The next hack is a convenient way to define scanners at the Lua end and get them
369-- available at the TeX end. There is some dirty magic needed to prevent overload
370-- during format loading. Nowadays we prefer to use the slightly less efficient way
371-- of defining interfaces using the implementer. There is a little more overhead in
372-- defining as well as runtime overhead, but we accept that.
373
374-- interfaces.scanners.foo = function() context("[%s]",tokens.scanners.string()) end : \scan_foo
375
376local storedscanners    = interfaces.storedscanners or { }
377local namesofscanners   = interfaces.namesofscanners or { }
378local interfacescanners = { }
379local privatenamespace  = "clf_"
380
381interfaces.storedscanners  = storedscanners
382interfaces.namesofscanners = namesofscanners
383
384storage.register("interfaces/storedscanners", storedscanners, "interfaces.storedscanners")
385
386-- local function registerscanner(name,action,protected,public,usage) -- todo: combine value and condition
387--     rawset(interfacescanners,name,action)
388--     local n = storedscanners[name]
389--     n = registerfunction("interfaces.scanners."..name,true,n)
390--     storedscanners[name] = n
391--     namesofscanners[n] = name
392--     name = public and name or (privatenamespace .. name)
393--  -- print(">>",name,protected and "protected" or "",usage or "macro")
394--     setluatoken(name,n,"global",protected and "protected" or "",usage or "macro")
395-- end
396
397-- todo: bitmap
398
399local registerscanner if CONTEXTLMTXMODE > 0 then
400
401    -- always permanent but we can consider to obey permanent==false
402
403    -- todo: make bitset instead of keys (nil is skipped anyway)
404
405    local function toflags(specification)
406        local protected = specification.protected and "protected"
407        local untraced  = specification.untraced  and "untraced"
408        local usage     = specification.usage
409        if usage == "value" then
410            return "global", "value", "permanent", "untraced", protected
411        elseif usage == "condition" then
412            return "global", "conditional", "permanent", "untraced", protected
413        elseif specification.frozen then
414            return "global", "frozen", untraced, protected
415        elseif specification.permanent == false or specification.onlyonce then -- for now onlyonce here
416            return "global", untraced, protected
417        else
418            return "global", "permanent", untraced, protected
419        end
420    end
421
422    registerscanner = function(name,action,specification)
423        rawset(interfacescanners,name,action)
424        local n = registerfunction("interfaces.scanners."..name,true,storedscanners[name])
425        storedscanners[name] = n
426        namesofscanners[n] = name
427        name = specification.public and name or (privatenamespace .. name)
428     -- print(name,n,toflags(specification))
429        setluatoken(name,n,toflags(specification))
430    end
431
432else
433
434    registerscanner = function(name,action,specification)
435        rawset(interfacescanners,name,action)
436        local n = storedscanners[name]
437        n = registerfunction("interfaces.scanners."..name,true,n)
438        storedscanners[name] = n
439        namesofscanners[n] = name
440        name = specification.public and name or (privatenamespace .. name)
441        setluatoken(name,n,"global",specification.protected and "protected" or "")
442    end
443
444end
445
446interfaces.registerscanner = registerscanner
447
448function interfaces.knownscanner(name)
449    return interfacescanners[name]
450end
451
452function interfaces.nameofscanner(slot)
453    return namesofscanners[slot] or slot
454end
455
456if CONTEXTLMTXMODE > 0 then
457
458    callback.register("show_lua_call", function(what, slot)
459        local name = namesofscanners[slot]
460     -- return name and formatters["%s: \\%s, slot: %i"](what,name,slot) or ""
461        return name and formatters["%s \\%s"](what,name) or ""
462    end)
463
464end
465
466setmetatablenewindex(interfacescanners, function(t,k,v)
467    report_cld("don't register scanner %a directly",k)
468 -- registerscanner(k,v)
469end)
470
471interfaces.scanners = storage.mark(interfacescanners)
472
473context.functions = {
474    register   = function(qualifiedname) return registerfunction(qualifiedname) end, -- only one argument
475    unregister = unregisterfunction,
476    reserve    = reservefunction,
477    known      = knownfunctions,
478    callonce   = callfunctiononce,
479}
480
481function commands.ctxfunction(code,namespace)
482    context(registerfunction(code,namespace))
483end
484
485function commands.ctxscanner(name,code,namespace)
486    local n = registerfunction(code,namespace)
487    if storedscanners[name] then
488        storedscanners[name] = n
489    end
490    context(n)
491end
492
493local function dummy() end
494
495function commands.ctxresetter(name) -- to be checked
496    return function()
497        if storedscanners[name] then
498            rawset(interfacescanners,name,dummy)
499            context.resetctxscanner(privatenamespace .. name)
500        end
501    end
502end
503
504-- Should we keep the catcodes with the function?
505
506local catcodestack    = { }
507local currentcatcodes = ctxcatcodes
508local contentcatcodes = ctxcatcodes
509
510local catcodes = {
511    ctx = ctxcatcodes, ctxcatcodes = ctxcatcodes, context  = ctxcatcodes,
512    prt = prtcatcodes, prtcatcodes = prtcatcodes, protect  = prtcatcodes,
513    tex = texcatcodes, texcatcodes = texcatcodes, plain    = texcatcodes,
514    txt = txtcatcodes, txtcatcodes = txtcatcodes, text     = txtcatcodes,
515    vrb = vrbcatcodes, vrbcatcodes = vrbcatcodes, verbatim = vrbcatcodes,
516    xml = xmlcatcodes, xmlcatcodes = xmlcatcodes,
517}
518
519-- maybe just increment / decrement
520
521-- local function pushcatcodes(c)
522--     insert(catcodestack,currentcatcodes)
523--     currentcatcodes = (c and catcodes[c] or tonumber(c)) or currentcatcodes
524--     contentcatcodes = currentcatcodes
525-- end
526--
527-- local function popcatcodes()
528--     currentcatcodes = remove(catcodestack) or currentcatcodes
529--     contentcatcodes = currentcatcodes
530-- end
531
532local catcodelevel = 0
533
534local function pushcatcodes(c)
535    catcodelevel = catcodelevel + 1
536    catcodestack[catcodelevel] = currentcatcodes
537    currentcatcodes = (c and catcodes[c] or tonumber(c)) or currentcatcodes
538    contentcatcodes = currentcatcodes
539end
540
541local function popcatcodes()
542    if catcodelevel > 0 then
543        currentcatcodes = catcodestack[catcodelevel] or currentcatcodes
544        catcodelevel = catcodelevel - 1
545    end
546    contentcatcodes = currentcatcodes
547end
548
549function context.unprotect()
550    -- at the lua end
551    catcodelevel = catcodelevel + 1
552    catcodestack[catcodelevel] = currentcatcodes
553    currentcatcodes = prtcatcodes
554    contentcatcodes = prtcatcodes
555    -- at the tex end
556    flush("\\unprotect")
557end
558
559function context.protect()
560    -- at the tex end
561    flush("\\protect")
562    -- at the lua end
563    if catcodelevel > 0 then
564        currentcatcodes = catcodestack[catcodelevel] or currentcatcodes
565        catcodelevel = catcodelevel - 1
566    end
567    contentcatcodes = currentcatcodes
568end
569
570context.catcodes     = catcodes
571context.pushcatcodes = pushcatcodes
572context.popcatcodes  = popcatcodes
573
574-- -- --
575
576local newline       = patterns.newline
577local space         = patterns.spacer
578local spacing       = newline * space^0
579local content       = lpegC((1-spacing)^1)            -- texsprint
580local emptyline     = space^0 * newline^2             -- texprint("")
581                    + newline * space^1 * newline^1
582local endofline     = space^0 * newline * space^0     -- texsprint(" ")
583local simpleline    = endofline * lpegP(-1)           --
584
585local verbose       = lpegC((1-space-newline)^1)
586local beginstripper = (lpegS(" \t")^1 * newline^1) / ""
587local endstripper   = beginstripper * lpegP(-1)
588
589local justaspace    = space * lpegCc("")
590local justanewline  = newline * lpegCc("")
591
592local function n_content   (s) flush      (contentcatcodes,s    ) end
593local function n_verbose   (s) flush      (vrbcatcodes,    s    ) end
594local function n_endofline ()  flush      (currentcatcodes," \r") end
595local function n_emptyline ()  flushdirect(currentcatcodes,"\r" ) end
596local function n_simpleline()  flush      (currentcatcodes," \r") end
597
598local n_exception = ""
599
600-- better a table specification
601
602function context.newtexthandler(specification)
603    specification = specification or { }
604    --
605    local s_catcodes   = specification.catcodes
606    --
607    local f_before     = specification.before
608    local f_after      = specification.after
609    --
610    local f_endofline  = specification.endofline  or n_endofline
611    local f_emptyline  = specification.emptyline  or n_emptyline
612    local f_simpleline = specification.simpleline or n_simpleline
613    local f_content    = specification.content    or n_content
614    local f_space      = specification.space
615    --
616    local p_exception  = specification.exception
617    --
618    if s_catcodes then
619        f_content = function(s)
620            flush(s_catcodes,s)
621        end
622    end
623    --
624    local pattern
625    if f_space then
626        if p_exception then
627            local content = lpegC((1-spacing-p_exception)^1)
628            pattern =
629              (
630                    justaspace   / f_space
631                  + justanewline / f_endofline
632                  + p_exception
633                  + content      / f_content
634                )^0
635        else
636            local content = lpegC((1-space-endofline)^1)
637            pattern =
638                (
639                    justaspace   / f_space
640                  + justanewline / f_endofline
641                  + content      / f_content
642                )^0
643        end
644    else
645        if p_exception then
646            local content = lpegC((1-spacing-p_exception)^1)
647            pattern =
648                simpleline / f_simpleline
649              +
650              (
651                    emptyline  / f_emptyline
652                  + endofline  / f_endofline
653                  + p_exception
654                  + content    / f_content
655                )^0
656        else
657            local content = lpegC((1-spacing)^1)
658            pattern =
659                simpleline / f_simpleline
660                +
661                (
662                    emptyline / f_emptyline
663                  + endofline / f_endofline
664                  + content   / f_content
665                )^0
666        end
667    end
668    --
669    if f_before then
670        pattern = (P(true) / f_before) * pattern
671    end
672    --
673    if f_after then
674        pattern = pattern * (P(true) / f_after)
675    end
676    --
677    return function(str) return lpegmatch(pattern,str) end, pattern
678end
679
680function context.newverbosehandler(specification) -- a special variant for e.g. cdata in lxml-tex
681    specification = specification or { }
682    --
683    local f_line    = specification.line    or function() flushdirect("\r") end
684    local f_space   = specification.space   or function() flush      (" ")  end
685    local f_content = specification.content or n_verbose
686    local f_before  = specification.before
687    local f_after   = specification.after
688    --
689    local pattern =
690        justanewline / f_line    -- so we get call{}
691      + verbose      / f_content
692      + justaspace   / f_space   -- so we get call{}
693    --
694    if specification.strip then
695        pattern = beginstripper^0 * (endstripper + pattern)^0
696    else
697        pattern = pattern^0
698    end
699    --
700    if f_before then
701        pattern = (lpegP(true) / f_before) * pattern
702    end
703    --
704    if f_after then
705        pattern = pattern * (lpegP(true) / f_after)
706    end
707    --
708    return function(str) return lpegmatch(pattern,str) end, pattern
709end
710
711local flushlines = context.newtexthandler {
712    content    = n_content,
713    endofline  = n_endofline,
714    emptyline  = n_emptyline,
715    simpleline = n_simpleline,
716}
717
718-- The next variant is only used in rare cases (buffer to mp):
719
720local printlines_ctx = (
721    (newline)     / function()  texprint("") end +
722    (1-newline)^1 / function(s) texprint(ctxcatcodes,s) end * newline^-1
723)^0
724
725local printlines_raw = (
726    (newline)     / function()  texprint("") end +
727    (1-newline)^1 / function(s) texprint(s)  end * newline^-1
728)^0
729
730function context.printlines(str,raw)     -- todo: see if via file is useable
731    if raw then
732        lpegmatch(printlines_raw,str)
733    else
734        lpegmatch(printlines_ctx,str)
735    end
736end
737
738function context.printtable(t,separator)     -- todo: see if via file is useable
739    if separator == nil or separator == true then
740        separator = "\r"
741    elseif separator == "" or separator == false then
742        separator = ""
743    end
744    local s = concat(t,separator)
745    if s ~= "" then
746        context(s)
747    end
748end
749
750-- -- -- "{" .. ti .. "}" is somewhat slower in a cld-mkiv run than "{",ti,"}"
751
752local containseol = patterns.containseol
753
754local lua_call_code = tokens.commands.lua_expandable_call or tokens.commands.lua_call
755
756local sortedhashindeed = false
757
758directives.register("context.sorthash",function(v)
759    sortedhashindeed = v and table.sortedhash or nil
760end)
761
762local function writer(parent,command,...) -- already optimized before call
763    if type(command) == "string" then -- for now
764        flush(currentcatcodes,command) -- todo: ctx|prt|texcatcodes
765    else
766        flush(command) -- todo: ctx|prt|texcatcodes
767    end
768    local direct = false
769    for i=1,select("#",...) do
770        local ti = select(i,...)
771        if direct then
772            local typ = type(ti)
773            if typ == "string" or typ == "number" then
774                flush(currentcatcodes,ti)
775            else -- node.write
776                report_context("error: invalid use of direct in %a, only strings and numbers can be flushed directly, not %a",command,typ)
777            end
778            direct = false
779        elseif ti == nil then
780            -- nothing
781        elseif ti == "" then
782            flush(currentcatcodes,"{}")
783     -- elseif ti == 1 then
784     --     flush(currentcatcodes,"{1}")
785        else
786            local typ = type(ti)
787            if typ == "string" then
788                -- is processlines seen ?
789                if processlines and lpegmatch(containseol,ti) then
790                    flush(currentcatcodes,"{")
791                    flushlines(ti)
792                    flush(currentcatcodes,"}")
793                elseif currentcatcodes == contentcatcodes then
794                    flush(currentcatcodes,"{",ti,"}")
795                else
796                    flush(currentcatcodes,"{")
797                    flush(contentcatcodes,ti)
798                    flush(currentcatcodes,"}")
799                end
800            elseif typ == "number" then
801                -- numbers never have funny catcodesz
802                flush(currentcatcodes,"{",ti,"}")
803            elseif typ == "table" then
804                local tn = #ti
805                if tn == 0 then
806                    local done = false
807                    if sortedhashindeed then
808                        for k, v in sortedhashindeed(ti) do
809                            if done then
810                                if v == "" then
811                                    flush(currentcatcodes,",",k,'=')
812                                else
813                                    flush(currentcatcodes,",",k,"={",v,"}")
814                                end
815                            else
816                                if v == "" then
817                                    flush(currentcatcodes,"[",k,"=")
818                                else
819                                    flush(currentcatcodes,"[",k,"={",v,"}")
820                                end
821                                done = true
822                            end
823                        end
824                    else
825                        for k, v in next, ti do
826                            if done then
827                                if v == "" then
828                                    flush(currentcatcodes,",",k,'=')
829                                else
830                                    flush(currentcatcodes,",",k,"={",v,"}")
831                                end
832                            else
833                                if v == "" then
834                                    flush(currentcatcodes,"[",k,"=")
835                                else
836                                    flush(currentcatcodes,"[",k,"={",v,"}")
837                                end
838                                done = true
839                            end
840                        end
841                    end
842                    if done then
843                        flush(currentcatcodes,"]")
844                    else
845                        flush(currentcatcodes,"[]")
846                    end
847                elseif tn == 1 then -- some 20% faster than the next loop
848                    local tj = ti[1]
849                    if type(tj) == "function" then
850                        tj = storefunction(tj)
851                        flush(currentcatcodes,"[",newtoken(tj,lua_call_code),"]")
852                    else
853                        flush(currentcatcodes,"[",tj,"]")
854                    end
855                else -- is concat really faster than flushes here? probably needed anyway (print artifacts)
856                    flush(currentcatcodes,"[")
857                    for j=1,tn do
858                        local tj = ti[j]
859                        if type(tj) == "function" then
860                            tj = storefunction(tj)
861                            flush(currentcatcodes,"[",newtoken(tj,lua_call_code),j == tn and "]" or ",")
862                        else
863                            if j == tn then
864                                flush(currentcatcodes,tj,"]")
865                            else
866                                flush(currentcatcodes,tj,",")
867                            end
868                        end
869                    end
870                end
871            elseif typ == "function" then
872                -- todo: ctx|prt|texcatcodes
873                ti = storefunction(ti)
874                flush(currentcatcodes,"{",newtoken(ti,lua_call_code),"}")
875            elseif typ == "boolean" then
876                if ti then
877                    flushdirect(currentcatcodes,"\r")
878                else
879                    direct = true
880                end
881            elseif typ == "thread" then
882                report_context("coroutines not supported as we cannot yield across boundaries")
883         -- elseif isnode(ti) or istoken(ti) then
884            elseif isprintable(ti) then
885                flush(currentcatcodes,"{",ti,"}")
886            else
887                local s = tostring(ti)
888                if s then
889                    flushdirect(currentcatcodes,s)
890                else
891                    report_context("error: %a gets a weird argument %a",command,ti)
892                end
893            end
894     -- else
895     --     local n = isnode(ti)
896     --     if n then
897     --         flush(ti)
898     --     else
899     --         report_context("error: %a gets a weird argument %a",command,ti)
900     --     end
901        end
902    end
903end
904
905local toks = tokens.cache
906context.tokenizedcs = toks
907
908local core = setmetatableindex(function(parent,k)
909    local t
910    local f = function(first,...)
911        if not t then
912            t = toks[k]
913        end
914        if first == nil then
915            flush(t)
916        else
917            return writer(context,t,first,...)
918        end
919    end
920    parent[k] = f
921    return f
922end)
923
924core.cs = setmetatableindex(function(parent,k)
925    local t
926    local f = function()
927        if not t then
928            t = toks[k]
929        end
930        flush(t)
931    end
932    parent[k] = f
933    return f
934end)
935
936local indexer = function(parent,k)
937    if type(k) == "string" then
938        return core[k]
939    else
940        return context -- catch
941    end
942end
943
944context.core = core
945
946-- only for internal usage:
947
948-- local prtindexer = nil
949--
950-- do
951--
952--     -- the only variant is not much faster than the full but it's more
953--     -- memory efficient
954--
955--     local protected     = { }
956--     local protectedcs   = { }
957--     context.protected   = protected
958--     context.protectedcs = protectedcs
959--
960--     local function fullindexer(t,k)
961--         local c = "\\" .. k -- tostring(k)
962--         local v = function(first,...)
963--             if first == nil then
964--                 flush(prtcatcodes,c)
965--             else
966--                 return prtwriter(c,first,...)
967--             end
968--         end
969--         rawset(t,k,v) -- protected namespace
970--         return v
971--     end
972--
973--     local function onlyindexer(t,k)
974--         local c = "\\" .. k -- tostring(k)
975--         local v = function()
976--             flush(prtcatcodes,c)
977--         end
978--         rawset(protected,k,v)
979--         rawset(t,k,v)
980--         return v
981--     end
982--
983--     protected.cs = setmetatableindex(function(parent,k)
984--         local c = "\\" .. k -- tostring(k)
985--         local v = function()
986--             flush(prtcatcodes,c)
987--         end
988--         parent[k] = v
989--         return v
990--     end
991--
992--     setmetatableindex(protected,fullindexer)
993--     setmetatablecall (protected,prtwriter)
994--
995--     setmetatableindex(protectedcs,onlyindexer)
996--     setmetatablecall (protectedcs,prtwriter)
997--
998-- end
999
1000-- local splitformatters = utilities.strings.formatters.new(true) -- not faster (yet)
1001
1002local caller = function(parent,f,a,...)
1003    if not parent then
1004        -- so we don't need to test in the calling (slower but often no issue)
1005    elseif f ~= nil then
1006        local typ = type(f)
1007        if typ == "string" then
1008            if f == "" then
1009                -- new, can save a bit sometimes
1010             -- if trace_context then
1011             --     report_context("empty argument to context()")
1012             -- end
1013            elseif a then
1014                flush(contentcatcodes,formatters[f](a,...)) -- was currentcatcodes
1015             -- flush(contentcatcodes,splitformatters[f](a,...)) -- was currentcatcodes
1016            elseif processlines and lpegmatch(containseol,f) then
1017                flushlines(f)
1018            else
1019                flush(contentcatcodes,f)
1020            end
1021        elseif typ == "number" then
1022            if a then
1023                flush(currentcatcodes,f,a,...)
1024            else
1025                flush(currentcatcodes,f)
1026            end
1027        elseif typ == "function" then
1028            -- ignored: a ...
1029            f = storefunction(f)
1030            flush(currentcatcodes,"{",newtoken(f,lua_call_code),"}")
1031        elseif typ == "boolean" then
1032            if f then
1033                if a ~= nil then
1034                    flushlines(a)
1035                else
1036                    flushdirect(currentcatcodes,"\n") -- no \r, else issues with \startlines ... use context.par() otherwise
1037                end
1038            else
1039                if a ~= nil then
1040                    -- no command, same as context(a,...)
1041                    writer(parent,"",a,...)
1042                else
1043                    -- ignored
1044                end
1045            end
1046        elseif typ == "thread" then
1047            report_context("coroutines not supported as we cannot yield across boundaries")
1048     -- elseif isnode(f) or istoken(f) then
1049        elseif isprintable(f) then
1050            flush(f)
1051        else
1052            local s = tostring(f)
1053            if s then
1054                flushdirect(currentcatcodes,s)
1055            else
1056                report_context("error: %a gets a weird argument %a","context",f)
1057            end
1058        end
1059 -- else
1060 --     local n = isnode(f)
1061 --     if n then
1062 --         flush(f)
1063 --     else
1064 --         report_context("error: %a gets a weird argument %a","context",f)
1065 --     end
1066    end
1067end
1068
1069context.nodes = { -- todo
1070    store = storenode,
1071    flush = function(n)
1072        flush(n)
1073    end,
1074}
1075
1076context.nuts = { -- todo
1077    store = function(n)
1078        return storenode(tonut(n))
1079    end,
1080    flush = function(n,d)
1081        flush(tonode(n))
1082    end,
1083}
1084
1085local defaultcaller = caller
1086
1087setmetatableindex(context,indexer)
1088setmetatablecall (context,caller)
1089
1090function  context.sprint(...) -- takes catcodes as first argument
1091    flush(...)
1092end
1093
1094function context.fprint(first,second,third,...)
1095    if type(first) == "number" then
1096        if third then
1097            flush(first,formatters[second](third,...))
1098        else
1099            flush(first,second)
1100        end
1101    else
1102        if second then
1103            flush(formatters[first](second,third,...))
1104        else
1105            flush(first)
1106        end
1107    end
1108end
1109
1110tex.fprint = context.fprint
1111
1112-- logging
1113
1114local trace_stack       = { report_context }
1115
1116local normalflush       = flush
1117local normalflushdirect = flushdirect
1118----- normalflushraw    = flushraw
1119local normalwriter      = writer
1120local currenttrace      = report_context
1121local nofwriters        = 0
1122local nofflushes        = 0
1123
1124local tracingpermitted  = true
1125
1126local visualizer = lpeg.replacer {
1127    { "\n", "<<newline>>" },
1128    { "\r", "<<par>>" },
1129}
1130
1131statistics.register("traced context", function()
1132    local used, freed = usedstack()
1133    local unreachable = used - freed
1134    if nofwriters > 0 or nofflushes > 0 then
1135        return format("writers: %s, flushes: %s, maxstack: %s",nofwriters,nofflushes,used,freed,unreachable)
1136    elseif showstackusage or unreachable > 0 then
1137        return format("maxstack: %s, freed: %s, unreachable: %s",used,freed,unreachable)
1138    end
1139end)
1140
1141-- The cmd names were synchronized with the normal call cmd names.
1142
1143local luacalls = {              --  luatex     luametatex
1144    lua_expandable_call = true, --  normal
1145    lua_call            = true, --  protected  normal
1146    lua_protected_call  = true, --             protected
1147}
1148
1149local function userdata(argument)
1150    if isnode(argument) then
1151        return formatters["<< %s node %i>>"](nodes.nodecodes[argument.id],tonut(argument))
1152    elseif istoken(argument) then
1153        local csname = argument.csname
1154        if csname then
1155         -- return formatters["<<\\%s>>"](csname)
1156            return formatters["\\%s"](csname)
1157        end
1158        if luacall[argument.cmdname] then
1159            return "<<function>>" -- argument.mode
1160        end
1161        return "<<token>>"
1162    else
1163        return "<<userdata>>"
1164    end
1165end
1166
1167
1168local tracedwriter = function(parent,...) -- also catcodes ?
1169    nofwriters = nofwriters + 1
1170    local savedflush       = flush
1171    local savedflushdirect = flushdirect -- unlikely to be used here
1172    local t = { "w : - : " }
1173    local n = 1
1174    local traced = function(catcodes,...) -- todo: check for catcodes
1175        local s = type(catcodes) == "number" and { ... } or { catcodes, ... }
1176        for i=1,#s do
1177            local argument = s[i]
1178            local argtype  = type(argument)
1179            if argtype == "string" then
1180                s[i] = lpegmatch(visualizer,argument)
1181            elseif argtype == "number" then
1182                s[i] = argument
1183            elseif argtype == "userdata" then
1184                s[i] = userdata(argument)
1185            else
1186                s[i] = formatters["<<%S>>"](argument)
1187            end
1188        end
1189        s = concat(s)
1190        s = lpegmatch(visualizer,s)
1191        n = n + 1
1192        t[n] = s
1193    end
1194    flush = function(...)
1195        normalflush(...)
1196        if tracingpermitted then
1197            traced(...)
1198        end
1199    end
1200    flushdirect = function(...)
1201        normalflushdirect(...)
1202        if tracingpermitted then
1203            traced(...)
1204        end
1205    end
1206    normalwriter(parent,...)
1207    flush       = savedflush
1208    flushdirect = savedflushdirect
1209    currenttrace(concat(t))
1210end
1211
1212-- we could reuse collapsed
1213
1214local traced = function(one,two,...)
1215    if two ~= nil then
1216        -- only catcodes if 'one' is number
1217        local catcodes  = type(one) == "number" and one
1218        local arguments = catcodes and { two, ... } or { one, two, ... }
1219        local collapsed = { formatters["f : %s : "](catcodes or '-') }
1220        local c         = 1
1221        for i=1,#arguments do
1222            local argument = arguments[i]
1223            local argtype = type(argument)
1224            c = c + 1
1225            if argtype == "string" then
1226                collapsed[c] = lpegmatch(visualizer,argument)
1227            elseif argtype == "number" then
1228                collapsed[c] = argument
1229            elseif argtype == "userdata" then
1230                collapsed[c] = userdata(argument)
1231            else
1232                collapsed[c] = formatters["<<%S>>"](argument)
1233            end
1234        end
1235        currenttrace(concat(collapsed))
1236    elseif one ~= nil then
1237        -- no catcodes
1238        local argtype = type(one)
1239        if argtype == "string" then
1240            currenttrace(formatters["f : - : %s"](lpegmatch(visualizer,one)))
1241        elseif argtype == "number" then
1242            currenttrace(formatters["f : - : %s"](one))
1243        elseif argtype == "userdata" then
1244            currenttrace(formatters["F : - : %s"](userdata(one)))
1245        else
1246            currenttrace(formatters["f : - : <<%S>>"](one))
1247        end
1248    end
1249end
1250
1251local tracedflush = function(one,two,...)
1252    nofflushes = nofflushes + 1
1253    if two ~= nil then
1254        normalflush(one,two,...)
1255    else
1256        normalflush(one)
1257    end
1258    if tracingpermitted then
1259        traced(one,two,...)
1260    end
1261end
1262
1263local tracedflushdirect = function(one,two,...)
1264    nofflushes = nofflushes + 1
1265    if two ~= nil then
1266        normalflushdirect(one,two,...)
1267    else
1268        normalflushdirect(one)
1269    end
1270    if tracingpermitted then
1271        traced(one,two,...)
1272    end
1273end
1274
1275function context.pushlogger(trace)
1276    trace = trace or report_context
1277    insert(trace_stack,currenttrace)
1278    currenttrace = trace
1279end
1280
1281function context.poplogger()
1282    if #trace_stack > 1 then
1283        currenttrace = remove(trace_stack) or report_context
1284    else
1285        currenttrace = report_context
1286    end
1287end
1288
1289function context.settracing(v)
1290    if v then
1291        flush       = tracedflush
1292        flushdirect = tracedflushdirect
1293        writer      = tracedwriter
1294    else
1295        flush       = normalflush
1296        flushdirect = normalflushdirect
1297        writer      = normalwriter
1298    end
1299    return flush, writer, flushdirect
1300end
1301
1302function context.getlogger()
1303    return flush, writer, flushdirect
1304end
1305
1306trackers.register("context.trace",context.settracing)
1307
1308local trace_cld = false  trackers.register("context.files", function(v) trace_cld = v end)
1309
1310do
1311
1312    -- This is the most reliable way to deal with nested buffers and other
1313    -- catcode sensitive data.
1314
1315    local resolve     = resolvers.savers.byscheme
1316    local validstring = string.valid
1317    local input       = context.input
1318
1319    local function viafile(data,tag)
1320        if data and data ~= "" then
1321            local filename = resolve("virtual",validstring(tag,"viafile"),data)
1322         -- context.startregime { "utf" }
1323            input(filename)
1324         -- context.stopregime()
1325        end
1326    end
1327
1328    context.viafile    = viafile
1329
1330    -- experiment for xtables, don't use it elsewhere yet
1331
1332    local collected    = nil
1333    local nofcollected = 0
1334    local sentinel     = string.char(26) -- ASCII SUB character : endoffileasciicode : ignorecatcode
1335    local level        = 0
1336
1337    local function collect(c,a,...) -- can be optimized
1338        if type(c) == "userdata" then
1339            nofcollected = nofcollected + 1
1340         -- collected[nofcollected] = userdata(c)
1341            collected[nofcollected] = "\\" .. c.csname
1342        end
1343        if a then
1344            for i=1,select("#",a,...) do
1345                local c = select(i,a,...)
1346                nofcollected = nofcollected + 1
1347                collected[nofcollected] = type(c) == "userdata" and userdata(c) or c
1348            end
1349        end
1350    end
1351
1352    local collectdirect = collect
1353    local permitted     = true
1354
1355    -- doesn't work well with tracing do we need to avoid that when
1356    -- collecting stuff
1357
1358    function context.startcollecting()
1359        if level == 0 then
1360            collected    = { }
1361            nofcollected = 0
1362            flush        = collect
1363            flushdirect  = collectdirect
1364            permitted    = tracingpermitted
1365        end
1366        level = level + 1
1367    end
1368
1369    function context.stopcollecting()
1370        level = level - 1
1371        if level < 1 then
1372            local result     = concat(collected,sentinel)
1373            flush            = normalflush
1374            flushdirect      = normalflushdirect
1375            tracingpermitted = permitted
1376            collected        = nil
1377            nofcollected     = 0
1378            level            = 0
1379            viafile(result)
1380        end
1381    end
1382
1383    local findtexfile = resolvers.findtexfile
1384    local findfile    = resolvers.findfile
1385
1386    function context.runfile(filename)
1387        local foundname = findtexfile(file.addsuffix(filename,"cld")) or ""
1388        if foundname ~= "" then
1389            local ok = dofile(foundname)
1390            if type(ok) == "function" then
1391                if trace_cld then
1392                    report_context("begin of file %a (function call)",foundname)
1393                end
1394                ok()
1395                if trace_cld then
1396                    report_context("end of file %a (function call)",foundname)
1397                end
1398            elseif ok then
1399                report_context("file %a is processed and returns true",foundname)
1400            else
1401                report_context("file %a is processed and returns nothing",foundname)
1402            end
1403        else
1404            report_context("unknown file %a",filename)
1405        end
1406    end
1407
1408    function context.loadfile(filename)
1409        context(stripstring(loaddata(findfile(filename))))
1410    end
1411
1412    function context.loadviafile(filename)
1413        viafile(stripstring(loaddata(findfile(filename))))
1414    end
1415
1416end
1417
1418-- some functions
1419
1420function context.direct(first,...)
1421    if first ~= nil then
1422        return writer(context,"",first,...)
1423    end
1424end
1425
1426-- context.delayed (todo: lines)
1427
1428do
1429
1430    local delayed = { }
1431
1432    local function indexer(parent,k)
1433        local f = function(...)
1434            local a = { ... } -- this also freezes ...
1435            return function()
1436             -- return context[k](unpack(a))
1437                return core[k](unpack(a))
1438            end
1439        end
1440        parent[k] = f
1441        return f
1442    end
1443
1444    local function caller(parent,...) -- todo: nodes
1445        local a = { ... }
1446        return function()
1447         -- return context(unpack(a))
1448            return defaultcaller(context,unpack(a))
1449        end
1450    end
1451
1452    setmetatableindex(delayed,indexer)
1453    setmetatablecall (delayed,caller)
1454
1455    context.delayed = delayed
1456
1457end
1458
1459-- do
1460--
1461--     -- context.nested (todo: lines), creates strings
1462--
1463--     local nested = { }
1464--
1465--     local function indexer(parent,k) -- not ok when traced
1466--         local f = function(...)
1467--             local t, savedflush, n = { }, flush, 0
1468--             flush = function(c,f,s,...) -- catcodes are ignored
1469--                 n = n + 1
1470--                 t[n] = s and concat{f,s,...} or f -- optimized for #args == 1
1471--             end
1472--          -- context[k](...)
1473--             core[k](...)
1474--             flush = savedflush
1475--             return concat(t)
1476--         end
1477--         parent[k] = f
1478--         return f
1479--     end
1480--
1481--     local function caller(parent,...)
1482--         local t, savedflush, n = { }, flush, 0
1483--         flush = function(c,f,s,...) -- catcodes are ignored
1484--             n = n + 1
1485--             t[n] = s and concat{f,s,...} or f -- optimized for #args == 1
1486--         end
1487--      -- context(...)
1488--         defaultcaller(context,...)
1489--         flush = savedflush
1490--         return concat(t)
1491--     end
1492--
1493--     setmetatableindex(nested,indexer)
1494--     setmetatablecall (nested,caller)
1495--
1496--     context.nested = nested
1497--
1498-- end
1499
1500context.nested = context.delayed
1501
1502-- verbatim
1503
1504function context.newindexer(catcodes,cmdcodes)
1505    local handler = { }
1506
1507    local function indexer(parent,k)
1508        local command = core[k]
1509        local f = function(...)
1510            local savedcatcodes = contentcatcodes
1511            contentcatcodes = catcodes
1512            command(...)
1513            contentcatcodes = savedcatcodes
1514        end
1515        parent[k] = f
1516        return f
1517    end
1518
1519    local function caller(parent,...)
1520        local savedcatcodes = contentcatcodes
1521        contentcatcodes = catcodes
1522        defaultcaller(parent,...)
1523        contentcatcodes = savedcatcodes
1524    end
1525
1526    handler.cs = core.cs
1527
1528    setmetatableindex(handler,indexer)
1529    setmetatablecall (handler,caller)
1530
1531    return handler
1532end
1533
1534context.verbatim  = context.newindexer(vrbcatcodes,ctxcatcodes)
1535context.puretext  = context.newindexer(txtcatcodes,ctxcatcodes)
1536context.protected = context.newindexer(prtcatcodes,prtcatcodes)
1537
1538-- formatted
1539
1540do
1541
1542    local formatted = { }
1543
1544    -- formatted.command([catcodes,]format[,...])
1545
1546    local function formattedflush(parent,c,catcodes,fmt,...)
1547        if not catcodes then
1548            return writer(parent,c)
1549        elseif not fmt then
1550            return writer(parent,c,catcodes)
1551        elseif type(catcodes) == "number" then
1552            local result
1553            pushcatcodes(catcodes)
1554            result = writer(parent,c,formatters[fmt](...))
1555            popcatcodes()
1556            return result
1557        else
1558            return writer(parent,c,formatters[catcodes](fmt,...))
1559        end
1560    end
1561
1562    local toks = tokens.cache
1563
1564    local indexer = function(parent,k)
1565        if type(k) == "string" then
1566            local t
1567            local f = function(first,...)
1568                if not t then
1569                    t = toks[k]
1570                end
1571                if first == nil then
1572                    flush(t)
1573                else
1574                    return formattedflush(parent,t,first,...)
1575                end
1576            end
1577            parent[k] = f
1578            return f
1579        else
1580            return context -- catch
1581        end
1582    end
1583
1584    -- formatted([catcodes,]format[,...])
1585
1586    local function caller(parent,catcodes,fmt,...)
1587        if not catcodes then
1588            -- nothing
1589        elseif not fmt then
1590            flush(catcodes)
1591        elseif type(catcodes) == "number" then
1592            flush(catcodes,formatters[fmt](...))
1593        else
1594            flush(formatters[catcodes](fmt,...))
1595        end
1596    end
1597
1598    setmetatableindex(formatted,indexer)
1599    setmetatablecall (formatted,caller)
1600
1601    context.formatted = formatted
1602
1603end
1604
1605do
1606
1607    -- metafun (this will move to another file)
1608
1609    local metafun = { }
1610
1611    function metafun.start()
1612        context.startMPcode()
1613    end
1614
1615    function metafun.stop()
1616        context.stopMPcode()
1617    end
1618
1619    setmetatablecall(metafun,defaultcaller)
1620
1621    function metafun.color(name) -- obsolete
1622        return name -- formatters[ [[\MPcolor{%s}]] ](name)
1623    end
1624
1625    -- metafun.delayed
1626
1627    local delayed = { }
1628
1629    local function indexer(parent,k)
1630        local f = function(...)
1631            local a = { ... }
1632            return function()
1633                return metafun[k](unpack(a))
1634            end
1635        end
1636        parent[k] = f
1637        return f
1638    end
1639
1640
1641    local function caller(parent,...)
1642        local a = { ... }
1643        return function()
1644            return metafun(unpack(a))
1645        end
1646    end
1647
1648    setmetatableindex(delayed,indexer)
1649    setmetatablecall (delayed,caller)
1650
1651    context.metafun = metafun
1652    metafun.delayed = delayed
1653
1654end
1655
1656-- helpers:
1657
1658do
1659
1660    function context.concat(...)
1661        context(concat(...))
1662    end
1663
1664    local p_texescape = patterns.texescape
1665
1666    function context.escaped(s)
1667        if s then
1668            context(lpegmatch(p_texescape,s) or s)
1669        else
1670         -- context("")
1671        end
1672    end
1673
1674    function context.escape(s)
1675        if s then
1676            return lpegmatch(p_texescape,s) or s
1677        else
1678            return ""
1679        end
1680    end
1681
1682end
1683
1684-- templates
1685
1686do
1687
1688    local single  = lpegP("%")
1689    local double  = lpegP("%%")
1690    local lquoted = lpegP("%[")
1691    local rquoted = lpegP("]%")
1692    local space   = lpegP(" ")
1693
1694    local start = [[
1695    local texescape = lpeg.patterns.texescape
1696    local lpegmatch = lpeg.match
1697    return function(variables) return
1698    ]]
1699
1700    local stop  = [[
1701    end
1702    ]]
1703
1704    local replacer = lpegP { "parser",
1705        parser   = lpegCs(lpegCc(start) * lpegV("step") * (lpegCc("..") * lpegV("step"))^0 * lpegCc(stop)),
1706        unquoted = (lquoted*space/'')
1707                 * ((lpegC((1-space*rquoted)^1)) / "lpegmatch(texescape,variables%0 or '')" )
1708                 * (space*rquoted/'')
1709                 + (lquoted/'')
1710                 * ((lpegC((1-rquoted)^1)) / "lpegmatch(texescape,variables['%0'] or '')" )
1711                 * (rquoted/''),
1712        key      = (single*space/'')
1713                 * ((lpegC((1-space*single)^1)) / "(variables%0 or '')" )
1714                 * (space*single/'')
1715                 + (single/'')
1716                 * ((lpegC((1-single)^1)) / "(variables['%0'] or '')" )
1717                 * (single/''),
1718        escape   = double/'%%',
1719        step     = lpegV("unquoted")
1720                 + lpegV("escape")
1721                 + lpegV("key")
1722                 + lpegCc("\n[===[") * (1 - lpegV("unquoted") - lpegV("escape") - lpegV("key"))^1 * lpegCc("]===]\n"),
1723    }
1724
1725    local templates = { }
1726
1727    local function indexer(parent,k)
1728        local v = lpegmatch(replacer,k)
1729        if not v then
1730         -- report_template("invalid template:\n%s",k)
1731            v = "error: no valid template (1)"
1732        else
1733            local f = loadstring(v)
1734            if type(f) ~= "function" then
1735             -- report_template("invalid template:\n%s\n=>\n%s",k,v)
1736                v = "error: no valid template (2)"
1737            else
1738                f = f()
1739                if not f then
1740                 -- report_template("invalid template:\n%s\n=>\n%s",k,v)
1741                    v = "error: no valid template (3)"
1742                else
1743                    v = f
1744                end
1745            end
1746        end
1747        if type(v) == "function" then
1748            local f = function(first,second)
1749                if second then
1750                    pushcatcodes(first)
1751                    flushlines(v(second))
1752                    popcatcodes()
1753                else
1754                    flushlines(v(first))
1755                end
1756            end
1757            parent[k] = f
1758            return f
1759        else
1760            return function()
1761                flush(v)
1762            end
1763        end
1764
1765    end
1766
1767    local function caller(parent,k,...)
1768        return parent[k](...)
1769    end
1770
1771    setmetatableindex(templates,indexer)
1772    setmetatablecall (templates,caller)
1773
1774    context.templates = templates
1775
1776end
1777
1778-- The above is a bit over the top as we could also stick to a simple context.replace
1779-- which is fast enough anyway, but the above fits in nicer, also with the catcodes.
1780--
1781-- local replace = utilities.templates.replace
1782--
1783-- function context.template(template,variables)
1784--     context(replace(template,variables))
1785-- end
1786
1787do
1788
1789    -- not the best namespace -- some day maybe texs a la nodes and tokens .. also,
1790    -- we already have tex.modes so we need a different name
1791
1792    local modelevels = tex.getmodevalues()
1793    local t          = table.keys(modelevels)
1794    tex.modelevels   = table.swapped(modelevels,modelevels) -- utilities.storage.allocate()
1795
1796    for i=1,#t do local k = t[i] modelevels[-k] = modelevels[k] end
1797
1798    if CONTEXTLMTXMODE > 0 then
1799
1800        -- also elsewhere
1801
1802        local flagcodes = tex.getflagvalues()
1803        tex.flagcodes   = table.swapped(flagcodes,flagcodes) -- utilities.storage.allocate()
1804
1805    end
1806
1807end
1808