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" } -- Here we implement a mechanism for chaining the special functions that we use in -- ConteXt 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. -- -- 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)