diff options
Diffstat (limited to 'tex/context/base/mkiv/cldf-ini.lua')
-rw-r--r-- | tex/context/base/mkiv/cldf-ini.lua | 1814 |
1 files changed, 1814 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/cldf-ini.lua b/tex/context/base/mkiv/cldf-ini.lua new file mode 100644 index 000000000..0253adde5 --- /dev/null +++ b/tex/context/base/mkiv/cldf-ini.lua @@ -0,0 +1,1814 @@ +if not modules then modules = { } end modules ['cldf-ini'] = { + version = 1.001, + comment = "companion to cldf-ini.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- see cldf-tod.* ! + +-- maybe: +-- +-- 0.528 local foo = tex.ctxcatcodes +-- 0.651 local foo = getcount("ctxcatcodes") +-- 0.408 local foo = getcount(ctxcatcodes) -- local ctxcatcodes = tex.iscount("ctxcatcodes") + +-- This started as an experiment: generating context code at the lua end. After all +-- it is surprisingly simple to implement due to metatables. I was wondering if +-- there was a more natural way to deal with commands at the lua end. Of course it's +-- a bit slower but often more readable when mixed with lua code. It can also be handy +-- when generating documents from databases or when constructing large tables or so. +-- +-- maybe optional checking against interface +-- currently no coroutine trickery +-- we could always use prtcatcodes (context.a_b_c) but then we loose protection +-- tflush needs checking ... sort of weird that it's not a table +-- __flushlines is an experiment and rather ugly so it will go away +-- +-- tex.print == line with endlinechar appended + +-- todo: context("%bold{total: }%s",total) +-- todo: context.documentvariable("title") + +-- during the crited project we ran into the situation that luajittex was 10-20 times +-- slower that luatex ... after 3 days of testing and probing we finally figured out that +-- the the differences between the lua and luajit hashers can lead to quite a slowdown +-- in some cases. + +-- context(lpeg.match(lpeg.patterns.texescape,"${}")) +-- context(string.formatters["%!tex!"]("${}")) +-- context("%!tex!","${}") + +local format, stripstring = string.format, string.strip +local next, type, tostring, tonumber, setmetatable, unpack, select, rawset = next, type, tostring, tonumber, setmetatable, unpack, select, rawset +local insert, remove, concat = table.insert, table.remove, table.concat +local lpegmatch, lpegC, lpegS, lpegP, lpegV, lpegCc, lpegCs, patterns = lpeg.match, lpeg.C, lpeg.S, lpeg.P, lpeg.V, lpeg.Cc, lpeg.Cs, lpeg.patterns +local formatters = string.formatters -- using formatters is slower in this case + +context = context or { } +commands = commands or { } +interfaces = interfaces or { } + +local context = context +local commands = commands +local interfaces = interfaces + +local loaddata = io.loaddata + +local tex = tex +local texsprint = tex.sprint -- just appended (no space,eol treatment) +local texprint = tex.print -- each arg a separate line (not last in directlua) +----- texwrite = tex.write -- all 'space' and 'character' +local texgetcount = tex.getcount + +-- local function texsprint(...) print("sprint",...) tex.sprint(...) end +-- local function texprint (...) print("print", ...) tex.print (...) end + +local isnode = node.is_node +local writenode = node.write +local copynodelist = node.copy_list + +local catcodenumbers = catcodes.numbers + +local ctxcatcodes = catcodenumbers.ctxcatcodes +local prtcatcodes = catcodenumbers.prtcatcodes +local texcatcodes = catcodenumbers.texcatcodes +local txtcatcodes = catcodenumbers.txtcatcodes +local vrbcatcodes = catcodenumbers.vrbcatcodes +local xmlcatcodes = catcodenumbers.xmlcatcodes + +local flush = texsprint -- snippets +local flushdirect = texprint -- lines +----- flushraw = texwrite + +local report_context = logs.reporter("cld","tex") +local report_cld = logs.reporter("cld","stack") + +local processlines = true -- experiments.register("context.processlines", function(v) processlines = v end) + +-- In earlier experiments a function tables was referred to as lua.calls and the +-- primitive \luafunctions was \luacall. + +local luafunctions = lua.get_functions_table and lua.get_functions_table() +local usedstack = nil +local showstackusage = false + +-- luafunctions = false + +trackers.register("context.stack",function(v) showstackusage = v end) + +local storefunction, flushfunction +local storenode, flushnode +local registerfunction, unregisterfunction, reservefunction, knownfunctions, callfunctiononce + +-- if luafunctions then + + local freed, nofused, noffreed = { }, 0, 0 -- maybe use the number of @@trialtypesetting + + usedstack = function() + return nofused, noffreed + end + + flushfunction = function(slot,arg) + if arg() then + -- keep + elseif texgetcount("@@trialtypesetting") == 0 then -- @@trialtypesetting is private! + noffreed = noffreed + 1 + freed[noffreed] = slot + luafunctions[slot] = false + else + -- keep + end + end + + storefunction = function(arg) + local f = function(slot) flushfunction(slot,arg) end + if noffreed > 0 then + local n = freed[noffreed] + freed[noffreed] = nil + noffreed = noffreed - 1 + luafunctions[n] = f + return n + else + nofused = nofused + 1 + luafunctions[nofused] = f + return nofused + end + end + + flushnode = function(slot,arg) + if texgetcount("@@trialtypesetting") == 0 then -- @@trialtypesetting is private! + writenode(arg) + noffreed = noffreed + 1 + freed[noffreed] = slot + luafunctions[slot] = false + else + writenode(copynodelist(arg)) + end + end + + storenode = function(arg) + local f = function(slot) flushnode(slot,arg) end + if noffreed > 0 then + local n = freed[noffreed] + freed[noffreed] = nil + noffreed = noffreed - 1 + luafunctions[n] = f + return n + else + nofused = nofused + 1 + luafunctions[nofused] = f + return nofused + end + end + + -- registerfunction = function(f) + -- if type(f) == "string" then + -- f = loadstring(f) + -- end + -- if type(f) ~= "function" then + -- f = function() report_cld("invalid function %A",f) end + -- end + -- if noffreed > 0 then + -- local n = freed[noffreed] + -- freed[noffreed] = nil + -- noffreed = noffreed - 1 + -- luafunctions[n] = f + -- return n + -- else + -- nofused = nofused + 1 + -- luafunctions[nofused] = f + -- return nofused + -- end + -- end + + storage.storedfunctions = storage.storedfunctions or { } + local storedfunctions = storage.storedfunctions + local initex = environment.initex + + storage.register("storage/storedfunctions", storedfunctions, "storage.storedfunctions") + + local f_resolve = nil + local p_resolve = ((1-lpegP("."))^1 / function(s) f_resolve = f_resolve[s] end * lpegP(".")^0)^1 + + function resolvestoredfunction(str) + f_resolve = global + lpegmatch(p_resolve,str) + return f_resolve + end + + local function expose(slot,f,...) -- so we can register yet undefined functions + local func = resolvestoredfunction(f) + if not func then + func = function() report_cld("beware: unknown function %i called: %s",slot,f) end + end + luafunctions[slot] = func + return func(...) + end + + if initex then + -- todo: log stored functions + else + local slots = table.sortedkeys(storedfunctions) + local last = #slots + if last > 0 then + -- we restore the references + for i=1,last do + local slot = slots[i] + local data = storedfunctions[slot] + luafunctions[slot] = function(...) + -- print(data) -- could be trace + return expose(slot,data,...) + end + end + -- we now know how many are defined + nofused = slots[last] + -- normally there are no holes in the list yet + for i=1,nofused do + if not luafunctions[i] then + noffreed = noffreed + 1 + freed[noffreed] = i + end + end + -- report_cld("%s registered functions, %s freed slots",last,noffreed) + end + end + + registerfunction = function(f,direct) -- either f=code or f=namespace,direct=name + local slot, func + if noffreed > 0 then + slot = freed[noffreed] + freed[noffreed] = nil + noffreed = noffreed - 1 + else + nofused = nofused + 1 + slot = nofused + end + if direct then + if initex then + func = function(...) + expose(slot,f,...) + end + if initex then + storedfunctions[slot] = f + end + else + func = resolvestoredfunction(f) + end + if type(func) ~= "function" then + func = function() report_cld("invalid resolve %A",f) end + end + elseif type(f) == "string" then + func = loadstring(f) + if type(func) ~= "function" then + func = function() report_cld("invalid code %A",f) end + end + elseif type(f) == "function" then + func = f + else + func = function() report_cld("invalid function %A",f) end + end + luafunctions[slot] = func + return slot + end + + -- do + -- commands.test = function(str) report_cld("test function: %s", str) end + -- if initex then + -- registerfunction("commands.test") -- number 1 + -- end + -- luafunctions[1]("okay") + -- end + + unregisterfunction = function(slot) + if luafunctions[slot] then + noffreed = noffreed + 1 + freed[noffreed] = slot + luafunctions[slot] = false + else + report_cld("invalid function slot %A",slot) + end + end + + reservefunction = function() + if noffreed > 0 then + local n = freed[noffreed] + freed[noffreed] = nil + noffreed = noffreed - 1 + return n + else + nofused = nofused + 1 + return nofused + end + end + + callfunctiononce = function(slot) + luafunctions[slot](slot) + noffreed = noffreed + 1 + freed[noffreed] = slot + luafunctions[slot] = false + end + + table.setmetatablecall(luafunctions,function(t,n) return luafunctions[n](n) end) + + knownfunctions = luafunctions + + -- The next hack is a convenient way to define scanners at the Lua end and + -- get them available at the TeX end. There is some dirty magic needed to + -- prevent overload during format loading. + + -- interfaces.scanners.foo = function() context("[%s]",tokens.scanners.string()) end : \scan_foo + + interfaces.storedscanners = interfaces.storedscanners or { } + local storedscanners = interfaces.storedscanners + + storage.register("interfaces/storedscanners", storedscanners, "interfaces.storedscanners") + + local interfacescanners = table.setmetatablenewindex(function(t,k,v) + if storedscanners[k] then + -- report_cld("warning: scanner %a is already set",k) + -- os.exit() + -- \scan_<k> is already in the format + -- report_cld("using interface scanner: %s",k) + else + -- todo: allocate slot here and pass it + storedscanners[k] = true + -- report_cld("installing interface scanner: %s",k) + context("\\installctxscanner{clf_%s}{interfaces.scanners.%s}",k,k) + end + rawset(t,k,v) + end) + + interfaces.scanners = interfacescanners + +-- else -- by now this is obsolete +-- +-- local luafunctions, noffunctions = { }, 0 +-- local luanodes, nofnodes = { }, 0 +-- +-- usedstack = function() +-- return noffunctions + nofnodes, 0 +-- end +-- +-- flushfunction = function(n) +-- local sn = luafunctions[n] +-- if not sn then +-- report_cld("data with id %a cannot be found on stack",n) +-- elseif not sn() and texgetcount("@@trialtypesetting") == 0 then -- @@trialtypesetting is private! +-- luafunctions[n] = nil +-- end +-- end +-- +-- storefunction = function(ti) +-- noffunctions = noffunctions + 1 +-- luafunctions[noffunctions] = ti +-- return noffunctions +-- end +-- +-- -- freefunction = function(n) +-- -- luafunctions[n] = nil +-- -- end +-- +-- flushnode = function(n) +-- local sn = luanodes[n] +-- if not sn then +-- report_cld("data with id %a cannot be found on stack",n) +-- elseif texgetcount("@@trialtypesetting") == 0 then -- @@trialtypesetting is private! +-- writenode(sn) +-- luanodes[n] = nil +-- else +-- writenode(copynodelist(sn)) +-- end +-- end +-- +-- storenode = function(ti) +-- nofnodes = nofnodes + 1 +-- luanodes[nofnodes] = ti +-- return nofnodes +-- end +-- +-- _cldf_ = flushfunction -- global +-- _cldn_ = flushnode -- global +-- -- _cldl_ = function(n) return luafunctions[n]() end -- luafunctions(n) +-- _cldl_ = luafunctions +-- +-- registerfunction = function(f) +-- if type(f) == "string" then +-- f = loadstring(f) +-- end +-- if type(f) ~= "function" then +-- f = function() report_cld("invalid function %A",f) end +-- end +-- noffunctions = noffunctions + 1 +-- luafunctions[noffunctions] = f +-- return noffunctions +-- end +-- +-- unregisterfunction = function(slot) +-- if luafunctions[slot] then +-- luafunctions[slot] = nil +-- else +-- report_cld("invalid function slot %A",slot) +-- end +-- end +-- +-- reservefunction = function() +-- noffunctions = noffunctions + 1 +-- return noffunctions +-- end +-- +-- callfunctiononce = function(slot) +-- luafunctions[slot](slot) +-- luafunctions[slot] = nil +-- end +-- +-- table.setmetatablecall(luafunctions,function(t,n) return luafunctions[n](n) end) +-- +-- knownfunctions = luafunctions +-- +-- end + +context.registerfunction = registerfunction +context.unregisterfunction = unregisterfunction +context.reservefunction = reservefunction +context.knownfunctions = knownfunctions +context.callfunctiononce = callfunctiononce _cldo_ = callfunctiononce +context.storenode = storenode -- private helper + +function commands.ctxfunction(code,namespace) + context(registerfunction(code,namespace)) +end + +function commands.ctxscanner(name,code,namespace) + local n = registerfunction(code,namespace) + if storedscanners[name] then + storedscanners[name] = n + end + context(n) +end + +local function dummy() end + +function commands.ctxresetter(name) + return function() + if storedscanners[name] then + rawset(interfacescanners,name,dummy) + context.resetctxscanner("clf_" .. name) + end + end +end + +function context.trialtypesetting() + return texgetcount("@@trialtypesetting") ~= 0 +end + +-- local f_cldo = formatters["_cldo_(%i)"] +-- local latelua_node = nodes.pool.latelua +-- +-- function context.lateluafunctionnnode(f) +-- return latelua_node(f_cldo(registerfunction(f))) +-- end + +-- Should we keep the catcodes with the function? + +local catcodestack = { } +local currentcatcodes = ctxcatcodes +local contentcatcodes = ctxcatcodes + +local catcodes = { + ctx = ctxcatcodes, ctxcatcodes = ctxcatcodes, context = ctxcatcodes, + prt = prtcatcodes, prtcatcodes = prtcatcodes, protect = prtcatcodes, + tex = texcatcodes, texcatcodes = texcatcodes, plain = texcatcodes, + txt = txtcatcodes, txtcatcodes = txtcatcodes, text = txtcatcodes, + vrb = vrbcatcodes, vrbcatcodes = vrbcatcodes, verbatim = vrbcatcodes, + xml = xmlcatcodes, xmlcatcodes = xmlcatcodes, +} + +local function pushcatcodes(c) + insert(catcodestack,currentcatcodes) + currentcatcodes = (c and catcodes[c] or tonumber(c)) or currentcatcodes + contentcatcodes = currentcatcodes +end + +local function popcatcodes() + currentcatcodes = remove(catcodestack) or currentcatcodes + contentcatcodes = currentcatcodes +end + +context.pushcatcodes = pushcatcodes +context.popcatcodes = popcatcodes + +-- -- -- + +local newline = patterns.newline +local space = patterns.spacer +local spacing = newline * space^0 +local content = lpegC((1-spacing)^1) -- texsprint +local emptyline = space^0 * newline^2 -- texprint("") +local endofline = space^0 * newline * space^0 -- texsprint(" ") +local simpleline = endofline * lpegP(-1) -- + +local verbose = lpegC((1-space-newline)^1) +local beginstripper = (lpegS(" \t")^1 * newline^1) / "" +local endstripper = beginstripper * lpegP(-1) + +local justaspace = space * lpegCc("") +local justanewline = newline * lpegCc("") + +local function n_content(s) + flush(contentcatcodes,s) +end + +local function n_verbose(s) + flush(vrbcatcodes,s) +end + +local function n_endofline() + flush(currentcatcodes," \r") +end + +local function n_emptyline() + flushdirect(currentcatcodes,"\r") +end + +local function n_simpleline() + flush(currentcatcodes," \r") +end + +local n_exception = "" + +-- better a table specification + +function context.newtexthandler(specification) -- can also be used for verbose + specification = specification or { } + -- + local s_catcodes = specification.catcodes + -- + local f_before = specification.before + local f_after = specification.after + -- + local f_endofline = specification.endofline or n_endofline + local f_emptyline = specification.emptyline or n_emptyline + local f_simpleline = specification.simpleline or n_simpleline + local f_content = specification.content or n_content + local f_space = specification.space + -- + local p_exception = specification.exception + -- + if s_catcodes then + f_content = function(s) + flush(s_catcodes,s) + end + end + -- + local pattern + if f_space then + if p_exception then + local content = lpegC((1-spacing-p_exception)^1) + pattern = + ( + justaspace / f_space + + justanewline / f_endofline + + p_exception + + content / f_content + )^0 + else + local content = lpegC((1-space-endofline)^1) + pattern = + ( + justaspace / f_space + + justanewline / f_endofline + + content / f_content + )^0 + end + else + if p_exception then + local content = lpegC((1-spacing-p_exception)^1) + pattern = + simpleline / f_simpleline + + + ( + emptyline / f_emptyline + + endofline / f_endofline + + p_exception + + content / f_content + )^0 + else + local content = lpegC((1-spacing)^1) + pattern = + simpleline / f_simpleline + + + ( + emptyline / f_emptyline + + endofline / f_endofline + + content / f_content + )^0 + end + end + -- + if f_before then + pattern = (P(true) / f_before) * pattern + end + -- + if f_after then + pattern = pattern * (P(true) / f_after) + end + -- + return function(str) return lpegmatch(pattern,str) end, pattern +end + +function context.newverbosehandler(specification) -- a special variant for e.g. cdata in lxml-tex + specification = specification or { } + -- + local f_line = specification.line or function() flushdirect("\r") end + local f_space = specification.space or function() flush(" ") end + local f_content = specification.content or n_verbose + local f_before = specification.before + local f_after = specification.after + -- + local pattern = + justanewline / f_line -- so we get call{} + + verbose / f_content + + justaspace / f_space -- so we get call{} + -- + if specification.strip then + pattern = beginstripper^0 * (endstripper + pattern)^0 + else + pattern = pattern^0 + end + -- + if f_before then + pattern = (lpegP(true) / f_before) * pattern + end + -- + if f_after then + pattern = pattern * (lpegP(true) / f_after) + end + -- + return function(str) return lpegmatch(pattern,str) end, pattern +end + +local flushlines = context.newtexthandler { + content = n_content, + endofline = n_endofline, + emptyline = n_emptyline, + simpleline = n_simpleline, +} + +context.__flushlines = flushlines -- maybe context.helpers.flushtexlines +context.__flush = flush +context.__flushdirect = flushdirect + +-- The next variant is only used in rare cases (buffer to mp): + +local printlines_ctx = ( + (newline) / function() texprint("") end + + (1-newline)^1 / function(s) texprint(ctxcatcodes,s) end * newline^-1 +)^0 + +local printlines_raw = ( + (newline) / function() texprint("") end + + (1-newline)^1 / function(s) texprint(s) end * newline^-1 +)^0 + +function context.printlines(str,raw) -- todo: see if via file is useable + if raw then + lpegmatch(printlines_raw,str) + else + lpegmatch(printlines_ctx,str) + end +end + +-- -- -- "{" .. ti .. "}" is somewhat slower in a cld-mkiv run than "{",ti,"}" + +local containseol = patterns.containseol + +local writer + +if luafunctions then + + writer = function (parent,command,first,...) -- already optimized before call + local t = { first, ... } + flush(currentcatcodes,command) -- todo: ctx|prt|texcatcodes + local direct = false + for i=1,#t do + local ti = t[i] + local typ = type(ti) + if direct then + if typ == "string" or typ == "number" then + flush(currentcatcodes,ti) + else -- node.write + report_context("error: invalid use of direct in %a, only strings and numbers can be flushed directly, not %a",command,typ) + end + direct = false + elseif ti == nil then + -- nothing + elseif ti == "" then + flush(currentcatcodes,"{}") + elseif typ == "string" then + -- is processelines seen ? + if processlines and lpegmatch(containseol,ti) then + flush(currentcatcodes,"{") + local flushlines = parent.__flushlines or flushlines + flushlines(ti) + flush(currentcatcodes,"}") + elseif currentcatcodes == contentcatcodes then + flush(currentcatcodes,"{",ti,"}") + else + flush(currentcatcodes,"{") + flush(contentcatcodes,ti) + flush(currentcatcodes,"}") + end + elseif typ == "number" then + -- numbers never have funny catcodes + flush(currentcatcodes,"{",ti,"}") + elseif typ == "table" then + local tn = #ti + if tn == 0 then + local done = false + for k, v in next, ti do + if done then + if v == "" then + flush(currentcatcodes,",",k,'=') + else + flush(currentcatcodes,",",k,"={",v,"}") + end + else + if v == "" then + flush(currentcatcodes,"[",k,"=") + else + flush(currentcatcodes,"[",k,"={",v,"}") + end + done = true + end + end + if done then + flush(currentcatcodes,"]") + else + flush(currentcatcodes,"[]") + end + elseif tn == 1 then -- some 20% faster than the next loop + local tj = ti[1] + if type(tj) == "function" then + flush(currentcatcodes,"[\\cldl",storefunction(tj),"]") + else + flush(currentcatcodes,"[",tj,"]") + end + else -- is concat really faster than flushes here? probably needed anyway (print artifacts) + flush(currentcatcodes,"[") + for j=1,tn do + local tj = ti[j] + if type(tj) == "function" then + if j == tn then + flush(currentcatcodes,"\\cldl",storefunction(tj),"]") + else + flush(currentcatcodes,"\\cldl",storefunction(tj),",") + end + else + if j == tn then + flush(currentcatcodes,tj,"]") + else + flush(currentcatcodes,tj,",") + end + end + end + end + elseif typ == "function" then + flush(currentcatcodes,"{\\cldl ",storefunction(ti),"}") -- todo: ctx|prt|texcatcodes + elseif typ == "boolean" then + if ti then + flushdirect(currentcatcodes,"\r") + else + direct = true + end + elseif typ == "thread" then + report_context("coroutines not supported as we cannot yield across boundaries") + elseif isnode(ti) then -- slow + flush(currentcatcodes,"{\\cldl",storenode(ti),"}") + else + report_context("error: %a gets a weird argument %a",command,ti) + end + end + end + +else + + writer = function (parent,command,first,...) -- already optimized before call + local t = { first, ... } + flush(currentcatcodes,command) -- todo: ctx|prt|texcatcodes + local direct = false + for i=1,#t do + local ti = t[i] + local typ = type(ti) + if direct then + if typ == "string" or typ == "number" then + flush(currentcatcodes,ti) + else -- node.write + report_context("error: invalid use of direct in %a, only strings and numbers can be flushed directly, not %a",command,typ) + end + direct = false + elseif ti == nil then + -- nothing + elseif ti == "" then + flush(currentcatcodes,"{}") + elseif typ == "string" then + -- is processelines seen ? + if processlines and lpegmatch(containseol,ti) then + flush(currentcatcodes,"{") + local flushlines = parent.__flushlines or flushlines + flushlines(ti) + flush(currentcatcodes,"}") + elseif currentcatcodes == contentcatcodes then + flush(currentcatcodes,"{",ti,"}") + else + flush(currentcatcodes,"{") + flush(contentcatcodes,ti) + flush(currentcatcodes,"}") + end + elseif typ == "number" then + -- numbers never have funny catcodes + flush(currentcatcodes,"{",ti,"}") + elseif typ == "table" then + local tn = #ti + if tn == 0 then + local done = false + for k, v in next, ti do + if done then + if v == "" then + flush(currentcatcodes,",",k,'=') + else + flush(currentcatcodes,",",k,"={",v,"}") + end + else + if v == "" then + flush(currentcatcodes,"[",k,"=") + else + flush(currentcatcodes,"[",k,"={",v,"}") + end + done = true + end + end + if done then + flush(currentcatcodes,"]") + else + flush(currentcatcodes,"[]") + end + elseif tn == 1 then -- some 20% faster than the next loop + local tj = ti[1] + if type(tj) == "function" then + flush(currentcatcodes,"[\\cldf{",storefunction(tj),"}]") + else + flush(currentcatcodes,"[",tj,"]") + end + else -- is concat really faster than flushes here? probably needed anyway (print artifacts) + for j=1,tn do + local tj = ti[j] + if type(tj) == "function" then + ti[j] = "\\cldf{" .. storefunction(tj) .. "}" + end + end + flush(currentcatcodes,"[",concat(ti,","),"]") + end + elseif typ == "function" then + flush(currentcatcodes,"{\\cldf{",storefunction(ti),"}}") -- todo: ctx|prt|texcatcodes + elseif typ == "boolean" then + if ti then + flushdirect(currentcatcodes,"\r") + else + direct = true + end + elseif typ == "thread" then + report_context("coroutines not supported as we cannot yield across boundaries") + elseif isnode(ti) then -- slow + flush(currentcatcodes,"{\\cldn{",storenode(ti),"}}") + else + report_context("error: %a gets a weird argument %a",command,ti) + end + end + end + +end + +local generics = { } context.generics = generics +local indexer = nil + +-- if environment.initex then + + indexer = function(parent,k) + if type(k) == "string" then + local c = "\\" .. tostring(generics[k] or k) + local f = function(first,...) + if first == nil then + flush(currentcatcodes,c) + else + return writer(parent,c,first,...) + end + end + parent[k] = f + return f + else + return context -- catch + end + end + +-- else +-- +-- local create = token.create +-- local twrite = token.write +-- local setmacro = token.set_macro +-- +-- indexer = function(parent,k) +-- if type(k) == "string" then +-- local s = tostring(generics[k] or k) +-- local t = create(s) +-- if t.cmdname == "undefined_cs" then +-- report_cld("macro \\%s is not yet defined",s) +-- token.set_macro(s,"") +-- t = create(s) +-- end +-- local i = t.id +-- local f = function(first,...) +-- twrite(t.tok) --= we need to keep t uncollected +-- if first ~= nil then +-- return writer(parent,first,...) +-- end +-- end +-- parent[k] = f +-- return f +-- else +-- return context -- catch +-- end +-- end +-- +-- end + +-- Potential optimization: after the first call we know if there will be an +-- argument. Of course there is the side effect that for instance abuse like +-- context.NC(str) fails as well as optional arguments. So, we don't do this +-- in practice. We just keep the next trick commented. The gain on some +-- 100000 calls is not that large: 0.100 => 0.95 which is neglectable. +-- +-- local function constructor(parent,k,c,first,...) +-- if first == nil then +-- local f = function() +-- flush(currentcatcodes,c) +-- end +-- parent[k] = f +-- return f() +-- else +-- local f = function(...) +-- return writer(parent,c,...) +-- end +-- parent[k] = f +-- return f(first,...) +-- end +-- end +-- +-- local function indexer(parent,k) +-- local c = "\\" .. tostring(generics[k] or k) +-- local f = function(...) +-- return constructor(parent,k,c,...) +-- end +-- parent[k] = f +-- return f +-- end + +-- only for internal usage: + +function context.constructcsonly(k) -- not much faster than the next but more mem efficient + local c = "\\" .. tostring(generics[k] or k) + local v = function() + flush(prtcatcodes,c) + end + rawset(context,k,v) + return v +end + +function context.constructcs(k) + local c = "\\" .. tostring(generics[k] or k) + local v = function(first,...) + if first == nil then + flush(prtcatcodes,c) + else + return writer(context,c,first,...) + end + end + rawset(context,k,v) + return v +end + +-- local splitformatters = utilities.strings.formatters.new(true) -- not faster (yet) + +local caller + +if luafunctions then + + caller = function(parent,f,a,...) + if not parent then + -- so we don't need to test in the calling (slower but often no issue) + elseif f ~= nil then + local typ = type(f) + if typ == "string" then + if f == "" then + -- new, can save a bit sometimes + -- if trace_context then + -- report_context("empty argument to context()") + -- end + elseif a then + flush(contentcatcodes,formatters[f](a,...)) -- was currentcatcodes + -- flush(contentcatcodes,splitformatters[f](a,...)) -- was currentcatcodes + elseif processlines and lpegmatch(containseol,f) then + local flushlines = parent.__flushlines or flushlines + flushlines(f) + else + flush(contentcatcodes,f) + end + elseif typ == "number" then + if a then + flush(currentcatcodes,f,a,...) + else + flush(currentcatcodes,f) + end + elseif typ == "function" then + -- ignored: a ... + flush(currentcatcodes,"{\\cldl",storefunction(f),"}") -- todo: ctx|prt|texcatcodes + elseif typ == "boolean" then + if f then + if a ~= nil then + local flushlines = parent.__flushlines or flushlines + flushlines(a) + else + flushdirect(currentcatcodes,"\n") -- no \r, else issues with \startlines ... use context.par() otherwise + end + else + if a ~= nil then + -- no command, same as context(a,...) + writer(parent,"",a,...) + else + -- ignored + end + end + elseif typ == "thread" then + report_context("coroutines not supported as we cannot yield across boundaries") + elseif isnode(f) then -- slow + -- writenode(f) + flush(currentcatcodes,"\\cldl",storenode(f)," ") + else + report_context("error: %a gets a weird argument %a","context",f) + end + end + end + + function context.flushnode(n) + flush(currentcatcodes,"\\cldl",storenode(n)," ") + end + +else + + caller = function(parent,f,a,...) + if not parent then + -- so we don't need to test in the calling (slower but often no issue) + elseif f ~= nil then + local typ = type(f) + if typ == "string" then + if f == "" then + -- new, can save a bit sometimes + -- if trace_context then + -- report_context("empty argument to context()") + -- end + elseif a then + flush(contentcatcodes,formatters[f](a,...)) -- was currentcatcodes + -- flush(contentcatcodes,splitformatters[f](a,...)) -- was currentcatcodes + elseif processlines and lpegmatch(containseol,f) then + local flushlines = parent.__flushlines or flushlines + flushlines(f) + else + flush(contentcatcodes,f) + end + elseif typ == "number" then + if a then + flush(currentcatcodes,f,a,...) + else + flush(currentcatcodes,f) + end + elseif typ == "function" then + -- ignored: a ... + flush(currentcatcodes,"{\\cldf{",storefunction(f),"}}") -- todo: ctx|prt|texcatcodes + elseif typ == "boolean" then + if f then + if a ~= nil then + local flushlines = parent.__flushlines or flushlines + flushlines(a) + else + flushdirect(currentcatcodes,"\n") -- no \r, else issues with \startlines ... use context.par() otherwise + end + else + if a ~= nil then + -- no command, same as context(a,...) + writer(parent,"",a,...) + else + -- ignored + end + end + elseif typ == "thread" then + report_context("coroutines not supported as we cannot yield across boundaries") + elseif isnode(f) then -- slow + -- writenode(f) + flush(currentcatcodes,"\\cldn{",storenode(f),"}") + else + report_context("error: %a gets a weird argument %a","context",f) + end + end + end + + function context.flushnode(n) + flush(currentcatcodes,"\\cldn{",storenode(n),"}") + end + +end + +local defaultcaller = caller + +setmetatable(context, { __index = indexer, __call = caller } ) + +-- now we tweak unprotect and protect + +function context.unprotect() + -- at the lua end + insert(catcodestack,currentcatcodes) + currentcatcodes = prtcatcodes + contentcatcodes = currentcatcodes + -- at the tex end + flush("\\unprotect") +end + +function context.protect() + -- at the tex end + flush("\\protect") + -- at the lua end + currentcatcodes = remove(catcodestack) or currentcatcodes + contentcatcodes = currentcatcodes +end + +function context.sprint(...) -- takes catcodes as first argument + flush(...) +end + +function context.fprint(catcodes,fmt,first,...) + if type(catcodes) == "number" then + if first then + flush(catcodes,formatters[fmt](first,...)) + else + flush(catcodes,fmt) + end + else + if fmt then + flush(formatters[catcodes](fmt,first,...)) + else + flush(catcodes) + end + end +end + +function tex.fprint(fmt,first,...) -- goodie + if first then + flush(currentcatcodes,formatters[fmt](first,...)) + else + flush(currentcatcodes,fmt) + end +end + +-- logging + +local trace_stack = { } + +local normalflush = flush +local normalflushdirect = flushdirect +----- normalflushraw = flushraw +local normalwriter = writer +local currenttrace = nil +local nofwriters = 0 +local nofflushes = 0 + +local visualizer = lpeg.replacer { + { "\n","<<newline>>" }, + { "\r","<<par>>" }, +} + +statistics.register("traced context", function() + local used, freed = usedstack() + local unreachable = used - freed + if nofwriters > 0 or nofflushes > 0 then + return format("writers: %s, flushes: %s, maxstack: %s",nofwriters,nofflushes,used,freed,unreachable) + elseif showstackusage or unreachable > 0 then + return format("maxstack: %s, freed: %s, unreachable: %s",used,freed,unreachable) + end +end) + +local tracedwriter = function(parent,...) -- also catcodes ? + nofwriters = nofwriters + 1 + local savedflush = flush + local savedflushdirect = flushdirect -- unlikely to be used here + local t, n = { "w : - : " }, 1 + local traced = function(normal,catcodes,...) -- todo: check for catcodes + local s = concat({...}) + s = lpegmatch(visualizer,s) + n = n + 1 + t[n] = s + normal(catcodes,...) + end + flush = function(...) traced(normalflush, ...) end + flushdirect = function(...) traced(normalflushdirect,...) end + normalwriter(parent,...) + flush = savedflush + flushdirect = savedflushdirect + currenttrace(concat(t)) +end + +-- we could reuse collapsed + +local traced = function(normal,one,two,...) + nofflushes = nofflushes + 1 + if two then + -- only catcodes if 'one' is number + normal(one,two,...) + local catcodes = type(one) == "number" and one + local arguments = catcodes and { two, ... } or { one, two, ... } + local collapsed, c = { formatters["f : %s : "](catcodes or '-') }, 1 + for i=1,#arguments do + local argument = arguments[i] + local argtype = type(argument) + c = c + 1 + if argtype == "string" then + collapsed[c] = lpegmatch(visualizer,argument) + elseif argtype == "number" then + collapsed[c] = argument + else + collapsed[c] = formatters["<<%S>>"](argument) + end + end + currenttrace(concat(collapsed)) + else + -- no catcodes + normal(one) + local argtype = type(one) + if argtype == "string" then + currenttrace(formatters["f : - : %s"](lpegmatch(visualizer,one))) + elseif argtype == "number" then + currenttrace(formatters["f : - : %s"](one)) + else + currenttrace(formatters["f : - : <<%S>>"](one)) + end + end +end + +local tracedflush = function(...) traced(normalflush, ...) end +local tracedflushdirect = function(...) traced(normalflushdirect,...) end + +local function pushlogger(trace) + trace = trace or report_context + insert(trace_stack,currenttrace) + currenttrace = trace + -- + flush = tracedflush + flushdirect = tracedflushdirect + writer = tracedwriter + -- + context.__flush = flush + context.__flushdirect = flushdirect + -- + return flush, writer, flushdirect +end + +local function poplogger() + currenttrace = remove(trace_stack) + if not currenttrace then + flush = normalflush + flushdirect = normalflushdirect + writer = normalwriter + -- + context.__flush = flush + context.__flushdirect = flushdirect + end + return flush, writer, flushdirect +end + +local function settracing(v) + if v then + return pushlogger(report_context) + else + return poplogger() + end +end + +-- todo: share flushers so that we can define in other files + +trackers.register("context.trace",settracing) + +context.pushlogger = pushlogger +context.poplogger = poplogger +context.settracing = settracing + +-- -- untested, no time now: +-- +-- local tracestack, tracestacktop = { }, false +-- +-- function context.pushtracing(v) +-- insert(tracestack,tracestacktop) +-- if type(v) == "function" then +-- pushlogger(v) +-- v = true +-- else +-- pushlogger() +-- end +-- tracestacktop = v +-- settracing(v) +-- end +-- +-- function context.poptracing() +-- poplogger() +-- tracestacktop = remove(tracestack) or false +-- settracing(tracestacktop) +-- end + +function context.getlogger() + return flush, writer, flush_direct +end + +local trace_cld = false trackers.register("context.files", function(v) trace_cld = v end) + +do + + -- This is the most reliable way to deal with nested buffers and other + -- catcode sensitive data. + + local resolve = resolvers.savers.byscheme + local validstring = string.valid + local input = context.input + + local function viafile(data,tag) + if data and data ~= "" then + local filename = resolve("virtual",validstring(tag,"viafile"),data) + -- context.startregime { "utf" } + context.input(filename) + -- context.stopregime() + end + end + + context.viafile = viafile + + -- experiment for xtables, don't use it elsewhere yet + + local collected = nil + local nofcollected = 0 + local sentinel = string.char(26) -- endoffileasciicode : ignorecatcode + local level = 0 + + local function collect(c,...) -- can be optimized + -- snippets + for i=1,select("#",...) do + nofcollected = nofcollected + 1 + collected[nofcollected] = (select(i,...)) + end + end + + -- local function collectdirect(c,...) -- can be optimized + -- -- lines + -- for i=1,select("#",...) do + -- n = n + 1 + -- t[n] = (select(i,...)) + -- n = n + 1 + -- t[n] = "\r" + -- end + -- end + + local collectdirect = collect + + function context.startcollecting() + if level == 0 then + collected = { } + nofcollected = 0 + -- + flush = collect + flushdirect = collectdirect + -- + context.__flush = flush + context.__flushdirect = flushdirect + end + level = level + 1 + end + + function context.stopcollecting() + level = level - 1 + if level < 1 then + flush = normalflush + flushdirect = normalflushdirect + -- + context.__flush = flush + context.__flushdirect = flushdirect + -- + viafile(concat(collected,sentinel)) + -- + collected = nil + nofcollected = 0 + level = 0 + end + end + +end + +-- + +function context.runfile(filename) + local foundname = resolvers.findtexfile(file.addsuffix(filename,"cld")) or "" + if foundname ~= "" then + local ok = dofile(foundname) + if type(ok) == "function" then + if trace_cld then + report_context("begin of file %a (function call)",foundname) + end + ok() + if trace_cld then + report_context("end of file %a (function call)",foundname) + end + elseif ok then + report_context("file %a is processed and returns true",foundname) + else + report_context("file %a is processed and returns nothing",foundname) + end + else + report_context("unknown file %a",filename) + end +end + +function context.loadfile(filename) + context(stripstring(loaddata(resolvers.findfile(filename)))) +end + +-- some functions + +function context.direct(first,...) + if first ~= nil then + return writer(context,"",first,...) + end +end + +-- context.delayed (todo: lines) + +local delayed = { } context.delayed = delayed -- creates function (maybe also store them) + +local function indexer(parent,k) + local f = function(...) + local a = { ... } + return function() + return context[k](unpack(a)) + end + end + parent[k] = f + return f +end + +local function caller(parent,...) -- todo: nodes + local a = { ... } + return function() + return context(unpack(a)) + end +end + +-- local function indexer(parent,k) +-- local f = function(a,...) +-- if not a then +-- return function() +-- return context[k]() +-- end +-- elseif select("#",...) == 0 then +-- return function() +-- return context[k](a) +-- end +-- elseif a then +-- local t = { ... } +-- return function() +-- return context[k](a,unpack(t)) +-- end +-- end +-- end +-- parent[k] = f +-- return f +-- end +-- +-- local function caller(parent,a,...) -- todo: nodes +-- if not a then +-- return function() +-- return context() +-- end +-- elseif select("#",...) == 0 then +-- return function() +-- return context(a) +-- end +-- elseif a then +-- local t = { ... } +-- return function() +-- return context(a,unpack(t)) +-- end +-- end +-- end + +setmetatable(delayed, { __index = indexer, __call = caller } ) + +-- context.nested (todo: lines) + +local nested = { } context.nested = nested -- creates strings + +local function indexer(parent,k) + local f = function(...) + local t, savedflush, n = { }, flush, 0 + flush = function(c,f,s,...) -- catcodes are ignored + n = n + 1 + t[n] = s and concat{f,s,...} or f -- optimized for #args == 1 + end + context[k](...) + flush = savedflush + return concat(t) + end + parent[k] = f + return f +end + +local function caller(parent,...) + local t, savedflush, n = { }, flush, 0 + flush = function(c,f,s,...) -- catcodes are ignored + n = n + 1 + t[n] = s and concat{f,s,...} or f -- optimized for #args == 1 + end + context(...) + flush = savedflush + return concat(t) +end + +setmetatable(nested, { __index = indexer, __call = caller } ) + +-- verbatim + +function context.newindexer(catcodes) + local handler = { } + + local function indexer(parent,k) + local command = context[k] + local f = function(...) + local savedcatcodes = contentcatcodes + contentcatcodes = catcodes + command(...) + contentcatcodes = savedcatcodes + end + parent[k] = f + return f + end + + local function caller(parent,...) + local savedcatcodes = contentcatcodes + contentcatcodes = catcodes + defaultcaller(parent,...) + contentcatcodes = savedcatcodes + end + + setmetatable(handler, { __index = indexer, __call = caller } ) + + return handler +end + +context.verbatim = context.newindexer(vrbcatcodes) +context.puretext = context.newindexer(txtcatcodes) +-------.protected = context.newindexer(prtcatcodes) + +-- formatted + +local formatted = { } context.formatted = formatted + +-- local function indexer(parent,k) +-- local command = context[k] +-- local f = function(fmt,...) +-- command(formatters[fmt](...)) +-- end +-- parent[k] = f +-- return f +-- end + +local function indexer(parent,k) + if type(k) == "string" then + local c = "\\" .. tostring(generics[k] or k) + local f = function(first,second,...) + if first == nil then + flush(currentcatcodes,c) + elseif second then + return writer(parent,c,formatters[first](second,...)) + else + return writer(parent,c,first) + end + end + parent[k] = f + return f + else + return context -- catch + end +end + +-- local function caller(parent,...) +-- context.fprint(...) +-- end + +local function caller(parent,catcodes,fmt,first,...) + if type(catcodes) == "number" then + if first then + flush(catcodes,formatters[fmt](first,...)) + else + flush(catcodes,fmt) + end + else + if fmt then + flush(formatters[catcodes](fmt,first,...)) + else + flush(catcodes) + end + end +end + +setmetatable(formatted, { __index = indexer, __call = caller } ) + +-- metafun (this will move to another file) + +local metafun = { } context.metafun = metafun + +local mpdrawing = "\\MPdrawing" + +local function caller(parent,f,a,...) + if not parent then + -- skip + elseif f then + local typ = type(f) + if typ == "string" then + if a then + flush(currentcatcodes,mpdrawing,"{",formatters[f](a,...),"}") + else + flush(currentcatcodes,mpdrawing,"{",f,"}") + end + elseif typ == "number" then + if a then + flush(currentcatcodes,mpdrawing,"{",f,a,...,"}") + else + flush(currentcatcodes,mpdrawing,"{",f,"}") + end + elseif typ == "function" then + -- ignored: a ... +-- flush(currentcatcodes,mpdrawing,"{\\cldf{",store_(f),"}}") + flush(currentcatcodes,mpdrawing,"{\\cldl",store_(f),"}") + elseif typ == "boolean" then + -- ignored: a ... + if f then + flush(currentcatcodes,mpdrawing,"{^^M}") + else + report_context("warning: %a gets argument 'false' which is currently unsupported","metafun") + end + else + report_context("error: %a gets a weird argument %a","metafun",tostring(f)) + end + end +end + +setmetatable(metafun, { __call = caller } ) + +function metafun.start() + context.resetMPdrawing() +end + +function metafun.stop() + context.MPdrawingdonetrue() + context.getMPdrawing() +end + +function metafun.color(name) + return formatters[ [[\MPcolor{%s}]] ](name) +end + +-- metafun.delayed + +local delayed = { } metafun.delayed = delayed + +local function indexer(parent,k) + local f = function(...) + local a = { ... } + return function() + return metafun[k](unpack(a)) + end + end + parent[k] = f + return f +end + + +local function caller(parent,...) + local a = { ... } + return function() + return metafun(unpack(a)) + end +end + +setmetatable(delayed, { __index = indexer, __call = caller } ) + +-- helpers: + +function context.concat(...) + context(concat(...)) +end + +local p_texescape = patterns.texescape + +function context.escaped(s) + return lpegmatch(p_texescape,s) or s +end + +-- templates + +local single = lpegP("%") +local double = lpegP("%%") +local lquoted = lpegP("%[") +local rquoted = lpegP("]%") + +local start = [[ +local texescape = lpeg.patterns.texescape +local lpegmatch = lpeg.match +return function(variables) return +]] + +local stop = [[ +end +]] + +local replacer = lpegP { "parser", + parser = lpegCs(lpegCc(start) * lpegV("step") * (lpegCc("..") * lpegV("step"))^0 * lpegCc(stop)), + unquoted = (lquoted/'') * ((lpegC((1-rquoted)^1)) / "lpegmatch(texescape,variables['%0'] or '')" ) * (rquoted/''), + escape = double/'%%', + key = (single/'') * ((lpegC((1-single)^1)) / "(variables['%0'] or '')" ) * (single/''), + step = lpegV("unquoted") + + lpegV("escape") + + lpegV("key") + + lpegCc("\n[===[") * (1 - lpegV("unquoted") - lpegV("escape") - lpegV("key"))^1 * lpegCc("]===]\n"), +} + +local templates = { } + +local function indexer(parent,k) + local v = lpegmatch(replacer,k) + if not v then + v = "error: no valid template (1)" + else + v = loadstring(v) + if type(v) ~= "function" then + v = "error: no valid template (2)" + else + v = v() + if not v then + v = "error: no valid template (3)" + end + end + end + if type(v) == "function" then + local f = function(first,second) + if second then + pushcatcodes(first) + flushlines(v(second)) + popcatcodes() + else + flushlines(v(first)) + end + end + parent[k] = f + return f + else + return function() + flush(v) + end + end + +end + +local function caller(parent,k,...) + return parent[k](...) +end + +setmetatable(templates, { __index = indexer, __call = caller } ) + +function context.template(template,...) + context(templates[template](...)) +end + +context.templates = templates + +-- The above is a bit over the top as we could also stick to a simple context.replace +-- which is fast enough anyway, but the above fits in nicer, also with the catcodes. +-- +-- local replace = utilities.templates.replace +-- +-- function context.template(template,variables) +-- context(replace(template,variables)) +-- end |