if not modules then modules = { } end modules ['node-res'] = { version = 1.001, comment = "companion to node-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } local type, next = type, next local gmatch, format = string.gmatch, string.format local nodes, node = nodes, node local report_nodes = logs.reporter("nodes","housekeeping") nodes.pool = nodes.pool or { } local nodepool = nodes.pool local whatsitcodes = nodes.whatsitcodes local gluecodes = nodes.gluecodes local kerncodes = nodes.kerncodes local rulecodes = nodes.rulecodes local nodecodes = nodes.nodecodes local boundarycodes = nodes.boundarycodes local usercodes = nodes.usercodes local nodeproperties = nodes.properties.data local glyph_code = nodecodes.glyph local rule_code = nodecodes.rule local kern_code = nodecodes.kern local glue_code = nodecodes.glue local gluespec_code = nodecodes.gluespec local whatsit_code = nodecodes.whatsit local currentfont = font.current local texgetcount = tex.getcount local allocate = utilities.storage.allocate local reserved = { } local nofreserved = 0 local userids = allocate() local lastid = 0 setmetatable(userids, { __index = function(t,k) if type(k) == "string" then lastid = lastid + 1 rawset(userids,lastid,k) rawset(userids,k,lastid) return lastid else rawset(userids,k,k) return k end end, __call = function(t,k) return t[k] end } ) -- nuts overload local nuts = nodes.nuts local nutpool = { } nuts.pool = nutpool local tonut = nuts.tonut local tonode = nuts.tonode local getbox = nuts.getbox local getid = nuts.getid local getlist = nuts.getlist local getglue = nuts.getglue local setfield = nuts.setfield local setchar = nuts.setchar local setlist = nuts.setlist local setwhd = nuts.setwhd local setglue = nuts.setglue local setdisc = nuts.setdisc local setfont = nuts.setfont local setkern = nuts.setkern local setpenalty = nuts.setpenalty local setdir = nuts.setdir local setdirection = nuts.setdirection local setshift = nuts.setshift local setwidth = nuts.setwidth local setsubtype = nuts.setsubtype local setleader = nuts.setleader local setdata = nuts.setdata local setruledata = nuts.setruledata local setvalue = nuts.setvalue local copy_nut = nuts.copy local new_nut = nuts.new local flush_nut = nuts.flush -- at some point we could have a dual set (the overhead of tonut is not much larger than -- metatable associations at the lua/c end esp if we also take assignments into account -- table.setmetatableindex(nodepool,function(t,k,v) -- -- report_nodes("defining nodepool[%s] instance",k) -- local f = nutpool[k] -- local v = function(...) -- return tonode(f(...)) -- end -- t[k] = v -- return v -- end) -- -- -- we delay one step because that permits us a forward reference -- -- e.g. in pdfsetmatrix table.setmetatableindex(nodepool,function(t,k,v) -- report_nodes("defining nodepool[%s] instance",k) local v = function(...) local f = nutpool[k] local v = function(...) return tonode(f(...)) end t[k] = v return v(...) end t[k] = v return v end) local function register_nut(n) nofreserved = nofreserved + 1 reserved[nofreserved] = n return n end local function register_node(n) nofreserved = nofreserved + 1 if type(n) == "number" then -- isnut(n) reserved[nofreserved] = n else reserved[nofreserved] = tonut(n) end return n end nodepool.userids = userids nodepool.register = register_node nutpool.userids = userids nutpool.register = register_node -- could be register_nut -- so far local disc = register_nut(new_nut(nodecodes.disc)) local kern = register_nut(new_nut(kern_code,kerncodes.userkern)) local fontkern = register_nut(new_nut(kern_code,kerncodes.fontkern)) local italickern = register_nut(new_nut(kern_code,kerncodes.italiccorrection)) local penalty = register_nut(new_nut(nodecodes.penalty)) local glue = register_nut(new_nut(glue_code)) local gluespec = register_nut(new_nut(gluespec_code)) local glyph = register_nut(new_nut(glyph_code,0)) local textdir = register_nut(new_nut(nodecodes.dir)) local latelua = register_nut(new_nut(whatsit_code,whatsitcodes.latelua)) local savepos = register_nut(new_nut(whatsit_code,whatsitcodes.savepos)) local user_node = new_nut(whatsit_code,whatsitcodes.userdefined) setfield(user_node,"type",usercodes.number) local left_margin_kern = register_nut(new_nut(nodecodes.marginkern,0)) local right_margin_kern = register_nut(new_nut(nodecodes.marginkern,1)) local lineskip = register_nut(new_nut(glue_code,gluecodes.lineskip)) local baselineskip = register_nut(new_nut(glue_code,gluecodes.baselineskip)) local leftskip = register_nut(new_nut(glue_code,gluecodes.leftskip)) local rightskip = register_nut(new_nut(glue_code,gluecodes.rightskip)) local lefthangskip = register_nut(new_nut(glue_code,gluecodes.lefthangskip)) local righthangskip = register_nut(new_nut(glue_code,gluecodes.righthangskip)) local indentskip = register_nut(new_nut(glue_code,gluecodes.indentskip)) local correctionskip = register_nut(new_nut(glue_code,gluecodes.correctionskip)) local temp = register_nut(new_nut(nodecodes.temp,0)) local noad = register_nut(new_nut(nodecodes.noad)) local delimiter = register_nut(new_nut(nodecodes.delimiter)) local fence = register_nut(new_nut(nodecodes.fence)) local submlist = register_nut(new_nut(nodecodes.submlist)) local accent = register_nut(new_nut(nodecodes.accent)) local radical = register_nut(new_nut(nodecodes.radical)) local fraction = register_nut(new_nut(nodecodes.fraction)) local subbox = register_nut(new_nut(nodecodes.subbox)) local mathchar = register_nut(new_nut(nodecodes.mathchar)) local mathtextchar = register_nut(new_nut(nodecodes.mathtextchar)) local choice = register_nut(new_nut(nodecodes.choice)) local boundary = register_nut(new_nut(nodecodes.boundary,boundarycodes.user)) local wordboundary = register_nut(new_nut(nodecodes.boundary,boundarycodes.word)) local cleader = register_nut(copy_nut(glue)) setsubtype(cleader,gluecodes.cleaders) setglue(cleader,0,65536,0,2,0) -- the dir field needs to be set otherwise crash: local lefttoright_code = nodes.dirvalues.lefttoright local rule = register_nut(new_nut(rule_code)) setdirection(rule, lefttoright_code) local emptyrule = register_nut(new_nut(rule_code,rulecodes.empty)) setdirection(rule, lefttoright_code) local userrule = register_nut(new_nut(rule_code,rulecodes.user)) setdirection(rule, lefttoright_code) local outlinerule = register_nut(new_nut(rule_code,rulecodes.outline)) setdirection(rule, lefttoright_code) local hlist = register_nut(new_nut(nodecodes.hlist)) setdirection(hlist,lefttoright_code) local vlist = register_nut(new_nut(nodecodes.vlist)) setdirection(vlist,lefttoright_code) function nutpool.glyph(fnt,chr) local n = copy_nut(glyph) if fnt then setfont(n,fnt == true and currentfont() or fnt,chr) elseif chr then setchar(n,chr) end return n end function nutpool.penalty(p) local n = copy_nut(penalty) if p and p ~= 0 then setpenalty(n,p) end return n end function nutpool.kern(k) local n = copy_nut(kern) if k and k ~= 0 then setkern(n,k) end return n end function nutpool.boundary(v) local n = copy_nut(boundary) if v and v ~= 0 then setvalue(n,v) end return n end function nutpool.wordboundary(v) local n = copy_nut(wordboundary) if v and v ~= 0 then setvalue(n,v) end return n end function nutpool.fontkern(k) local n = copy_nut(fontkern) if k and k ~= 0 then setkern(n,k) end return n end function nutpool.italickern(k) local n = copy_nut(italickern) if k and k ~= 0 then setkern(n,k) end return n end function nutpool.gluespec(width,stretch,shrink,stretch_order,shrink_order) local n = copy_nut(gluespec) if width or stretch or shrink or stretch_order or shrink_order then setglue(n,width,stretch,shrink,stretch_order,shrink_order) end return n end local function someskip(skip,width,stretch,shrink,stretch_order,shrink_order) -- maybe setglue local n = copy_nut(skip) if width or stretch or shrink or stretch_order or shrink_order then setglue(n,width,stretch,shrink,stretch_order,shrink_order) end return n end function nutpool.stretch(a,b) -- width stretch shrink stretch_order shrink_order local n = copy_nut(glue) if not b then a, b = 1, a or 1 end setglue(n,0,a,0,b,0) return n end function nutpool.shrink(a,b) local n = copy_nut(glue) if not b then a, b = 1, a or 1 end setglue(n,0,0,a,0,0,b) return n end function nutpool.glue(width,stretch,shrink,stretch_order,shrink_order) return someskip(glue,width,stretch,shrink,stretch_order,shrink_order) end function nutpool.negatedglue(glue) local n = copy_nut(glue) local width, stretch, shrink = getglue(n) setglue(n,-width,-stretch,-shrink) return n end function nutpool.leftskip(width,stretch,shrink,stretch_order,shrink_order) return someskip(leftskip,width,stretch,shrink,stretch_order,shrink_order) end function nutpool.rightskip(width,stretch,shrink,stretch_order,shrink_order) return someskip(rightskip,width,stretch,shrink,stretch_order,shrink_order) end function nutpool.lefthangskip(width,stretch,shrink,stretch_order,shrink_order) return someskip(lefthangskip,width,stretch,shrink,stretch_order,shrink_order) end function nutpool.righthangskip(width,stretch,shrink,stretch_order,shrink_order) return someskip(righthangskip,width,stretch,shrink,stretch_order,shrink_order) end function nutpool.indentskip(width,stretch,shrink,stretch_order,shrink_order) return someskip(indentskip,width,stretch,shrink,stretch_order,shrink_order) end function nutpool.lineskip(width,stretch,shrink,stretch_order,shrink_order) return someskip(lineskip,width,stretch,shrink,stretch_order,shrink_order) end function nutpool.baselineskip(width,stretch,shrink) return someskip(baselineskip,width,stretch,shrink) end function nutpool.disc(pre,post,replace) local d = copy_nut(disc) if pre or post or replace then setdisc(d,pre,post,replace) end return d end function nutpool.direction(dir,swap) local t = copy_nut(textdir) if not dir then -- just a l2r start node elseif swap then setdirection(t,dir,true) else setdirection(t,dir,false) end return t end function nutpool.rule(width,height,depth) -- w/h/d == nil will let them adapt local n = copy_nut(rule) if width or height or depth then setwhd(n,width,height,depth) end return n end function nutpool.emptyrule(width,height,depth) -- w/h/d == nil will let them adapt local n = copy_nut(emptyrule) if width or height or depth then setwhd(n,width,height,depth) end return n end function nutpool.userrule(width,height,depth) -- w/h/d == nil will let them adapt local n = copy_nut(userrule) if width or height or depth then setwhd(n,width,height,depth) end return n end function nutpool.outlinerule(width,height,depth,line) -- w/h/d == nil will let them adapt local n = copy_nut(outlinerule) if width or height or depth then setwhd(n,width,height,depth) end if line then setruledata(n,line) end return n end function nutpool.leader(width,list) local n = copy_nut(cleader) if width then setwidth(n,width) end if list then setleader(n,list) end return n end function nutpool.savepos() return copy_nut(savepos) end function nutpool.latelua(code) local n = copy_nut(latelua) if type(code) == "table" then local action = code.action local specification = code.specification or code code = function() action(specification) end end setdata(n,code) return n end function nutpool.leftmarginkern(glyph,width) local n = copy_nut(left_margin_kern) if not glyph then report_nodes("invalid pointer to left margin glyph node") elseif getid(glyph) ~= glyph_code then report_nodes("invalid node type %a for %s margin glyph node",nodecodes[glyph],"left") else setfield(n,"glyph",glyph) end if width and width ~= 0 then setwidth(n,width) end return n end function nutpool.rightmarginkern(glyph,width) local n = copy_nut(right_margin_kern) if not glyph then report_nodes("invalid pointer to right margin glyph node") elseif getid(glyph) ~= glyph_code then report_nodes("invalid node type %a for %s margin glyph node",nodecodes[p],"right") else setfield(n,"glyph",glyph) end if width and width ~= 0 then setwidth(n,width) end return n end function nutpool.temp() return copy_nut(temp) end function nutpool.noad() return copy_nut(noad) end function nutpool.delimiter() return copy_nut(delimiter) end nutpool.delim = nutpool.delimiter function nutpool.fence() return copy_nut(fence) end function nutpool.submlist() return copy_nut(submlist) end function nutpool.noad() return copy_nut(noad) end function nutpool.fence() return copy_nut(fence) end function nutpool.accent() return copy_nut(accent) end function nutpool.radical() return copy_nut(radical) end function nutpool.fraction() return copy_nut(fraction) end function nutpool.subbox() return copy_nut(subbox) end function nutpool.mathchar() return copy_nut(mathchar) end function nutpool.mathtextchar() return copy_nut(mathtextchar) end function nutpool.choice() return copy_nut(choice) end local function new_hlist(list,width,height,depth,shift) local n = copy_nut(hlist) if list then setlist(n,list) end if width or height or depth then setwhd(n,width,height,depth) end if shift and shift ~= 0 then setshift(n,shift) end return n end local function new_vlist(list,width,height,depth,shift) local n = copy_nut(vlist) if list then setlist(n,list) end if width or height or depth then setwhd(n,width,height,depth) end if shift and shift ~= 0 then setshift(n,shift) end return n end nutpool.hlist = new_hlist nutpool.vlist = new_vlist function nodepool.hlist(list,width,height,depth,shift) return tonode(new_hlist(list and tonut(list),width,height,depth,shift)) end function nodepool.vlist(list,width,height,depth,shift) return tonode(new_vlist(list and tonut(list),width,height,depth,shift)) end function nutpool.usernode(id,data) local n = copy_nut(user_node) nodeproperties[n] = { id = id, data = data, } return n end -- housekeeping local function cleanup(nofboxes) -- todo local tracers = nodes.tracers if tracers and tracers.steppers then -- to be resolved tracers.steppers.reset() -- todo: make a registration subsystem end local nl = 0 local nr = nofreserved for i=1,nofreserved do local ri = reserved[i] flush_nut(reserved[i]) end if nofboxes then for i=0,nofboxes do local l = getbox(i) if l then flush_nut(l) -- also list ? nl = nl + 1 end end end reserved = { } nofreserved = 0 return nr, nl, nofboxes -- can be nil end local function usage() local t = { } for n, tag in gmatch(status.node_mem_usage,"(%d+) ([a-z_]+)") do t[tag] = tonumber(n) or 0 end return t end local stock = { } nutpool .cleanup = cleanup nodepool.cleanup = cleanup nutpool .usage = usage nodepool.usage = usage nutpool .stock = stock nodepool.stock = stock -- end statistics.register("cleaned up reserved nodes", function() return format("%s nodes, %s lists of %s", cleanup(texgetcount("c_syst_last_allocated_box"))) end) -- \topofboxstack statistics.register("node memory usage", function() -- comes after cleanup ! local used = usage() if next(used) then local t, n = { }, 0 for k, v in table.sortedhash(used) do n = n + 1 ; t[n] = format("%s %s",v,k) end return table.concat(t,", ") end end) lua.registerinitexfinalizer(cleanup, "cleanup reserved nodes") -- experiment do local glyph = tonode(glyph) local traverseid = nodes.traverseid local traversers = table.setmetatableindex(function(t,k) local v = traverseid(type(k) == "number" and k or nodecodes[k],glyph) t[k] = v return v end) traversers.node = nodes.traverse (glyph) traversers.char = nodes.traversechar (glyph) if nodes.traverseglyph then traversers.glyph = nodes.traverseglyph(glyph) end if nodes.traverselist then traversers.list = nodes.traverselist (glyph) end nodes.traversers = traversers end do local glyph = glyph local traverseid = nuts.traverseid local traversers = table.setmetatableindex(function(t,k) local v = traverseid(type(k) == "number" and k or nodecodes[k],glyph) t[k] = v return v end) traversers.node = nuts.traverse (glyph) traversers.char = nuts.traversechar (glyph) if nuts.traverseglyph then traversers.glyph = nuts.traverseglyph (glyph) end if nuts.traverselist then traversers.list = nuts.traverselist (glyph) end if nuts.traversecontent then traversers.content = nuts.traversecontent(glyph) end nuts.traversers = traversers end