util-seq.lua /size: 12 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['util-seq'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.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--[[ldx--
10<p>Here we implement a mechanism for chaining the special functions
11that we use in <l n="context"> to deal with mode list processing. We
12assume that namespaces for the functions are used, but for speed we
13use locals to refer to them when compiling the chain.</p>
14--ldx]]--
15
16-- todo: delayed: i.e. we register them in the right order already but delay usage
17
18-- todo: protect groups (as in tasks)
19
20local gsub, gmatch = string.gsub, string.gmatch
21local concat, sortedkeys = table.concat, table.sortedkeys
22local type, load, next, tostring = type, load, next, tostring
23
24utilities               = utilities or { }
25local tables            = utilities.tables
26local allocate          = utilities.storage.allocate
27
28local formatters        = string.formatters
29local replacer          = utilities.templates.replacer
30
31local trace_used        = false
32local trace_details     = false
33local report            = logs.reporter("sequencer")
34local usedcount         = 0
35local usednames         = { }
36
37trackers.register("sequencers.used",    function(v) trace_used    = v end)
38trackers.register("sequencers.details", function(v) trace_details = v end)
39
40local sequencers        = { }
41utilities.sequencers    = sequencers
42
43local functions         = allocate()
44sequencers.functions    = functions
45
46local removevalue       = tables.removevalue
47local replacevalue      = tables.replacevalue
48local insertaftervalue  = tables.insertaftervalue
49local insertbeforevalue = tables.insertbeforevalue
50
51local usedsequences     = { }
52
53local function validaction(action)
54    if type(action) == "string" then
55        local g = _G
56        for str in gmatch(action,"[^%.]+") do
57            g = g[str]
58            if not g then
59                return false
60            end
61        end
62    end
63    return true
64end
65
66local compile
67
68local known = { } -- just a convenience, in case we want public access (only to a few methods)
69
70function sequencers.new(t) -- was reset
71    local s = {
72        list   = { },
73        order  = { },
74        kind   = { },
75        askip  = { },
76        gskip  = { },
77        dirty  = true,
78        runner = nil,
79        steps  = 0,
80    }
81    if t then
82        s.arguments    = t.arguments
83        s.templates    = t.templates
84        s.returnvalues = t.returnvalues
85        s.results      = t.results
86        local name     = t.name
87        if name and name ~= "" then
88            s.name      = name
89            known[name] = s
90        end
91    end
92    table.setmetatableindex(s,function(t,k)
93        -- this will automake a dirty runner
94        if k == "runner" then
95            local v = compile(t,t.compiler)
96            return v
97        end
98    end)
99    known[s] = s -- saves test for string later on
100    return s
101end
102
103function sequencers.prependgroup(t,group,where)
104    if t and group then
105        t = known[t]
106        if t then
107            local order = t.order
108            removevalue(order,group)
109            insertbeforevalue(order,where,group)
110            t.list[group] = { }
111            t.dirty       = true
112            t.runner      = nil
113        end
114    end
115end
116
117function sequencers.appendgroup(t,group,where)
118    if t and group then
119        t = known[t]
120        if t then
121            local order = t.order
122            removevalue(order,group)
123            insertaftervalue(order,where,group)
124            t.list[group] = { }
125            t.dirty       = true
126            t.runner      = nil
127        end
128    end
129end
130
131function sequencers.prependaction(t,group,action,where,kind,force)
132    if t and group and action then
133        t = known[t]
134        if t then
135            local g = t.list[group]
136            if g and (force or validaction(action)) then
137                removevalue(g,action)
138                insertbeforevalue(g,where,action)
139                t.kind[action] = kind
140                t.dirty        = true
141                t.runner       = nil
142            end
143        end
144    end
145end
146
147function sequencers.appendaction(t,group,action,where,kind,force)
148    if t and group and action then
149        t = known[t]
150        if t then
151            local g = t.list[group]
152            if g and (force or validaction(action)) then
153                removevalue(g,action)
154                insertaftervalue(g,where,action)
155                t.kind[action] = kind
156                t.dirty        = true
157                t.runner       = nil
158            end
159        end
160    end
161end
162
163function sequencers.enableaction(t,action)
164    if t and action then
165        t = known[t]
166        if t then
167            t.askip[action] = false
168            t.dirty         = true
169            t.runner        = nil
170        end
171    end
172end
173
174function sequencers.disableaction(t,action)
175    if t and action then
176        t = known[t]
177        if t then
178            t.askip[action] = true
179            t.dirty         = true
180            t.runner        = nil
181        end
182    end
183end
184
185function sequencers.enablegroup(t,group)
186    if t and group then
187        t = known[t]
188        if t then
189            t.gskip[group] = false
190            t.dirty        = true
191            t.runner       = nil
192        end
193    end
194end
195
196function sequencers.disablegroup(t,group)
197    if t and group then
198        t = known[t]
199        if t then
200            t.gskip[group] = true
201            t.dirty        = true
202            t.runner       = nil
203        end
204    end
205end
206
207function sequencers.setkind(t,action,kind)
208    if t and action then
209        t = known[t]
210        if t then
211            t.kind[action] = kind
212            t.dirty        = true
213            t.runner       = nil
214        end
215    end
216end
217
218function sequencers.removeaction(t,group,action,force)
219    if t and group and action then
220        t = known[t]
221        local g = t and t.list[group]
222        if g and (force or validaction(action)) then
223            removevalue(g,action)
224            t.dirty  = true
225            t.runner = nil
226        end
227    end
228end
229
230function sequencers.replaceaction(t,group,oldaction,newaction,force)
231    if t and group and oldaction and newaction then
232        t = known[t]
233        if t then
234            local g = t.list[group]
235            if g and (force or validaction(oldaction)) then
236                replacevalue(g,oldaction,newaction)
237                t.dirty  = true
238                t.runner = nil
239            end
240        end
241    end
242end
243
244local function localize(str)
245    return (gsub(str,"[%.: ]+","_"))
246end
247
248local function construct(t)
249    local list         = t.list
250    local order        = t.order
251    local kind         = t.kind
252    local gskip        = t.gskip
253    local askip        = t.askip
254    local name         = t.name or "?"
255    local arguments    = t.arguments or "..."
256    local returnvalues = t.returnvalues
257    local results      = t.results
258    local variables    = { }
259    local calls        = { }
260    local n            = 0
261    usedcount          = usedcount + 1
262    for i=1,#order do
263        local group = order[i]
264        if not gskip[group] then
265            local actions = list[group]
266            for i=1,#actions do
267                local action = actions[i]
268                if not askip[action] then
269                    if trace_used then
270                        local action = tostring(action)
271                        report("%02i: category %a, group %a, action %a",usedcount,name,group,action)
272                        usednames[action] = true
273                    end
274                    local localized
275                    if type(action) == "function" then
276                        local name = localize(tostring(action))
277                        functions[name] = action
278                        action = formatters["utilities.sequencers.functions.%s"](name)
279                        localized = localize(name) -- shorter than action
280                    else
281                        localized = localize(action)
282                    end
283                    n = n + 1
284                    variables[n] = formatters["local %s = %s"](localized,action)
285                    if not returnvalues then
286                        calls[n] = formatters["%s(%s)"](localized,arguments)
287                    elseif n == 1 then
288                        calls[n] = formatters["local %s = %s(%s)"](returnvalues,localized,arguments)
289                    else
290                        calls[n] = formatters["%s = %s(%s)"](returnvalues,localized,arguments)
291                    end
292                end
293            end
294        end
295    end
296    t.dirty = false
297    t.steps = n
298    if n == 0 then
299        t.compiled = ""
300    else
301        variables = concat(variables,"\n")
302        calls = concat(calls,"\n")
303        if results then
304            t.compiled = formatters["%s\nreturn function(%s)\n%s\nreturn %s\nend"](variables,arguments,calls,results)
305        else
306            t.compiled = formatters["%s\nreturn function(%s)\n%s\nend"](variables,arguments,calls)
307        end
308    end
309    return t.compiled -- also stored so that we can trace
310end
311
312sequencers.tostring = construct
313sequencers.localize = localize
314
315compile = function(t,compiler,...) -- already referred to in sequencers.new
316    local compiled
317    if not t or type(t) == "string" then
318        return false
319    end
320    if compiler then
321        compiled = compiler(t,...)
322        t.compiled = compiled
323    else
324        compiled = construct(t,...)
325    end
326    local runner
327    if compiled == "" then
328        runner = false
329    else
330        runner = compiled and load(compiled)() -- we can use loadstripped here
331    end
332    t.runner = runner
333    return runner
334end
335
336sequencers.compile = compile
337
338function sequencers.nodeprocessor(t,nofarguments)
339    --
340    local templates = nofarguments
341    --
342    if type(templates) ~= "table" then
343        return ""
344    end
345    --
346    local replacers = { }
347    for k, v in next, templates do
348        replacers[k] = replacer(v)
349    end
350    --
351    local construct = replacers.process
352    local step      = replacers.step
353    if not construct or not step then
354        return ""
355    end
356    --
357    local calls     = { }
358    local aliases   = { }
359    local ncalls    = 0
360    local naliases  = 0
361    local f_alias   = formatters["local %s = %s"]
362    --
363    local list  = t.list
364    local order = t.order
365    local kind  = t.kind
366    local gskip = t.gskip
367    local askip = t.askip
368    local name  = t.name or "?"
369    local steps = 0
370    usedcount   = usedcount + 1
371    --
372    if trace_details then
373        naliases = naliases + 1
374        aliases[naliases] = formatters["local report = logs.reporter('sequencer',%q)"](name)
375        ncalls = ncalls + 1
376        calls[ncalls] = [[report("start")]]
377    end
378    for i=1,#order do
379        local group = order[i]
380        if not gskip[group] then
381            local actions = list[group]
382            for i=1,#actions do
383                local action = actions[i]
384                if not askip[action] then
385                    steps = steps + 1
386                    if trace_used or trace_details then
387                        local action = tostring(action)
388                        report("%02i: category %a, group %a, action %a",usedcount,name,group,action)
389                        usednames[action] = true
390                    end
391                    if trace_details then
392                        ncalls = ncalls + 1
393                        calls[ncalls] = formatters[ [[report("  step %a, action %a")]] ](steps,tostring(action))
394                    end
395                    local localized = localize(action)
396                    local onestep   = replacers[kind[action]] or step
397                    naliases = naliases + 1
398                    ncalls   = ncalls + 1
399                    aliases[naliases] = f_alias(localized,action)
400                    calls  [ncalls]   = onestep { action = localized }
401                end
402            end
403        end
404    end
405    t.steps = steps
406    local processor
407    if steps == 0 then
408        processor = templates.default or construct { }
409    else
410        if trace_details then
411            ncalls = ncalls + 1
412            calls[ncalls] = [[report("stop")]]
413        end
414        processor = construct {
415            localize = concat(aliases,"\n"),
416            actions  = concat(calls,"\n"),
417        }
418    end
419 -- processor = "print('running : " .. (t.name or "?") .. "')\n" .. processor
420 -- print(processor)
421    return processor
422end
423
424statistics.register("used sequences",function()
425    if next(usednames) then
426        return concat(sortedkeys(usednames)," ")
427    end
428end)
429