1if not modules then modules = { } end modules ['cldf-ini'] = {
2 version = 1.001,
3 comment = "companion to cldf-ini.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68local format, stripstring = string.format, string.strip
69local next, type, tostring, tonumber, unpack, select, rawset = next, type, tostring, tonumber, unpack, select, rawset
70local insert, remove, concat = table.insert, table.remove, table.concat
71local lpegmatch, lpegC, lpegS, lpegP, lpegV, lpegCc, lpegCs, patterns = lpeg.match, lpeg.C, lpeg.S, lpeg.P, lpeg.V, lpeg.Cc, lpeg.Cs, lpeg.patterns
72
73local formatters = string.formatters
74local setmetatableindex = table.setmetatableindex
75local setmetatablecall = table.setmetatablecall
76local setmetatablenewindex = table.setmetatablenewindex
77
78context = context or { }
79commands = commands or { }
80interfaces = interfaces or { }
81
82local context = context
83local commands = commands
84local interfaces = interfaces
85
86local loaddata = io.loaddata
87
88local tex = tex
89local texsprint = tex.sprint
90local texprint = tex.print
91
92
93
94
95local isnode = node.isnode or node.is_node
96local copynodelist = node.copylist or node.copy_list
97local writenode = node.write
98local tonut = node.direct.todirect
99local tonode = node.direct.tonode
100
101local newtoken = token.new
102local createtoken = token.create
103
104local istoken = token.istoken or token.is_token
105local setluatoken = token.setlua or token.set_lua
106
107
108
109local isprintable = tex.isprintable or function(n)
110 return n and (type(n) == "string" or isnode(n) or istoken(n))
111end
112
113if tex.runlocal then
114
115 tex.runtoks = tex.runlocal
116 tex.quittoks = tex.quitlocal
117else
118 tex.runlocal = tex.runtoks
119 tex.quitlocal = tex.quittoks
120end
121
122local catcodenumbers = catcodes.numbers
123
124local ctxcatcodes = catcodenumbers.ctxcatcodes
125local prtcatcodes = catcodenumbers.prtcatcodes
126local texcatcodes = catcodenumbers.texcatcodes
127local txtcatcodes = catcodenumbers.txtcatcodes
128local vrbcatcodes = catcodenumbers.vrbcatcodes
129local xmlcatcodes = catcodenumbers.xmlcatcodes
130
131local flush = texsprint
132local flushdirect = texprint
133
134
135local report_context = logs.reporter("cld","tex")
136local report_cld = logs.reporter("cld","stack")
137
138
139local processlines = true
140
141local trialtypesetting = function() return false end
142
143function context.settrialtypesettingmethod(f)
144 trialtypesetting = f
145 context.trialtypesetting = f
146end
147
148context.trialtypesetting = function() return trialtypesetting() end
149
150local knownfunctions = (lua.getfunctionstable or lua.get_functions_table)(true)
151local showstackusage = false
152
153trackers.register("context.stack",function(v) showstackusage = v end)
154
155local freed = { }
156local nofused = 0
157local noffreed = 0
158
159local usedstack = function()
160 return nofused, noffreed
161end
162
163local flushfunction = function(slot,arg)
164 if arg() or trialtypesetting() then
165
166 else
167 noffreed = noffreed + 1
168 freed[noffreed] = slot
169 knownfunctions[slot] = false
170 end
171end
172
173local storefunction = function(arg)
174 local f = function(slot) flushfunction(slot,arg) end
175 if noffreed > 0 then
176 local n = freed[noffreed]
177 freed[noffreed] = nil
178 noffreed = noffreed - 1
179 knownfunctions[n] = f
180 return n
181 else
182 nofused = nofused + 1
183 knownfunctions[nofused] = f
184 return nofused
185 end
186end
187
188local flushnode = function(slot,arg)
189 if trialtypesetting() then
190 writenode(copynodelist(arg))
191 else
192 writenode(arg)
193 noffreed = noffreed + 1
194 freed[noffreed] = slot
195 knownfunctions[slot] = false
196 end
197end
198
199local storenode = function(arg)
200 local f = function(slot) flushnode(slot,arg) end
201 if noffreed > 0 then
202 local n = freed[noffreed]
203 freed[noffreed] = nil
204 noffreed = noffreed - 1
205 knownfunctions[n] = f
206 return n
207 else
208 nofused = nofused + 1
209 knownfunctions[nofused] = f
210 return nofused
211 end
212end
213
214storage.storedfunctions = storage.storedfunctions or { }
215local storedfunctions = storage.storedfunctions
216local initex = environment.initex
217
218storage.register("storage/storedfunctions", storedfunctions, "storage.storedfunctions")
219
220local f_resolve = nil
221local p_resolve = ((1-lpegP("."))^1 / function(s) f_resolve = f_resolve[s] end * lpegP(".")^0)^1
222
223local function resolvestoredfunction(str)
224 if type(str) == "string" then
225 f_resolve = global
226 lpegmatch(p_resolve,str)
227 return f_resolve
228 else
229 return str
230 end
231end
232
233local function expose(slot,f,...)
234 local func = resolvestoredfunction(f)
235 if not func then
236 func = function() report_cld("beware: unknown function %i called: %s",slot,f) end
237 end
238 knownfunctions[slot] = func
239 return func(...)
240end
241
242if initex then
243
244else
245 local slots = table.sortedkeys(storedfunctions)
246 local last = #slots
247 if last > 0 then
248
249 for i=1,last do
250 local slot = slots[i]
251 local data = storedfunctions[slot]
252 knownfunctions[slot] = function(...)
253
254 return expose(slot,data,...)
255 end
256 end
257
258 nofused = slots[last]
259
260 for i=1,nofused do
261 if not knownfunctions[i] then
262 noffreed = noffreed + 1
263 freed[noffreed] = i
264 end
265 end
266
267 end
268end
269
270local registerfunction = function(f,direct,slot)
271 local func
272 if slot then
273
274 elseif noffreed > 0 then
275 slot = freed[noffreed]
276 freed[noffreed] = nil
277 noffreed = noffreed - 1
278 else
279 nofused = nofused + 1
280 slot = nofused
281 end
282 if direct then
283 if initex then
284 func = function(...) expose(slot,f,...) end
285 storedfunctions[slot] = f
286 else
287 func = resolvestoredfunction(f)
288 end
289 if type(func) ~= "function" then
290 func = function() report_cld("invalid resolve %A, case %s",f,1) end
291 end
292 elseif type(f) == "string" then
293 func = loadstring(f)
294 if type(func) ~= "function" then
295 func = function() report_cld("invalid code %A, case %s",f,2) end
296 end
297 elseif type(f) == "function" then
298 func = f
299 else
300 func = function() report_cld("invalid function %A, case %s",f,3) end
301 end
302 knownfunctions[slot] = func
303 return slot
304end
305
306local unregisterfunction = function(slot)
307 if knownfunctions[slot] then
308 noffreed = noffreed + 1
309 freed[noffreed] = slot
310 knownfunctions[slot] = false
311 else
312 report_cld("invalid function slot %A",slot)
313 end
314end
315
316local reservefunction = function()
317 if noffreed > 0 then
318 local n = freed[noffreed]
319 freed[noffreed] = nil
320 noffreed = noffreed - 1
321 return n
322 else
323 nofused = nofused + 1
324 return nofused
325 end
326end
327
328local callfunctiononce = function(slot)
329 knownfunctions[slot](slot)
330 noffreed = noffreed + 1
331 freed[noffreed] = slot
332 knownfunctions[slot] = false
333end
334
335setmetatablecall(knownfunctions,function(t,n) return knownfunctions[n](n) end)
336
337
338
339do
340
341 local stub = { }
342 local done = false
343 local message = function()
344
345 if not done then
346 report_cld("")
347 report_cld("use : slot = context.functions.register(f)")
348 report_cld("and : context.functions.unregister(slot)")
349 report_cld("")
350 done = true
351 end
352 end
353
354 setmetatable(stub, {
355 __index = message,
356 __newindex = message,
357 })
358
359 function lua.getfunctionstable()
360 message()
361 return stub
362 end
363
364 lua.get_functions_table = lua.getfunctionstable
365
366end
367
368
369
370
371
372
373
374
375
376local storedscanners = interfaces.storedscanners or { }
377local namesofscanners = interfaces.namesofscanners or { }
378local interfacescanners = { }
379local privatenamespace = "clf_"
380
381interfaces.storedscanners = storedscanners
382interfaces.namesofscanners = namesofscanners
383
384storage.register("interfaces/storedscanners", storedscanners, "interfaces.storedscanners")
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399local registerscanner if CONTEXTLMTXMODE > 0 then
400
401
402
403
404
405 local function toflags(specification)
406 local protected = specification.protected and "protected"
407 local untraced = specification.untraced and "untraced"
408 local usage = specification.usage
409 if usage == "value" then
410 return "global", "value", "permanent", "untraced", protected
411 elseif usage == "condition" then
412 return "global", "conditional", "permanent", "untraced", protected
413 elseif specification.frozen then
414 return "global", "frozen", untraced, protected
415 elseif specification.permanent == false or specification.onlyonce then
416 return "global", untraced, protected
417 else
418 return "global", "permanent", untraced, protected
419 end
420 end
421
422 registerscanner = function(name,action,specification)
423 rawset(interfacescanners,name,action)
424 local n = registerfunction("interfaces.scanners."..name,true,storedscanners[name])
425 storedscanners[name] = n
426 namesofscanners[n] = name
427 name = specification.public and name or (privatenamespace .. name)
428
429 setluatoken(name,n,toflags(specification))
430 end
431
432else
433
434 registerscanner = function(name,action,specification)
435 rawset(interfacescanners,name,action)
436 local n = storedscanners[name]
437 n = registerfunction("interfaces.scanners."..name,true,n)
438 storedscanners[name] = n
439 namesofscanners[n] = name
440 name = specification.public and name or (privatenamespace .. name)
441 setluatoken(name,n,"global",specification.protected and "protected" or "")
442 end
443
444end
445
446interfaces.registerscanner = registerscanner
447
448function interfaces.knownscanner(name)
449 return interfacescanners[name]
450end
451
452function interfaces.nameofscanner(slot)
453 return namesofscanners[slot] or slot
454end
455
456if CONTEXTLMTXMODE > 0 then
457
458 callback.register("show_lua_call", function(what, slot)
459 local name = namesofscanners[slot]
460
461 return name and formatters["%s \\%s"](what,name) or ""
462 end)
463
464end
465
466setmetatablenewindex(interfacescanners, function(t,k,v)
467 report_cld("don't register scanner %a directly",k)
468
469end)
470
471interfaces.scanners = storage.mark(interfacescanners)
472
473context.functions = {
474 register = function(qualifiedname) return registerfunction(qualifiedname) end,
475 unregister = unregisterfunction,
476 reserve = reservefunction,
477 known = knownfunctions,
478 callonce = callfunctiononce,
479}
480
481function commands.ctxfunction(code,namespace)
482 context(registerfunction(code,namespace))
483end
484
485function commands.ctxscanner(name,code,namespace)
486 local n = registerfunction(code,namespace)
487 if storedscanners[name] then
488 storedscanners[name] = n
489 end
490 context(n)
491end
492
493local function dummy() end
494
495function commands.ctxresetter(name)
496 return function()
497 if storedscanners[name] then
498 rawset(interfacescanners,name,dummy)
499 context.resetctxscanner(privatenamespace .. name)
500 end
501 end
502end
503
504
505
506local catcodestack = { }
507local currentcatcodes = ctxcatcodes
508local contentcatcodes = ctxcatcodes
509
510local catcodes = {
511 ctx = ctxcatcodes, ctxcatcodes = ctxcatcodes, context = ctxcatcodes,
512 prt = prtcatcodes, prtcatcodes = prtcatcodes, protect = prtcatcodes,
513 tex = texcatcodes, texcatcodes = texcatcodes, plain = texcatcodes,
514 txt = txtcatcodes, txtcatcodes = txtcatcodes, text = txtcatcodes,
515 vrb = vrbcatcodes, vrbcatcodes = vrbcatcodes, verbatim = vrbcatcodes,
516 xml = xmlcatcodes, xmlcatcodes = xmlcatcodes,
517}
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532local catcodelevel = 0
533
534local function pushcatcodes(c)
535 catcodelevel = catcodelevel + 1
536 catcodestack[catcodelevel] = currentcatcodes
537 currentcatcodes = (c and catcodes[c] or tonumber(c)) or currentcatcodes
538 contentcatcodes = currentcatcodes
539end
540
541local function popcatcodes()
542 if catcodelevel > 0 then
543 currentcatcodes = catcodestack[catcodelevel] or currentcatcodes
544 catcodelevel = catcodelevel - 1
545 end
546 contentcatcodes = currentcatcodes
547end
548
549function context.unprotect()
550
551 catcodelevel = catcodelevel + 1
552 catcodestack[catcodelevel] = currentcatcodes
553 currentcatcodes = prtcatcodes
554 contentcatcodes = prtcatcodes
555
556 flush("\\unprotect")
557end
558
559function context.protect()
560
561 flush("\\protect")
562
563 if catcodelevel > 0 then
564 currentcatcodes = catcodestack[catcodelevel] or currentcatcodes
565 catcodelevel = catcodelevel - 1
566 end
567 contentcatcodes = currentcatcodes
568end
569
570context.catcodes = catcodes
571context.pushcatcodes = pushcatcodes
572context.popcatcodes = popcatcodes
573
574
575
576local newline = patterns.newline
577local space = patterns.spacer
578local spacing = newline * space^0
579local content = lpegC((1-spacing)^1)
580local emptyline = space^0 * newline^2
581 + newline * space^1 * newline^1
582local endofline = space^0 * newline * space^0
583local simpleline = endofline * lpegP(-1)
584
585local verbose = lpegC((1-space-newline)^1)
586local beginstripper = (lpegS(" \t")^1 * newline^1) / ""
587local endstripper = beginstripper * lpegP(-1)
588
589local justaspace = space * lpegCc("")
590local justanewline = newline * lpegCc("")
591
592local function n_content (s) flush (contentcatcodes,s ) end
593local function n_verbose (s) flush (vrbcatcodes, s ) end
594local function n_endofline () flush (currentcatcodes," \r") end
595local function n_emptyline () flushdirect(currentcatcodes,"\r" ) end
596local function n_simpleline() flush (currentcatcodes," \r") end
597
598local n_exception = ""
599
600
601
602function context.newtexthandler(specification)
603 specification = specification or { }
604
605 local s_catcodes = specification.catcodes
606
607 local f_before = specification.before
608 local f_after = specification.after
609
610 local f_endofline = specification.endofline or n_endofline
611 local f_emptyline = specification.emptyline or n_emptyline
612 local f_simpleline = specification.simpleline or n_simpleline
613 local f_content = specification.content or n_content
614 local f_space = specification.space
615
616 local p_exception = specification.exception
617
618 if s_catcodes then
619 f_content = function(s)
620 flush(s_catcodes,s)
621 end
622 end
623
624 local pattern
625 if f_space then
626 if p_exception then
627 local content = lpegC((1-spacing-p_exception)^1)
628 pattern =
629 (
630 justaspace / f_space
631 + justanewline / f_endofline
632 + p_exception
633 + content / f_content
634 )^0
635 else
636 local content = lpegC((1-space-endofline)^1)
637 pattern =
638 (
639 justaspace / f_space
640 + justanewline / f_endofline
641 + content / f_content
642 )^0
643 end
644 else
645 if p_exception then
646 local content = lpegC((1-spacing-p_exception)^1)
647 pattern =
648 simpleline / f_simpleline
649 +
650 (
651 emptyline / f_emptyline
652 + endofline / f_endofline
653 + p_exception
654 + content / f_content
655 )^0
656 else
657 local content = lpegC((1-spacing)^1)
658 pattern =
659 simpleline / f_simpleline
660 +
661 (
662 emptyline / f_emptyline
663 + endofline / f_endofline
664 + content / f_content
665 )^0
666 end
667 end
668
669 if f_before then
670 pattern = (P(true) / f_before) * pattern
671 end
672
673 if f_after then
674 pattern = pattern * (P(true) / f_after)
675 end
676
677 return function(str) return lpegmatch(pattern,str) end, pattern
678end
679
680function context.newverbosehandler(specification)
681 specification = specification or { }
682
683 local f_line = specification.line or function() flushdirect("\r") end
684 local f_space = specification.space or function() flush (" ") end
685 local f_content = specification.content or n_verbose
686 local f_before = specification.before
687 local f_after = specification.after
688
689 local pattern =
690 justanewline / f_line
691 + verbose / f_content
692 + justaspace / f_space
693
694 if specification.strip then
695 pattern = beginstripper^0 * (endstripper + pattern)^0
696 else
697 pattern = pattern^0
698 end
699
700 if f_before then
701 pattern = (lpegP(true) / f_before) * pattern
702 end
703
704 if f_after then
705 pattern = pattern * (lpegP(true) / f_after)
706 end
707
708 return function(str) return lpegmatch(pattern,str) end, pattern
709end
710
711local flushlines = context.newtexthandler {
712 content = n_content,
713 endofline = n_endofline,
714 emptyline = n_emptyline,
715 simpleline = n_simpleline,
716}
717
718
719
720local printlines_ctx = (
721 (newline) / function() texprint("") end +
722 (1-newline)^1 / function(s) texprint(ctxcatcodes,s) end * newline^-1
723)^0
724
725local printlines_raw = (
726 (newline) / function() texprint("") end +
727 (1-newline)^1 / function(s) texprint(s) end * newline^-1
728)^0
729
730function context.printlines(str,raw)
731 if raw then
732 lpegmatch(printlines_raw,str)
733 else
734 lpegmatch(printlines_ctx,str)
735 end
736end
737
738function context.printtable(t,separator)
739 if separator == nil or separator == true then
740 separator = "\r"
741 elseif separator == "" or separator == false then
742 separator = ""
743 end
744 local s = concat(t,separator)
745 if s ~= "" then
746 context(s)
747 end
748end
749
750
751
752local containseol = patterns.containseol
753
754local lua_call_code = tokens.commands.lua_expandable_call or tokens.commands.lua_call
755
756local sortedhashindeed = false
757
758directives.register("context.sorthash",function(v)
759 sortedhashindeed = v and table.sortedhash or nil
760end)
761
762local function writer(parent,command,...)
763 if type(command) == "string" then
764 flush(currentcatcodes,command)
765 else
766 flush(command)
767 end
768 local direct = false
769 for i=1,select("#",...) do
770 local ti = select(i,...)
771 if direct then
772 local typ = type(ti)
773 if typ == "string" or typ == "number" then
774 flush(currentcatcodes,ti)
775 else
776 report_context("error: invalid use of direct in %a, only strings and numbers can be flushed directly, not %a",command,typ)
777 end
778 direct = false
779 elseif ti == nil then
780
781 elseif ti == "" then
782 flush(currentcatcodes,"{}")
783
784
785 else
786 local typ = type(ti)
787 if typ == "string" then
788
789 if processlines and lpegmatch(containseol,ti) then
790 flush(currentcatcodes,"{")
791 flushlines(ti)
792 flush(currentcatcodes,"}")
793 elseif currentcatcodes == contentcatcodes then
794 flush(currentcatcodes,"{",ti,"}")
795 else
796 flush(currentcatcodes,"{")
797 flush(contentcatcodes,ti)
798 flush(currentcatcodes,"}")
799 end
800 elseif typ == "number" then
801
802 flush(currentcatcodes,"{",ti,"}")
803 elseif typ == "table" then
804 local tn = #ti
805 if tn == 0 then
806 local done = false
807 if sortedhashindeed then
808 for k, v in sortedhashindeed(ti) do
809 if done then
810 if v == "" then
811 flush(currentcatcodes,",",k,'=')
812 else
813 flush(currentcatcodes,",",k,"={",v,"}")
814 end
815 else
816 if v == "" then
817 flush(currentcatcodes,"[",k,"=")
818 else
819 flush(currentcatcodes,"[",k,"={",v,"}")
820 end
821 done = true
822 end
823 end
824 else
825 for k, v in next, ti do
826 if done then
827 if v == "" then
828 flush(currentcatcodes,",",k,'=')
829 else
830 flush(currentcatcodes,",",k,"={",v,"}")
831 end
832 else
833 if v == "" then
834 flush(currentcatcodes,"[",k,"=")
835 else
836 flush(currentcatcodes,"[",k,"={",v,"}")
837 end
838 done = true
839 end
840 end
841 end
842 if done then
843 flush(currentcatcodes,"]")
844 else
845 flush(currentcatcodes,"[]")
846 end
847 elseif tn == 1 then
848 local tj = ti[1]
849 if type(tj) == "function" then
850 tj = storefunction(tj)
851 flush(currentcatcodes,"[",newtoken(tj,lua_call_code),"]")
852 else
853 flush(currentcatcodes,"[",tj,"]")
854 end
855 else
856 flush(currentcatcodes,"[")
857 for j=1,tn do
858 local tj = ti[j]
859 if type(tj) == "function" then
860 tj = storefunction(tj)
861 flush(currentcatcodes,"[",newtoken(tj,lua_call_code),j == tn and "]" or ",")
862 else
863 if j == tn then
864 flush(currentcatcodes,tj,"]")
865 else
866 flush(currentcatcodes,tj,",")
867 end
868 end
869 end
870 end
871 elseif typ == "function" then
872
873 ti = storefunction(ti)
874 flush(currentcatcodes,"{",newtoken(ti,lua_call_code),"}")
875 elseif typ == "boolean" then
876 if ti then
877 flushdirect(currentcatcodes,"\r")
878 else
879 direct = true
880 end
881 elseif typ == "thread" then
882 report_context("coroutines not supported as we cannot yield across boundaries")
883
884 elseif isprintable(ti) then
885 flush(currentcatcodes,"{",ti,"}")
886 else
887 local s = tostring(ti)
888 if s then
889 flushdirect(currentcatcodes,s)
890 else
891 report_context("error: %a gets a weird argument %a",command,ti)
892 end
893 end
894
895
896
897
898
899
900
901 end
902 end
903end
904
905local toks = tokens.cache
906context.tokenizedcs = toks
907
908local core = setmetatableindex(function(parent,k)
909 local t
910 local f = function(first,...)
911 if not t then
912 t = toks[k]
913 end
914 if first == nil then
915 flush(t)
916 else
917 return writer(context,t,first,...)
918 end
919 end
920 parent[k] = f
921 return f
922end)
923
924core.cs = setmetatableindex(function(parent,k)
925 local t
926 local f = function()
927 if not t then
928 t = toks[k]
929 end
930 flush(t)
931 end
932 parent[k] = f
933 return f
934end)
935
936local indexer = function(parent,k)
937 if type(k) == "string" then
938 return core[k]
939 else
940 return context
941 end
942end
943
944context.core = core
945
946
947
948
949
950
951
952
953
954
955
956
957
958
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
1002local caller = function(parent,f,a,...)
1003 if not parent then
1004
1005 elseif f ~= nil then
1006 local typ = type(f)
1007 if typ == "string" then
1008 if f == "" then
1009
1010
1011
1012
1013 elseif a then
1014 flush(contentcatcodes,formatters[f](a,...))
1015
1016 elseif processlines and lpegmatch(containseol,f) then
1017 flushlines(f)
1018 else
1019 flush(contentcatcodes,f)
1020 end
1021 elseif typ == "number" then
1022 if a then
1023 flush(currentcatcodes,f,a,...)
1024 else
1025 flush(currentcatcodes,f)
1026 end
1027 elseif typ == "function" then
1028
1029 f = storefunction(f)
1030 flush(currentcatcodes,"{",newtoken(f,lua_call_code),"}")
1031 elseif typ == "boolean" then
1032 if f then
1033 if a ~= nil then
1034 flushlines(a)
1035 else
1036 flushdirect(currentcatcodes,"\n")
1037 end
1038 else
1039 if a ~= nil then
1040
1041 writer(parent,"",a,...)
1042 else
1043
1044 end
1045 end
1046 elseif typ == "thread" then
1047 report_context("coroutines not supported as we cannot yield across boundaries")
1048
1049 elseif isprintable(f) then
1050 flush(f)
1051 else
1052 local s = tostring(f)
1053 if s then
1054 flushdirect(currentcatcodes,s)
1055 else
1056 report_context("error: %a gets a weird argument %a","context",f)
1057 end
1058 end
1059
1060
1061
1062
1063
1064
1065
1066 end
1067end
1068
1069context.nodes = {
1070 store = storenode,
1071 flush = function(n)
1072 flush(n)
1073 end,
1074}
1075
1076context.nuts = {
1077 store = function(n)
1078 return storenode(tonut(n))
1079 end,
1080 flush = function(n,d)
1081 flush(tonode(n))
1082 end,
1083}
1084
1085local defaultcaller = caller
1086
1087setmetatableindex(context,indexer)
1088setmetatablecall (context,caller)
1089
1090function context.sprint(...)
1091 flush(...)
1092end
1093
1094function context.fprint(first,second,third,...)
1095 if type(first) == "number" then
1096 if third then
1097 flush(first,formatters[second](third,...))
1098 else
1099 flush(first,second)
1100 end
1101 else
1102 if second then
1103 flush(formatters[first](second,third,...))
1104 else
1105 flush(first)
1106 end
1107 end
1108end
1109
1110tex.fprint = context.fprint
1111
1112
1113
1114local trace_stack = { report_context }
1115
1116local normalflush = flush
1117local normalflushdirect = flushdirect
1118
1119local normalwriter = writer
1120local currenttrace = report_context
1121local nofwriters = 0
1122local nofflushes = 0
1123
1124local tracingpermitted = true
1125
1126local visualizer = lpeg.replacer {
1127 { "\n", "<<newline>>" },
1128 { "\r", "<<par>>" },
1129}
1130
1131statistics.register("traced context", function()
1132 local used, freed = usedstack()
1133 local unreachable = used - freed
1134 if nofwriters > 0 or nofflushes > 0 then
1135 return format("writers: %s, flushes: %s, maxstack: %s",nofwriters,nofflushes,used,freed,unreachable)
1136 elseif showstackusage or unreachable > 0 then
1137 return format("maxstack: %s, freed: %s, unreachable: %s",used,freed,unreachable)
1138 end
1139end)
1140
1141
1142
1143local luacalls = {
1144 lua_expandable_call = true,
1145 lua_call = true,
1146 lua_protected_call = true,
1147}
1148
1149local function userdata(argument)
1150 if isnode(argument) then
1151 return formatters["<< %s node %i>>"](nodes.nodecodes[argument.id],tonut(argument))
1152 elseif istoken(argument) then
1153 local csname = argument.csname
1154 if csname then
1155
1156 return formatters["\\%s"](csname)
1157 end
1158 if luacall[argument.cmdname] then
1159 return "<<function>>"
1160 end
1161 return "<<token>>"
1162 else
1163 return "<<userdata>>"
1164 end
1165end
1166
1167
1168local tracedwriter = function(parent,...)
1169 nofwriters = nofwriters + 1
1170 local savedflush = flush
1171 local savedflushdirect = flushdirect
1172 local t = { "w : - : " }
1173 local n = 1
1174 local traced = function(catcodes,...)
1175 local s = type(catcodes) == "number" and { ... } or { catcodes, ... }
1176 for i=1,#s do
1177 local argument = s[i]
1178 local argtype = type(argument)
1179 if argtype == "string" then
1180 s[i] = lpegmatch(visualizer,argument)
1181 elseif argtype == "number" then
1182 s[i] = argument
1183 elseif argtype == "userdata" then
1184 s[i] = userdata(argument)
1185 else
1186 s[i] = formatters["<<%S>>"](argument)
1187 end
1188 end
1189 s = concat(s)
1190 s = lpegmatch(visualizer,s)
1191 n = n + 1
1192 t[n] = s
1193 end
1194 flush = function(...)
1195 normalflush(...)
1196 if tracingpermitted then
1197 traced(...)
1198 end
1199 end
1200 flushdirect = function(...)
1201 normalflushdirect(...)
1202 if tracingpermitted then
1203 traced(...)
1204 end
1205 end
1206 normalwriter(parent,...)
1207 flush = savedflush
1208 flushdirect = savedflushdirect
1209 currenttrace(concat(t))
1210end
1211
1212
1213
1214local traced = function(one,two,...)
1215 if two ~= nil then
1216
1217 local catcodes = type(one) == "number" and one
1218 local arguments = catcodes and { two, ... } or { one, two, ... }
1219 local collapsed = { formatters["f : %s : "](catcodes or '-') }
1220 local c = 1
1221 for i=1,#arguments do
1222 local argument = arguments[i]
1223 local argtype = type(argument)
1224 c = c + 1
1225 if argtype == "string" then
1226 collapsed[c] = lpegmatch(visualizer,argument)
1227 elseif argtype == "number" then
1228 collapsed[c] = argument
1229 elseif argtype == "userdata" then
1230 collapsed[c] = userdata(argument)
1231 else
1232 collapsed[c] = formatters["<<%S>>"](argument)
1233 end
1234 end
1235 currenttrace(concat(collapsed))
1236 elseif one ~= nil then
1237
1238 local argtype = type(one)
1239 if argtype == "string" then
1240 currenttrace(formatters["f : - : %s"](lpegmatch(visualizer,one)))
1241 elseif argtype == "number" then
1242 currenttrace(formatters["f : - : %s"](one))
1243 elseif argtype == "userdata" then
1244 currenttrace(formatters["F : - : %s"](userdata(one)))
1245 else
1246 currenttrace(formatters["f : - : <<%S>>"](one))
1247 end
1248 end
1249end
1250
1251local tracedflush = function(one,two,...)
1252 nofflushes = nofflushes + 1
1253 if two ~= nil then
1254 normalflush(one,two,...)
1255 else
1256 normalflush(one)
1257 end
1258 if tracingpermitted then
1259 traced(one,two,...)
1260 end
1261end
1262
1263local tracedflushdirect = function(one,two,...)
1264 nofflushes = nofflushes + 1
1265 if two ~= nil then
1266 normalflushdirect(one,two,...)
1267 else
1268 normalflushdirect(one)
1269 end
1270 if tracingpermitted then
1271 traced(one,two,...)
1272 end
1273end
1274
1275function context.pushlogger(trace)
1276 trace = trace or report_context
1277 insert(trace_stack,currenttrace)
1278 currenttrace = trace
1279end
1280
1281function context.poplogger()
1282 if #trace_stack > 1 then
1283 currenttrace = remove(trace_stack) or report_context
1284 else
1285 currenttrace = report_context
1286 end
1287end
1288
1289function context.settracing(v)
1290 if v then
1291 flush = tracedflush
1292 flushdirect = tracedflushdirect
1293 writer = tracedwriter
1294 else
1295 flush = normalflush
1296 flushdirect = normalflushdirect
1297 writer = normalwriter
1298 end
1299 return flush, writer, flushdirect
1300end
1301
1302function context.getlogger()
1303 return flush, writer, flushdirect
1304end
1305
1306trackers.register("context.trace",context.settracing)
1307
1308local trace_cld = false trackers.register("context.files", function(v) trace_cld = v end)
1309
1310do
1311
1312
1313
1314
1315 local resolve = resolvers.savers.byscheme
1316 local validstring = string.valid
1317 local input = context.input
1318
1319 local function viafile(data,tag)
1320 if data and data ~= "" then
1321 local filename = resolve("virtual",validstring(tag,"viafile"),data)
1322
1323 input(filename)
1324
1325 end
1326 end
1327
1328 context.viafile = viafile
1329
1330
1331
1332 local collected = nil
1333 local nofcollected = 0
1334 local sentinel = string.char(26)
1335 local level = 0
1336
1337 local function collect(c,a,...)
1338 if type(c) == "userdata" then
1339 nofcollected = nofcollected + 1
1340
1341 collected[nofcollected] = "\\" .. c.csname
1342 end
1343 if a then
1344 for i=1,select("#",a,...) do
1345 local c = select(i,a,...)
1346 nofcollected = nofcollected + 1
1347 collected[nofcollected] = type(c) == "userdata" and userdata(c) or c
1348 end
1349 end
1350 end
1351
1352 local collectdirect = collect
1353 local permitted = true
1354
1355
1356
1357
1358 function context.startcollecting()
1359 if level == 0 then
1360 collected = { }
1361 nofcollected = 0
1362 flush = collect
1363 flushdirect = collectdirect
1364 permitted = tracingpermitted
1365 end
1366 level = level + 1
1367 end
1368
1369 function context.stopcollecting()
1370 level = level - 1
1371 if level < 1 then
1372 local result = concat(collected,sentinel)
1373 flush = normalflush
1374 flushdirect = normalflushdirect
1375 tracingpermitted = permitted
1376 collected = nil
1377 nofcollected = 0
1378 level = 0
1379 viafile(result)
1380 end
1381 end
1382
1383 local findtexfile = resolvers.findtexfile
1384 local findfile = resolvers.findfile
1385
1386 function context.runfile(filename)
1387 local foundname = findtexfile(file.addsuffix(filename,"cld")) or ""
1388 if foundname ~= "" then
1389 local ok = dofile(foundname)
1390 if type(ok) == "function" then
1391 if trace_cld then
1392 report_context("begin of file %a (function call)",foundname)
1393 end
1394 ok()
1395 if trace_cld then
1396 report_context("end of file %a (function call)",foundname)
1397 end
1398 elseif ok then
1399 report_context("file %a is processed and returns true",foundname)
1400 else
1401 report_context("file %a is processed and returns nothing",foundname)
1402 end
1403 else
1404 report_context("unknown file %a",filename)
1405 end
1406 end
1407
1408 function context.loadfile(filename)
1409 context(stripstring(loaddata(findfile(filename))))
1410 end
1411
1412 function context.loadviafile(filename)
1413 viafile(stripstring(loaddata(findfile(filename))))
1414 end
1415
1416end
1417
1418
1419
1420function context.direct(first,...)
1421 if first ~= nil then
1422 return writer(context,"",first,...)
1423 end
1424end
1425
1426
1427
1428do
1429
1430 local delayed = { }
1431
1432 local function indexer(parent,k)
1433 local f = function(...)
1434 local a = { ... }
1435 return function()
1436
1437 return core[k](unpack(a))
1438 end
1439 end
1440 parent[k] = f
1441 return f
1442 end
1443
1444 local function caller(parent,...)
1445 local a = { ... }
1446 return function()
1447
1448 return defaultcaller(context,unpack(a))
1449 end
1450 end
1451
1452 setmetatableindex(delayed,indexer)
1453 setmetatablecall (delayed,caller)
1454
1455 context.delayed = delayed
1456
1457end
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500context.nested = context.delayed
1501
1502
1503
1504function context.newindexer(catcodes,cmdcodes)
1505 local handler = { }
1506
1507 local function indexer(parent,k)
1508 local command = core[k]
1509 local f = function(...)
1510 local savedcatcodes = contentcatcodes
1511 contentcatcodes = catcodes
1512 command(...)
1513 contentcatcodes = savedcatcodes
1514 end
1515 parent[k] = f
1516 return f
1517 end
1518
1519 local function caller(parent,...)
1520 local savedcatcodes = contentcatcodes
1521 contentcatcodes = catcodes
1522 defaultcaller(parent,...)
1523 contentcatcodes = savedcatcodes
1524 end
1525
1526 handler.cs = core.cs
1527
1528 setmetatableindex(handler,indexer)
1529 setmetatablecall (handler,caller)
1530
1531 return handler
1532end
1533
1534context.verbatim = context.newindexer(vrbcatcodes,ctxcatcodes)
1535context.puretext = context.newindexer(txtcatcodes,ctxcatcodes)
1536context.protected = context.newindexer(prtcatcodes,prtcatcodes)
1537
1538
1539
1540do
1541
1542 local formatted = { }
1543
1544
1545
1546 local function formattedflush(parent,c,catcodes,fmt,...)
1547 if not catcodes then
1548 return writer(parent,c)
1549 elseif not fmt then
1550 return writer(parent,c,catcodes)
1551 elseif type(catcodes) == "number" then
1552 local result
1553 pushcatcodes(catcodes)
1554 result = writer(parent,c,formatters[fmt](...))
1555 popcatcodes()
1556 return result
1557 else
1558 return writer(parent,c,formatters[catcodes](fmt,...))
1559 end
1560 end
1561
1562 local toks = tokens.cache
1563
1564 local indexer = function(parent,k)
1565 if type(k) == "string" then
1566 local t
1567 local f = function(first,...)
1568 if not t then
1569 t = toks[k]
1570 end
1571 if first == nil then
1572 flush(t)
1573 else
1574 return formattedflush(parent,t,first,...)
1575 end
1576 end
1577 parent[k] = f
1578 return f
1579 else
1580 return context
1581 end
1582 end
1583
1584
1585
1586 local function caller(parent,catcodes,fmt,...)
1587 if not catcodes then
1588
1589 elseif not fmt then
1590 flush(catcodes)
1591 elseif type(catcodes) == "number" then
1592 flush(catcodes,formatters[fmt](...))
1593 else
1594 flush(formatters[catcodes](fmt,...))
1595 end
1596 end
1597
1598 setmetatableindex(formatted,indexer)
1599 setmetatablecall (formatted,caller)
1600
1601 context.formatted = formatted
1602
1603end
1604
1605do
1606
1607
1608
1609 local metafun = { }
1610
1611 function metafun.start()
1612 context.startMPcode()
1613 end
1614
1615 function metafun.stop()
1616 context.stopMPcode()
1617 end
1618
1619 setmetatablecall(metafun,defaultcaller)
1620
1621 function metafun.color(name)
1622 return name
1623 end
1624
1625
1626
1627 local delayed = { }
1628
1629 local function indexer(parent,k)
1630 local f = function(...)
1631 local a = { ... }
1632 return function()
1633 return metafun[k](unpack(a))
1634 end
1635 end
1636 parent[k] = f
1637 return f
1638 end
1639
1640
1641 local function caller(parent,...)
1642 local a = { ... }
1643 return function()
1644 return metafun(unpack(a))
1645 end
1646 end
1647
1648 setmetatableindex(delayed,indexer)
1649 setmetatablecall (delayed,caller)
1650
1651 context.metafun = metafun
1652 metafun.delayed = delayed
1653
1654end
1655
1656
1657
1658do
1659
1660 function context.concat(...)
1661 context(concat(...))
1662 end
1663
1664 local p_texescape = patterns.texescape
1665
1666 function context.escaped(s)
1667 if s then
1668 context(lpegmatch(p_texescape,s) or s)
1669 else
1670
1671 end
1672 end
1673
1674 function context.escape(s)
1675 if s then
1676 return lpegmatch(p_texescape,s) or s
1677 else
1678 return ""
1679 end
1680 end
1681
1682end
1683
1684
1685
1686do
1687
1688 local single = lpegP("%")
1689 local double = lpegP("%%")
1690 local lquoted = lpegP("%[")
1691 local rquoted = lpegP("]%")
1692 local space = lpegP(" ")
1693
1694 local start = [[
1695 local texescape = lpeg.patterns.texescape
1696 local lpegmatch = lpeg.match
1697 return function(variables) return
1698 ]]
1699
1700 local stop = [[
1701 end
1702 ]]
1703
1704 local replacer = lpegP { "parser",
1705 parser = lpegCs(lpegCc(start) * lpegV("step") * (lpegCc("..") * lpegV("step"))^0 * lpegCc(stop)),
1706 unquoted = (lquoted*space/'')
1707 * ((lpegC((1-space*rquoted)^1)) / "lpegmatch(texescape,variables%0 or '')" )
1708 * (space*rquoted/'')
1709 + (lquoted/'')
1710 * ((lpegC((1-rquoted)^1)) / "lpegmatch(texescape,variables['%0'] or '')" )
1711 * (rquoted/''),
1712 key = (single*space/'')
1713 * ((lpegC((1-space*single)^1)) / "(variables%0 or '')" )
1714 * (space*single/'')
1715 + (single/'')
1716 * ((lpegC((1-single)^1)) / "(variables['%0'] or '')" )
1717 * (single/''),
1718 escape = double/'%%',
1719 step = lpegV("unquoted")
1720 + lpegV("escape")
1721 + lpegV("key")
1722 + lpegCc("\n[===[") * (1 - lpegV("unquoted") - lpegV("escape") - lpegV("key"))^1 * lpegCc("]===]\n"),
1723 }
1724
1725 local templates = { }
1726
1727 local function indexer(parent,k)
1728 local v = lpegmatch(replacer,k)
1729 if not v then
1730
1731 v = "error: no valid template (1)"
1732 else
1733 local f = loadstring(v)
1734 if type(f) ~= "function" then
1735
1736 v = "error: no valid template (2)"
1737 else
1738 f = f()
1739 if not f then
1740
1741 v = "error: no valid template (3)"
1742 else
1743 v = f
1744 end
1745 end
1746 end
1747 if type(v) == "function" then
1748 local f = function(first,second)
1749 if second then
1750 pushcatcodes(first)
1751 flushlines(v(second))
1752 popcatcodes()
1753 else
1754 flushlines(v(first))
1755 end
1756 end
1757 parent[k] = f
1758 return f
1759 else
1760 return function()
1761 flush(v)
1762 end
1763 end
1764
1765 end
1766
1767 local function caller(parent,k,...)
1768 return parent[k](...)
1769 end
1770
1771 setmetatableindex(templates,indexer)
1772 setmetatablecall (templates,caller)
1773
1774 context.templates = templates
1775
1776end
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787do
1788
1789
1790
1791
1792 local modelevels = tex.getmodevalues()
1793 local t = table.keys(modelevels)
1794 tex.modelevels = table.swapped(modelevels,modelevels)
1795
1796 for i=1,#t do local k = t[i] modelevels[-k] = modelevels[k] end
1797
1798 if CONTEXTLMTXMODE > 0 then
1799
1800
1801
1802 local flagcodes = tex.getflagvalues()
1803 tex.flagcodes = table.swapped(flagcodes,flagcodes)
1804
1805 end
1806
1807end
1808 |