if not modules then modules = { } end modules ['util-seq'] = {
version = 1.001,
comment = "companion to luat-lib.mkiv",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
--[[ldx--
Here we implement a mechanism for chaining the special functions
that we use in to deal with mode list processing. We
assume that namespaces for the functions are used, but for speed we
use locals to refer to them when compiling the chain.
--ldx]]--
-- todo: delayed: i.e. we register them in the right order already but delay usage
-- todo: protect groups (as in tasks)
local gsub, gmatch = string.gsub, string.gmatch
local concat, sortedkeys = table.concat, table.sortedkeys
local type, load, next, tostring = type, load, next, tostring
utilities = utilities or { }
local tables = utilities.tables
local allocate = utilities.storage.allocate
local formatters = string.formatters
local replacer = utilities.templates.replacer
local trace_used = false
local trace_details = false
local report = logs.reporter("sequencer")
local usedcount = 0
local usednames = { }
trackers.register("sequencers.used", function(v) trace_used = v end)
trackers.register("sequencers.details", function(v) trace_details = v end)
local sequencers = { }
utilities.sequencers = sequencers
local functions = allocate()
sequencers.functions = functions
local removevalue = tables.removevalue
local replacevalue = tables.replacevalue
local insertaftervalue = tables.insertaftervalue
local insertbeforevalue = tables.insertbeforevalue
local usedsequences = { }
local function validaction(action)
if type(action) == "string" then
local g = _G
for str in gmatch(action,"[^%.]+") do
g = g[str]
if not g then
return false
end
end
end
return true
end
local compile
local known = { } -- just a convenience, in case we want public access (only to a few methods)
function sequencers.new(t) -- was reset
local s = {
list = { },
order = { },
kind = { },
askip = { },
gskip = { },
dirty = true,
runner = nil,
steps = 0,
}
if t then
s.arguments = t.arguments
s.templates = t.templates
s.returnvalues = t.returnvalues
s.results = t.results
local name = t.name
if name and name ~= "" then
s.name = name
known[name] = s
end
end
table.setmetatableindex(s,function(t,k)
-- this will automake a dirty runner
if k == "runner" then
local v = compile(t,t.compiler)
return v
end
end)
known[s] = s -- saves test for string later on
return s
end
function sequencers.prependgroup(t,group,where)
if t and group then
t = known[t]
if t then
local order = t.order
removevalue(order,group)
insertbeforevalue(order,where,group)
t.list[group] = { }
t.dirty = true
t.runner = nil
end
end
end
function sequencers.appendgroup(t,group,where)
if t and group then
t = known[t]
if t then
local order = t.order
removevalue(order,group)
insertaftervalue(order,where,group)
t.list[group] = { }
t.dirty = true
t.runner = nil
end
end
end
function sequencers.prependaction(t,group,action,where,kind,force)
if t and group and action then
t = known[t]
if t then
local g = t.list[group]
if g and (force or validaction(action)) then
removevalue(g,action)
insertbeforevalue(g,where,action)
t.kind[action] = kind
t.dirty = true
t.runner = nil
end
end
end
end
function sequencers.appendaction(t,group,action,where,kind,force)
if t and group and action then
t = known[t]
if t then
local g = t.list[group]
if g and (force or validaction(action)) then
removevalue(g,action)
insertaftervalue(g,where,action)
t.kind[action] = kind
t.dirty = true
t.runner = nil
end
end
end
end
function sequencers.enableaction(t,action)
if t and action then
t = known[t]
if t then
t.askip[action] = false
t.dirty = true
t.runner = nil
end
end
end
function sequencers.disableaction(t,action)
if t and action then
t = known[t]
if t then
t.askip[action] = true
t.dirty = true
t.runner = nil
end
end
end
function sequencers.enablegroup(t,group)
if t and group then
t = known[t]
if t then
t.gskip[group] = false
t.dirty = true
t.runner = nil
end
end
end
function sequencers.disablegroup(t,group)
if t and group then
t = known[t]
if t then
t.gskip[group] = true
t.dirty = true
t.runner = nil
end
end
end
function sequencers.setkind(t,action,kind)
if t and action then
t = known[t]
if t then
t.kind[action] = kind
t.dirty = true
t.runner = nil
end
end
end
function sequencers.removeaction(t,group,action,force)
if t and group and action then
t = known[t]
local g = t and t.list[group]
if g and (force or validaction(action)) then
removevalue(g,action)
t.dirty = true
t.runner = nil
end
end
end
function sequencers.replaceaction(t,group,oldaction,newaction,force)
if t and group and oldaction and newaction then
t = known[t]
if t then
local g = t.list[group]
if g and (force or validaction(oldaction)) then
replacevalue(g,oldaction,newaction)
t.dirty = true
t.runner = nil
end
end
end
end
local function localize(str)
return (gsub(str,"[%.: ]+","_"))
end
local function construct(t)
local list = t.list
local order = t.order
local kind = t.kind
local gskip = t.gskip
local askip = t.askip
local name = t.name or "?"
local arguments = t.arguments or "..."
local returnvalues = t.returnvalues
local results = t.results
local variables = { }
local calls = { }
local n = 0
usedcount = usedcount + 1
for i=1,#order do
local group = order[i]
if not gskip[group] then
local actions = list[group]
for i=1,#actions do
local action = actions[i]
if not askip[action] then
if trace_used then
local action = tostring(action)
report("%02i: category %a, group %a, action %a",usedcount,name,group,action)
usednames[action] = true
end
local localized
if type(action) == "function" then
local name = localize(tostring(action))
functions[name] = action
action = formatters["utilities.sequencers.functions.%s"](name)
localized = localize(name) -- shorter than action
else
localized = localize(action)
end
n = n + 1
variables[n] = formatters["local %s = %s"](localized,action)
if not returnvalues then
calls[n] = formatters["%s(%s)"](localized,arguments)
elseif n == 1 then
calls[n] = formatters["local %s = %s(%s)"](returnvalues,localized,arguments)
else
calls[n] = formatters["%s = %s(%s)"](returnvalues,localized,arguments)
end
end
end
end
end
t.dirty = false
t.steps = n
if n == 0 then
t.compiled = ""
else
variables = concat(variables,"\n")
calls = concat(calls,"\n")
if results then
t.compiled = formatters["%s\nreturn function(%s)\n%s\nreturn %s\nend"](variables,arguments,calls,results)
else
t.compiled = formatters["%s\nreturn function(%s)\n%s\nend"](variables,arguments,calls)
end
end
return t.compiled -- also stored so that we can trace
end
sequencers.tostring = construct
sequencers.localize = localize
compile = function(t,compiler,...) -- already referred to in sequencers.new
local compiled
if not t or type(t) == "string" then
return false
end
if compiler then
compiled = compiler(t,...)
t.compiled = compiled
else
compiled = construct(t,...)
end
local runner
if compiled == "" then
runner = false
else
runner = compiled and load(compiled)() -- we can use loadstripped here
end
t.runner = runner
return runner
end
sequencers.compile = compile
function sequencers.nodeprocessor(t,nofarguments)
--
local templates = nofarguments
--
if type(templates) ~= "table" then
return ""
end
--
local replacers = { }
for k, v in next, templates do
replacers[k] = replacer(v)
end
--
local construct = replacers.process
local step = replacers.step
if not construct or not step then
return ""
end
--
local calls = { }
local aliases = { }
local ncalls = 0
local naliases = 0
local f_alias = formatters["local %s = %s"]
--
local list = t.list
local order = t.order
local kind = t.kind
local gskip = t.gskip
local askip = t.askip
local name = t.name or "?"
local steps = 0
usedcount = usedcount + 1
--
if trace_details then
naliases = naliases + 1
aliases[naliases] = formatters["local report = logs.reporter('sequencer',%q)"](name)
ncalls = ncalls + 1
calls[ncalls] = [[report("start")]]
end
for i=1,#order do
local group = order[i]
if not gskip[group] then
local actions = list[group]
for i=1,#actions do
local action = actions[i]
if not askip[action] then
steps = steps + 1
if trace_used or trace_details then
local action = tostring(action)
report("%02i: category %a, group %a, action %a",usedcount,name,group,action)
usednames[action] = true
end
if trace_details then
ncalls = ncalls + 1
calls[ncalls] = formatters[ [[report(" step %a, action %a")]] ](steps,tostring(action))
end
local localized = localize(action)
local onestep = replacers[kind[action]] or step
naliases = naliases + 1
ncalls = ncalls + 1
aliases[naliases] = f_alias(localized,action)
calls [ncalls] = onestep { action = localized }
end
end
end
end
t.steps = steps
local processor
if steps == 0 then
processor = templates.default or construct { }
else
if trace_details then
ncalls = ncalls + 1
calls[ncalls] = [[report("stop")]]
end
processor = construct {
localize = concat(aliases,"\n"),
actions = concat(calls,"\n"),
}
end
-- processor = "print('running : " .. (t.name or "?") .. "')\n" .. processor
-- print(processor)
return processor
end
statistics.register("used sequences",function()
if next(usednames) then
return concat(sortedkeys(usednames)," ")
end
end)