syst-aux.lua /size: 18 Kb    last modification: 2021-10-28 13:50
1
    if 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    = P(" ")^0 / ""
125local nohash    = 1 - P("#")
126local digit     = R("09")
127local double    = P("##") / "#"
128local single    = P("#")
129local sentinel  = spaces * (nohash^1 / "\\%0")
130local sargument = (single * digit)^1
131local dargument = (double * digit)^1
132
133-- third variant:
134
135local global    = nil
136local protected = nil
137local expanded  = nil
138local optional  = nil
139local csname    = nil
140local rest      = nil
141
142local function catcodes_s()
143    setcatcode(32,10) -- space
144    setcatcode(13, 5) -- endofline
145end
146
147local function catcodes_n()
148    setcatcode(32, 9) -- ignore
149    setcatcode(13, 9) -- ignore
150end
151
152local space  = P(" ")
153local spaces = space^0
154
155local option = (
156        P("single")
157      + P("double")
158      + P("triple")
159      + P("quadruple")
160      + P("quintuple")
161      + P("sixtuple")
162  ) * (P("empty") + P("argument"))
163
164local pattern = (
165    (
166        spaces * (
167            ( P("spaces")     * space / catcodes_s )
168          + ( P("nospaces")   * space / catcodes_n )
169          + ( P("global")     * space / function()  global    = true end )
170          + ( P("protected")  * space / function()  protected = true end)
171          + ( P("permanent")  * space )
172          + ( P("expanded")   * space / function()  expanded  = true end)
173          + ( P("tolerant")   * space )
174          + ( P("instance")   * space )
175          + ( P("frozen")     * space )
176          + ( P("mutable")    * space )
177          + ( P("immutable")  * space )
178          + ( P("unexpanded") * space / function()  protected = true end)
179          + ( C(option)       * space / function(s) optional  = s    end)
180        )
181    )^0
182  * spaces * ( C((1-S(" #["))^1) )
183  * spaces *   Cs(
184        ( P("[") * dargument * P("]") + dargument)^1 * sentinel^-1 * double^-1
185      + ( P("[") * sargument * P("]") + sargument)^1 * sentinel^-1 * single^-1
186      + sentinel^-1 * (double+single)^-1
187    )
188)
189
190local ctx_dostarttexdefinition = context.dostarttexdefinition
191
192local function texdefinition_one(str)
193    global    = false
194    protected = false
195    expanded  = false
196    optional  = false
197    csname, rest = lpegmatch(pattern,str)
198    ctx_dostarttexdefinition()
199end
200
201local function texdefinition_two()
202    if optional then
203        context (
204            (protected and [[\protected]] or "") ..
205            [[\expandafter]] .. (global and [[\xdef]] or [[\edef]]) ..
206            [[\csname ]] .. csname .. [[\endcsname{\expandafter\noexpand\expandafter\do]] .. optional ..
207            [[\csname _do_]] .. csname .. [[_\endcsname}\expandafter]] .. (global and [[\gdef]] or  [[\edef]]) ..
208            [[\csname _do_]] .. csname .. [[_\endcsname ]] ..
209            rest
210        )
211    else
212        context (
213            (protected and [[\protected]] or "") ..
214            [[\expandafter]] .. (global and (expanded and [[\xdef]] or [[\gdef]]) or (expanded and [[\edef]] or [[\def]])) ..
215            [[\csname ]] .. csname .. [[\endcsname ]] ..
216            rest
217        )
218    end
219end
220
221implement { name = "texdefinition_one", actions = texdefinition_one, scope = "private", arguments = "string" }
222implement { name = "texdefinition_two", actions = texdefinition_two, scope = "private" }
223
224do
225
226    -- Quite probably we don't yet have characters loaded so we delay some
227    -- aliases.
228
229    local _lower_, _upper_, _strip_
230
231    _lower_ = function(s)
232        if characters and characters.lower then
233            _lower_ = characters.lower
234            return _lower_(s)
235        end
236        return string.lower(s)
237    end
238
239    _upper_ = function(s)
240        if characters and characters.upper then
241            _upper_ = characters.upper
242            return _upper_(s)
243        end
244        return string.upper(s)
245    end
246
247    _strip_ = function(s)
248        -- or utf.strip
249        if string.strip then
250            _strip_ = string.strip
251            return _strip_(s)
252        end
253        return s
254    end
255
256    local function lower(s) context(_lower_(s)) end
257    local function upper(s) context(_upper_(s)) end
258    local function strip(s) context(_strip_(s)) end
259
260    implement { name = "upper", arguments = "string", actions = upper }
261    implement { name = "lower", arguments = "string", actions = lower }
262    implement { name = "strip", arguments = "string", actions = strip }
263
264end
265
266implement {
267    name      = "converteddimen",
268    arguments = { "dimen", "string" },
269    actions   = function(dimen,unit)
270        context(number.todimen(dimen,unit or "pt","%0.5f")) -- no unit appended (%F)
271    end
272}
273
274-- where, not really the best spot for this:
275
276implement {
277    name      = "immediatemessage",
278    arguments = { "'message'", "string" },
279    actions   = logs.status
280}
281
282implement {
283    name    = "resettimer",
284    actions = function()
285        statistics.resettiming("whatever")
286        statistics.starttiming("whatever")
287    end
288}
289
290implement {
291    name    = "benchmarktimer",
292    actions = function()
293        statistics.benchmarktimer("whatever")
294    end
295}
296
297implement {
298    name    = "elapsedtime",
299    actions = function()
300        statistics.stoptiming("whatever")
301        context(statistics.elapsedtime("whatever"))
302    end
303}
304
305implement {
306    name      = "elapsedsteptime",
307    arguments = "integer",
308    actions   = function(n)
309        statistics.stoptiming("whatever")
310        local t = statistics.elapsed("whatever")/(n > 0 and n or 1)
311        if t > 0 then
312            context("%0.9f",t)
313        else
314            context(0)
315        end
316    end
317}
318
319local accuracy = table.setmetatableindex(function(t,k)
320    local v = formatters["%0." ..k .. "f"]
321    t[k] = v
322    return v
323end)
324
325implement {
326    name      = "rounded",
327    arguments = "integer",
328    actions   = function(n,m) context(accuracy[n](m)) end
329}
330
331-- not faster but just less tracing:
332
333local ctx_protected_cs         = context.protected.cs -- more efficient
334
335local ctx_firstoftwoarguments  = ctx_protected_cs.firstoftwoarguments
336local ctx_secondoftwoarguments = ctx_protected_cs.secondoftwoarguments
337local ctx_firstofoneargument   = ctx_protected_cs.firstofoneargument
338local ctx_gobbleoneargument    = ctx_protected_cs.gobbleoneargument
339
340context.firstoftwoarguments    = ctx_firstoftwoarguments
341context.secondoftwoarguments   = ctx_secondoftwoarguments
342context.firstofoneargument     = ctx_firstofoneargument
343context.gobbleoneargument      = ctx_gobbleoneargument
344
345local ctx_iftrue  = context.iftrue
346local ctx_iffalse = context.iffalse
347
348local hash = utilities.parsers.hashes.settings_to_set
349
350local function doifelsecommon(a,b)
351    if a == b then
352        setmacro("commalistelement",a)
353        if a == "" then
354            ctx_secondoftwoarguments()
355        else
356            ctx_firstoftwoarguments()
357        end
358        return
359    end
360    local ba = find(a,",",1,true)
361    local bb = find(b,",",1,true)
362    if ba and bb then
363        local ha = hash[a]
364        local hb = hash[b]
365     -- local ha = settings_to_set(a)
366     -- local hb = settings_to_set(b)
367        for k in next, ha do
368            if hb[k] then
369                setmacro("commalistelement",k)
370                ctx_firstoftwoarguments()
371                return
372            end
373        end
374    elseif ba then
375        if hash[a][b] then
376     -- if settings_to_set(a)[b] then
377            setmacro("commalistelement",b)
378            ctx_firstoftwoarguments()
379            return
380        end
381    elseif bb then
382        if hash[b][a] then
383     -- if settings_to_set(b)[a] then
384            setmacro("commalistelement",a)
385            ctx_firstoftwoarguments()
386            return
387        end
388    end
389    setmacro("commalistelement","")
390    ctx_secondoftwoarguments()
391end
392
393local function doifcommon(a,b)
394    if a == b then
395        setmacro("commalistelement",a)
396        if a == "" then
397            ctx_gobbleoneargument()
398        else
399            ctx_firstofoneargument()
400        end
401        return
402    end
403    local ba = find(a,",",1,true)
404    local bb = find(b,",",1,true)
405    if ba and bb then
406        local ha = hash[a]
407        local hb = hash[b]
408     -- local ha = settings_to_set(a)
409     -- local hb = settings_to_set(b)
410        for k in next, ha do
411            if hb[k] then
412                setmacro("commalistelement",k)
413                ctx_firstofoneargument()
414                return
415            end
416        end
417    elseif ba then
418        if hash[a][b] then
419     -- if settings_to_set(a)[b] then
420            setmacro("commalistelement",b)
421            ctx_firstofoneargument()
422            return
423        end
424    elseif bb then
425        if hash[b][a] then
426     -- if settings_to_set(b)[a] then
427            setmacro("commalistelement",a)
428            ctx_firstofoneargument()
429            return
430        end
431    end
432    setmacro("commalistelement","")
433    ctx_gobbleoneargument()
434end
435
436local function doifnotcommon(a,b)
437    if a == b then
438        setmacro("commalistelement",a)
439        if a == "" then
440            ctx_firstofoneargument()
441        else
442            ctx_gobbleoneargument()
443        end
444        return
445    end
446    local ba = find(a,",",1,true)
447    local bb = find(b,",",1,true)
448    if ba and bb then
449        local ha = hash[a]
450        local hb = hash[b]
451     -- local ha = settings_to_set(a)
452     -- local hb = settings_to_set(b)
453        for k in next, ha do
454            if hb[k] then
455                setmacro("commalistelement",k)
456                ctx_gobbleoneargument()
457                return
458            end
459        end
460    elseif ba then
461        if hash[a][b] then
462     -- if settings_to_set(a)[b] then
463            setmacro("commalistelement",b)
464            ctx_gobbleoneargument()
465            return
466        end
467    elseif bb then
468        if hash[b][a] then
469     -- if settings_to_set(b)[a] then
470            setmacro("commalistelement",a)
471            ctx_gobbleoneargument()
472            return
473        end
474    end
475    setmacro("commalistelement","")
476    ctx_firstofoneargument()
477end
478
479-- local function hascommonargumentcondition(a,b)
480--     if a == b then
481--         setmacro("commalistelement",a)
482--         if a == "" then
483--             ctx_iffalse()
484--         else
485--             ctx_iftrue()
486--         end
487--         return
488--     end
489--     local ba = find(a,",",1,true)
490--     local bb = find(b,",",1,true)
491--     if ba and bb then
492--         local ha = hash[a]
493--         local hb = hash[b]
494--         for k in next, ha do
495--             if hb[k] then
496--                 setmacro("commalistelement",k)
497--                 ctx_iftrue()
498--                 return
499--             end
500--         end
501--     elseif ba then
502--         if hash[a][b] then
503--             setmacro("commalistelement",b)
504--             ctx_iftrue()
505--             return
506--         end
507--     elseif bb then
508--         if hash[b][a] then
509--             setmacro("commalistelement",a)
510--             ctx_iftrue()
511--             return
512--         end
513--     end
514--     setmacro("commalistelement","")
515--     ctx_iffalse()
516-- end
517
518local function doifelseinset(a,b)
519    if a == b then
520        setmacro("commalistelement",a)
521        if a == "" then
522            ctx_secondoftwoarguments()
523        else
524            ctx_firstoftwoarguments()
525        end
526        return
527    end
528    local bb = find(b,",",1,true)
529    if bb then
530        if hash[b][a] then
531     -- if settings_to_set(b)[a] then
532            setmacro("commalistelement",a)
533            ctx_firstoftwoarguments()
534            return
535        end
536    end
537    setmacro("commalistelement","")
538    ctx_secondoftwoarguments()
539end
540
541local function doifinset(a,b)
542    if a == b then
543        setmacro("commalistelement",a)
544        if a == "" then
545            ctx_gobbleoneargument()
546        else
547            ctx_firstofoneargument()
548        end
549        return
550    end
551    local bb = find(b,",",1,true)
552    if bb then
553       if hash[b][a] then
554    -- if settings_to_set(b)[a] then
555            setmacro("commalistelement",a)
556            ctx_firstofoneargument()
557            return
558        end
559    end
560    setmacro("commalistelement","")
561    ctx_gobbleoneargument()
562end
563
564local function doifnotinset(a,b)
565    if a == b then
566        setmacro("commalistelement",a)
567        if a == "" then
568            ctx_firstofoneargument()
569        else
570            ctx_gobbleoneargument()
571        end
572        return
573    end
574    local bb = find(b,",",1,true)
575    if bb then
576        if hash[b][a] then
577     -- if settings_to_set(b)[a] then
578            setmacro("commalistelement",a)
579            ctx_gobbleoneargument()
580            return
581        end
582    end
583    setmacro("commalistelement","")
584    ctx_firstofoneargument()
585end
586
587implement {
588    name      = "doifelsecommon",
589    actions   = doifelsecommon,
590    arguments = "2 strings",
591}
592
593implement {
594    name      = "doifcommon",
595    actions   = doifcommon,
596    arguments = "2 strings",
597}
598
599implement {
600    name      = "doifnotcommon",
601    actions   = doifnotcommon,
602    arguments = "2 strings",
603}
604
605-- implement {
606--     name      = "hascommonargumentcondition",
607--     actions   = hascommonargumentcondition,
608--     arguments = "2 strings",
609--     arguments = { "argument", "argument" },
610-- }
611
612implement {
613    name      = "doifelseinset",
614    actions   = doifelseinset,
615    arguments = "2 strings",
616--     arguments = { "argument", "argument" },
617}
618
619implement {
620    name      = "doifinset",
621    actions   = doifinset,
622    arguments = "2 strings",
623}
624
625implement {
626    name      = "doifnotinset",
627    actions   = doifnotinset,
628    arguments = "2 strings",
629}
630
631-- done elsewhere:
632--
633-- local function firstinset(a)
634--     local aa = hash[a]
635--     context(aa and aa[1] or a)
636-- end
637--
638-- implement {
639--     name      = "firstinset",
640--     actions   = firstinset,
641--     arguments = "string",
642--     private   = false,
643-- }
644
645-- implement {
646--     name      = "stringcompare",
647--     arguments = "2 strings",
648--     actions   = function(a,b)
649--         context((a == b and 0) or (a > b and 1) or -1)
650--     end
651-- }
652--
653-- implement {
654--     name      = "doifelsestringafter",
655--     arguments = "2 strings",
656--     actions   = function(a,b)
657--         ctx_doifelse((a == b and 0) or (a > b and 1) or -1)
658--     end
659-- }
660--
661-- implement {
662--     name      = "doifelsestringbefore",
663--     arguments = "2 strings",
664--     actions   = function(a,b)
665--         ctx_doifelse((a == b and 0) or (a < b and -1) or 1)
666--     end
667-- }
668
669-- implement { -- not faster than addtocommalist
670--     name      = "additemtolist", -- unique
671--     arguments = "2 strings",
672--     actions   = function(l,s)
673--         if l == "" or s == l then
674--          -- s = s
675--         elseif find("," .. l .. ",","," .. s .. ",") then
676--             s = l
677--         else
678--             s = l .. "," .. s
679--         end
680--         context(s)
681--     end
682-- }
683
684local bp = number.dimenfactors.bp
685
686implement {
687    name      = "tobigpoints",
688    actions   = function(d) context("%.5F",bp * d) end,
689    arguments = "dimension",
690}
691
692implement {
693    name      = "towholebigpoints",
694    actions   = function(d) context("%r",bp * d) end,
695    arguments = "dimension",
696}
697
698-- for now here:
699
700local function getshape(s)
701    local t = texget(s)
702    local n = t and #t or 0
703    context(n)
704    if n > 0 then
705        for i=1,n do
706            local ti = t[i]
707            if type(ti) == "table" then
708                context(" %isp %isp",ti[1],ti[2])
709            else
710                context(" %i",ti)
711            end
712        end
713    end
714end
715
716implement {
717    name    = "getparshape",
718    public  = true,
719    actions = function() getshape("parshape") end,
720}
721implement {
722    name    = "getclubpenalties",
723    public  = true,
724    actions = function() getshape("clubpenalties") end,
725}
726implement {
727    name    = "getinterlinepenalties",
728    public  = true,
729    actions = function() getshape("interlinepenalties") end,
730    }
731implement {
732    name    = "getdisplaywidowpenalties",
733    public  = true,
734    actions = function() getshape("displaywidowpenalties") end,
735}
736implement {
737    name    = "getwidowpenalties",
738    public  = true,
739    actions = function() getshape("widowpenalties") end,
740}
741