syst-aux.lmt /size: 15 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['syst-aux'] = {
2    version   = 1.001,
3    comment   = "companion to syst-aux.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-- slower than lpeg:
10--
11-- utfmatch(str,"(.?)(.*)$")
12-- utf.sub(str,1,1)
13
14local tonumber, next, type = tonumber, next, type
15local utfsub = utf.sub
16local P, S, R, C, Cc, Cs, Carg, lpegmatch = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Cc, lpeg.Cs, lpeg.Carg, lpeg.match
17local find, formatters = string.find, string.formatters
18
19local context           = context
20local implement         = interfaces.implement
21local setmacro          = interfaces.setmacro
22local setcatcode        = tex.setcatcode
23local texget            = tex.get
24local utf8character     = lpeg.patterns.utf8character
25local settings_to_array = utilities.parsers.settings_to_array
26local settings_to_set   = utilities.parsers.settings_to_set
27
28local pattern           = C(utf8character^-1) * C(P(1)^0)
29
30implement {
31    name      = "getfirstcharacter",
32    arguments = "string",
33    actions   = function(str)
34        local first, rest = lpegmatch(pattern,str)
35        setmacro("firstcharacter",first)
36        setmacro("remainingcharacters",rest)
37    end
38}
39
40implement {
41    name      = "thefirstcharacter",
42    arguments = "string",
43    actions   = function(str)
44        local first, rest = lpegmatch(pattern,str)
45        context(first)
46    end
47}
48
49implement {
50    name      = "theremainingcharacters",
51    arguments = "string",
52    actions   = function(str)
53        local first, rest = lpegmatch(pattern,str)
54        context(rest)
55    end
56}
57
58local pattern      = C(utf8character^-1)
59local ctx_doifelse = commands.doifelse
60
61implement {
62    name      = "doifelsefirstchar",
63    arguments = "2 strings",
64    actions   = function(str,chr)
65        ctx_doifelse(lpegmatch(pattern,str) == chr)
66    end
67}
68
69implement {
70    name      = "getsubstring",
71    arguments = "3 strings",
72    actions   = function(str,first,last)
73        context(utfsub(str,tonumber(first),tonumber(last)))
74    end
75}
76
77-- function commands.addtocommalist(list,item)
78--     if list == "" then
79--         context(item)
80--     else
81--         context("%s,%s",list,item) -- using tex.print is some 10% faster
82--     end
83-- end
84--
85-- function commands.removefromcommalist(list,item)
86--     if list == "" then
87--         context(item)
88--     else
89--         -- okay, using a proper lpeg is probably faster
90--         -- we could also check for #l = 1
91--         local l = settings_to_array(list)
92--         local t, n = { }
93--         for i=1,#l do
94--             if l[i] ~= item then
95--                 n = n + 1
96--                 t[n] = item
97--             end
98--         end
99--         if n == 0 then
100--             context(item)
101--         else
102--             context(concat(list,","))
103--         end
104--     end
105-- end
106
107local pattern = (C((1-P("%"))^1) * Carg(1)) / function(n,d)
108    return formatters["%.0fsp"](d * tonumber(n)/100) end * P("%") * P(-1) -- .0 ?
109
110-- percentageof("10%",65536*10)
111
112implement {
113    name      = "percentageof",
114    arguments = { "string", "dimen" },
115    actions   = function(str,dim)
116        context(lpegmatch(pattern,str,1,dim) or str)
117    end
118}
119
120-- \gdef\setpercentdimen#1#2%
121--   {#1=\ctxcommand{percentageof("#2",\number#1)}\relax}
122
123local space     = P(" ")
124local spaces    = space^0
125local nospaces  = spaces / ""
126local nohash    = 1 - P("#")
127local digit     = R("09")
128local double    = P("##") / "#"
129local single    = P("#")
130local sentinel  = nospaces * (nohash^1 / "\\%0")
131local whatever  = S("+-/*_^=:") + digit + R("AZ")
132local sargument = (single * digit)^1
133local dargument = (double * digit)^1
134local swhatever = (single * whatever)^1
135local dwhatever = (double * whatever)^1
136
137-- see syst-aux.lua for historic variants
138
139local global    = nil
140local protected = nil
141local permanent = nil
142local expanded  = nil
143local mutable   = nil
144local immutable = nil
145local optional  = nil
146local tolerant  = nil
147local instance  = nil
148local frozen    = nil
149local csname    = nil
150local rest      = nil
151
152local function catcodes_s()
153    setcatcode(32,10) -- space
154    setcatcode(13, 5) -- endofline
155end
156
157local function catcodes_n()
158    setcatcode(32, 9) -- ignore
159    setcatcode(13, 9) -- ignore
160end
161
162local function oldoption(s)
163    if optional > 1 then
164        optional = optional - 1
165        return s .. "#*"
166    else
167        return s
168    end
169end
170
171local option = (
172        P("single")    * Cc(1)
173      + P("double")    * Cc(2)
174      + P("triple")    * Cc(3)
175      + P("quadruple") * Cc(4)
176      + P("quintuple") * Cc(5)
177      + P("sixtuple")
178  ) * (P("empty") + P("argument"))
179
180local pattern = (
181    (
182        spaces * (
183            ( P("spaces")        * space / catcodes_s )
184          + ( P("nospaces")      * space / catcodes_n )
185          + ( P("global")        * space / function()  global    = true end)
186          + ( P("protected")     * space / function()  protected = 1    end)
187          + ( P("semiprotected") * space / function()  protected = 2    end)
188          + ( P("permanent")     * space / function()  permanent = true end)
189          + ( P("expanded")      * space / function()  expanded  = true end)
190          + ( P("tolerant")      * space / function()  tolerant  = true end)
191          + ( P("instance")      * space / function()  instance  = true end)
192          + ( P("frozen")        * space / function()  frozen    = true end)
193          + ( P("mutable")       * space / function()  mutable   = true end)
194          + ( P("immutable")     * space / function()  immutable = true end)
195          + ( P("unexpanded")    * space / function()  protected = 1    end)
196          + ( option             * space / function(s) tolerant  = true
197                                                       optional  = s    end)
198        )
199    )^0
200  * spaces * ( C((1-S(" #["))^1) )
201  * spaces *   Cs(
202        (Cs(P("[") * dargument * P("]")) / oldoption + nospaces * dwhatever)^1 * sentinel^-1 * double^-1
203      + (Cs(P("[") * sargument * P("]")) / oldoption + nospaces * swhatever)^1 * sentinel^-1 * single^-1
204      + sentinel^-1 * (double+single)^-1
205    )
206)
207
208local ctx_dostarttexdefinition = context.dostarttexdefinition
209
210local function texdefinition_one(str)
211    global    = false
212    protected = false
213    permanent = false
214    expanded  = false
215    mutable   = false
216    immutable = false
217    optional  = 0
218    tolerant  = false
219    instance  = false
220    frozen    = false
221    csname, rest = lpegmatch(pattern,str)
222-- print("str  : " .. str)
223-- print("cs   : " .. csname)
224-- print("rest : " .. rest)
225    ctx_dostarttexdefinition()
226end
227
228local function texdefinition_two()
229    context (
230        (tolerant  and [[\tolerant]]  or "") ..
231        (frozen    and [[\frozen]]    or "") ..
232        (protected and (protected == 1 and [[\protected]] or [[\semiprotected]]) or "") ..
233        (permanent and [[\permanent]] or "") ..
234        (instance  and [[\instance]]  or "") ..
235        (mutable   and [[\mutable]]   or "") ..
236        (immutable and [[\immutable]] or "") ..
237--         [[\expandafter]] .. (global and (expanded and [[\xdef]] or [[\gdef]]) or (expanded and [[\edef]] or [[\def]])) ..
238--         [[\csname ]] .. csname .. [[\endcsname ]] ..
239        (global and (expanded and [[\xdefcsname ]] or [[\gdefcsname ]]) or (expanded and [[\edefcsname ]] or [[\defcsname ]])) ..
240        csname .. [[\endcsname ]] ..
241        rest
242    )
243end
244
245implement { name = "texdefinition_one", actions = texdefinition_one, scope = "private", arguments = "tokenstring" }
246implement { name = "texdefinition_two", actions = texdefinition_two, scope = "private" }
247
248do
249
250    -- Quite probably we don't yet have characters loaded so we delay some
251    -- aliases.
252
253    local _lower_, _upper_, _strip_
254
255    _lower_ = function(s)
256        if characters and characters.lower then
257            _lower_ = characters.lower
258            return _lower_(s)
259        end
260        return string.lower(s)
261    end
262
263    _upper_ = function(s)
264        if characters and characters.upper then
265            _upper_ = characters.upper
266            return _upper_(s)
267        end
268        return string.upper(s)
269    end
270
271    _strip_ = function(s)
272        -- or utf.strip
273        if string.strip then
274            _strip_ = string.strip
275            return _strip_(s)
276        end
277        return s
278    end
279
280    local function lower(s) context(_lower_(s)) end
281    local function upper(s) context(_upper_(s)) end
282    local function strip(s) context(_strip_(s)) end
283
284    implement { name = "upper", arguments = "string", actions = upper }
285    implement { name = "lower", arguments = "string", actions = lower }
286    implement { name = "strip", arguments = "string", actions = strip }
287
288end
289
290implement {
291    name      = "converteddimen",
292    arguments = { "dimen", "string" },
293    actions   = function(dimen,unit)
294        context(number.todimen(dimen,unit or "pt","%0.5f")) -- no unit appended (%F)
295    end
296}
297
298-- where, not really the best spot for this:
299
300implement {
301    name      = "immediatemessage",
302    public    = true,
303    arguments = { "'message'", "string" },
304    actions   = logs.status
305}
306
307implement {
308    name      = "writestring",
309    public    = true,
310    protected = true,
311    arguments = "string",
312    actions   = function (s)
313        logs.writer(s,"\n")
314    end,
315}
316
317implement {
318    name      = "writeline",
319    public    = true,
320    protected = true,
321    actions   = logs.newline,
322}
323
324implement {
325    name    = "resettimer",
326    actions = function()
327        statistics.resettiming("whatever")
328        statistics.starttiming("whatever")
329    end
330}
331
332implement {
333    name    = "benchmarktimer",
334    actions = function()
335        statistics.benchmarktimer("whatever")
336    end
337}
338
339implement {
340    name    = "elapsedtime",
341    actions = function()
342        statistics.stoptiming("whatever")
343        context(statistics.elapsedtime("whatever"))
344    end
345}
346
347implement {
348    name      = "elapsedsteptime",
349    arguments = "integer",
350    actions   = function(n)
351        statistics.stoptiming("whatever")
352        local t = statistics.elapsed("whatever")/(n > 0 and n or 1)
353        if t > 0 then
354            context("%0.9f",t)
355        else
356            context(0)
357        end
358    end
359}
360
361local accuracy = table.setmetatableindex(function(t,k)
362    local v = formatters["%0." ..k .. "f"]
363    t[k] = v
364    return v
365end)
366
367implement {
368    name      = "rounded",
369    arguments = "integer",
370    actions   = function(n,m) context(accuracy[n](m)) end
371}
372
373-- not faster but just less tracing:
374
375local ctx_protected_cs         = context.protected.cs -- more efficient
376
377local ctx_firstoftwoarguments  = ctx_protected_cs.firstoftwoarguments
378local ctx_secondoftwoarguments = ctx_protected_cs.secondoftwoarguments
379local ctx_firstofoneargument   = ctx_protected_cs.firstofoneargument
380local ctx_gobbleoneargument    = ctx_protected_cs.gobbleoneargument
381
382context.firstoftwoarguments    = ctx_firstoftwoarguments
383context.secondoftwoarguments   = ctx_secondoftwoarguments
384context.firstofoneargument     = ctx_firstofoneargument
385context.gobbleoneargument      = ctx_gobbleoneargument
386
387local boolean_value <const> = tokens.values.boolean
388
389local hash = utilities.parsers.hashes.settings_to_set
390
391implement {
392    name      = "if_common",
393    usage     = "condition",
394    arguments = "2 arguments",
395    actions   = function(a,b)
396        if a == b then
397            setmacro("commalistelement",a)
398            return boolean_value, a ~= ""
399        end
400        local ba = find(a,",",1,true)
401        local bb = find(b,",",1,true)
402        if ba and bb then
403            local ha = hash[a]
404            local hb = hash[b]
405            for k in next, ha do
406                if hb[k] then
407                    setmacro("commalistelement",k)
408                    return boolean_value, true
409                end
410            end
411        elseif ba then
412            if hash[a][b] then
413                setmacro("commalistelement",b)
414                return boolean_value, true
415            end
416        elseif bb then
417            if hash[b][a] then
418                setmacro("commalistelement",a)
419                return boolean_value, true
420            end
421        end
422        setmacro("commalistelement","")
423        return boolean_value, false
424    end
425}
426
427implement {
428    name      = "ifcommon",
429    public    = true,
430    usage     = "condition",
431    arguments = "2 arguments",
432    actions   = function(a,b)
433        if a == b then
434            return boolean_value, a ~= ""
435        end
436        local ba = find(a,",",1,true)
437        local bb = find(b,",",1,true)
438        if ba and bb then
439            local ha = hash[a]
440            local hb = hash[b]
441            for k in next, ha do
442                if hb[k] then
443                    return boolean_value, true
444                end
445            end
446        elseif ba then
447            if hash[a][b] then
448                return boolean_value, true
449            end
450        elseif bb then
451            if hash[b][a] then
452                return boolean_value, true
453            end
454        end
455        return boolean_value, false
456    end
457}
458
459-- -- This one slower than the tex based one:
460--
461-- implement {
462--     name      = "if_inset",
463--     usage     = "condition",
464--     arguments = "2 arguments",
465--     actions   = function(a,b)
466--         if a == b then
467--             setmacro("commalistelement",a)
468--             return boolean_value, a ~= ""
469--         end
470--         local bb = find(b,",",1,true)
471--         if bb then
472--             if hash[b][a] then
473--                 setmacro("commalistelement",a)
474--                 return boolean_value, true
475--             end
476--         end
477--         setmacro("commalistelement","")
478--         return boolean_value, false
479--     end
480-- }
481
482implement {
483    name      = "ifinset",
484    public    = true,
485    usage     = "condition",
486    arguments = "2 arguments",
487    actions   = function(a,b)
488        if a == b then
489            return boolean_value, a ~= ""
490        end
491        local bb = find(b,",",1,true)
492        if bb then
493            if hash[b][a] then
494                return boolean_value, true
495            end
496        end
497        return boolean_value, false
498    end
499}
500
501local bp = number.dimenfactors.bp
502
503implement {
504    name      = "tobigpoints",
505    actions   = function(d) context("%.5F",bp * d) end,
506    arguments = "dimension",
507}
508
509implement {
510    name      = "towholebigpoints",
511    actions   = function(d) context("%r",bp * d) end,
512    arguments = "dimension",
513}
514
515-- for now here:
516
517local function getshape(s)
518    local t = texget(s)
519    local n = t and #t or 0
520    context(n)
521    if n > 0 then
522        for i=1,n do
523            local ti = t[i]
524            if type(ti) == "table" then
525                context(" %isp %isp",ti[1],ti[2])
526            else
527                context(" %i",ti)
528            end
529        end
530    end
531end
532
533implement {
534    name    = "getparshape",
535    public  = true,
536    actions = function() getshape("parshape") end,
537}
538implement {
539    name    = "getclubpenalties",
540    public  = true,
541    actions = function() getshape("clubpenalties") end,
542}
543implement {
544    name    = "getinterlinepenalties",
545    public  = true,
546    actions = function() getshape("interlinepenalties") end,
547    }
548implement {
549    name    = "getdisplaywidowpenalties",
550    public  = true,
551    actions = function() getshape("displaywidowpenalties") end,
552}
553implement {
554    name    = "getwidowpenalties",
555    public  = true,
556    actions = function() getshape("widowpenalties") end,
557}
558
559-- These have beem replaced by loop primitives:
560
561implement {
562    name      = "loopcs",
563    public    = true,
564    arguments = { "integerargument", "csname" },
565    actions   = function(n,cs)
566       local c = context[cs]
567       if n < 0 then
568           for i=-n,1 do c() end
569       else
570           for i= 1,n do c() end
571       end
572    end
573}
574
575implement {
576    name      = "loopcsn",
577    public    = true,
578    arguments = { "integerargument", "csname" },
579    actions   = function(n,cs)
580       local c = context[cs]
581       if n < 0 then
582           for i=-n,1 do c(i) end
583       else
584           for i= 1,n do c(i) end
585       end
586    end
587}
588