summaryrefslogtreecommitdiff
path: root/tex/context/base/cldf-ini.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/cldf-ini.lua')
-rw-r--r--tex/context/base/cldf-ini.lua746
1 files changed, 745 insertions, 1 deletions
diff --git a/tex/context/base/cldf-ini.lua b/tex/context/base/cldf-ini.lua
index 89c5bfe58..42333a65f 100644
--- a/tex/context/base/cldf-ini.lua
+++ b/tex/context/base/cldf-ini.lua
@@ -6,4 +6,748 @@ if not modules then modules = { } end modules ['cldf-ini'] = {
license = "see context related readme files"
}
--- This is a placeholder, maybe mult-cld moves here.
+-- This is 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.
+--
+-- Todo: optional checking against interface
+-- Todo: coroutine trickery
+-- Todo: maybe use txtcatcodes
+
+-- tflush needs checking ... sort of weird that it's not a table
+
+-- __flushlines is an experiment and rather ugly so it will go away
+
+context = context or { }
+local context = context
+
+local format, find, gmatch, splitlines = string.format, string.find, string.gmatch, string.splitlines
+local next, type, tostring, setmetatable = next, type, tostring, setmetatable
+local insert, remove, concat = table.insert, table.remove, table.concat
+local lpegmatch = lpeg.match
+
+local tex = tex
+
+local texsprint = tex.sprint
+local textprint = tex.tprint
+local texprint = tex.print
+local texiowrite = texio.write
+local texcount = tex.count
+
+local isnode = node.is_node -- after 0.65 just node.type
+local writenode = node.write
+local copynodelist = node.copylist
+
+local ctxcatcodes = tex.ctxcatcodes
+local prtcatcodes = tex.prtcatcodes
+local texcatcodes = tex.texcatcodes
+local txtcatcodes = tex.txtcatcodes
+local vrbcatcodes = tex.vrbcatcodes
+local xmlcatcodes = tex.xmlcatcodes
+
+local flush = texsprint
+
+local report_context = logs.new("context") -- here
+local report_cld = logs.new("cld")
+
+local processlines = true -- experiments.register("context.processlines", function(v) processlines = v end)
+
+-- for tracing it's easier to have two stacks
+
+local _stack_f_, _n_f_ = { }, 0
+local _stack_n_, _n_n_ = { }, 0
+
+local function _store_f_(ti)
+ _n_f_ = _n_f_ + 1
+ _stack_f_[_n_f_] = ti
+ return _n_f_
+end
+
+local function _store_n_(ti)
+ _n_n_ = _n_n_ + 1
+ _stack_n_[_n_n_] = ti
+ return _n_n_
+end
+
+local function _flush_f_(n)
+ local sn = _stack_f_[n]
+ if not sn then
+ report_cld("data with id %s cannot be found on stack",n)
+ else
+ local tn = type(sn)
+ if tn == "function" then
+ if not sn() and texcount["@@trialtypesetting"] == 0 then -- @@trialtypesetting is private!
+ _stack_f_[n] = nil
+ else
+ -- keep, beware, that way the stack can grow
+ end
+ else
+ if texcount["@@trialtypesetting"] == 0 then -- @@trialtypesetting is private!
+ writenode(sn)
+ _stack_f_[n] = nil
+ else
+ writenode(copynodelist(sn))
+ -- keep, beware, that way the stack can grow
+ end
+ end
+ end
+end
+
+local function _flush_n_(n)
+ local sn = _stack_n_[n]
+ if not sn then
+ report_cld("data with id %s cannot be found on stack",n)
+ elseif texcount["@@trialtypesetting"] == 0 then -- @@trialtypesetting is private!
+ writenode(sn)
+ _stack_n_[n] = nil
+ else
+ writenode(copynodelist(sn))
+ -- keep, beware, that way the stack can grow
+ end
+end
+
+function context.restart()
+ _stack_f_, _n_f_ = { }, 0
+ _stack_n_, _n_n_ = { }, 0
+end
+
+context._stack_f_ = _stack_f_
+context._store_f_ = _store_f_
+context._flush_f_ = _flush_f_ cldff = _flush_f_
+
+context._stack_n_ = _stack_n_
+context._store_n_ = _store_n_
+context._flush_n_ = _flush_n_ cldfn = _flush_n_
+
+-- 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,
+}
+
+function context.pushcatcodes(c)
+ insert(catcodestack,currentcatcodes)
+ currentcatcodes = (c and catcodes[c] or tonumber(c)) or currentcatcodes
+ contentcatcodes = currentcatcodes
+end
+
+function context.popcatcodes()
+ currentcatcodes = remove(catcodestack) or currentcatcodes
+ contentcatcodes = currentcatcodes
+end
+
+function tex.fprint(...) -- goodie
+ texsprint(currentcatcodes,format(...))
+end
+
+-- -- -- todo: tracing
+
+local newline = lpeg.patterns.newline
+local space = lpeg.patterns.spacer
+local spacing = newline * space^0
+local content = lpeg.C((1-spacing)^1)
+local emptyline = space^0 * newline^2
+local endofline = space^0 * newline * space^0
+local simpleline = endofline * lpeg.P(-1)
+
+local function n_content(s)
+ flush(contentcatcodes,s)
+end
+
+local function n_endofline()
+ texsprint(" \r")
+end
+
+local function n_emptyline()
+ texprint("\r")
+end
+
+local function n_simpleline()
+ texprint("\r")
+end
+
+function lpeg.texlinesplitter(f_content,f_endofline,f_emptyline,f_simpleline)
+ local splitlines =
+ simpleline / (f_simpleline or n_simpleline)
+ + (
+ emptyline / (f_emptyline or n_emptyline)
+ + endofline / (f_endofline or n_emptyline)
+ + content / (f_content or n_content)
+ )^0
+ return function(str) return lpegmatch(splitlines,str) end
+end
+
+local flushlines = lpeg.texlinesplitter(n_content,n_endofline,n_emptyline,n_simpleline)
+
+context.__flushlines = flushlines -- maybe context.helpers.flushtexlines
+context.__flush = flush
+
+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)
+ if raw then
+ lpegmatch(printlines_raw,str)
+ else
+ lpegmatch(printlines_ctx,str)
+ end
+end
+
+-- -- --
+
+local methodhandler = resolvers.methodhandler
+
+function context.viafile(data)
+ -- this is the only way to deal with nested buffers
+ -- and other catcode sensitive data
+ local filename = resolvers.savers.byscheme("virtual","viafile",data)
+ context.input(filename)
+end
+
+-- -- --
+
+local function writer(parent,command,first,...)
+ 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 '%s', only strings and numbers can be flushed directly, not '%s'",command,typ)
+ end
+ direct = false
+ elseif ti == nil then
+ -- nothing
+ elseif ti == "" then
+ flush(currentcatcodes,"{}")
+ elseif typ == "string" then
+ if processlines and find(ti,"[\n\r]") then -- we can check for ti == "\n"
+ 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
+ flush(currentcatcodes,"]")
+ elseif tn == 1 then -- some 20% faster than the next loop
+ local tj = ti[1]
+ if type(tj) == "function" then
+ flush(currentcatcodes,"[\\cldff{",_store_f_(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] = "\\cldff{" .. _store_f_(tj) .. "}"
+ end
+ end
+ flush(currentcatcodes,"[",concat(ti,","),"]")
+ end
+ elseif typ == "function" then
+ flush(currentcatcodes,"{\\cldff{",_store_f_(ti),"}}") -- todo: ctx|prt|texcatcodes
+ elseif typ == "boolean" then
+ if ti then
+ -- flush(currentcatcodes,"^^M")
+ texprint("")
+ 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,"{\\cldfn{",_store_n_(ti),"}}")
+ else
+ report_context("error: '%s' gets a weird argument '%s'",command,tostring(ti))
+ end
+ end
+end
+
+local generics = { } context.generics = generics
+
+local function indexer(parent,k)
+ 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
+end
+
+local function caller(parent,f,a,...)
+ if not parent then
+ -- so we don't need to test in the calling (slower but often no issue) (will go)
+ elseif f ~= nil then
+ local typ = type(f)
+ if typ == "string" then
+ if a then
+ flush(contentcatcodes,format(f,a,...)) -- was currentcatcodes
+ elseif processlines and find(f,"[\n\r]") 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,"{\\cldff{",_store_f_(f),"}}") -- todo: ctx|prt|texcatcodes
+ elseif typ == "boolean" then
+ if f then
+ if a ~= nil then
+ local flushlines = parent.__flushlines or flushlines
+ flushlines(f)
+ -- ignore ... maybe some day
+ else
+ -- flush(currentcatcodes,"^^M")
+ texprint("")
+ 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,"\\cldfn{",_store_n_(f),"}")
+ else
+ report_context("error: 'context' gets a weird argument '%s'",tostring(f))
+ end
+ 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
+
+-- logging
+
+local trace_stack = { }
+
+local normalflush = flush
+local normalwriter = writer
+local currenttrace = nil
+local nofwriters = 0
+local nofflushes = 0
+
+statistics.register("traced context", function()
+ if nofwriters > 0 or nofflushes > 0 then
+ return format("writers: %s, flushes: %s, maxstack: %s",nofwriters,nofflushes,_n_f_)
+ end
+end)
+
+local tracedwriter = function(parent,...)
+ nofwriters = nofwriters + 1
+ local t, f, n = { "w : " }, flush, 0
+ flush = function(...)
+ n = n + 1
+ t[n] = concat({...},"",2)
+ normalflush(...)
+ end
+ normalwriter(parent,...)
+ flush = f
+ currenttrace(concat(t))
+end
+
+local tracedflush = function(...)
+ nofflushes = nofflushes + 1
+ normalflush(...)
+ local t = { ... }
+ t[1] = "f : " -- replaces the catcode
+ for i=2,#t do
+ local ti = t[i]
+ local tt = type(ti)
+ if tt == "string" then
+ -- ok
+ elseif tt == "number" then
+ -- ok
+ else
+ t[i] = format("<%s>",tostring(ti))
+ end
+ -- currenttrace(format("%02i: %s",i-1,tostring(t[i])))
+ end
+ currenttrace(concat(t))
+end
+
+local function pushlogger(trace)
+ insert(trace_stack,currenttrace)
+ currenttrace = trace
+ flush, writer = tracedflush, tracedwriter
+ context.__flush = flush
+end
+
+local function poplogger()
+ currenttrace = remove(trace_stack)
+ if not currenttrace then
+ flush, writer = normalflush, normalwriter
+ context.__flush = flush
+ end
+end
+
+local function settracing(v)
+ if v then
+ pushlogger(report_context)
+ else
+ 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
+
+local trace_cld = false trackers.register("context.files", function(v) trace_cld = v 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 '%s' (function call)",foundname)
+ end
+ ok()
+ if trace_cld then
+ report_context("end of file '%s' (function call)",foundname)
+ end
+ elseif ok then
+ report_context("file '%s' is processed and returns true",foundname)
+ else
+ report_context("file '%s' is processed and returns nothing",foundname)
+ end
+ else
+ report_context("unknown file '%s'",filename)
+ end
+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 -- 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
+
+setmetatable(delayed, { __index = indexer, __call = caller } )
+
+-- context.nested (todo: lines)
+
+local nested = { } context.nested = nested
+
+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
+
+local verbatim = { } context.verbatim = verbatim
+
+local function indexer(parent,k)
+ local command = context[k]
+ local f = function(...)
+ local savedcatcodes = contentcatcodes
+ contentcatcodes = vrbcatcodes
+ command(...)
+ contentcatcodes = savedcatcodes
+ end
+ parent[k] = f
+ return f
+end
+
+local function caller(parent,...)
+ local savedcatcodes = contentcatcodes
+ contentcatcodes = vrbcatcodes
+ defaultcaller(parent,...)
+ contentcatcodes = savedcatcodes
+end
+
+setmetatable(verbatim, { __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,"{",format(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,"{\\cldff{",store_(f),"}}")
+ elseif typ == "boolean" then
+ -- ignored: a ...
+ if f then
+ flush(currentcatcodes,mpdrawing,"{^^M}")
+ else
+ report_context("warning: 'metafun' gets argument 'false' which is currently unsupported")
+ end
+ else
+ report_context("error: 'metafun' gets a weird argument '%s'",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 format([[\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 } )
+
+--~ Not that useful yet. Maybe something like this when the main loop
+--~ is a coroutine. It also does not help taking care of nested calls.
+--~ Even worse, it interferes with other mechanisms using context calls.
+--~
+--~ local create, yield, resume = coroutine.create, coroutine.yield, coroutine.resume
+--~ local getflush, setflush = context.getflush, context.setflush
+--~ local texsprint, ctxcatcodes = tex.sprint, tex.ctxcatcodes
+--~
+--~ function context.getflush()
+--~ return flush
+--~ end
+--~
+--~ function context.setflush(newflush)
+--~ local oldflush = flush
+--~ flush = newflush or flush
+--~ return oldflush
+--~ end
+--~
+--~ function context.direct(f)
+--~ local routine = create(f)
+--~ local oldflush = getflush()
+--~ function newflush(...)
+--~ oldflush(...)
+--~ yield(true)
+--~ end
+--~ setflush(newflush)
+--~
+--~ -- local function resumecontext()
+--~ -- local done = resume(routine)
+--~ -- if not done then
+--~ -- return
+--~ -- end
+--~ -- resumecontext() -- stack overflow ... no tail recursion
+--~ -- end
+--~ -- context.resume = resumecontext
+--~ -- texsprint(ctxcatcodes,"\\ctxlua{context.resume()}")
+--~
+--~ local function resumecontext()
+--~ local done = resume(routine)
+--~ if not done then
+--~ return
+--~ end
+--~ -- texsprint(ctxcatcodes,"\\exitloop")
+--~ texsprint(ctxcatcodes,"\\ctxlua{context.resume()}") -- can be simple macro call
+--~ end
+--~ context.resume = resumecontext
+--~ -- texsprint(ctxcatcodes,"\\doloop{\\ctxlua{context.resume()}}") -- can be fast loop at the tex end
+--~ texsprint(ctxcatcodes,"\\ctxlua{context.resume()}")
+--~
+--~ end
+--~
+--~ function something()
+--~ context("\\setbox0")
+--~ context("\\hbox{hans hagen xx}")
+--~ context("\\the\\wd0/\\box0")
+--~ end
+--~
+--~ context.direct(something)
+
+-- helpers:
+
+function context.concat(t,separator)
+ local done = false
+ for i=1,#t do
+ local ti = t[i]
+ if ti ~= "" then
+ if done then
+ context(separator)
+ end
+ context(ti)
+ done = true
+ end
+ end
+end