1if not modules then modules = { } end modules ['luat-mac'] = {
2 version = 1.001,
3 comment = "companion to luat-lib.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
17local P, V, S, R, C, Cs, Cmt, Carg = lpeg.P, lpeg.V, lpeg.S, lpeg.R, lpeg.C, lpeg.Cs, lpeg.Cmt, lpeg.Carg
18local lpegmatch, patterns = lpeg.match, lpeg.patterns
19
20local insert, remove = table.insert, table.remove
21local rep, sub = string.rep, string.sub
22local setmetatable = setmetatable
23local filesuffix = file.suffix
24local convertlmxstring = lmx and lmx.convertstring
25local savedata = io.savedata
26
27local pushtarget, poptarget = logs.pushtarget, logs.poptarget
28
29local report_macros = logs.reporter("interface","macros")
30
31local stack, top, n, hashes = { }, nil, 0, { }
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
68
69
70
71
72
73
74
75local set = CONTEXTLMTXMODE > 0 and
76 function(s)
77 if top then
78 local ns = #stack
79 local h = hashes[ns]
80 if not h then
81 h = rep("#",2^(ns-1))
82 hashes[ns] = h
83 end
84 if s == "ignore" then
85 return h .. "-"
86 elseif s == "spacer" then
87 return h .. "*"
88 elseif s == "keepspacer" then
89 return h .. ","
90 elseif s == "pickup" then
91 return h .. ":"
92 else
93 n = n + 1
94 if n > 9 then
95 report_macros("number of arguments > 9, ignoring %s",s)
96 elseif s == "discard" then
97 top[s] = ""
98 return h .. "0"
99 elseif s == "keepbraces" then
100 top[s] = ""
101 return h .. "+"
102 elseif s == "mandate" then
103 top[s] = ""
104 return h .. "="
105 elseif s == "keepmandate" then
106 top[s] = ""
107 return h .. "_"
108 elseif s == "prunespacing" then
109 top[s] = ""
110 return h .. "/"
111 else
112 local m = h .. n
113 top[s] = m
114 return m
115 end
116 end
117 end
118 end
119or
120 function(s)
121 if top then
122 local ns = #stack
123 local h = hashes[ns]
124 if not h then
125 h = rep("#",2^(ns-1))
126 hashes[ns] = h
127 end
128 n = n + 1
129 if n > 9 then
130 report_macros("number of arguments > 9, ignoring %s",s)
131 else
132 local m = h .. n
133 top[s] = m
134 return m
135 end
136 end
137 end
138
139local function get(s)
140 if s == "ignore" or s == "discard" then
141 return ""
142 else
143 if not top then
144 report_macros("keeping #%s, no stack",s)
145 return "#" .. s
146 end
147 local m = top[s]
148 if m then
149 return m
150 else
151 report_macros("keeping #%s, not on stack",s)
152 return "#" .. s
153 end
154 end
155end
156
157local function push()
158 top = { }
159 n = 0
160 local s = stack[#stack]
161 if s then
162 setmetatable(top,{ __index = s })
163 end
164 insert(stack,top)
165end
166
167local function pop()
168 top = remove(stack)
169end
170
171local leftbrace = P("{")
172local rightbrace = P("}")
173local escape = P("\\")
174
175local space = patterns.space
176local spaces = space^1
177local newline = patterns.newline
178local nobrace = 1 - leftbrace - rightbrace
179
180local longleft = leftbrace
181local longright = rightbrace
182local nolong = 1 - longleft - longright
183
184local utf8character = P(1) * R("\128\191")^1
185
186
187
188local name = ((R("az") + utf8character) * (R("AZ","az") + utf8character)^0)
189 + ((R("AZ","az") + utf8character) * (R("AZ","az") + utf8character)^1)
190local csname = (R("AZ","az") + S("@?!_:-*") + utf8character)^1
191local longname = (longleft/"") * (nolong^1) * (longright/"")
192local variable = P("#") * Cs(name + longname)
193local bcsname = P("csname")
194local ecsname = escape * P("endcsname")
195local escapedname = escape * csname
196local definer = escape * (P("u")^-1 * S("egx")^-1 * P("def"))
197local setter = escape * P("set") * (P("u")^-1 * S("egx")^-1) * P("value")
198
199local defcsname = escape * S("egx")^-1 * P("defcsname")
200 * (1 - ecsname)^1
201 * ecsname
202local startcode = P("\\starttexdefinition")
203local stopcode = P("\\stoptexdefinition")
204local anything = patterns.anything
205local always = patterns.alwaysmatched
206
207
208
209
210
211
212
213
214
215
216local commenttoken = P("%")
217local crorlf = S("\n\r")
218local commentline = commenttoken * ((1-crorlf)^0)
219local leadingcomment = (commentline * crorlf^1)^1
220local furthercomment = (crorlf^1 * commentline)^1
221
222local pushlocal = always / push
223local poplocal = always / pop
224local declaration = variable / set
225local identifier = variable / get
226
227local argument = P { leftbrace * ((identifier + V(1) + (1 - leftbrace - rightbrace))^0) * rightbrace }
228
229local function matcherror(str,pos)
230 report_macros("runaway definition at: %s",sub(str,pos-30,pos))
231 os.exit()
232end
233
234local csname_endcsname = P("\\csname") * (identifier + (1 - P("\\endcsname")))^1
235
236local grammar = { "converter",
237 texcode = pushlocal
238 * startcode
239 * spaces
240 * (csname * spaces)^1
241 * ((declaration * (space^0/""))^1 + furthercomment + (1 - newline - space))^0
242 * V("texbody")
243 * stopcode
244 * poplocal,
245 texbody = (
246 leadingcomment
247 + V("definition")
248 + identifier
249 + V("braced")
250 + (1 - stopcode)
251 )^0,
252 definition = pushlocal
253 * (definer * spaces^0 * escapedname)
254 * (declaration + furthercomment + commentline + csname_endcsname + (1-leftbrace))^0
255 * V("braced")
256 * poplocal,
257 csnamedef = pushlocal
258 * defcsname
259 * (declaration + furthercomment + commentline + csname_endcsname + (1-leftbrace))^0
260 * V("braced")
261 * poplocal,
262 setcode = pushlocal
263 * setter
264 * argument
265 * (declaration + furthercomment + commentline + (1-leftbrace))^0
266 * V("braced")
267 * poplocal,
268 braced = leftbrace
269 * ( V("definition")
270 + identifier
271 + V("setcode")
272 + V("texcode")
273 + V("braced")
274 + furthercomment
275 + leadingcomment
276 + nobrace
277 )^0
278 * (rightbrace + Cmt(always,matcherror)),
279
280 pattern = leadingcomment
281 + V("definition")
282 + V("csnamedef")
283 + V("setcode")
284 + V("texcode")
285 + furthercomment
286 + anything,
287
288 converter = V("pattern")^1,
289}
290
291local parser = Cs(grammar)
292
293local checker = P("%") * (1 - newline - P("macros"))^0
294 * P("macros") * space^0 * P("=") * space^0 * C(patterns.letter^1)
295
296
297
298local resolvers = resolvers
299
300local macros = { }
301resolvers.macros = macros
302
303local loadtexfile = resolvers.loadtexfile
304
305function macros.preprocessed(str,strip)
306 return lpegmatch(parser,str,1,strip)
307end
308
309function macros.convertfile(oldname,newname)
310 local data = (loadtexfile or io.loaddata)(oldname)
311 data = macros.preprocessed(data) or ""
312 savedata(newname,data)
313end
314
315
316
317function macros.version(data)
318 return lpegmatch(checker,data)
319end
320
321
322
323local processors = { }
324
325function processors.mkvi(str,filename)
326 local oldsize = #str
327 str = lpegmatch(parser,str,1,true) or str
328 pushtarget("logfile")
329 report_macros("processed mkvi file %a, delta %s",filename,oldsize-#str)
330 poptarget()
331 return str
332end
333
334function processors.mkix(str,filename)
335 if not document then
336 document = { }
337 end
338 if not document.variables then
339 document.variables = { }
340 end
341 local oldsize = #str
342 str = convertlmxstring(str,document.variables,false) or str
343 pushtarget("logfile")
344 report_macros("processed mkix file %a, delta %s",filename,oldsize-#str)
345 poptarget()
346 return str
347end
348
349function processors.mkxi(str,filename)
350 if not document then
351 document = { }
352 end
353 if not document.variables then
354 document.variables = { }
355 end
356 local oldsize = #str
357 str = convertlmxstring(str,document.variables,false) or str
358 str = lpegmatch(parser,str,1,true) or str
359 pushtarget("logfile")
360 report_macros("processed mkxi file %a, delta %s",filename,oldsize-#str)
361 poptarget()
362 return str
363end
364
365processors.mklx = processors.mkvi
366processors.mkxl = processors.mkiv
367
368function macros.processmk(str,filename)
369 if filename then
370 local suffix = filesuffix(filename)
371 local processor = processors[suffix] or processors[lpegmatch(checker,str)]
372 if processor then
373 str = processor(str,filename)
374 end
375 end
376 return str
377end
378
379local function validvi(filename,str)
380 local suffix = filesuffix(filename)
381 if suffix == "mkvi" or suffix == "mklx" then
382 return true
383 else
384 local check = lpegmatch(checker,str)
385 return check == "mkvi" or check == "mklx"
386 end
387end
388
389function macros.processmkvi(str,filename)
390 if filename and filename ~= "" and validvi(filename,str) then
391 local oldsize = #str
392 str = lpegmatch(parser,str,1,true) or str
393 pushtarget("logfile")
394 report_macros("processed mkvi file %a, delta %s",filename,oldsize-#str)
395 poptarget()
396 end
397 return str
398end
399
400macros.processmklx = macros.processmkvi
401
402
403
404local schemes = resolvers.schemes
405
406if schemes then
407
408 local function handler(protocol,name,cachename)
409 local hashed = url.hashed(name)
410 local path = hashed.path
411 if path and path ~= "" then
412 local str = loadtexfile(path)
413 if validvi(path,str) then
414
415 savedata(cachename,str)
416 else
417 local result = lpegmatch(parser,str,1,true) or str
418 pushtarget("logfile")
419 report_macros("processed scheme %a, delta %s",filename,#str-#result)
420 poptarget()
421 savedata(cachename,result)
422 end
423 end
424 return cachename
425 end
426
427 schemes.install('mkvi',handler,1)
428 schemes.install('mklx',handler,1)
429
430end
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512local txtcatcodes = false
513
514local commentsignal = utf.char(0x10FF25)
515
516local encodecomment = P("%%") / commentsignal
517
518local encodepattern = Cs((encodecomment + 1)^0)
519local decodecomment = P(commentsignal) / "%%%%"
520local decodepattern = Cs((decodecomment + 1)^0)
521
522function macros.encodecomment(str)
523 if txtcatcodes and tex.catcodetable == txtcatcodes then
524 return lpegmatch(encodepattern,str) or str
525 else
526 return str
527 end
528end
529
530function macros.decodecomment(str)
531 return txtcatcodes and lpegmatch(decodepattern,str) or str
532end
533
534
535
536
537
538local sequencers = utilities.sequencers
539local appendaction = sequencers and sequencers.appendaction
540
541if appendaction then
542
543 local textlineactions = resolvers.openers.helpers.textlineactions
544 local textfileactions = resolvers.openers.helpers.textfileactions
545
546 appendaction(textfileactions,"system","resolvers.macros.processmk")
547 appendaction(textfileactions,"system","resolvers.macros.processmkvi")
548
549 function macros.enablecomment(thecatcodes)
550 if not txtcatcodes then
551 txtcatcodes = thecatcodes or catcodes.numbers.txtcatcodes
552 appendaction(textlineactions,"system","resolvers.macros.encodecomment")
553 end
554 end
555
556end
557 |