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
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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()
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
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
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,
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
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
191 local s = scansymbol(false,false)
192 if s == close then
193 break;
194 elseif s == "," then
195
196 else
197
198
199
200
201
202
203
204
205
206
207 local kind = scantoken(true)
208 if kind == equals_code or kind == colon_code then
209
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
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
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
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
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()
319
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
333
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 = { }
360 local n = 0
361 while true do
362 local t = scantoken(true)
363
364 if t == numeric_code then
365 n = n + 1 l[n] = scannumeric(1)
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()
372 n = n + 1 l[n] = scaninteger(1)
373 scantoken()
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
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
481
482
483
484
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
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
507 local o = list[n]
508 if v == o then
509
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
516 return true
517 end
518 end
519 end
520 end
521 end
522
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
534 local top = stack[#stack]
535 if top then
536 vl = top[l]
537 end
538 end
539 if vl == nil then
540
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
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
563 return list[n]
564 end
565 else
566 local last = list[n]
567 if last == "*" then
568
569 local m = getmetatable(namespaces[list[1]])
570 if n then
571 m = m.__index
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
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
591 return 0
592 else
593
594 return last
595 end
596 end
597 end
598 v = vl
599 end
600 if v == nil then
601
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
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
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
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
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
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766function metapost.scanparameters(gobblesemicolon)
767 if gobblesemicolon then
768 scantoken()
769 end
770 return get_parameters()
771end
772
773local registerscript = metapost.registerscript
774local registerdirect = metapost.registerdirect
775local registertokens = metapost.registertokens
776
777registerdirect("getparameters", getparameters)
778registertokens("applyparameters", applyparameters)
779registerdirect("mergeparameters", mergeparameters)
780registerdirect("presetparameters", presetparameters)
781registerdirect("hasparameter", hasparameter)
782registerdirect("hasoption", hasoption)
783registerdirect("getparameter", getparameter)
784registerdirect("getparameterdefault", getparameterdefault)
785registerdirect("getparametercount", getparametercount)
786registerdirect("getmaxparametercount",getmaxparametercount)
787registerscript("getparameterpath", getparameterpath)
788registerscript("getparameterpen", getparameterpen)
789registerscript("getparametertext", getparametertext)
790
791registerdirect("pushparameters", pushparameters)
792registerdirect("popparameters", popparameters)
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
845
846do
847
848 local records = { }
849 local stack = setmetatableindex("table")
850 local nofrecords = 0
851 local interim = 0
852 local names = { }
853
854
855 registerdirect("newrecord", function()
856 scantoken()
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()
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
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
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
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
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
1070 names[name] = index
1071
1072 elseif action == 1 then
1073
1074 insert(stack[index],records[index])
1075 interim = index
1076 elseif action == 2 then
1077
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
1087
1088registerdirect("definecolor", function()
1089 scantoken()
1090 local s = get_parameters()
1091 attributes.colors.defineprocesscolordirect(s)
1092end)
1093
1094
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 |