diff options
Diffstat (limited to 'tex/context/base/page-mix.lua')
-rw-r--r-- | tex/context/base/page-mix.lua | 590 |
1 files changed, 397 insertions, 193 deletions
diff --git a/tex/context/base/page-mix.lua b/tex/context/base/page-mix.lua index 7d13d9e4e..61a4f944d 100644 --- a/tex/context/base/page-mix.lua +++ b/tex/context/base/page-mix.lua @@ -13,48 +13,81 @@ if not modules then modules = { } end modules ["page-mix"] = { -- local trackers, logs, storage = trackers, logs, storage -- local number, table = number, table +local next, type = next, type local concat = table.concat - -local nodecodes = nodes.nodecodes -local gluecodes = nodes.gluecodes -local nodepool = nodes.pool - -local hlist_code = nodecodes.hlist -local vlist_code = nodecodes.vlist -local kern_code = nodecodes.kern -local glue_code = nodecodes.glue -local penalty_code = nodecodes.penalty -local insert_code = nodecodes.ins -local mark_code = nodecodes.mark - -local new_hlist = nodepool.hlist -local new_vlist = nodepool.vlist -local new_glue = nodepool.glue - -local hpack = node.hpack -local vpack = node.vpack -local freenode = node.free -local concatnodes = nodes.concat - -local texgetbox = tex.getbox -local texsetbox = tex.setbox -local texgetskip = tex.getskip - -local points = number.points - -local settings_to_hash = utilities.parsers.settings_to_hash - -local variables = interfaces.variables -local v_yes = variables.yes -local v_global = variables["global"] -local v_local = variables["local"] -local v_columns = variables.columns +local ceil, floor = math.ceil, math.floor local trace_state = false trackers.register("mixedcolumns.trace", function(v) trace_state = v end) local trace_detail = false trackers.register("mixedcolumns.detail", function(v) trace_detail = v end) local report_state = logs.reporter("mixed columns") +local nodecodes = nodes.nodecodes +local gluecodes = nodes.gluecodes + +local hlist_code = nodecodes.hlist +local vlist_code = nodecodes.vlist +local kern_code = nodecodes.kern +local glue_code = nodecodes.glue +local penalty_code = nodecodes.penalty +local insert_code = nodecodes.ins +local mark_code = nodecodes.mark +local rule_code = nodecodes.rule + +local topskip_code = gluecodes.topskip +local lineskip_code = gluecodes.lineskip +local baselineskip_code = gluecodes.baselineskip +local userskip_code = gluecodes.userskip + +local nuts = nodes.nuts +local tonode = nuts.tonode +local nodetostring = nuts.tostring +local listtoutf = nodes.listtoutf + +local hpack = nuts.hpack +local vpack = nuts.vpack +local freenode = nuts.free +local concatnodes = nuts.concat +local slidenodes = nuts.slide -- ok here as we mess with prev links intermediately +local traversenodes = nuts.traverse + +local getfield = nuts.getfield +local setfield = nuts.setfield +local getnext = nuts.getnext +local getprev = nuts.getprev +local getid = nuts.getid +local getlist = nuts.getlist +local getsubtype = nuts.getsubtype +local getbox = nuts.getbox +local setbox = nuts.setbox +local getskip = nuts.getskip +local getattribute = nuts.getattribute + +local nodepool = nuts.pool + +local new_hlist = nodepool.hlist +local new_vlist = nodepool.vlist +local new_glue = nodepool.glue + +local points = number.points + +local settings_to_hash = utilities.parsers.settings_to_hash + +local variables = interfaces.variables +local v_yes = variables.yes +local v_global = variables["global"] +local v_local = variables["local"] +local v_columns = variables.columns +local v_fixed = variables.fixed +local v_auto = variables.auto +local v_none = variables.none +local v_more = variables.more +local v_less = variables.less +local v_halfline = variables.halfline + +local context = context +local implement = interfaces.implement + pagebuilders = pagebuilders or { } pagebuilders.mixedcolumns = pagebuilders.mixedcolumns or { } local mixedcolumns = pagebuilders.mixedcolumns @@ -77,13 +110,13 @@ local function collectinserts(result,nxt,nxtid) local inserts, currentskips, nextskips, inserttotal = { }, 0, 0, 0 while nxt do if nxtid == insert_code then - inserttotal = inserttotal + nxt.height + nxt.depth - local s = nxt.subtype + inserttotal = inserttotal + getfield(nxt,"height") + getfield(nxt,"depth") + local s = getsubtype(nxt) local c = inserts[s] if not c then c = { } inserts[s] = c - local width = texgetskip(s).width + local width = getfield(getskip(s),"width") if not result.inserts[s] then currentskips = currentskips + width end @@ -100,9 +133,9 @@ local function collectinserts(result,nxt,nxtid) else break end - nxt = nxt.next + nxt = getnext(nxt) if nxt then - nxtid = nxt.id + nxtid = getid(nxt) else break end @@ -128,30 +161,30 @@ end local function discardtopglue(current,discarded) local size = 0 while current do - local id = current.id + local id = getid(current) if id == glue_code then - size = size + current.spec.width + size = size + getfield(getfield(current,"spec"),"width") discarded[#discarded+1] = current - current = current.next + current = getnext(current) elseif id == penalty_code then - if current.penalty == forcedbreak then + if getfield(current,"penalty") == forcedbreak then discarded[#discarded+1] = current - current = current.next - while current and current.id == glue_code do - size = size + current.spec.width + current = getnext(current) + while current and getid(current) == glue_code do + size = size + getfield(getfield(current,"spec"),"width") discarded[#discarded+1] = current - current = current.next + current = getnext(current) end else discarded[#discarded+1] = current - current = current.next + current = getnext(current) end else break end end if current then - current.prev = nil + setfield(current,"prev",nil) -- prevent look back end return current, size end @@ -162,13 +195,13 @@ local function stripbottomglue(results,discarded) local r = results[i] local t = r.tail while t and t ~= r.head do - local prev = t.prev + local prev = getprev(t) if not prev then break end - local id = t.id + local id = getid(t) if id == penalty_code then - if t.penalty == forcedbreak then + if getfield(t,"penalty") == forcedbreak then break else discarded[#discarded+1] = t @@ -177,7 +210,7 @@ local function stripbottomglue(results,discarded) end elseif id == glue_code then discarded[#discarded+1] = t - local width = t.spec.width + local width = getfield(getfield(t,"spec"),"width") if trace_state then report_state("columns %s, discarded bottom glue %p",i,width) end @@ -195,51 +228,52 @@ local function stripbottomglue(results,discarded) return height end -local function setsplit(specification) -- a rather large function +local function preparesplit(specification) -- a rather large function local box = specification.box if not box then report_state("fatal error, no box") return end - local list = texgetbox(box) + local list = getbox(box) if not list then report_state("fatal error, no list") return end - local head = list.head or specification.originalhead + local head = getlist(list) or specification.originalhead if not head then report_state("fatal error, no head") return end - local discarded = { } - local originalhead = head - local originalwidth = specification.originalwidth or list.width - local originalheight = specification.originalheight or list.height - local current = head - local skipped = 0 - local height = 0 - local depth = 0 - local skip = 0 - local splitmethod = specification.splitmethod or false + slidenodes(head) -- we can have set prev's to nil to prevent backtracking + local discarded = { } + local originalhead = head + local originalwidth = specification.originalwidth or getfield(list,"width") + local originalheight = specification.originalheight or getfield(list,"height") + local current = head + local skipped = 0 + local height = 0 + local depth = 0 + local skip = 0 + local splitmethod = specification.splitmethod or false if splitmethod == v_none then splitmethod = false end - local options = settings_to_hash(specification.option or "") + local options = settings_to_hash(specification.option or "") local stripbottom = specification.alternative == v_local - local cycle = specification.cycle or 1 - local nofcolumns = specification.nofcolumns or 1 + local cycle = specification.cycle or 1 + local nofcolumns = specification.nofcolumns or 1 if nofcolumns == 0 then nofcolumns = 1 end local preheight = specification.preheight or 0 - local extra = specification.extra or 0 + local extra = specification.extra or 0 local maxheight = specification.maxheight - local optimal = originalheight/nofcolumns + local optimal = originalheight/nofcolumns if specification.balance ~= v_yes then optimal = maxheight end - local target = optimal + extra - local overflow = target > maxheight - preheight + local target = optimal + extra + local overflow = target > maxheight - preheight local threshold = specification.threshold or 0 if overflow then target = maxheight - preheight @@ -267,33 +301,73 @@ local function setsplit(specification) -- a rather large function local rest = nil local lastlocked = nil local lastcurrent = nil + local lastcontent = nil local backtracked = false if trace_state then report_state("setting collector to column %s",column) end + local function unlock(penalty) + if lastlocked then + if trace_state then + report_state("penalty %s, unlocking in column %s",penalty or "-",column) + end + lastlocked = nil + end + lastcurrent = nil + lastcontent = nil + end + + local function lock(penalty,current) + if trace_state then + report_state("penalty %s, locking in column %s",penalty,column) + end + lastlocked = penalty + lastcurrent = current or lastcurrent + lastcontent = nil + end + local function backtrack(start) local current = start -- first skip over glue and penalty while current do - local id = current.id - if id == glue_code or id == penalty_code then - current = current.prev + local id = getid(current) + if id == glue_code then + if trace_state then + report_state("backtracking over %s in column %s","glue",column) + end + current = getprev(current) + elseif id == penalty_code then + if trace_state then + report_state("backtracking over %s in column %s","penalty",column) + end + current = getprev(current) else break end end -- then skip over content while current do - local id = current.id - if id == glue_code or id == penalty_code then + local id = getid(current) + if id == glue_code then + if trace_state then + report_state("quitting at %s in column %s","glue",column) + end + break + elseif id == penalty_code then + if trace_state then + report_state("quitting at %s in column %s","penalty",column) + end break else - current = current.prev + current = getprev(current) end end if not current then + if trace_state then + report_state("no effective backtracking in column %s",column) + end current = start end return current @@ -310,7 +384,12 @@ local function setsplit(specification) -- a rather large function backtracked = true end lastcurrent = nil - lastlocked = nil + if lastlocked then + if trace_state then + report_state("unlocking in column %s",column) + end + lastlocked = nil + end end if head == lasthead then if trace_state then @@ -324,7 +403,7 @@ local function setsplit(specification) -- a rather large function if current == head then result.tail = head else - result.tail = current.prev + result.tail = getprev(current) end result.height = height result.depth = depth @@ -344,6 +423,9 @@ local function setsplit(specification) -- a rather large function report_state("setting collector to column %s",column) end current, skipped = discardtopglue(current,discarded) + if trace_detail and skipped ~= 0 then + report_state("check > column 1, discarded %p",skipped) + end head = current return true, skipped end @@ -352,6 +434,7 @@ local function setsplit(specification) -- a rather large function local function checked(advance,where,locked) local total = skip + height + depth + advance local delta = total - target +-- - 65536*3 local state = "same" local okay = false local skipped = 0 @@ -366,7 +449,7 @@ local function setsplit(specification) -- a rather large function end end if trace_detail then - report_state("%-7s > column %s, delta %p, threshold %p, advance %p, total %p, target %p, discarded %p => %a (height %p, depth %p, skip %p)", + report_state("%-7s > column %s, delta %p, threshold %p, advance %p, total %p, target %p => %a (height %p, depth %p, skip %p)", where,curcol,delta,threshold,advance,total,target,state,skipped,height,depth,skip) end return state, skipped @@ -387,7 +470,7 @@ local function setsplit(specification) -- a rather large function head = current local function process_skip(current,nxt) - local advance = current.spec.width + local advance = getfield(getfield(current,"spec"),"width") if advance ~= 0 then local state, skipped = checked(advance,"glue") if trace_state then @@ -401,17 +484,28 @@ local function setsplit(specification) -- a rather large function end height = height + depth + skip depth = 0 +if advance < 0 then + height = height + advance + skip = 0 + if height < 0 then + height = 0 + end +else skip = height > 0 and advance or 0 +end if trace_state then report_state("%-7s > column %s, height %p, depth %p, skip %p","glue",column,height,depth,skip) end else -- what else? ignore? treat as valid as usual? end + if lastcontent then + unlock() + end end local function process_kern(current,nxt) - local advance = current.kern + local advance = getfield(current,"kern") if advance ~= 0 then local state, skipped = checked(advance,"kern") if trace_state then @@ -434,25 +528,28 @@ local function setsplit(specification) -- a rather large function local function process_rule(current,nxt) -- simple variant of h|vlist - local advance = current.height -- + current.depth - local state, skipped = checked(advance+currentskips,"rule") - if trace_state then - report_state("%-7s > column %s, state %a, rule, advance %p, height %p","line",column,state,advance,inserttotal,height) - if skipped ~= 0 then - report_state("%-7s > column %s, discarded %p","rule",column,skipped) + local advance = getfield(current,"height") -- + getfield(current,"depth") + if advance ~= 0 then + local state, skipped = checked(advance,"rule") + if trace_state then + report_state("%-7s > column %s, state %a, rule, advance %p, height %p","rule",column,state,advance,inserttotal,height) + if skipped ~= 0 then + report_state("%-7s > column %s, discarded %p","rule",column,skipped) + end end + if state == "quit" then + return true + end + height = height + depth + skip + advance + -- if state == "next" then + -- height = height + nextskips + -- else + -- height = height + currentskips + -- end + depth = getfield(current,"depth") + skip = 0 end - if state == "quit" then - return true - end - height = height + depth + skip + advance - if state == "next" then - height = height + nextskips - else - height = height + currentskips - end - depth = current.depth - skip = 0 + lastcontent = current end -- okay, here we could do some badness like magic but we want something @@ -462,12 +559,11 @@ local function setsplit(specification) -- a rather large function -- [chapter] [penalty] [section] [penalty] [first line] local function process_penalty(current,nxt) - local penalty = current.penalty + local penalty = getfield(current,"penalty") if penalty == 0 then - lastlocked = nil - lastcurrent = nil + unlock(penalty) elseif penalty == forcedbreak then - local needed = current[a_checkedbreak] + local needed = getattribute(current,a_checkedbreak) local proceed = not needed or needed == 0 if not proceed then local available = target - height @@ -477,8 +573,7 @@ local function setsplit(specification) -- a rather large function end end if proceed then - lastlocked = nil - lastcurrent = nil + unlock(penalty) local okay, skipped = gotonext() if okay then if trace_state then @@ -499,28 +594,26 @@ local function setsplit(specification) -- a rather large function end elseif penalty < 0 then -- we don't care too much - lastlocked = nil - lastcurrent = nil + unlock(penalty) elseif penalty >= 10000 then if not lastcurrent then - lastcurrent = current - lastlocked = penalty + lock(penalty,current) elseif penalty > lastlocked then - lastlocked = penalty + lock(penalty) end else - lastlocked = nil - lastcurrent = nil + unlock(penalty) end end local function process_list(current,nxt) - local nxtid = nxt and nxt.id + local nxtid = nxt and getid(nxt) line = line + 1 local inserts, currentskips, nextskips, inserttotal = nil, 0, 0, 0 - local advance = current.height -- + current.depth + local advance = getfield(current,"height") +-- + getfield(current,"depth") -- when > strutdp if trace_state then - report_state("%-7s > column %s, content: %s","line",column,listtoutf(current.list,true,true)) + report_state("%-7s > column %s, content: %s","line",column,listtoutf(getlist(current),true,true)) end if nxt and (nxtid == insert_code or nxtid == mark_code) then nxt, inserts, localskips, insertskips, inserttotal = collectinserts(result,nxt,nxtid) @@ -541,7 +634,7 @@ local function setsplit(specification) -- a rather large function else height = height + currentskips end - depth = current.depth + depth = getfield(current,"depth") skip = 0 if inserts then -- so we already collect them ... makes backtracking tricky ... alternatively @@ -551,12 +644,15 @@ local function setsplit(specification) -- a rather large function if trace_state then report_state("%-7s > column %s, height %p, depth %p, skip %p","line",column,height,depth,skip) end + lastcontent = current end +local kept = head + while current do - local id = current.id - local nxt = current.next + local id = getid(current) + local nxt = getnext(current) backtracked = false @@ -602,14 +698,16 @@ local function setsplit(specification) -- a rather large function if not current then if trace_state then - report_state("nilling rest") + report_state("nothing left") end - rest = nil - elseif rest == lasthead then + -- needs well defined case + -- rest = nil + elseif rest == lasthead then if trace_state then - report_state("nilling rest as rest is lasthead") + report_state("rest equals lasthead") end - rest = nil + -- test case: x\index{AB} \index{AA}x \blank \placeindex + -- makes line disappear: rest = nil end if stripbottom then @@ -629,24 +727,26 @@ local function setsplit(specification) -- a rather large function specification.overflow = overflow specification.discarded = discarded - texgetbox(specification.box).list = nil + setfield(getbox(specification.box),"list",nil) return specification end -function mixedcolumns.finalize(result) +local function finalize(result) if result then - local results = result.results - for i=1,result.nofcolumns do + local results = result.results + local columns = result.nofcolumns + local maxtotal = 0 + for i=1,columns do local r = results[i] local h = r.head if h then - h.prev = nil + setfield(h,"prev",nil) local t = r.tail if t then - t.next = nil + setfield(t,"next",nil) else - h.next = nil + setfield(h,"next",nil) r.tail = h end for c, list in next, r.inserts do @@ -655,16 +755,26 @@ function mixedcolumns.finalize(result) local l = list[i] local h = new_hlist() t[i] = h - h.head = l.head - h.height = l.height - h.depth = l.depth - l.head = nil + setfield(h,"list",getfield(l,"head")) + setfield(h,"height",getfield(l,"height")) + setfield(h,"depth",getfield(l,"depth")) + setfield(l,"head",nil) end - t[1].prev = nil -- needs checking - t[#t].next = nil -- needs checking + setfield(t[1],"prev",nil) -- needs checking + setfield(t[#t],"next",nil) -- needs checking r.inserts[c] = t end end + local total = r.height + r.depth + if total > maxtotal then + maxtotal = total + end + r.total = total + end + result.maxtotal = maxtotal + for i=1,columns do + local r = results[i] + r.extra = maxtotal - r.total end end end @@ -679,12 +789,12 @@ local function report_deltas(result,str) report_state("%s, cycles %s, deltas % | t",str,result.cycle or 1,t) end -function mixedcolumns.setsplit(specification) +local function setsplit(specification) splitruns = splitruns + 1 if trace_state then report_state("split run %s",splitruns) end - local result = setsplit(specification) + local result = preparesplit(specification) if result then if result.overflow then if trace_state then @@ -697,7 +807,7 @@ function mixedcolumns.setsplit(specification) local cycles = specification.cycles or 100 while result.rest and cycle <= cycles do specification.extra = cycle * step - result = setsplit(specification) or result + result = preparesplit(specification) or result if trace_state then report_state("cycle: %s.%s, original height %p, total height %p", splitruns,cycle,result.originalheight,result.nofcolumns*result.targetheight) @@ -719,7 +829,7 @@ function mixedcolumns.setsplit(specification) end end -function mixedcolumns.getsplit(result,n) +local function getsplit(result,n) if not result then report_state("flush, column %s, no result",n) return @@ -733,17 +843,18 @@ function mixedcolumns.getsplit(result,n) return new_glue(result.originalwidth) end - h.prev = nil -- move up + setfield(h,"prev",nil) -- move up local strutht = result.strutht local strutdp = result.strutdp local lineheight = strutht + strutdp + local isglobal = result.alternative == v_global local v = new_vlist() - v.head = h + setfield(v,"list",h) -- local v = vpack(h,"exactly",height) - if result.alternative == v_global then -- option + if isglobal then -- option result.height = result.maxheight end @@ -751,24 +862,69 @@ function mixedcolumns.getsplit(result,n) local dp = 0 local wd = result.originalwidth - local grid = result.grid + local grid = result.grid + local internalgrid = result.internalgrid + local httolerance = .25 + local dptolerance = .50 + local lineheight = internalgrid == v_halfline and lineheight/2 or lineheight + + local function amount(r,s,t) + local l = ceil((r-t)/lineheight) + local a = lineheight * l + if a > s then + return a - s + else + return s + end + end if grid then - ht = lineheight * math.ceil(result.height/lineheight) - strutdp - dp = strutdp + -- print(n,result.maxtotal,r.total,r.extra) + if isglobal then + local rh = r.height + -- ht = (lineheight * ceil(result.height/lineheight) - strutdp + ht = amount(rh,strutdp,0) + dp = strutdp + else + -- natural dimensions + local rh = r.height + local rd = r.depth + if rh > ht then + ht = amount(rh,strutdp,httolerance*strutht) + end + if rd > dp then + dp = amount(rd,strutht,dptolerance*strutdp) + end + -- forced dimensions + local rh = result.height or 0 + local rd = result.depth or 0 + if rh > ht then + ht = amount(rh,strutdp,httolerance*strutht) + end + if rd > dp then + dp = amount(rd,strutht,dptolerance*strutdp) + end + -- always one line at least + if ht < strutht then + ht = strutht + end + if dp < strutdp then + dp = strutdp + end + end else ht = result.height dp = result.depth end - v.width = wd - v.height = ht - v.depth = dp + setfield(v,"width",wd) + setfield(v,"height",ht) + setfield(v,"depth",dp) if trace_state then - local id = h.id + local id = getid(h) if id == hlist_code then - report_state("flush, column %s, grid %a, width %p, height %p, depth %p, %s: %s",n,grid,wd,ht,dp,"top line",nodes.toutf(h.list)) + report_state("flush, column %s, grid %a, width %p, height %p, depth %p, %s: %s",n,grid,wd,ht,dp,"top line",listtoutf(getlist(h))) else report_state("flush, column %s, grid %a, width %p, height %p, depth %p, %s: %s",n,grid,wd,ht,dp,"head node",nodecodes[id]) end @@ -777,27 +933,27 @@ function mixedcolumns.getsplit(result,n) for c, list in next, r.inserts do local l = concatnodes(list) local b = vpack(l) -- multiple arguments, todo: fastvpack - -- texsetbox("global",c,b) - texsetbox(c,b) + -- setbox("global",c,b) + setbox(c,b) r.inserts[c] = nil end return v end -function mixedcolumns.getrest(result) +local function getrest(result) local rest = result and result.rest result.rest = nil -- to be sure return rest end -function mixedcolumns.getlist(result) +local function getlist(result) local originalhead = result and result.originalhead result.originalhead = nil -- to be sure return originalhead end -function mixedcolumns.cleanup(result) +local function cleanup(result) local discarded = result.discarded for i=1,#discarded do freenode(discarded[i]) @@ -805,52 +961,100 @@ function mixedcolumns.cleanup(result) result.discarded = { } end +mixedcolumns.setsplit = setsplit +mixedcolumns.getsplit = getsplit +mixedcolumns.finalize = finalize +mixedcolumns.getrest = getrest +mixedcolumns.getlist = getlist +mixedcolumns.cleanup = cleanup + -- interface -- local result -function commands.mixsetsplit(specification) - if result then - for k, v in next, specification do - result[k] = v +implement { + name = "mixsetsplit", + actions = function(specification) + if result then + for k, v in next, specification do + result[k] = v + end + result = setsplit(result) + else + result = setsplit(specification) end - result = mixedcolumns.setsplit(result) - else - result = mixedcolumns.setsplit(specification) - end -end + end, + arguments = { + { + { "box", "integer" }, + { "nofcolumns", "integer" }, + { "maxheight", "dimen" }, + { "step", "dimen" }, + { "cycles", "integer" }, + { "preheight", "dimen" }, + { "prebox", "integer" }, + { "strutht", "dimen" }, + { "strutdp", "dimen" }, + { "threshold", "dimen" }, + { "splitmethod" }, + { "balance" }, + { "alternative" }, + { "internalgrid" }, + { "grid", "boolean" }, + } + } +} -function commands.mixgetsplit(n) - if result then - context(mixedcolumns.getsplit(result,n)) - end -end +implement { + name = "mixgetsplit", + arguments = "integer", + actions = function(n) + if result then + context(tonode(getsplit(result,n))) + end + end, +} -function commands.mixfinalize() - if result then - mixedcolumns.finalize(result) +implement { + name = "mixfinalize", + actions = function() + if result then + finalize(result) + end end -end +} -function commands.mixflushrest() - if result then - context(mixedcolumns.getrest(result)) +implement { + name = "mixflushrest", + actions = function() + if result then + context(tonode(getrest(result))) + end end -end +} -function commands.mixflushlist() - if result then - context(mixedcolumns.getlist(result)) +implement { + name = "mixflushlist", + actions = function() + if result then + context(tonode(getlist(result))) + end end -end +} -function commands.mixstate() - context(result and result.rest and 1 or 0) -end +implement { + name = "mixstate", + actions = function() + context(result and result.rest and 1 or 0) + end +} -function commands.mixcleanup() - if result then - mixedcolumns.cleanup(result) - result = nil +implement { + name = "mixcleanup", + actions = function() + if result then + cleanup(result) + result = nil + end end -end +} |