node-ppt.lua /size: 12 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['node-ppt'] = {
2    version   = 1.001,
3    comment   = "companion to node-ini.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-- This is all very exeperimental and likely to change.
10
11local next, type, unpack, load = next, type, table.unpack, load
12
13local serialize = table.serialize
14local formatters = string.formatters
15
16local report           = logs.reporter("properties")
17local report_setting   = logs.reporter("properties","setting")
18local trace_setting    = false trackers.register("properties.setting", function(v) trace_setting = v end)
19
20-- report("using experimental properties")
21
22local nuts             = nodes.nuts
23local tonut            = nuts.tonut
24local tonode           = nuts.tonode
25local getid            = nuts.getid
26local getnext          = nuts.getnext
27local getprev          = nuts.getprev
28local getsubtype       = nuts.getsubtype
29local getlist          = nuts.getlist
30local setlist          = nuts.setlist
31local getprop          = nuts.getprop
32
33local removenode       = nuts.remove
34
35local nextnode         = nuts.traversers.node
36local nextwhatsit      = nuts.traversers.whatsit
37
38local nodecodes        = nodes.nodecodes
39local whatsitcodes     = nodes.whatsitcodes
40
41local whatsit_code     = nodecodes.whatsit
42local hlist_code       = nodecodes.hlist
43local vlist_code       = nodecodes.vlist
44
45local userdefinedwhatsit_code = whatsitcodes.userdefined
46
47local nodepool         = nodes.pool
48local new_usernode     = nodepool.usernode
49
50local variables        = interfaces.variables
51local v_before         = variables.before
52local v_after          = variables.after
53local v_here           = variables.here
54
55local property_id      = nodepool.userids["property"]
56
57local properties       = nodes.properties
58local propertydata     = properties.data
59
60local starttiming      = statistics.starttiming
61local stoptiming       = statistics.stoptiming
62
63if not propertydata then
64    return
65end
66
67-- management
68
69local function register(where,data,...)
70    if not data then
71        data  = where
72        where = v_after
73    end
74    if data then
75        nofslots = nofslots + 1
76        return new_usernode(property_id,{ where, data, ... })
77    end
78end
79
80local writenode = nodes.write
81local flushnode = context.nodes.flush
82
83function commands.deferredproperty(...)
84--  context(register(...))
85    flushnode(register(...))
86end
87
88function commands.immediateproperty(...)
89    writenode(register(...))
90end
91
92commands.attachproperty = commands.deferredproperty
93
94local actions = { } properties.actions = actions
95
96table.setmetatableindex(actions,function(t,k)
97    report("unknown property action %a",k)
98    return function() end
99end)
100
101local f_delayed   = formatters["return function(target,head,where,propdata,parent) %s end"]
102local f_immediate = formatters["return function(target,head,where,propdata) %s end"]
103
104local nofdelayed  = 0 -- better is to keep track of it per page ... we can have deleted nodes with properties
105local nofslots    = 0
106
107function actions.delayed(target,head,where,propdata,code,...) -- this one is used at the tex end
108    if code then
109        local delayed = propdata.delayed
110        if delayed then
111            delayed[#delayed+1] = { where, code, ... }
112        else
113            propdata.delayed = { { where, code, ... } }
114            nofdelayed = nofdelayed + 1
115        end
116    end
117end
118
119function actions.fdelayed(target,head,where,propdata,code,...) -- this one is used at the tex end
120    if code then
121        local delayed = propdata.delayed
122        if delayed then
123            delayed[#delayed+1] = { false, code, ... }
124        else
125            propdata.delayed = { { false, code, ... } }
126            nofdelayed = nofdelayed + 1
127        end
128    end
129end
130
131function actions.immediate(target,head,where,propdata,code,...) -- this one is used at the tex end
132    local kind = type(code)
133    if kind == "string" then
134        local f = f_immediate(code)
135        local okay, err = load(f)
136        if okay then
137            local h = okay()(target,head,where,propdata,...)
138            if h and h ~= head then
139                return h
140            end
141        end
142    elseif kind == "function" then
143        local h = code()(target,head,where,propdata,...)
144        if h and h ~= head then
145            return h
146        end
147    end
148end
149
150local function delayed(head,parent) -- direct based
151    for target, id in nextnode, head do
152        local p = propertydata[target]
153        if p then
154            local delayed = p.delayed
155            if delayed then
156                for i=1,#delayed do
157                    local d = delayed[i]
158                    local code  = d[2]
159                    local kind = type(code)
160                    if kind == "string" then
161                        code, err = load(f_delayed(code))
162                        if code then
163                            code = code()
164                        end
165                    end
166                    local where = d[1]
167                    if where then
168                        local h = code(target,where,head,p,parent,unpack(d,3)) -- target where propdata head parent
169                        if h and h ~= head then
170                            head = h
171                        end
172                    else
173                        code(unpack(d,3))
174                    end
175                end
176                p.delayed = nil
177                if nofdelayed == 1 then
178                    nofdelayed = 0
179                    return head
180                else
181                    nofdelayed = nofdelayed - 1
182                end
183            end
184        end
185        if id == hlist_code or id == vlist_code then
186            local list = getlist(target)
187            if list then
188                local done = delayed(list,parent)
189                if done then
190                    setlist(target,done)
191                end
192                if nofdelayed == 0 then
193                    return head
194                end
195            end
196        else
197            -- maybe also some more lists? but we will only use this for some
198            -- special cases .. who knows
199        end
200    end
201    return head
202end
203
204function properties.delayed(head) --
205    if nofdelayed > 0 then
206     -- if next(propertydata) then
207            starttiming(properties)
208            head = delayed(head)
209            stoptiming(properties)
210     -- else
211     --     delayed = 0
212     -- end
213    end
214    return head
215end
216
217-- more explicit ones too
218
219local anchored = {
220    [v_before] = function(n)
221        while n do
222            n = getprev(n)
223            if getid(n) == whatsit_code and getsubtype(n) == user_code and getprop(n,"id") == property_id then
224                -- continue
225            else
226                return n
227            end
228        end
229    end,
230    [v_after] = function(n)
231        while n do
232            n = getnext(n)
233            if getid(n) == whatsit_code then
234                local subtype = getsubtype(n)
235                if (subtype == userdefinedwhatsit_code and getprop(n,"id") == property_id) then
236                    -- continue
237                else
238                    return n
239                end
240            else
241                return n
242            end
243        end
244    end,
245    [v_here] = function(n)
246        -- todo
247    end,
248}
249
250table.setmetatableindex(anchored,function(t,k)
251    local v = anchored[v_after]
252    t[k] = v
253    return v
254end)
255
256function properties.attach(head)
257
258    if nofslots <= 0 then
259        return head
260    end
261
262    local last = nil
263
264    starttiming(properties)
265
266    for source, subtype in nextwhatsit, head do
267        if subtype == userdefinedwhatsit_code then
268            if last then
269                removenode(head,last,true)
270                last = nil
271            end
272            if getprop(source,"id") == property_id then
273                local data = getprop(source,"data")
274                if data then
275                    local where  = data[1]
276                    local target = anchored[where](source)
277                    if target then
278                        local first    = data[2]
279                        local method   = type(first)
280                        local p_target = propertydata[target]
281                        local p_source = propertydata[source]
282                        if p_target then
283                            if p_source then
284                                for k, v in next, p_source do
285                                    p_target[k] = v
286                                end
287                            end
288                            if method == "table" then
289                                for k, v in next, first do
290                                    p_target[k] = v
291                                end
292                            elseif method == "function" then
293                                first(target,head,where,p_target,unpack(data,3))
294                            elseif method == "string" then
295                                actions[first](target,head,where,p_target,unpack(data,3))
296                            end
297                        elseif p_source then
298                            if method == "table" then
299                                propertydata[target] = p_source
300                                for k, v in next, first do
301                                    p_source[k] = v
302                                end
303                            elseif method == "function" then
304                                propertydata[target] = p_source
305                                first(target,head,where,p_source,unpack(data,3))
306                            elseif method == "string" then
307                                propertydata[target] = p_source
308                                actions[first](target,head,where,p_source,unpack(data,3))
309                            end
310                        else
311                            if method == "table" then
312                                propertydata[target] = first
313                            elseif method == "function" then
314                                local t = { }
315                                propertydata[target] = t
316                                first(target,head,where,t,unpack(data,3))
317                            elseif method == "string" then
318                                local t = { }
319                                propertydata[target] = t
320                                actions[first](target,head,where,t,unpack(data,3))
321                            end
322                        end
323                        if trace_setting then
324                            report_setting("node %i, id %s, data %s",
325                                target,nodecodes[getid(target)],serialize(propertydata[target],false))
326                        end
327                    end
328                    if nofslots == 1 then
329                        nofslots = 0
330                        last = source
331                        break
332                    else
333                        nofslots = nofslots - 1
334                    end
335                end
336                last = source
337            end
338        end
339    end
340
341    if last then
342        removenode(head,last,true)
343    end
344
345    stoptiming(properties)
346
347    return head
348
349end
350
351-- maybe better hard coded in-place
352
353statistics.register("properties processing time", function()
354    return statistics.elapsedseconds(properties)
355end)
356
357-- only for development
358
359-- local tasks = nodes.tasks
360--
361-- local function show(head,level,report)
362--     for target in nextnode, head do
363--         local p = propertydata[target]
364--         if p then
365--             report("level %i, node %i, id %s, data %s",
366--                 level,target,nodecodes[getid(target)],serialize(propertydata[target],false))
367--         end
368--         local id = getid(target)
369--         if id == hlist_code or id == vlist_code then
370--             local list = getlist(target)
371--             if list then
372--                 show(list,level+1,report)
373--             end
374--         else
375--             -- maybe more lists
376--         end
377--     end
378--     return head, false
379-- end
380--
381-- local report_shipout    = logs.reporter("properties","shipout")
382-- local report_processors = logs.reporter("properties","processors")
383--
384-- function properties.showshipout   (head) return tonode(show(tonut(head),1,report_shipout   )), true end
385-- function properties.showprocessors(head) return tonode(show(tonut(head),1,report_processors)), true end
386--
387-- tasks.prependaction("shipouts","before","nodes.properties.showshipout")
388-- tasks.disableaction("shipouts","nodes.properties.showshipout")
389--
390-- trackers.register("properties.shipout",function(v)
391--     tasks.setaction("shipouts","nodes.properties.showshipout",v)
392-- end)
393--
394-- tasks.appendaction ("processors","after","nodes.properties.showprocessors")
395-- tasks.disableaction("processors","nodes.properties.showprocessors")
396--
397-- trackers.register("properties.processors",function(v)
398--     tasks.setaction("processors","nodes.properties.showprocessors",v)
399-- end)
400