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