diff options
Diffstat (limited to 'tex/context/base/mkiv/strc-num.lua')
-rw-r--r-- | tex/context/base/mkiv/strc-num.lua | 718 |
1 files changed, 718 insertions, 0 deletions
diff --git a/tex/context/base/mkiv/strc-num.lua b/tex/context/base/mkiv/strc-num.lua new file mode 100644 index 000000000..0203334ff --- /dev/null +++ b/tex/context/base/mkiv/strc-num.lua @@ -0,0 +1,718 @@ +if not modules then modules = { } end modules ['strc-num'] = { + version = 1.001, + comment = "companion to strc-num.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +local format = string.format +local next, type = next, type +local min, max = math.min, math.max +local texsetcount = tex.setcount + +-- Counters are managed here. They can have multiple levels which makes it easier to synchronize +-- them. Synchronization is sort of special anyway, as it relates to document structuring. + +local allocate = utilities.storage.allocate +local setmetatableindex = table.setmetatableindex + +local trace_counters = false trackers.register("structures.counters", function(v) trace_counters = v end) +local report_counters = logs.reporter("structure","counters") + +local implement = interfaces.implement + +local structures = structures +local helpers = structures.helpers +local sections = structures.sections +local counters = structures.counters +local documents = structures.documents + +local variables = interfaces.variables +local v_start = variables.start +local v_page = variables.page +local v_reverse = variables.reverse +local v_first = variables.first +local v_next = variables.next +local v_previous = variables.previous +local v_prev = variables.prev +local v_last = variables.last +----- v_no = variables.no +local v_backward = variables.backward +local v_forward = variables.forward +----- v_subs = variables.subs or "subs" + +-- states: start stop none reset + +-- specials are used for counters that are set and incremented in special ways, like +-- pagecounters that get this treatment in the page builder + +counters.specials = counters.specials or { } +local counterspecials = counters.specials + +local counterranges, tbs = { }, 0 + +counters.collected = allocate() +counters.tobesaved = counters.tobesaved or { } +counters.data = counters.data or { } + +storage.register("structures/counters/data", counters.data, "structures.counters.data") +storage.register("structures/counters/tobesaved", counters.tobesaved, "structures.counters.tobesaved") + +local collected = counters.collected +local tobesaved = counters.tobesaved +local counterdata = counters.data + +local function initializer() -- not really needed + collected = counters.collected + tobesaved = counters.tobesaved + counterdata = counters.data +end + +local function finalizer() + for name, cd in next, counterdata do + local cs = tobesaved[name] + local data = cd.data + for i=1,#data do + local d = data[i] + local r = d.range + cs[i][r] = d.number + d.range = r + 1 + end + end +end + +job.register('structures.counters.collected', tobesaved, initializer, finalizer) + +local constructor = { -- maybe some day we will provide an installer for more variants + + last = function(t,name,i) + local cc = collected[name] + local stop = (cc and cc[i] and cc[i][t.range]) or 0 -- stop is available for diagnostics purposes only + t.stop = stop + if t.offset then + return stop - t.step + else + return stop + end + end, + + first = function(t,name,i) + local start = t.start + if start > 0 then + return start -- brrr + elseif t.offset then + return start + t.step + 1 + else + return start + 1 + end + end, + + prev = function(t,name,i) + return max(t.first,t.number-1) -- todo: step + end, + + previous = function(t,name,i) + return max(t.first,t.number-1) -- todo: step + end, + + next = function(t,name,i) + return min(t.last,t.number+1) -- todo: step + end, + + backward =function(t,name,i) + if t.number - 1 < t.first then + return t.last + else + return t.previous + end + end, + + forward = function(t,name,i) + if t.number + 1 > t.last then + return t.first + else + return t.next + end + end, + + subs = function(t,name,i) + local cc = collected[name] + t.subs = (cc and cc[i+1] and cc[i+1][t.range]) or 0 + return t.subs + end, + +} + +local function dummyconstructor(t,name,i) + return nil -- was 0, but that is fuzzy in testing for e.g. own +end + +setmetatableindex(constructor,function(t,k) + -- if trace_counters then + -- report_counters("unknown constructor %a",k) + -- end + return dummyconstructor +end) + +local function enhance() + for name, cd in next, counterdata do + local data = cd.data + for i=1,#data do + local ci = data[i] + setmetatableindex(ci, function(t,s) return constructor[s](t,name,i) end) + end + end + enhance = nil +end + +local function allocate(name,i) -- can be metatable + local cd = counterdata[name] + if not cd then + cd = { + level = 1, + -- block = "", -- todo + numbers = nil, + state = v_start, -- true + data = { }, + saved = { }, + } + tobesaved[name] = { } + counterdata[name] = cd + end + cd = cd.data + local ci = cd[i] + if not ci then + ci = { + number = 0, + start = 0, + saved = 0, + step = 1, + range = 1, + offset = false, + stop = 0, -- via metatable: last, first, stop only for tracing + } + setmetatableindex(ci, function(t,s) return constructor[s](t,name,i) end) + cd[i] = ci + tobesaved[name][i] = { } + else + if enhance then enhance() end -- not stored in bytecode + end + return ci +end + +local pattern = lpeg.P(variables.by)^-1 * lpeg.C(lpeg.P(1)^1) +local lpegmatch = lpeg.match + +function counters.way(way) + if not way or way == "" then + return "" + else + return lpegmatch(pattern,way) + end +end + +implement { + name = "way", + actions = { counters.way, context }, + arguments = "string" +} + + +function counters.record(name,i) + return allocate(name,i or 1) +end + +local function savevalue(name,i) + if name then + local cd = counterdata[name].data[i] + local cs = tobesaved[name][i] + local cc = collected[name] + if trace_counters then + report_counters("action %a, counter %s, value %s","save",name,cd.number) + end + local cr = cd.range + local old = (cc and cc[i] and cc[i][cr]) or 0 + local number = cd.number + if cd.method == v_page then + -- we can be one page ahead + number = number - 1 + end + cs[cr] = (number >= 0) and number or 0 + cd.range = cr + 1 + return old + else + return 0 + end +end + +function counters.define(specification) + local name = specification.name + if name and name ~= "" then + -- todo: step + local d = allocate(name,1) + d.start = tonumber(specification.start) or 0 + d.state = v_state or "" + local counter = specification.counter + if counter and counter ~= "" then + d.counter = counter -- only for special purposes, cannot be false + d.method = specification.method -- frozen at define time + end + end +end + +function counters.raw(name) + return counterdata[name] +end + +function counters.compact(name,level,onlynumbers) + local cd = counterdata[name] + if cd then + local data = cd.data + local compact = { } + for i=1,level or #data do + local d = data[i] + if d.number ~= 0 then + compact[i] = (onlynumbers and d.number) or d + end + end + return compact + end +end + +-- depends on when incremented, before or after (driven by d.offset) + +function counters.previous(name,n) + return allocate(name,n).previous +end + +function counters.next(name,n) + return allocate(name,n).next +end + +counters.prev = counters.previous + +function counters.currentvalue(name,n) + return allocate(name,n).number +end + +function counters.first(name,n) + return allocate(name,n).first +end + +function counters.last(name,n) + return allocate(name,n).last +end + +function counters.subs(name,n) + return counterdata[name].data[n].subs or 0 +end + +local function setvalue(name,tag,value) + local cd = counterdata[name] + if cd then + cd[tag] = value + end +end + +counters.setvalue = setvalue + +function counters.setstate(name,value) -- true/false + value = variables[value] + if value then + setvalue(name,"state",value) + end +end + +function counters.setlevel(name,value) + setvalue(name,"level",value) +end + +function counters.setoffset(name,value) + setvalue(name,"offset",value) +end + +local function synchronize(name,d) + local dc = d.counter + if dc then + if trace_counters then + report_counters("action %a, name %a, counter %a, value %a","synchronize",name,dc,d.number) + end + texsetcount("global",dc,d.number) + end + local cs = counterspecials[name] + if cs then + if trace_counters then + report_counters("action %a, name %a, counter %a","synccommand",name,dc) + end + cs(name) + end +end + +local function reset(name,n) + local cd = counterdata[name] + if cd then + for i=n or 1,#cd.data do + local d = cd.data[i] + savevalue(name,i) + local number = d.start or 0 + d.number = number + d.own = nil + if trace_counters then + report_counters("action %a, name %a, sub %a, value %a","reset",name,i,number) + end + synchronize(name,d) + end + cd.numbers = nil + else + end +end + +local function set(name,n,value) + local cd = counterdata[name] + if cd then + local d = allocate(name,n) + local number = value or 0 + d.number = number + d.own = nil + if trace_counters then + report_counters("action %a, name %a, sub %a, value %a","set",name,"no",number) + end + synchronize(name,d) + end +end + +local function check(name,data,start,stop) + for i=start or 1,stop or #data do + local d = data[i] + savevalue(name,i) + local number = d.start or 0 + d.number = number + d.own = nil + if trace_counters then + report_counters("action %a, name %a, sub %a, value %a","check",name,i,number) + end + synchronize(name,d) + end +end + + +local function setown(name,n,value) + local cd = counterdata[name] + if cd then + local d = allocate(name,n) + d.own = value + d.number = (d.number or d.start or 0) + (d.step or 0) + local level = cd.level + if not level or level == -1 then + -- -1 is signal that we reset manually + elseif level > 0 or level == -3 then + check(name,d,n+1) + elseif level == 0 then + -- happens elsewhere, check this for block + end + synchronize(name,d) + end +end + +local function restart(name,n,newstart,noreset) + local cd = counterdata[name] + if cd then + newstart = tonumber(newstart) + if newstart then + local d = allocate(name,n) + d.start = newstart + if not noreset then -- why / when needed ? + reset(name,n) -- hm + end + end + end +end + +function counters.save(name) -- or just number + local cd = counterdata[name] + if cd then + table.insert(cd.saved,table.copy(cd.data)) + end +end + +function counters.restore(name) + local cd = counterdata[name] + if cd and cd.saved then + cd.data = table.remove(cd.saved) + end +end + +local function add(name,n,delta) + local cd = counterdata[name] + if cd and (cd.state == v_start or cd.state == "") then + local data = cd.data + local d = allocate(name,n) + d.number = (d.number or d.start or 0) + delta*(d.step or 0) + -- d.own = nil + local level = cd.level + if not level or level == -1 then + -- -1 is signal that we reset manually + if trace_counters then + report_counters("action %a, name %a, sub %a, how %a","add",name,"no","no checking") + end + elseif level == -2 then + -- -2 is signal that we work per text + if trace_counters then + report_counters("action %a, name %a, sub %a, how %a","add",name,"text","checking") + end + check(name,data,n+1) + elseif level > 0 or level == -3 then + -- within countergroup + if trace_counters then + report_counters("action %a, name %a, sub %a, how %a","add",name,level,"checking within group") + end + check(name,data,n+1) + elseif level == 0 then + -- happens elsewhere + if trace_counters then + report_counters("action %a, name %a, sub %a, how %a","add",name,level,"no checking") + end + else + if trace_counters then + report_counters("action %a, name %a, sub %a, how %a","add",name,"unknown","no checking") + end + end + synchronize(name,d) + return d.number -- not needed + end + return 0 +end + +function counters.check(level) + for name, cd in next, counterdata do + if level > 0 and cd.level == -3 then -- could become an option + if trace_counters then + report_counters("action %a, name %a, sub %a, detail %a","reset",name,level,"head") + end + reset(name) + elseif cd.level == level then + if trace_counters then + report_counters("action %a, name %a, sub %a, detail %a","reset",name,level,"normal") + end + reset(name) + end + end +end + +local function get(name,n,key) + local d = allocate(name,n) + d = d and d[key] + if not d then + return 0 + elseif type(d) == "function" then + return d() + else + return d + end +end + +counters.reset = reset +counters.set = set +counters.add = add +counters.get = get +counters.setown = setown +counters.restart = restart + +function counters.value(name,n) -- what to do with own + return get(name,n or 1,'number') or 0 +end + +function counters.converted(name,spec) -- name can be number and reference to storage + local cd + if type(name) == "number" then + cd = specials.retrieve("counter",name) + cd = cd and cd.counter + else + cd = counterdata[name] + end + if cd then + local spec = spec or { } + local numbers, ownnumbers = { }, { } + local reverse = spec.order == v_reverse + local kind = spec.type or "number" + local data = cd.data + for k=1,#data do + local v = data[k] + -- somewhat messy, what if subnr? only last must honour kind? + local vn + if v.own then + numbers[k], ownnumbers[k] = v.number, v.own + else + if kind == v_first then + vn = v.first + elseif kind == v_next then + vn = v.next + elseif kind == v_prev or kind == v_previous then + vn = v.prev + elseif kind == v_last then + vn = v.last + else + vn = v.number + if reverse then + local vf = v.first + local vl = v.last + if vl > 0 then + -- vn = vl - vn + 1 + vf + vn = vl - vn + vf -- see testbed for test + end + end + end + numbers[k], ownnumbers[k] = vn or v.number, nil + end + end + cd.numbers = numbers + cd.ownnumbers = ownnumbers + sections.typesetnumber(cd,'number',spec) + cd.numbers = nil + cd.ownnumbers = nil + end +end + +-- interfacing + +local function showcounter(name) + local cd = counterdata[name] + if cd then + context("[%s:",name) + local data = cd.data + for i=1,#data do + local d = data[i] + context(" (%s: %s,%s,%s s:%s r:%s)",i,d.start or 0,d.number or 0,d.last,d.step or 0,d.range or 0) + end + context("]") + end +end + +-- the noreset is somewhat messy ... always false messes up e.g. itemize but true the pagenumbers +-- +-- if this fails i'll clean up this still somewhat experimental mechanism (but i need use cases) + +local function checkcountersetup(name,level,start,state) + local noreset = true -- level > 0 -- was true + counters.restart(name,1,start,noreset) -- was true + counters.setstate(name,state) + counters.setlevel(name,level) + sections.setchecker(name,level,counters.reset) +end + +-- + +implement { name = "addcounter", actions = add, arguments = { "string", "integer", "integer" } } +implement { name = "setcounter", actions = set, arguments = { "string", 1, "integer" } } +implement { name = "setowncounter", actions = setown, arguments = { "string", 1, "string" } } +implement { name = "restartcounter", actions = restart, arguments = { "string", 1, "integer" } } +implement { name = "resetcounter", actions = reset, arguments = { "string", 1 } } +implement { name = "incrementcounter", actions = add, arguments = { "string", 1, 1 } } +implement { name = "decrementcounter", actions = add, arguments = { "string", 1, -1 } } + +implement { name = "setsubcounter", actions = set, arguments = { "string", "integer", "integer" } } +implement { name = "setownsubcounter", actions = setown, arguments = { "string", "integer", "string" } } +implement { name = "restartsubcounter", actions = restart, arguments = { "string", "integer", "integer" } } +implement { name = "resetsubcounter", actions = reset, arguments = { "string", "integer" } } +implement { name = "incrementsubcounter", actions = add, arguments = { "string", "integer", 1 } } +implement { name = "decrementsubcounter", actions = add, arguments = { "string", "integer", -1 } } + +implement { name = "rawcountervalue", actions = { counters.raw , context }, arguments = { "string", 1 } } +implement { name = "countervalue", actions = { counters.value , context }, arguments = { "string", 1 } } +implement { name = "lastcountervalue", actions = { counters.last , context }, arguments = { "string", 1 } } +implement { name = "firstcountervalue", actions = { counters.first , context }, arguments = { "string", 1 } } +implement { name = "nextcountervalue", actions = { counters.next , context }, arguments = { "string", 1 } } +implement { name = "prevcountervalue", actions = { counters.previous, context }, arguments = { "string", 1 } } +implement { name = "subcountervalues", actions = { counters.subs , context }, arguments = { "string", 1 } } + +implement { name = "rawsubcountervalue", actions = { counters.raw , context }, arguments = { "string", "integer" } } +implement { name = "subcountervalue", actions = { counters.value , context }, arguments = { "string", "integer" } } +implement { name = "lastsubcountervalue", actions = { counters.last , context }, arguments = { "string", "integer" } } +implement { name = "firstsubcountervalue", actions = { counters.first , context }, arguments = { "string", "integer" } } +implement { name = "nextsubcountervalue", actions = { counters.next , context }, arguments = { "string", "integer" } } +implement { name = "previoussubcountervalue", actions = { counters.previous, context }, arguments = { "string", "integer" } } +implement { name = "subsubcountervalues", actions = { counters.subs , context }, arguments = { "string", "integer" } } + +implement { name = "savecounter", actions = counters.save, arguments = "string" } +implement { name = "restorecounter", actions = counters.restore, arguments = "string" } + +implement { name = "incrementedcounter", actions = { add, context }, arguments = { "string", 1, 1 } } +implement { name = "decrementedcounter", actions = { add, context }, arguments = { "string", 1, -1 } } + +implement { name = "showcounter", actions = showcounter, arguments = "string" } -- todo +implement { name = "checkcountersetup", actions = checkcountersetup, arguments = { "string", "integer", "integer", "string" } } + +table.setmetatablecall(counterdata,function(t,k) return t[k] end) + +implement { name = "doifelsecounter", actions = { counterdata, commands.doifelse }, arguments = "string" } +implement { name = "doifcounter", actions = { counterdata, commands.doif }, arguments = "string" } +implement { name = "doifnotcounter", actions = { counterdata, commands.doifnot }, arguments = "string" } + +implement { + name = "definecounter", + actions = counters.define, + arguments = { + { + { "name" } , + { "start", "integer" }, + { "counter" }, + { "method" }, + } + } +} + +------------------------------------------------------------------ +------------------------------------------------------------------ + +-- -- move to strc-pag.lua +-- +-- function counters.analyze(name,counterspecification) +-- local cd = counterdata[name] +-- -- safeguard +-- if not cd then +-- return false, false, "no counter data" +-- end +-- -- section data +-- local sectiondata = sections.current() +-- if not sectiondata then +-- return cd, false, "not in section" +-- end +-- local references = sectiondata.references +-- if not references then +-- return cd, false, "no references" +-- end +-- local section = references.section +-- if not section then +-- return cd, false, "no section" +-- end +-- sectiondata = sections.collected[references.section] +-- if not sectiondata then +-- return cd, false, "no section data" +-- end +-- -- local preferences +-- local no = v_no +-- if counterspecification and counterspecification.prefix == no then +-- return cd, false, "current spec blocks prefix" +-- end +-- -- stored preferences (not used) +-- if cd.prefix == no then +-- return cd, false, "entry blocks prefix" +-- end +-- -- sectioning +-- -- if sectiondata.prefix == no then +-- -- return false, false, "sectiondata blocks prefix" +-- -- end +-- -- final verdict +-- return cd, sectiondata, "okay" +-- end +-- +-- function counters.prefixedconverted(name,prefixspec,numberspec) +-- local cd, prefixdata, result = counters.analyze(name,prefixspec) +-- if cd then +-- if prefixdata then +-- sections.typesetnumber(prefixdata,"prefix",prefixspec or false,cd or false) +-- end +-- counters.converted(name,numberspec) +-- end +-- end |