node-res.lua /size: 18 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['node-res'] = {
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
9local type, next = type, next
10local gmatch, format = string.gmatch, string.format
11
12local nodes, node = nodes, node
13
14local report_nodes   = logs.reporter("nodes","housekeeping")
15
16nodes.pool           = nodes.pool or { }
17local nodepool       = nodes.pool
18
19local whatsitcodes   = nodes.whatsitcodes
20local gluecodes      = nodes.gluecodes
21local kerncodes      = nodes.kerncodes
22local rulecodes      = nodes.rulecodes
23local nodecodes      = nodes.nodecodes
24local boundarycodes  = nodes.boundarycodes
25local usercodes      = nodes.usercodes
26
27local nodeproperties = nodes.properties.data
28
29local glyph_code     = nodecodes.glyph
30local rule_code      = nodecodes.rule
31local kern_code      = nodecodes.kern
32local glue_code      = nodecodes.glue
33local gluespec_code  = nodecodes.gluespec
34local whatsit_code   = nodecodes.whatsit
35
36local currentfont    = font.current
37local texgetcount    = tex.getcount
38
39local allocate       = utilities.storage.allocate
40
41local reserved       = { }
42local nofreserved    = 0
43local userids        = allocate()
44local lastid         = 0
45
46setmetatable(userids, {
47    __index = function(t,k)
48        if type(k) == "string" then
49            lastid = lastid + 1
50            rawset(userids,lastid,k)
51            rawset(userids,k,lastid)
52            return lastid
53        else
54            rawset(userids,k,k)
55            return k
56        end
57    end,
58    __call = function(t,k)
59        return t[k]
60    end
61} )
62
63-- nuts overload
64
65local nuts         = nodes.nuts
66local nutpool      = { }
67nuts.pool          = nutpool
68
69local tonut        = nuts.tonut
70local tonode       = nuts.tonode
71
72local getbox       = nuts.getbox
73local getid        = nuts.getid
74local getlist      = nuts.getlist
75local getglue      = nuts.getglue
76
77local setfield     = nuts.setfield
78local setchar      = nuts.setchar
79local setlist      = nuts.setlist
80local setwhd       = nuts.setwhd
81local setglue      = nuts.setglue
82local setdisc      = nuts.setdisc
83local setfont      = nuts.setfont
84local setkern      = nuts.setkern
85local setpenalty   = nuts.setpenalty
86local setdir       = nuts.setdir
87local setdirection = nuts.setdirection
88local setshift     = nuts.setshift
89local setwidth     = nuts.setwidth
90local setsubtype   = nuts.setsubtype
91local setleader    = nuts.setleader
92
93local setdata      = nuts.setdata
94local setruledata  = nuts.setruledata
95local setvalue     = nuts.setvalue
96
97local copy_nut     = nuts.copy
98local new_nut      = nuts.new
99local flush_nut    = nuts.flush
100
101-- at some point we could have a dual set (the overhead of tonut is not much larger than
102-- metatable associations at the lua/c end esp if we also take assignments into account
103
104-- table.setmetatableindex(nodepool,function(t,k,v)
105--  -- report_nodes("defining nodepool[%s] instance",k)
106--     local f = nutpool[k]
107--     local v = function(...)
108--         return tonode(f(...))
109--     end
110--     t[k] = v
111--     return v
112-- end)
113--
114-- -- we delay one step because that permits us a forward reference
115-- -- e.g. in pdfsetmatrix
116
117table.setmetatableindex(nodepool,function(t,k,v)
118 -- report_nodes("defining nodepool[%s] instance",k)
119    local v = function(...)
120        local f = nutpool[k]
121        local v = function(...)
122            return tonode(f(...))
123        end
124        t[k] = v
125        return v(...)
126    end
127    t[k] = v
128    return v
129end)
130
131local function register_nut(n)
132    nofreserved = nofreserved + 1
133    reserved[nofreserved] = n
134    return n
135end
136
137local function register_node(n)
138    nofreserved = nofreserved + 1
139    if type(n) == "number" then -- isnut(n)
140        reserved[nofreserved] = n
141    else
142        reserved[nofreserved] = tonut(n)
143    end
144    return n
145end
146
147nodepool.userids  = userids
148nodepool.register = register_node
149
150nutpool.userids   = userids
151nutpool.register  = register_node -- could be register_nut
152
153-- so far
154
155local disc              = register_nut(new_nut(nodecodes.disc))
156local kern              = register_nut(new_nut(kern_code,kerncodes.userkern))
157local fontkern          = register_nut(new_nut(kern_code,kerncodes.fontkern))
158local italickern        = register_nut(new_nut(kern_code,kerncodes.italiccorrection))
159local penalty           = register_nut(new_nut(nodecodes.penalty))
160local glue              = register_nut(new_nut(glue_code))
161local gluespec          = register_nut(new_nut(gluespec_code))
162local glyph             = register_nut(new_nut(glyph_code,0))
163
164local textdir           = register_nut(new_nut(nodecodes.dir))
165
166local latelua           = register_nut(new_nut(whatsit_code,whatsitcodes.latelua))
167local savepos           = register_nut(new_nut(whatsit_code,whatsitcodes.savepos))
168
169local user_node         = new_nut(whatsit_code,whatsitcodes.userdefined)
170
171setfield(user_node,"type",usercodes.number)
172
173local left_margin_kern  = register_nut(new_nut(nodecodes.marginkern,0))
174local right_margin_kern = register_nut(new_nut(nodecodes.marginkern,1))
175
176local lineskip          = register_nut(new_nut(glue_code,gluecodes.lineskip))
177local baselineskip      = register_nut(new_nut(glue_code,gluecodes.baselineskip))
178local leftskip          = register_nut(new_nut(glue_code,gluecodes.leftskip))
179local rightskip         = register_nut(new_nut(glue_code,gluecodes.rightskip))
180local lefthangskip      = register_nut(new_nut(glue_code,gluecodes.lefthangskip))
181local righthangskip     = register_nut(new_nut(glue_code,gluecodes.righthangskip))
182local indentskip        = register_nut(new_nut(glue_code,gluecodes.indentskip))
183local correctionskip    = register_nut(new_nut(glue_code,gluecodes.correctionskip))
184
185local temp              = register_nut(new_nut(nodecodes.temp,0))
186
187local noad              = register_nut(new_nut(nodecodes.noad))
188local delimiter         = register_nut(new_nut(nodecodes.delimiter))
189local fence             = register_nut(new_nut(nodecodes.fence))
190local submlist          = register_nut(new_nut(nodecodes.submlist))
191local accent            = register_nut(new_nut(nodecodes.accent))
192local radical           = register_nut(new_nut(nodecodes.radical))
193local fraction          = register_nut(new_nut(nodecodes.fraction))
194local subbox            = register_nut(new_nut(nodecodes.subbox))
195local mathchar          = register_nut(new_nut(nodecodes.mathchar))
196local mathtextchar      = register_nut(new_nut(nodecodes.mathtextchar))
197local choice            = register_nut(new_nut(nodecodes.choice))
198
199local boundary          = register_nut(new_nut(nodecodes.boundary,boundarycodes.user))
200local wordboundary      = register_nut(new_nut(nodecodes.boundary,boundarycodes.word))
201
202local cleader           = register_nut(copy_nut(glue)) setsubtype(cleader,gluecodes.cleaders) setglue(cleader,0,65536,0,2,0)
203
204-- the dir field needs to be set otherwise crash:
205
206local lefttoright_code  = nodes.dirvalues.lefttoright
207
208local rule              = register_nut(new_nut(rule_code))                   setdirection(rule, lefttoright_code)
209local emptyrule         = register_nut(new_nut(rule_code,rulecodes.empty))   setdirection(rule, lefttoright_code)
210local userrule          = register_nut(new_nut(rule_code,rulecodes.user))    setdirection(rule, lefttoright_code)
211local outlinerule       = register_nut(new_nut(rule_code,rulecodes.outline)) setdirection(rule, lefttoright_code)
212local hlist             = register_nut(new_nut(nodecodes.hlist))             setdirection(hlist,lefttoright_code)
213local vlist             = register_nut(new_nut(nodecodes.vlist))             setdirection(vlist,lefttoright_code)
214
215function nutpool.glyph(fnt,chr)
216    local n = copy_nut(glyph)
217    if fnt then
218        setfont(n,fnt == true and currentfont() or fnt,chr)
219    elseif chr then
220        setchar(n,chr)
221    end
222    return n
223end
224
225function nutpool.penalty(p)
226    local n = copy_nut(penalty)
227    if p and p ~= 0 then
228        setpenalty(n,p)
229    end
230    return n
231end
232
233function nutpool.kern(k)
234    local n = copy_nut(kern)
235    if k and k ~= 0 then
236        setkern(n,k)
237    end
238    return n
239end
240
241function nutpool.boundary(v)
242    local n = copy_nut(boundary)
243    if v and v ~= 0 then
244        setvalue(n,v)
245    end
246    return n
247end
248
249function nutpool.wordboundary(v)
250    local n = copy_nut(wordboundary)
251    if v and v ~= 0 then
252        setvalue(n,v)
253    end
254    return n
255end
256
257function nutpool.fontkern(k)
258    local n = copy_nut(fontkern)
259    if k and k ~= 0 then
260        setkern(n,k)
261    end
262    return n
263end
264
265function nutpool.italickern(k)
266    local n = copy_nut(italickern)
267    if k and k ~= 0 then
268        setkern(n,k)
269    end
270    return n
271end
272
273function nutpool.gluespec(width,stretch,shrink,stretch_order,shrink_order)
274    local n = copy_nut(gluespec)
275    if width or stretch or shrink or stretch_order or shrink_order then
276        setglue(n,width,stretch,shrink,stretch_order,shrink_order)
277    end
278    return n
279end
280
281local function someskip(skip,width,stretch,shrink,stretch_order,shrink_order)
282    -- maybe setglue
283    local n = copy_nut(skip)
284    if width or stretch or shrink or stretch_order or shrink_order then
285        setglue(n,width,stretch,shrink,stretch_order,shrink_order)
286    end
287    return n
288end
289
290function nutpool.stretch(a,b)
291    -- width stretch shrink stretch_order shrink_order
292    local n = copy_nut(glue)
293    if not b then
294        a, b = 1, a or 1
295    end
296    setglue(n,0,a,0,b,0)
297    return n
298end
299
300function nutpool.shrink(a,b)
301    local n = copy_nut(glue)
302    if not b then
303        a, b = 1, a or 1
304    end
305    setglue(n,0,0,a,0,0,b)
306    return n
307end
308
309function nutpool.glue(width,stretch,shrink,stretch_order,shrink_order)
310    return someskip(glue,width,stretch,shrink,stretch_order,shrink_order)
311end
312
313function nutpool.negatedglue(glue)
314    local n = copy_nut(glue)
315    local width, stretch, shrink = getglue(n)
316    setglue(n,-width,-stretch,-shrink)
317    return n
318end
319
320function nutpool.leftskip(width,stretch,shrink,stretch_order,shrink_order)
321    return someskip(leftskip,width,stretch,shrink,stretch_order,shrink_order)
322end
323
324function nutpool.rightskip(width,stretch,shrink,stretch_order,shrink_order)
325    return someskip(rightskip,width,stretch,shrink,stretch_order,shrink_order)
326end
327
328function nutpool.lefthangskip(width,stretch,shrink,stretch_order,shrink_order)
329    return someskip(lefthangskip,width,stretch,shrink,stretch_order,shrink_order)
330end
331
332function nutpool.righthangskip(width,stretch,shrink,stretch_order,shrink_order)
333    return someskip(righthangskip,width,stretch,shrink,stretch_order,shrink_order)
334end
335
336function nutpool.indentskip(width,stretch,shrink,stretch_order,shrink_order)
337    return someskip(indentskip,width,stretch,shrink,stretch_order,shrink_order)
338end
339
340function nutpool.lineskip(width,stretch,shrink,stretch_order,shrink_order)
341    return someskip(lineskip,width,stretch,shrink,stretch_order,shrink_order)
342end
343
344function nutpool.baselineskip(width,stretch,shrink)
345    return someskip(baselineskip,width,stretch,shrink)
346end
347
348function nutpool.disc(pre,post,replace)
349    local d = copy_nut(disc)
350    if pre or post or replace then
351        setdisc(d,pre,post,replace)
352    end
353    return d
354end
355
356function nutpool.direction(dir,swap)
357    local t = copy_nut(textdir)
358    if not dir then
359        -- just a l2r start node
360    elseif swap then
361        setdirection(t,dir,true)
362    else
363        setdirection(t,dir,false)
364    end
365    return t
366end
367
368function nutpool.rule(width,height,depth) -- w/h/d == nil will let them adapt
369    local n = copy_nut(rule)
370    if width or height or depth then
371        setwhd(n,width,height,depth)
372    end
373    return n
374end
375
376function nutpool.emptyrule(width,height,depth) -- w/h/d == nil will let them adapt
377    local n = copy_nut(emptyrule)
378    if width or height or depth then
379        setwhd(n,width,height,depth)
380    end
381    return n
382end
383
384function nutpool.userrule(width,height,depth) -- w/h/d == nil will let them adapt
385    local n = copy_nut(userrule)
386    if width or height or depth then
387        setwhd(n,width,height,depth)
388    end
389    return n
390end
391
392function nutpool.outlinerule(width,height,depth,line) -- w/h/d == nil will let them adapt
393    local n = copy_nut(outlinerule)
394    if width or height or depth then
395        setwhd(n,width,height,depth)
396    end
397    if line then
398        setruledata(n,line)
399    end
400    return n
401end
402
403function nutpool.leader(width,list)
404    local n = copy_nut(cleader)
405    if width then
406        setwidth(n,width)
407    end
408    if list then
409        setleader(n,list)
410    end
411    return n
412end
413
414function nutpool.savepos()
415    return copy_nut(savepos)
416end
417
418function nutpool.latelua(code)
419    local n = copy_nut(latelua)
420    if type(code) == "table" then
421        local action        = code.action
422        local specification = code.specification or code
423        code = function() action(specification) end
424    end
425    setdata(n,code)
426    return n
427end
428
429function nutpool.leftmarginkern(glyph,width)
430    local n = copy_nut(left_margin_kern)
431    if not glyph then
432        report_nodes("invalid pointer to left margin glyph node")
433    elseif getid(glyph) ~= glyph_code then
434        report_nodes("invalid node type %a for %s margin glyph node",nodecodes[glyph],"left")
435    else
436        setfield(n,"glyph",glyph)
437    end
438    if width and width ~= 0 then
439        setwidth(n,width)
440    end
441    return n
442end
443
444function nutpool.rightmarginkern(glyph,width)
445    local n = copy_nut(right_margin_kern)
446    if not glyph then
447        report_nodes("invalid pointer to right margin glyph node")
448    elseif getid(glyph) ~= glyph_code then
449        report_nodes("invalid node type %a for %s margin glyph node",nodecodes[p],"right")
450    else
451        setfield(n,"glyph",glyph)
452    end
453    if width and width ~= 0 then
454        setwidth(n,width)
455    end
456    return n
457end
458
459function nutpool.temp()
460    return copy_nut(temp)
461end
462
463function nutpool.noad()         return copy_nut(noad)         end
464function nutpool.delimiter()    return copy_nut(delimiter)    end  nutpool.delim = nutpool.delimiter
465function nutpool.fence()        return copy_nut(fence)        end
466function nutpool.submlist()     return copy_nut(submlist)     end
467function nutpool.noad()         return copy_nut(noad)         end
468function nutpool.fence()        return copy_nut(fence)        end
469function nutpool.accent()       return copy_nut(accent)       end
470function nutpool.radical()      return copy_nut(radical)      end
471function nutpool.fraction()     return copy_nut(fraction)     end
472function nutpool.subbox()       return copy_nut(subbox)       end
473function nutpool.mathchar()     return copy_nut(mathchar)     end
474function nutpool.mathtextchar() return copy_nut(mathtextchar) end
475function nutpool.choice()       return copy_nut(choice)       end
476
477local function new_hlist(list,width,height,depth,shift)
478    local n = copy_nut(hlist)
479    if list then
480        setlist(n,list)
481    end
482    if width or height or depth then
483        setwhd(n,width,height,depth)
484    end
485    if shift and shift ~= 0 then
486        setshift(n,shift)
487    end
488    return n
489end
490
491local function new_vlist(list,width,height,depth,shift)
492    local n = copy_nut(vlist)
493    if list then
494        setlist(n,list)
495    end
496    if width or height or depth then
497        setwhd(n,width,height,depth)
498    end
499    if shift and shift ~= 0 then
500        setshift(n,shift)
501    end
502    return n
503end
504
505nutpool.hlist = new_hlist
506nutpool.vlist = new_vlist
507
508function nodepool.hlist(list,width,height,depth,shift)
509    return tonode(new_hlist(list and tonut(list),width,height,depth,shift))
510end
511
512function nodepool.vlist(list,width,height,depth,shift)
513    return tonode(new_vlist(list and tonut(list),width,height,depth,shift))
514end
515
516function nutpool.usernode(id,data)
517    local n = copy_nut(user_node)
518    nodeproperties[n] = {
519        id   = id,
520        data = data,
521    }
522    return n
523end
524
525-- housekeeping
526
527local function cleanup(nofboxes) -- todo
528    local tracers = nodes.tracers
529    if tracers and tracers.steppers then -- to be resolved
530        tracers.steppers.reset() -- todo: make a registration subsystem
531    end
532    local nl = 0
533    local nr = nofreserved
534    for i=1,nofreserved do
535        local ri = reserved[i]
536        flush_nut(reserved[i])
537    end
538    if nofboxes then
539        for i=0,nofboxes do
540            local l = getbox(i)
541            if l then
542                flush_nut(l) -- also list ?
543                nl = nl + 1
544            end
545        end
546    end
547    reserved    = { }
548    nofreserved = 0
549    return nr, nl, nofboxes -- can be nil
550end
551
552local function usage()
553    local t = { }
554    for n, tag in gmatch(status.node_mem_usage,"(%d+) ([a-z_]+)") do
555        t[tag] = tonumber(n) or 0
556    end
557    return t
558end
559
560local stock = { }
561
562nutpool .cleanup = cleanup
563nodepool.cleanup = cleanup
564
565nutpool .usage   = usage
566nodepool.usage   = usage
567
568nutpool .stock   = stock
569nodepool.stock   = stock
570
571-- end
572
573statistics.register("cleaned up reserved nodes", function()
574    return format("%s nodes, %s lists of %s", cleanup(texgetcount("c_syst_last_allocated_box")))
575end) -- \topofboxstack
576
577statistics.register("node memory usage", function() -- comes after cleanup !
578    local used = usage()
579    if next(used) then
580        local t, n = { }, 0
581        for k, v in table.sortedhash(used) do
582            n = n + 1 ; t[n] = format("%s %s",v,k)
583        end
584        return table.concat(t,", ")
585    end
586end)
587
588lua.registerinitexfinalizer(cleanup, "cleanup reserved nodes")
589
590-- experiment
591
592do
593
594    local glyph      = tonode(glyph)
595    local traverseid = nodes.traverseid
596
597    local traversers = table.setmetatableindex(function(t,k)
598        local v = traverseid(type(k) == "number" and k or nodecodes[k],glyph)
599        t[k] = v
600        return v
601    end)
602
603                                traversers.node  = nodes.traverse     (glyph)
604                                traversers.char  = nodes.traversechar (glyph)
605    if nodes.traverseglyph then traversers.glyph = nodes.traverseglyph(glyph) end
606    if nodes.traverselist  then traversers.list  = nodes.traverselist (glyph) end
607
608    nodes.traversers = traversers
609
610end
611
612do
613
614    local glyph      = glyph
615    local traverseid = nuts.traverseid
616
617    local traversers = table.setmetatableindex(function(t,k)
618        local v = traverseid(type(k) == "number" and k or nodecodes[k],glyph)
619        t[k] = v
620        return v
621    end)
622
623                                 traversers.node    = nuts.traverse       (glyph)
624                                 traversers.char    = nuts.traversechar   (glyph)
625    if nuts.traverseglyph   then traversers.glyph   = nuts.traverseglyph  (glyph) end
626    if nuts.traverselist    then traversers.list    = nuts.traverselist   (glyph) end
627    if nuts.traversecontent then traversers.content = nuts.traversecontent(glyph) end
628
629    nuts.traversers = traversers
630
631end
632