strc-syn.lmt /size: 11 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['strc-syn'] = {
2    version   = 1.001,
3    comment   = "companion to str-syn.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 next, type = next, type
10local sortedkeys = table.sortedkeys
11
12local context      = context
13local implement    = interfaces.implement
14
15local allocate     = utilities.storage.allocate
16
17local sorters      = sorters
18
19local structures   = structures
20local synonyms     = structures.synonyms
21local tags         = structures.tags
22
23local collected    = allocate()
24local tobesaved    = allocate()
25
26local firstofsplit = sorters.firstofsplit
27local strip        = sorters.strip
28local splitter     = sorters.splitters.utf
29
30synonyms.collected = collected
31synonyms.tobesaved = tobesaved
32
33local progressions = { } -- false=not_yet_shown  true=shown
34
35local c_realpageno <const> = tex.iscount("realpageno")
36
37local texgetcount  = tex.getcount
38
39local v_all        <const> = interfaces.variables.all
40local v_current    <const> = interfaces.variables.current
41
42local function initializer()
43    collected = synonyms.collected
44    tobesaved = synonyms.tobesaved
45end
46
47local function finalizer()
48    for entry, data in next, tobesaved do
49        data.hash = nil
50     -- being sparse can be an option but often we actually do want the
51     -- whole list so we don't do this ... only as possible option
52     --
53-- maybe pages = sortedkeys(pages) is more efficient
54     --
55     -- local entries = data.entries
56     -- local t = { }
57     -- local n = 0
58     -- for i=1,#entries do
59     --     local e = entries[i]
60     --     if e.definition.shown then
61     --         n = n + 1
62     --         t[n] = e
63     --     end
64     -- end
65     -- data.entries = t
66    end
67end
68
69job.register('structures.synonyms.collected', tobesaved, initializer, finalizer)
70
71-- todo: allocate becomes metatable
72
73table.setmetatableindex(tobesaved,function(t,k)
74    local v = {
75        metadata = {
76            language = 'en',
77            sorted   = false,
78            class    = v
79        },
80        entries  = {
81        },
82        hash = {
83        }
84    }
85    t[k] = v
86    return v
87end)
88
89function synonyms.define(class,kind)
90    local data = tobesaved[class]
91    data.metadata.kind = kind
92end
93
94function synonyms.register(class,kind,spec)
95    local data       = tobesaved[class]
96    local hash       = data.hash
97    local definition = spec.definition
98    local tag        = definition.tag or ""
99    data.metadata.kind = kind -- runtime, not saved in format (yet)
100    if not hash[tag] then
101        local entries = data.entries
102        entries[#entries+1] = spec
103        hash[tag] = spec
104    end
105end
106
107function synonyms.registerused(class,tag)
108    local data = tobesaved[class]
109    local okay = data.hash[tag]
110    if okay then
111        local definition = okay.definition
112        definition.used = true
113        definition.list = true
114    end
115end
116
117function synonyms.registershown(class,tag)
118    local data = tobesaved[class]
119    local okay = data.hash[tag]
120    if okay then
121        local definition = okay.definition
122        definition.shown = true
123        definition.list  = true
124    end
125end
126
127function synonyms.isused(class,tag)
128    local data = tobesaved[class]
129    local okay = data.hash[tag]
130    return okay and okay.definition.used or false
131end
132
133function synonyms.isshown(class,tag)
134    local data = tobesaved[class]
135    local okay = data.hash[tag]
136    return okay and okay.definition.shown or false
137end
138
139local function resetused(class)
140    for tag, data in next, tobesaved[class].hash do
141        data.definition.used = nil
142    end
143end
144
145local function resetshown(class)
146    for tag, data in next, tobesaved[class].hash do
147        data.definition.shown = nil
148    end
149end
150
151local function resetlist(class)
152    for tag, data in next, tobesaved[class].hash do
153        data.definition.list = nil
154    end
155end
156
157local function resetall(class)
158    for tag, data in next, tobesaved[class].hash do
159        local definition = data.definition
160        definition.used  = nil
161        definition.shown = nil
162        definition.list  = nil
163    end
164end
165
166synonyms.resetused  = resetused
167synonyms.resetshown = resetshown
168synonyms.resetlist  = resetlist
169synonyms.resetall   = resetall
170
171function synonyms.reset(class,what)
172    if what == "progress" then
173        progressions = { }
174    elseif what == "used" then
175        resetused(class)
176    elseif what == "shown" then
177        resetshown(class)
178    elseif what == "list" then
179        resetlist(class)
180    else
181        resetall(class)
182    end
183end
184
185function synonyms.synonym(class,tag)
186    local data = tobesaved[class]
187    local okay = data.hash[tag]
188    if okay then
189        local definition = okay.definition
190        definition.used = true
191        definition.list = true
192        context(definition.synonym)
193    end
194    if progressions[tag] == nil then
195        progressions[tag] = false -- not yet shown
196    end
197end
198
199function synonyms.meaning(class,tag)
200    local data = tobesaved[class]
201    local okay = data.hash[tag]
202    if okay then
203        local definition = okay.definition
204        definition.shown = true
205        definition.list  = true
206        context(definition.meaning)
207    end
208end
209
210function synonyms.pages(class,tag)
211    local data = tobesaved[class]
212    local okay = data.hash[tag]
213    if okay then
214        local definition = okay.definition
215        local pages = definition.pages
216        if pages and next(pages) then
217            context("%,t",sortedkeys(pages))
218        end
219    end
220end
221
222local ctx_latelua = context.latelua
223
224local function enhance(data)
225    data.pages[texgetcount(c_realpageno)] = true
226end
227
228function synonyms.enhance(class,tag)
229    local data = tobesaved[class]
230    local okay = data.hash[tag]
231    if okay then
232        local definition = okay.definition
233        local pages      = definition.pages
234        if not pages then
235            pages            = { }
236            definition.pages = pages
237        end
238        ctx_latelua { action = enhance, pages = pages }
239    end
240end
241
242synonyms.compare = sorters.comparers.basic -- (a,b)
243
244function synonyms.filter(data,options)
245    local result    = { }
246    local entries   = data.entries
247    local criterium = options and options.criterium
248    if criterium == v_all then
249        for i=1,#entries do
250            result[i] = entries[i]
251        end
252    else
253        for i=1,#entries do
254            local entry      = entries[i]
255            local definition = entry.definition
256            if definition.list then
257                local tag  = definition.tag
258                local done = progressions[tag]
259                if done == false then
260                    result[#result+1] = entry
261                    progressions[tag] = true
262                end
263            end
264        end
265        if criterium == v_current then
266            progressions = { }
267        end
268    end
269    data.result = result
270end
271
272function synonyms.prepare(data)
273    local result = data.result
274    if result then
275        for i=1, #result do
276            local entry      = result[i]
277            local definition = entry.definition
278            if definition then
279                local srt = definition.sortkey or ""
280                local tag = definition.tag or ""
281                local key = (srt ~= "" and srt) or (tag ~= "" and tag) or definition.synonym
282                if key then
283                    entry.split = splitter(strip(key))
284                end
285            end
286        end
287    end
288end
289
290function synonyms.sort(data,options)
291    sorters.sort(data.result,synonyms.compare)
292    data.metadata.sorted = true
293end
294
295function synonyms.finalize(data,options) -- mostly the same as registers so we will generalize it: sorters.split
296    local result   = data.result
297    local split    = { }
298    local nofsplit = 0
299    local lasttag  = nil
300    local lasttag  = nil
301    local nofdone  = 0
302    for k=1,#result do
303        local entry = result[k]
304        local first, tag = firstofsplit(entry)
305        if tag ~= lasttag then
306         -- if trace_registers then
307         --     report_registers("splitting at %a",tag)
308         -- end
309            done     = { }
310            nofdone  = 0
311            nofsplit = nofsplit + 1
312            lasttag  = tag
313            split[nofsplit] = { tag = tag, data = done }
314        end
315        nofdone = nofdone + 1
316        done[nofdone] = entry
317    end
318    data.result = split
319end
320
321-- for now, maybe at some point we will do a multipass or so
322-- maybe pass the settings differently
323
324local ctx_synonymentry = context.synonymentry
325
326function synonyms.flush(data,options)
327    local result = data.result
328    for i=1,#result do
329        local sublist = result[i]
330        local data    = sublist.data
331        for d=1,#data do
332            local entry = data[d].definition
333            ctx_synonymentry(d,entry.tag,entry.synonym,entry.meaning or "")
334        end
335    end
336    data.result          = nil
337    data.metadata.sorted = false
338end
339
340function synonyms.analyzed(class,options)
341    local data = collected[class]
342    if data and data.entries then
343        options = options or { }
344        sorters.setlanguage(options.language,options.method)
345        synonyms.filter(data,options)   -- filters entries to result
346        synonyms.prepare(data,options)  -- adds split table parallel to list table
347        synonyms.sort(data,options)     -- sorts entries in result
348        synonyms.finalize(data,options) -- do things with data.entries
349        data.metadata.sorted = true
350    end
351    return data and data.metadata.sorted and data.result and next(data.result)
352end
353
354function synonyms.process(class,options)
355    if synonyms.analyzed(class,options) then
356        synonyms.flush(collected[class],options)
357    end
358end
359
360-- todo: local higher up
361
362implement { name = "registerusedsynonym",  actions = synonyms.registerused,  arguments = "2 strings" }
363implement { name = "registershownsynonym", actions = synonyms.registershown, arguments = "2 strings" }
364implement { name = "synonymmeaning",       actions = synonyms.meaning,       arguments = "2 arguments" }
365implement { name = "synonymname",          actions = synonyms.synonym,       arguments = "2 arguments" }
366implement { name = "synonympages",         actions = synonyms.pages,         arguments = "2 arguments" }
367implement { name = "enhancesynonym",       actions = synonyms.enhance,       arguments = "2 arguments" }
368--        { name = "resetusedsynonyms",    actions = resetused,              arguments = "string" }
369--        { name = "resetshownsynonyms",   actions = resetshown,             arguments = "string" }
370--        { name = "resetlistsynonyms",    actions = resetlist,              arguments = "string" }
371implement { name = "resetsynonyms",        actions = synonyms.reset,         arguments = "2 strings" }
372
373implement {
374    name      = "doifelsesynonymused",
375    actions   = { synonyms.isused, commands.doifelse },
376    arguments = "2 arguments",
377}
378
379implement {
380    name      = "doifelsesynonymshown",
381    actions   = { synonyms.isshown, commands.doifelse },
382    arguments = "2 arguments",
383}
384
385implement {
386    name      = "registersynonym",
387    actions   = synonyms.register,
388    arguments = {
389        "string",
390        "string",
391        {
392            { "metadata", {
393                    { "catcodes", "integer" },
394                    { "coding" },
395                    { "xmlroot" }
396                }
397            },
398            {
399                "definition", {
400                    { "tag" },
401                    { "synonym" },
402                    { "meaning" },
403                    { "sortkey" },
404                    { "used", "boolean" }
405                }
406            }
407        }
408    }
409}
410
411implement {
412    name      = "processsynonyms",
413    actions   = synonyms.process,
414    arguments = {
415        "string",
416        {
417            { "criterium" },
418            { "language" },
419            { "method" }
420        }
421    }
422}
423