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
186local name = (R("AZ","az") + utf8character)^1
187local csname = (R("AZ","az") + S("@?!_:-*") + utf8character)^1
188local longname = (longleft/"") * (nolong^1) * (longright/"")
189local variable = P("#") * Cs(name + longname)
190local bcsname = P("csname")
191local ecsname = escape * P("endcsname")
192local escapedname = escape * csname
193local definer = escape * (P("u")^-1 * S("egx")^-1 * P("def"))
194local setter = escape * P("set") * (P("u")^-1 * S("egx")^-1) * P("value")
195
196local defcsname = escape * S("egx")^-1 * P("defcsname")
197 * (1 - ecsname)^1
198 * ecsname
199local startcode = P("\\starttexdefinition")
200local stopcode = P("\\stoptexdefinition")
201local anything = patterns.anything
202local always = patterns.alwaysmatched
203
204
205
206
207
208
209
210
211
212
213local commenttoken = P("%")
214local crorlf = S("\n\r")
215local commentline = commenttoken * ((1-crorlf)^0)
216local leadingcomment = (commentline * crorlf^1)^1
217local furthercomment = (crorlf^1 * commentline)^1
218
219local pushlocal = always / push
220local poplocal = always / pop
221local declaration = variable / set
222local identifier = variable / get
223
224local argument = P { leftbrace * ((identifier + V(1) + (1 - leftbrace - rightbrace))^0) * rightbrace }
225
226local function matcherror(str,pos)
227 report_macros("runaway definition at: %s",sub(str,pos-30,pos))
228end
229
230local csname_endcsname = P("\\csname") * (identifier + (1 - P("\\endcsname")))^1
231
232local grammar = { "converter",
233 texcode = pushlocal
234 * startcode
235 * spaces
236 * (csname * spaces)^1
237 * ((declaration * (space^0/""))^1 + furthercomment + (1 - newline - space))^0
238 * V("texbody")
239 * stopcode
240 * poplocal,
241 texbody = (
242 leadingcomment
243 + V("definition")
244 + identifier
245 + V("braced")
246 + (1 - stopcode)
247 )^0,
248 definition = pushlocal
249 * (definer * spaces^0 * escapedname)
250 * (declaration + furthercomment + commentline + csname_endcsname + (1-leftbrace))^0
251 * V("braced")
252 * poplocal,
253 csnamedef = pushlocal
254 * defcsname
255 * (declaration + furthercomment + commentline + csname_endcsname + (1-leftbrace))^0
256 * V("braced")
257 * poplocal,
258 setcode = pushlocal
259 * setter
260 * argument
261 * (declaration + furthercomment + commentline + (1-leftbrace))^0
262 * V("braced")
263 * poplocal,
264 braced = leftbrace
265 * ( V("definition")
266 + identifier
267 + V("setcode")
268 + V("texcode")
269 + V("braced")
270 + furthercomment
271 + leadingcomment
272 + nobrace
273 )^0
274 * (rightbrace + Cmt(always,matcherror)),
275
276 pattern = leadingcomment
277 + V("definition")
278 + V("csnamedef")
279 + V("setcode")
280 + V("texcode")
281 + furthercomment
282 + anything,
283
284 converter = V("pattern")^1,
285}
286
287local parser = Cs(grammar)
288
289local checker = P("%") * (1 - newline - P("macros"))^0
290 * P("macros") * space^0 * P("=") * space^0 * C(patterns.letter^1)
291
292
293
294local resolvers = resolvers
295
296local macros = { }
297resolvers.macros = macros
298
299local loadtexfile = resolvers.loadtexfile
300
301function macros.preprocessed(str,strip)
302 return lpegmatch(parser,str,1,strip)
303end
304
305function macros.convertfile(oldname,newname)
306 local data = loadtexfile(oldname)
307 data = macros.preprocessed(data) or ""
308 savedata(newname,data)
309end
310
311function macros.version(data)
312 return lpegmatch(checker,data)
313end
314
315
316
317local processors = { }
318
319function processors.mkvi(str,filename)
320 local oldsize = #str
321 str = lpegmatch(parser,str,1,true) or str
322 pushtarget("logfile")
323 report_macros("processed mkvi file %a, delta %s",filename,oldsize-#str)
324 poptarget()
325 return str
326end
327
328function processors.mkix(str,filename)
329 if not document then
330 document = { }
331 end
332 if not document.variables then
333 document.variables = { }
334 end
335 local oldsize = #str
336 str = convertlmxstring(str,document.variables,false) or str
337 pushtarget("logfile")
338 report_macros("processed mkix file %a, delta %s",filename,oldsize-#str)
339 poptarget()
340 return str
341end
342
343function processors.mkxi(str,filename)
344 if not document then
345 document = { }
346 end
347 if not document.variables then
348 document.variables = { }
349 end
350 local oldsize = #str
351 str = convertlmxstring(str,document.variables,false) or str
352 str = lpegmatch(parser,str,1,true) or str
353 pushtarget("logfile")
354 report_macros("processed mkxi file %a, delta %s",filename,oldsize-#str)
355 poptarget()
356 return str
357end
358
359processors.mklx = processors.mkvi
360processors.mkxl = processors.mkiv
361
362function macros.processmk(str,filename)
363 if filename then
364 local suffix = filesuffix(filename)
365 local processor = processors[suffix] or processors[lpegmatch(checker,str)]
366 if processor then
367 str = processor(str,filename)
368 end
369 end
370 return str
371end
372
373local function validvi(filename,str)
374 local suffix = filesuffix(filename)
375 if suffix == "mkvi" or suffix == "mklx" then
376 return true
377 else
378 local check = lpegmatch(checker,str)
379 return check == "mkvi" or check == "mklx"
380 end
381end
382
383function macros.processmkvi(str,filename)
384 if filename and filename ~= "" and validvi(filename,str) then
385 local oldsize = #str
386 str = lpegmatch(parser,str,1,true) or str
387 pushtarget("logfile")
388 report_macros("processed mkvi file %a, delta %s",filename,oldsize-#str)
389 poptarget()
390 end
391 return str
392end
393
394macros.processmklx = macros.processmkvi
395
396
397
398local schemes = resolvers.schemes
399
400if schemes then
401
402 local function handler(protocol,name,cachename)
403 local hashed = url.hashed(name)
404 local path = hashed.path
405 if path and path ~= "" then
406 local str = loadtexfile(path)
407 if validvi(path,str) then
408
409 savedata(cachename,str)
410 else
411 local result = lpegmatch(parser,str,1,true) or str
412 pushtarget("logfile")
413 report_macros("processed scheme %a, delta %s",filename,#str-#result)
414 poptarget()
415 savedata(cachename,result)
416 end
417 end
418 return cachename
419 end
420
421 schemes.install('mkvi',handler,1)
422 schemes.install('mklx',handler,1)
423
424end
425
426
427
428
429
430
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
506local txtcatcodes = false
507
508local commentsignal = utf.char(0x10FF25)
509
510local encodecomment = P("%%") / commentsignal
511
512local encodepattern = Cs((encodecomment + 1)^0)
513local decodecomment = P(commentsignal) / "%%%%"
514local decodepattern = Cs((decodecomment + 1)^0)
515
516function macros.encodecomment(str)
517 if txtcatcodes and tex.catcodetable == txtcatcodes then
518 return lpegmatch(encodepattern,str) or str
519 else
520 return str
521 end
522end
523
524function macros.decodecomment(str)
525 return txtcatcodes and lpegmatch(decodepattern,str) or str
526end
527
528
529
530
531
532local sequencers = utilities.sequencers
533local appendaction = sequencers and sequencers.appendaction
534
535if appendaction then
536
537 local textlineactions = resolvers.openers.helpers.textlineactions
538 local textfileactions = resolvers.openers.helpers.textfileactions
539
540 appendaction(textfileactions,"system","resolvers.macros.processmk")
541 appendaction(textfileactions,"system","resolvers.macros.processmkvi")
542
543 function macros.enablecomment(thecatcodes)
544 if not txtcatcodes then
545 txtcatcodes = thecatcodes or catcodes.numbers.txtcatcodes
546 appendaction(textlineactions,"system","resolvers.macros.encodecomment")
547 end
548 end
549
550end
551 |