core-dat.lua /size: 9441 b    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['core-dat'] = {
2    version   = 1.001,
3    comment   = "companion to core-dat.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 module provides a (multipass) container for arbitrary data. It replaces the
10-- twopass data mechanism.
11
12local tonumber, tostring, type = tonumber, tostring, type
13
14local context          = context
15local commands         = commands
16local ctx_latelua      = context.latelua
17
18local trace_datasets   = false  trackers.register("job.datasets" ,  function(v) trace_datasets   = v end)
19local trace_pagestates = false  trackers.register("job.pagestates", function(v) trace_pagestates = v end)
20
21local report_dataset   = logs.reporter("dataset")
22local report_pagestate = logs.reporter("pagestate")
23
24local allocate         = utilities.storage.allocate
25local settings_to_hash = utilities.parsers.settings_to_hash
26
27local texgetcount      = tex.getcount
28local texsetcount      = tex.setcount
29
30local formatters       = string.formatters
31
32local v_yes            = interfaces.variables.yes
33
34local new_latelua      = nodes.pool.latelua
35
36local implement        = interfaces.implement
37local getnamespace     = interfaces.getnamespace
38
39local collected = allocate()
40local tobesaved = allocate()
41
42local datasets = {
43    collected = collected,
44    tobesaved = tobesaved,
45}
46
47job.datasets = datasets
48
49local function initializer()
50    collected = datasets.collected
51    tobesaved = datasets.tobesaved
52end
53
54job.register('job.datasets.collected', tobesaved, initializer, nil)
55
56local sets = { }
57
58table.setmetatableindex(tobesaved, function(t,k)
59    local v = { }
60    t[k] = v
61    return v
62end)
63
64table.setmetatableindex(sets, function(t,k)
65    local v = {
66        index = 0,
67        order = 0,
68    }
69    t[k] = v
70    return v
71end)
72
73local function setdata(settings)
74    local name = settings.name
75    local tag  = settings.tag
76    local data = settings.data
77    local list = tobesaved[name]
78    if settings.convert and type(data) == "string" then
79        data = settings_to_hash(data)
80    end
81    if type(data) ~= "table" then
82        data = { data = data }
83    end
84    if not tag then
85        tag = #list + 1
86    else
87        tag = tonumber(tag) or tag -- autonumber saves keys
88    end
89    list[tag] = data
90    if settings.delay == v_yes then
91        local set = sets[name]
92        local index = set.index + 1
93        set.index = index
94        data.index = index
95        data.order = index
96        data.realpage = texgetcount("realpageno")
97        if trace_datasets then
98            report_dataset("action %a, name %a, tag %a, index %a","assign delayed",name,tag,index)
99        end
100    elseif trace_datasets then
101        report_dataset("action %a, name %a, tag %a","assign immediate",name,tag)
102    end
103    return name, tag, data
104end
105
106datasets.setdata = setdata
107
108function datasets.extend(name,tag)
109    if type(name) == "table" then
110        name, tag = name.name, name.tag
111    end
112    local set = sets[name]
113    local order = set.order + 1
114    local realpage = texgetcount("realpageno")
115    set.order = order
116    local t = tobesaved[name][tag]
117    t.realpage = realpage
118    t.order = order
119    if trace_datasets then
120        report_dataset("action %a, name %a, tag %a, page %a, index %a","flush by order",name,tag,t.index or 0,order,realpage)
121    end
122end
123
124function datasets.getdata(name,tag,key,default)
125    local t = collected[name]
126    if t == nil then
127        if trace_datasets then
128            report_dataset("error: unknown dataset, name %a",name)
129        end
130    elseif type(t) ~= "table" then
131        return t
132    else
133        t = t[tag] or t[tonumber(tag)]
134        if not t then
135            if trace_datasets then
136                report_dataset("error: unknown dataset, name %a, tag %a",name,tag)
137            end
138        elseif key then
139            return t[key] or default
140        else
141            return t
142        end
143    end
144    return default
145end
146
147local function setdataset(settings)
148    settings.convert = true
149    local name, tag = setdata(settings)
150    if settings.delay ~= v_yes then
151        --
152    else
153        context(new_latelua { action = job.datasets.extend, name = name, tag = tag })
154    end
155end
156
157local cache = table.setmetatableindex(function(t,k)
158    local v = table.load(k..".tuc")
159    if v then
160        v = v.job
161        if v then
162            v = v.datasets
163            if v then
164                v = v.collected
165            end
166        end
167    end
168    if not v then
169        v = { }
170        if trace_datasets then
171            report_dataset("error: unknown dataset job %a",k)
172        end
173    end
174    t[k] = v
175    return v
176end)
177
178local function datasetvariable(name,tag,key,cache)
179    local t = (cache or collected)[name]
180    if t == nil then
181        if trace_datasets then
182            report_dataset("error: unknown dataset, name %a, tag %a, not passed to tex",name) -- no tag
183        end
184    elseif type(t) ~= "table" then
185        context(tostring(t))
186    else
187        t = t and (t[tag] or t[tonumber(tag)])
188        if not t then
189            if trace_datasets then
190                report_dataset("error: unknown dataset, name %a, tag %a, not passed to tex",name,tag)
191            end
192        elseif type(t) == "table" then
193            local s = t[key]
194            if type(s) ~= "table" then
195                context(tostring(s))
196            elseif trace_datasets then
197                report_dataset("error: unknown dataset, name %a, tag %a, not passed to tex",name,tag)
198            end
199        end
200    end
201end
202
203local function datasetvariablefromjob(jobnname,name,tag,key)
204    datasetvariable(name,tag,key,cache[jobnname])
205end
206
207implement {
208    name      = "setdataset",
209    actions   = setdataset,
210    arguments = {
211        {
212            { "name" },
213            { "tag" },
214            { "delay" },
215            { "data" },
216        }
217    }
218}
219
220implement {
221    name      = "datasetvariable",
222    actions   = datasetvariable,
223    arguments = "3 strings",
224}
225
226implement {
227    name      = "datasetvariablefromjob",
228    arguments = { "string", "string", "string", "string" },
229    actions   = datasetvariablefromjob
230}
231
232-- We also provide an efficient variant for page states.
233
234local collected = allocate()
235local tobesaved = allocate()
236
237local pagestates = {
238    collected = collected,
239    tobesaved = tobesaved,
240}
241
242job.pagestates = pagestates
243
244local function initializer()
245    collected = pagestates.collected
246    tobesaved = pagestates.tobesaved
247end
248
249job.register("job.pagestates.collected", tobesaved, initializer, nil)
250
251table.setmetatableindex(tobesaved, "table")
252
253local function setstate(settings)
254    local name = settings.name
255    local tag  = settings.tag
256    local list = tobesaved[name]
257    if not tag then
258        tag = #list + 1
259    else
260        tag = tonumber(tag) or tag -- autonumber saves keys
261    end
262    local realpage = texgetcount("realpageno")
263    local data = realpage
264    list[tag] = data
265    if trace_pagestates then
266        report_pagestate("action %a, name %a, tag %a, preset %a","set",name,tag,realpage)
267    end
268    return name, tag, data
269end
270
271local function extend(name,tag)
272    local realpage = texgetcount("realpageno")
273    if trace_pagestates then
274        report_pagestate("action %a, name %a, tag %a, preset %a","synchronize",name,tag,realpage)
275    end
276    tobesaved[name][tag] = realpage
277end
278
279local function realpage(name,tag,default)
280    local t = collected[name]
281    if t then
282        t = t[tag] or t[tonumber(tag)]
283        if t then
284            return tonumber(t or default)
285        elseif trace_pagestates then
286            report_pagestate("error: unknown dataset, name %a, tag %a",name,tag)
287        end
288    elseif trace_pagestates then
289        report_pagestate("error: unknown dataset, name %a, tag %a",name) -- nil
290    end
291    return default
292end
293
294local function realpageorder(name,tag)
295    local t = collected[name]
296    if t then
297        local p = t[tag]
298        if p then
299            local n = 1
300            for i=tag-1,1,-1 do
301                if t[i] == p then
302                    n = n  +1
303                end
304            end
305            return n
306        end
307    end
308    return 0
309end
310
311pagestates.setstate      = setstate
312pagestates.extend        = extend
313pagestates.realpage      = realpage
314pagestates.realpageorder = realpageorder
315
316function pagestates.countervalue(name)
317    return name and texgetcount(getnamespace("pagestatecounter") .. name) or 0
318end
319
320local function setpagestate(settings)
321    local name, tag = setstate(settings)
322 -- context(new_latelua(function() extend(name,tag) end))
323    ctx_latelua(function() extend(name,tag) end)
324end
325
326local function setpagestaterealpageno(name,tag)
327    local t = collected[name]
328    t = t and (t[tag] or t[tonumber(tag)])
329    texsetcount("realpagestateno",t or texgetcount("realpageno"))
330end
331
332implement {
333    name      = "setpagestate",
334    actions   = setpagestate,
335    arguments = {
336        {
337            { "name" },
338            { "tag" },
339            { "delay" },
340        }
341    }
342}
343
344implement {
345    name      = "pagestaterealpage",
346    actions   = { realpage, context },
347    arguments = "2 strings",
348}
349
350implement {
351    name      = "setpagestaterealpageno",
352    actions   = setpagestaterealpageno,
353    arguments = "2 strings",
354}
355
356implement {
357    name      = "pagestaterealpageorder",
358    actions   = { realpageorder, context },
359    arguments = { "string", "integer" }
360}
361