mlib-scn.lmt /size: 31 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['mlib-scn'] = {
2    version   = 1.001,
3    comment   = "companion to mlib-ctx.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-- Very experimental, for Alan and me.
10
11-- for i = 1 upto 32000 : % 0.062
12--     ts := 5mm / 20;
13-- endfor ;
14--
15-- for i = 1 upto 32000 : % 0.219
16--     ts := (getparameter "axis" "sy") / 20;
17-- endfor ;
18--
19-- for i = 1 upto 32000 : % 0.266
20--     ts := (getparameterx "axis" "sy") / 20;
21-- endfor ;
22--
23-- pushparameters "axis";
24-- for i = 1 upto 32000 : % 0.250
25--     ts := (getparameterx "sy") / 20;
26-- endfor ;
27-- popparameters;
28
29local type, next, rawget, rawset, getmetatable, tonumber = type, next, rawget, rawset, getmetatable, tonumber
30local byte, gmatch = string.byte, string.gmatch
31local insert, remove, combine = table.insert, table.remove, table.combine
32
33local mplib    = mplib
34local metapost = metapost
35
36local codes = metapost.codes
37local types = metapost.types
38
39local setmetatableindex   = table.setmetatableindex
40
41local scanners            = mp.scan
42local injectors           = mp.inject
43
44local scannext            = scanners.next
45local scanexpression      = scanners.expression
46local scantoken           = scanners.token
47local scansymbol          = scanners.symbol
48local scanproperty        = scanners.property
49local scannumeric         = scanners.numeric
50local scannumber          = scanners.number
51local scaninteger         = scanners.integer
52local scanboolean         = scanners.boolean
53local scanstring          = scanners.string
54local scanpair            = scanners.pair
55local scancolor           = scanners.color
56local scancmykcolor       = scanners.cmykcolor
57local scantransform       = scanners.transform
58local scanpath            = scanners.path
59local scanpen             = scanners.pen
60
61local mpprint             = mp.print
62local injectnumeric       = injectors.numeric
63local injectstring        = injectors.string
64local injectboolean       = injectors.boolean
65local injectpair          = injectors.pair
66local injecttriplet       = injectors.color
67local injectquadruple     = injectors.cmykcolor
68local injecttransform     = injectors.transform
69local injectpath          = injectors.path
70
71local report              = logs.reporter("metapost")
72
73local semicolon_code      = codes.semicolon
74local equals_code         = codes.equals
75local comma_code          = codes.comma
76local colon_code          = codes.colon
77local leftbrace_code      = codes.leftbrace
78local rightbrace_code     = codes.rightbrace
79local leftbracket_code    = codes.leftbracket
80local rightbracket_code   = codes.rightbracket
81local leftdelimiter_code  = codes.leftdelimiter
82local rightdelimiter_code = codes.rightdelimiter
83local numeric_code        = codes.numeric
84local string_code         = codes.string
85local capsule_code        = codes.capsule
86local nullary_code        = codes.nullary
87local tag_code            = codes.tag
88local definedmacro_code   = codes.definedmacro
89
90local typescanners   = nil
91local tokenscanners  = nil
92local scanset        = nil
93local scanparameters = nil
94
95scanset = function() -- can be optimized, we now read twice
96    scantoken()
97    if scantoken(true) == rightbrace_code then
98        scantoken()
99        return { }
100    else
101        local l = { }
102        local i = 0
103        while true do
104            i = i + 1
105            local s = scansymbol(true)
106            if s == "{" then
107                l[i] = scanset()
108            elseif s == "[" then
109                local d = { }
110                scansymbol()
111                while true do
112                    local s = scansymbol()
113                    if s == "]" then
114                        break;
115                    elseif s == "," then
116                        -- continue
117                    else
118                        local t = scantoken(true)
119                        if t == equals_code or t == colon_code then
120                            scantoken()
121                        end
122                        d[s] = tokenscanners[scantoken(true)]()
123                    end
124                end
125                l[i] = d
126            else
127                local e = scanexpression(true)
128                l[i] = (typescanners[e] or scanexpression)()
129            end
130            if scantoken() == rightbrace_code then
131                break
132            else
133             -- whatever
134            end
135        end
136        return l
137    end
138end
139
140tokenscanners = {
141    [leftbrace_code] = scanset,
142    [numeric_code]   = scannumeric,
143    [string_code]    = scanstring,
144    [nullary_code]   = scanboolean,   -- todo
145}
146
147typescanners = {
148    [types.known]     = scannumeric,
149    [types.numeric]   = scannumeric,
150    [types.string]    = scanstring,
151    [types.boolean]   = scanboolean,
152    [types.pair]      = function() return scanpair     (true) end,
153    [types.color]     = function() return scancolor    (true) end,
154    [types.cmykcolor] = function() return scancmykcolor(true) end,
155    [types.transform] = function() return scantransform(true) end,
156    [types.path]      = scanpath,
157    [types.pen]       = scanpen,
158}
159
160table.setmetatableindex(tokenscanners,function()
161    local e = scanexpression(true)
162    return typescanners[e] or scanexpression
163end)
164
165scanners.typescanners  = typescanners
166scanners.tokenscanners = tokenscanners
167
168scanners.whatever = function()
169    local kind = scantoken(true)
170    if kind == leftdelimiter_code or kind == tag_code or kind == capsule_code then
171        return (typescanners[scanexpression(true)] or scanexpression)()
172    else
173        return tokenscanners[kind]()
174    end
175end
176
177-- a key like 'color' has code 'declare'
178
179local function scanparameters(fenced)
180    local data  = { }
181    local close = "]"
182    if not fenced then
183        close = ";"
184    elseif scansymbol(true) == "[" then
185        scansymbol()
186    else
187        return data
188    end
189    while true do
190     -- local s = scansymbol()
191        local s = scansymbol(false,false) -- keep expand
192        if s == close then
193            break;
194        elseif s == "," then
195            -- continue
196        else
197--             local t = scantoken(true)
198--             if t == equals_code or t == colon_code then
199--                 -- optional equal or :
200--                 scantoken()
201--             else
202--             end
203--             local kind = scantoken(true)
204
205-- test:
206--
207            local kind = scantoken(true)
208            if kind == equals_code or kind == colon_code then
209                -- optional equal or :
210                scantoken()
211                local kind = scantoken(true)
212            end
213
214            if kind == leftdelimiter_code or kind == tag_code or kind == capsule_code then
215                kind = scanexpression(true)
216                data[s] = (typescanners[kind] or scanexpression)()
217            elseif kind == leftbracket_code then
218                data[s] = get_parameters(true)
219            else
220                data[s] = tokenscanners[kind]()
221            end
222        end
223    end
224    return data
225end
226
227local namespaces = { }
228local presets    = { }
229local passed     = { }
230
231local function get_parameters(nested)
232    local data = { }
233    if nested or scansymbol(true) == "[" then
234        scansymbol()
235    else
236        return data
237    end
238    while true do
239        local s = scansymbol(false,false)
240        if s == "]" then
241            break;
242        elseif s == "," then
243            goto again
244        elseif s == "[" then
245--             s = scannumeric()
246            s = scaninteger()
247            if scantoken() == rightbracket_code then
248                goto assign
249            else
250                report("] expected")
251            end
252        else
253            goto assign
254        end
255      ::assign::
256        local t = scantoken(true)
257        if t == equals_code or t == colon_code then
258            -- optional equal or :
259            scantoken()
260        end
261        local kind = scantoken(true)
262        if kind == leftdelimiter_code or kind == tag_code or kind == capsule_code then
263            kind = scanexpression(true)
264            data[s] = (typescanners[kind] or scanexpression)()
265        elseif kind == leftbracket_code then
266            data[s] = get_parameters(true)
267        elseif kind == comma_code then
268            goto again
269        else
270            data[s] = tokenscanners[kind]()
271        end
272      ::again::
273    end
274    return data
275end
276
277local function getparameters()
278    local namespace  = scanstring()
279    -- same as below
280    local parameters = get_parameters()
281    local presets    = presets[namespace]
282    local passed     = passed[namespace]
283    if passed then
284        if presets then
285            setmetatableindex(passed,presets)
286        end
287        setmetatableindex(parameters,passed)
288    elseif presets then
289        setmetatableindex(parameters,presets)
290    end
291    namespaces[namespace] = parameters
292end
293
294local function mergeparameters()
295    local namespace  = scanstring()
296    local parameters = get_parameters()
297    local target     = namespaces[namespace]
298    if target then
299        combine(target,parameters)
300    else
301        -- same as below
302        local presets = presets[namespace]
303        local passed  = passed[namespace]
304        if passed then
305            if presets then
306                setmetatableindex(passed,presets)
307            end
308            setmetatableindex(parameters,passed)
309        elseif presets then
310            setmetatableindex(parameters,presets)
311        end
312    end
313end
314
315local function applyparameters()
316    local saved      = namespaces
317    local namespace  = scanstring()
318    local action     = scanstring() -- before we scan the parameters
319    -- same as above
320    local parameters = get_parameters()
321    local presets    = presets[namespace]
322    local passed     = passed[namespace]
323    if passed then
324        if presets then
325            setmetatableindex(passed,presets)
326        end
327        setmetatableindex(parameters,passed)
328    elseif presets then
329        setmetatableindex(parameters,presets)
330    end
331    namespaces[namespace] = parameters
332    -- till here
333 -- mpprint(action)
334    namespaces = saved
335    return action
336end
337
338local knownparameters    = { }
339metapost.knownparameters = knownparameters
340
341local function presetparameters()
342    local namespace = scanstring()
343    local parent = nil
344    local t = scantoken(true)
345    if t == string_code then
346        parent = presets[scanstring()]
347    end
348    local p = get_parameters()
349    for k in next, p do
350        knownparameters[k] = true
351    end
352    if parent then
353        setmetatableindex(p,parent)
354    end
355    presets[namespace] = p
356end
357
358local function collectnames()
359    local l = { } -- can be reused but then we can't nest
360    local n = 0
361    while true do
362        local t = scantoken(true)
363        -- (1) not really needed
364        if t == numeric_code then
365            n = n + 1 l[n] = scannumeric(1) -- so a float even if it is an index
366        elseif t == string_code then
367            n = n + 1 l[n] = scanstring(1)
368        elseif t == nullary_code then
369            n = n + 1 l[n] = scanboolean(1)
370        elseif t == leftbracket_code then
371            scantoken() -- leftbacket
372            n = n + 1 l[n] = scaninteger(1) -- forces an index
373            scantoken() -- rightbacket
374        elseif t == leftdelimiter_code or t == tag_code or t == capsule_code then
375            t = scanexpression(true)
376            n = n + 1 l[n] = (typescanners[t] or scanexpression)()
377        else
378            break
379        end
380    end
381    return l, n
382end
383
384local function get(v)
385    local t = type(v)
386    if t == "number" then
387        return injectnumeric(v)
388    elseif t == "boolean" then
389        return injectboolean(v)
390    elseif t == "string" then
391        return injectstring(v)
392    elseif t == "table" then
393        local n = #v
394        if type(v[1]) == "table" then
395            return injectpath(v)
396        elseif n == 2 then
397            return injectpair(v)
398        elseif n == 3 then
399            return injecttriplet(v)
400        elseif n == 4 then
401            return injectquadruple(v)
402        elseif n == 6 then
403            return injecttransform(v)
404        end
405    end
406    return injectnumeric(0)
407end
408
409local stack = { }
410
411local function pushparameters()
412    local l, n = collectnames()
413    insert(stack,namespaces)
414    for i=1,n do
415        local n = namespaces[l[i]]
416        if type(n) == "table" then
417            namespaces = n
418        else
419            break
420        end
421    end
422end
423
424local function popparameters()
425    local n = remove(stack)
426    if n then
427        namespaces = n
428    else
429        report("stack error")
430    end
431end
432
433-- todo:
434
435local function getparameter(v)
436    local list, n = collectnames()
437    if not v then
438        v = namespaces
439    end
440    for i=1,n do
441        local l = list[i]
442        local vl = v[l]
443        if vl == nil then
444            if type(l) == "number" then
445                vl = v[1]
446                if vl == nil then
447                    return injectnumeric(0)
448                end
449            else
450                return injectnumeric(0)
451            end
452        end
453        v = vl
454    end
455    if v == nil then
456        return 0
457    else
458        return v
459    end
460end
461
462local function hasparameter()
463    local list, n = collectnames()
464    local v = namespaces
465    for i=1,n do
466        local l = list[i]
467        local vl = rawget(v,l)
468        if vl == nil then
469            if type(l) == "number" then
470                vl = rawget(v,1)
471                if vl == nil then
472                    return injectboolean(false)
473                end
474            else
475                return injectboolean(false)
476            end
477        end
478        v = vl
479    end
480 -- if v == nil then
481 --     return injectboolean(false)
482 -- else
483 --     return injectboolean(true)
484 -- end
485    return v ~= nil
486end
487
488local function hasoption()
489    local list, n = collectnames()
490    if n > 1 then
491        local v = namespaces
492        if n > 2 then
493            for i=1,n-1 do
494                local l = list[i]
495                local vl = v[l]
496                if vl == nil then
497                 -- return injectboolean(false)
498                    return false
499                end
500                v = vl
501            end
502        else
503            v = v[list[1]]
504        end
505        if type(v) == "string" then
506            -- no caching .. slow anyway
507            local o = list[n]
508            if v == o then
509             -- return injectboolean(true)
510                return true
511            end
512            for vv in gmatch(v,"[^%s,]+") do
513                for oo in gmatch(o,"[^%s,]+") do
514                    if vv == oo then
515                     -- return injectboolean(true)
516                        return true
517                    end
518                end
519            end
520        end
521    end
522 -- return injectboolean(false)
523    return false
524end
525
526local function getparameterdefault()
527    local list, n = collectnames()
528    local v = namespaces
529    if n == 1 then
530        local l = list[1]
531        local vl = v[l]
532        if vl == nil then
533            -- maybe backtrack
534            local top = stack[#stack]
535            if top then
536                vl = top[l]
537            end
538        end
539        if vl == nil then
540         -- return injectnumeric(0)
541            return 0
542        else
543            if type(vl) == "string" then
544                local td = type(list[n])
545                if td == "number" then
546                    vl = tonumber(vl)
547                elseif td == "boolean" then
548                    vl = vl == "true"
549                end
550            end
551         -- return get(vl)
552            return vl
553        end
554    else
555        for i=1,n-1 do
556            local l = list[i]
557            local vl = v[l]
558            if vl == nil then
559                if type(l) == "number" then
560                    vl = v[1]
561                    if vl == nil then
562                     -- return get(list[n])
563                        return list[n]
564                    end
565                else
566                    local last = list[n]
567                    if last == "*" then
568                        -- so, only when not pushed
569                        local m = getmetatable(namespaces[list[1]])
570                        if n then
571                            m = m.__index -- can also be a _m_
572                        end
573                        if m then
574                            local v = m
575                            for i=2,n-1 do
576                                local l = list[i]
577                                local vl = v[l]
578                                if vl == nil then
579                                 -- return injectnumeric(0)
580                                    return 0
581                                end
582                                v = vl
583                            end
584                            if v == nil then
585                                return 0
586                            else
587                                return v
588                            end
589                        end
590                     -- return injectnumeric(0)
591                        return 0
592                    else
593                     -- return get(last)
594                        return last
595                    end
596                end
597            end
598            v = vl
599        end
600        if v == nil then
601         -- return get(list[n])
602            return list[n]
603        else
604            if type(v) == "string" then
605                local td = type(list[n])
606                if td == "number" then
607                    v = tonumber(v)
608                elseif td == "boolean" then
609                    v = v == "true"
610                end
611            end
612         -- return get(v)
613            return v
614        end
615    end
616end
617
618local function getparametercount()
619    local list, n = collectnames()
620    local v = namespaces
621    for i=1,n do
622        v = v[list[i]]
623        if not v then
624            break
625        end
626    end
627 -- return injectnumeric(type(v) == "table" and #v or 0)
628    return type(v) == "table" and #v or 0
629end
630
631local function getmaxparametercount()
632    local list, n = collectnames()
633    local v = namespaces
634    for i=1,n do
635        v = v[list[i]]
636        if not v then
637            break
638        end
639    end
640    local n = 0
641    if type(v) == "table" then
642        local v1 = v[1]
643        if type(v1) == "table" then
644            n = #v1
645            for i=2,#v do
646                local vi = v[i]
647                if type(vi) == "table" then
648                    local vn = #vi
649                    if vn > n then
650                        n = vn
651                    end
652                else
653                    break;
654                end
655            end
656        end
657
658    end
659 -- return injectnumeric(n)
660    return n
661end
662
663local validconnectors = {
664    [".."]  = true,
665    ["..."] = true,
666    ["--"]  = true,
667}
668
669local function getparameterpath()
670    local list, n = collectnames()
671    local close = list[n]
672    if type(close) == "boolean" then
673        n = n - 1
674    else
675     -- close = false
676        close = nil
677    end
678    local connector = list[n]
679    if type(connector) == "string" and validconnectors[connector] then
680        n = n - 1
681    else
682        connector = "--"
683    end
684    local v = namespaces
685    for i=1,n do
686        v = v[list[i]]
687        if not v then
688            break
689        end
690    end
691    if type(v) == "table" then
692        return injectpath(v,connector,close)
693    elseif type(v) == "string" then
694        local code = load("return " .. v)
695        if code then
696            return code()
697        end
698    else
699        return injectpair(0,0)
700    end
701end
702
703local function getparameterpen()
704    local list, n = collectnames()
705    local v = namespaces
706    for i=1,n do
707        v = v[list[i]]
708        if not v then
709            break
710        end
711    end
712    if type(v) == "table" then
713        return injectpath(v,"..",true)
714    else
715        return injectpair(0,0)
716    end
717end
718
719local function getparametertext()
720    local list, n = collectnames()
721    local strut = list[n]
722    if type(strut) == "boolean" then
723        n = n - 1
724    else
725        strut = false
726    end
727    local v = namespaces
728    for i=1,n do
729        v = v[list[i]]
730        if not v then
731            break
732        end
733    end
734    if type(v) == "string" then
735        return injectstring("\\strut " .. v)
736    else
737        return injectstring("")
738    end
739end
740
741-- local function getparameteroption()
742--     local list, n = collectnames()
743--     local last = list[n]
744--     if type(last) == "string" then
745--         n = n - 1
746--     else
747--         return false
748--     end
749--     local v = namespaces
750--     for i=1,n do
751--         v = v[list[i]]
752--         if not v then
753--             break
754--         end
755--     end
756--     if type(v) == "string" and v ~= "" then
757--         for s in gmatch(v,"[^ ,]+") do
758--             if s == last then
759--                 return true
760--             end
761--         end
762--     end
763--     return false
764-- end
765
766function metapost.scanparameters(gobblesemicolon)
767    if gobblesemicolon then
768        scantoken() -- we scan the semicolon
769    end
770    return get_parameters()
771end
772
773local registerscript = metapost.registerscript
774local registerdirect = metapost.registerdirect
775local registertokens = metapost.registertokens
776
777registerdirect("getparameters",       getparameters)        -- nothing
778registertokens("applyparameters",     applyparameters)      -- action    : todo "token"
779registerdirect("mergeparameters",     mergeparameters)      -- nothing
780registerdirect("presetparameters",    presetparameters)     -- nothing
781registerdirect("hasparameter",        hasparameter)         -- boolean
782registerdirect("hasoption",           hasoption)            -- boolean
783registerdirect("getparameter",        getparameter)         -- whatever
784registerdirect("getparameterdefault", getparameterdefault)  -- whatever
785registerdirect("getparametercount",   getparametercount)    -- numeric
786registerdirect("getmaxparametercount",getmaxparametercount) -- numeric
787registerscript("getparameterpath",    getparameterpath)     -- tricky
788registerscript("getparameterpen",     getparameterpen)      -- tricky
789registerscript("getparametertext",    getparametertext)     -- tricky
790--------direct("getparameteroption",  getparameteroption)   -- boolean
791registerdirect("pushparameters",      pushparameters)       -- nothing
792registerdirect("popparameters",       popparameters)        -- nothing
793
794function metapost.getparameter(list)
795    local n = #list
796    local v = namespaces
797    for i=1,n do
798        local l = list[i]
799        local vl = v[l]
800        if vl == nil then
801            return
802        end
803        v = vl
804    end
805    return v
806end
807
808function metapost.getparameterset(namespace)
809    return namespace and namespaces[namespace] or namespaces
810end
811
812function metapost.setparameter(k,v)
813    rawset(namespaces,k,v)
814end
815
816function metapost.setparameterset(namespace,t)
817    namespaces[namespace] = t
818end
819
820function metapost.getparameterpreset(namespace,t)
821    return namespace and presets[namespace] or presets
822end
823
824local function setluaparameter()
825    local namespace = scanstring()
826    local name      = scanstring()
827    local value     = scanstring()
828    local code      = load("return " .. value)
829    if type(code) == "function" then
830        local result = code()
831        if result then
832            local data = namespace and namespaces[namespace] or namespaces
833            data[name] = result
834        else
835            report("no result from lua code: %s",value)
836        end
837    else
838        report("invalid lua code: %s",value)
839    end
840end
841
842registerdirect("setluaparameter", setluaparameter)
843
844-- This is an experiment for Alan and me.
845
846do
847
848    local records    = { }
849    local stack      = setmetatableindex("table")
850    local nofrecords = 0
851    local interim    = 0
852    local names      = { }
853 -- local types      = { }
854
855    registerdirect("newrecord", function()
856        scantoken() -- semicolon
857        local p = get_parameters()
858        local n = 0
859        if interim > 0 then
860            records[interim] = p
861            local top = stack[interim]
862            if top then
863                top = stack[interim][#top]
864                if top then
865                    setmetatableindex(p,top)
866                end
867            end
868            n = interim
869            interim = 0
870        else
871            nofrecords = nofrecords + 1
872            records[nofrecords] = p
873            n = nofrecords
874        end
875        return n
876    end)
877
878    local function merge(old,new)
879        for knew, vnew in next, new do
880            local vold = old[knew]
881            if vold then
882                if type(vnew) == "table" then
883                    if type(vold) == "table" then
884                        merge(vold,vnew)
885                    else
886                        old[knew] = vnew
887                    end
888                else
889                    old[knew] = vnew
890                end
891            else
892                old[knew] = vnew
893            end
894        end
895    end
896
897    registerdirect("setrecord", function()
898        scantoken() -- semicolon
899        local p = get_parameters()
900        local n = 0
901        if interim > 0 then
902            local r = records[interim]
903            if r then
904                merge(r,p)
905            else
906                records[interim] = p
907            end
908            local top = stack[interim]
909            if top then
910                top = stack[interim][#top]
911                if top then
912                    setmetatableindex(p,top)
913                end
914            end
915            n = interim
916            interim = 0
917        else
918            nofrecords = nofrecords + 1
919            records[nofrecords] = p
920            n = nofrecords
921        end
922        return n
923    end)
924
925
926    registerdirect("getrecord", function()
927        local n = scaninteger()
928        local v = records[n]
929        while true do
930            local t = scansymbol(true)
931            if t == ";" or t == ")" or t == ":" then
932                return v
933            elseif t == "." then
934                scansymbol()
935            elseif t == "#" or t == "##" then -- from tex's we get a double
936                scansymbol()
937                t = scansymbol()
938                v = v[t]
939                return type(v) == "table" and #v or 0
940            elseif t == "[" then
941                scansymbol()
942                t = scansymbol(true)
943                if t == "]" then
944                    scansymbol()
945                    return #v
946                else
947                    t = scaninteger()
948                    v = v[t]
949                    if scansymbol() ~= "]" then
950                        report("] expected")
951                    end
952                end
953            else
954                t = scansymbol()
955                v = v[t]
956            end
957        end
958    end)
959
960--     registerdirect("getrecord", function()
961--         local n = scaninteger()
962--         local v = records[n]
963--         local l = 0
964--         while true do
965--             local t = scansymbol(true)
966--             if t == ";" or t == ":" then
967--                 return v
968--             elseif t == "(" then
969--                 scansymbol()
970--                 l = l + 1
971--             elseif t == ")" then
972--                 if l > 1 then
973--                     scansymbol()
974--                     l = l - 1
975--                 elseif l == 1 then
976--                     scansymbol()
977--                     return v
978--                 else
979--                     return v
980--                 end
981--             elseif t == "." then
982--                 scansymbol()
983--             elseif t == "#" or t == "##" then -- from tex's we get a double
984--                 scansymbol()
985--                 t = scansymbol()
986--                 v = v[t]
987--                 local tv = type(v)
988--                 return (tv == "table" or tv == "string") and #v or 0
989--             elseif t == "[" then
990--                 scansymbol()
991-- t = scansymbol(true)
992-- if t == "#" or r == "##" then
993--     scansymbol()
994--     if scansymbol() ~= "]" then
995--         report("] expected")
996--     end
997--     return #v
998-- else
999--                     t = scaninteger()
1000--                     v = v[t]
1001--                     if scansymbol() ~= "]" then
1002--                         report("] expected")
1003--                     end
1004-- end
1005--             else
1006--                 t = scansymbol()
1007--                 v = v[t]
1008--             end
1009--         end
1010--     end)
1011
1012    registerdirect("cntrecord", function()
1013        local n = scaninteger()
1014        local v = records[n]
1015        local l = 0
1016        while true do
1017            local t = scansymbol(true)
1018            if t == ";" or t == ":" then
1019                break
1020            elseif t == "(" then
1021                scansymbol()
1022                l = l + 1
1023            elseif t == ")" then
1024                if l > 1 then
1025                    scansymbol()
1026                    l = l - 1
1027                elseif l == 1 then
1028                    scansymbol()
1029                    break
1030                else
1031                    break
1032                end
1033            elseif t == "." then
1034                scansymbol()
1035            elseif t == "[" then
1036                scansymbol()
1037                t = scaninteger()
1038                v = v[t]
1039                if scansymbol() ~= "]" then
1040                    report("] expected")
1041                end
1042            else
1043                t = scansymbol()
1044                v = v[t]
1045            end
1046        end
1047        local tv = type(v)
1048        return (tv == "table" or tv == "string") and #v or 0 -- integer
1049    end)
1050
1051    function metapost.getrecord(name)
1052        local index = names[name]
1053        if index then
1054            return records[index]
1055        end
1056    end
1057
1058    function metapost.setrecord(name,data)
1059        if type(data) == "table" then
1060            local index = names[name]
1061            if index then
1062                records[index] = data
1063            end
1064        end
1065    end
1066
1067    function metapost.runinternal(action,index,kind,name)
1068        if action == 0 then
1069            -- allocate
1070            names[name] = index
1071         -- types[index] = kind
1072        elseif action == 1 then
1073            -- save
1074            insert(stack[index],records[index])
1075            interim = index
1076        elseif action == 2 then
1077            -- restore
1078            records[index] = remove(stack[index]) or records[index]
1079        elseif action == 3 then
1080            metapost.checktracingonline(kind)
1081        end
1082    end
1083
1084end
1085
1086-- goodies
1087
1088registerdirect("definecolor", function()
1089    scantoken() -- we scan the semicolon
1090    local s = get_parameters()
1091    attributes.colors.defineprocesscolordirect(s)
1092end)
1093
1094-- tex scanners
1095
1096local scanners      = tokens.scanners
1097local scanhash      = scanners.hash
1098local scanstring    = scanners.string
1099local scanvalue     = scanners.value
1100local scaninteger   = scanners.integer
1101local scanboolean   = scanners.boolean
1102local scanfloat     = scanners.float
1103local scandimension = scanners.dimension
1104
1105local definitions   = { }
1106
1107local bpfactor      = number.dimenfactors.bp
1108local comma         = byte(",")
1109local close         = byte("]")
1110
1111local scanrest      = function() return scanvalue(comma,close) or "" end
1112local scandimension = function() return scandimension() * bpfactor end
1113
1114local scanners = {
1115    ["integer"]   = scaninteger,
1116    ["number"]    = scanfloat,
1117    ["numeric"]   = scanfloat,
1118    ["boolean"]   = scanboolean,
1119    ["string"]    = scanrest,
1120    ["dimension"] = scandimension,
1121}
1122
1123interfaces.implement {
1124    name      = "lmt_parameters_define",
1125    arguments = "string",
1126    actions   = function(namespace)
1127        local d = scanhash()
1128        for k, v in next, d do
1129            d[k] = scanners[v] or scanrest
1130        end
1131        definitions[namespace] = d
1132    end,
1133}
1134
1135interfaces.implement {
1136    name      = "lmt_parameters_preset",
1137    arguments = "string",
1138    actions   = function(namespace)
1139        passed[namespace] = scanhash(definitions[namespace])
1140    end,
1141}
1142
1143interfaces.implement {
1144    name      = "lmt_parameters_reset",
1145    arguments = "string",
1146    actions   = function(namespace)
1147        passed[namespace] = nil
1148    end,
1149}
1150