luatex-preprocessor.lua /size: 4626 b    last modification: 2023-12-21 09:45
1if not modules then modules = { } end modules ['luatex-preprocessor'] = {
2    version   = 1.001,
3    comment   = "companion to luatex-preprocessor.tex",
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-- This is a stripped down version of the preprocessor. In ConTeXt we have a bit
10-- more, use a different logger, and use a few optimizations. A few examples are
11-- shown at the end.
12
13local rep, sub, gmatch = string.rep, string.sub, string.gmatch
14local insert, remove = table.insert, table.remove
15local setmetatable = setmetatable
16
17local stack, top, n, hashes = { }, nil, 0, { }
18
19local function set(s)
20    if top then
21        n = n + 1
22        if n > 9 then
23            texio.write_nl("number of arguments > 9, ignoring: " .. s)
24        else
25            local ns = #stack
26            local h = hashes[ns]
27            if not h then
28                h = rep("#",ns)
29                hashes[ns] = h
30            end
31            m = h .. n
32            top[s] = m
33            return m
34        end
35    end
36end
37
38local function get(s)
39    local m = top and top[s] or s
40    return m
41end
42
43local function push()
44    top = { }
45    n = 0
46    local s = stack[#stack]
47    if s then
48        setmetatable(top,{ __index = s })
49    end
50    insert(stack,top)
51end
52
53local function pop()
54    top = remove(stack)
55end
56
57local leftbrace   = lpeg.P("{")
58local rightbrace  = lpeg.P("}")
59local escape      = lpeg.P("\\")
60
61local space       = lpeg.P(" ")
62local spaces      = space^1
63local newline     = lpeg.S("\r\n")
64local nobrace     = 1 - leftbrace - rightbrace
65
66local name        = lpeg.R("AZ","az")^1
67local longname    = (leftbrace/"") * (nobrace^1) * (rightbrace/"")
68local variable    = lpeg.P("#") * lpeg.Cs(name + longname)
69local escapedname = escape * name
70local definer     = escape * (lpeg.P("def") + lpeg.P("egdx") * lpeg.P("def"))
71local anything    = lpeg.P(1)
72local always      = lpeg.P(true)
73
74local pushlocal   = always   / push
75local poplocal    = always   / pop
76local declaration = variable / set
77local identifier  = variable / get
78
79local function matcherror(str,pos)
80    texio.write_nl("runaway definition at: " .. sub(str,pos-30,pos))
81end
82
83local parser = lpeg.Cs { "converter",
84    definition  = pushlocal
85                * definer
86                * escapedname
87                * (declaration + (1-leftbrace))^0
88                * lpeg.V("braced")
89                * poplocal,
90    braced      = leftbrace
91                * (   lpeg.V("definition")
92                    + identifier
93                    + lpeg.V("braced")
94                    + nobrace
95                  )^0
96                * (rightbrace + lpeg.Cmt(always,matcherror)),
97    converter   = (lpeg.V("definition") + anything)^1,
98}
99
100-- local texkpse
101
102local function find_file(...)
103 -- texkpse = texkpse or kpse.new("luatex","tex")
104 -- return texkpse:find_file(...) or ""
105    return kpse.find_file(...) or ""
106end
107
108commands = commands or { }
109
110function commands.preprocessed(str)
111    return lpeg.match(parser,str)
112end
113
114function commands.inputpreprocessed(name)
115    local name = find_file(name) or ""
116    if name ~= "" then
117     -- we could use io.loaddata as it's loaded in luatex-plain
118        local f = io.open(name,'rb')
119        if f then
120            texio.write("("..name)
121            local d = commands.preprocessed(f:read("*a"))
122            if d and d ~= "" then
123                texio.write("processed: " .. name)
124                for s in gmatch(d,"[^\n\r]+") do
125                    tex.print(s) -- we do a dumb feedback
126                end
127            end
128            f:close()
129            texio.write(")")
130        else
131            tex.error("preprocessor error, invalid file: " .. name)
132        end
133    else
134        tex.error("preprocessor error, unknown file: " .. name)
135    end
136end
137
138function commands.preprocessfile(oldfile,newfile) -- no checking
139    if oldfile and oldfile ~= newfile then
140        local f = io.open(oldfile,'rb')
141        if f then
142            local g = io.open(newfile,'wb')
143            if g then
144                g:write(lpeg.match(parser,f:read("*a") or ""))
145                g:close()
146            end
147            f:close()
148        end
149    end
150end
151
152--~ print(preprocessed([[\def\test#oeps{test:#oeps}]]))
153--~ print(preprocessed([[\def\test#oeps{test:#{oeps}}]]))
154--~ print(preprocessed([[\def\test#{oeps:1}{test:#{oeps:1}}]]))
155--~ print(preprocessed([[\def\test#{oeps}{test:#oeps}]]))
156--~ preprocessed([[\def\test#{oeps}{test:#oeps \halign{##\cr #oeps\cr}]])
157--~ print(preprocessed([[\def\test#{oeps}{test:#oeps \halign{##\cr #oeps\cr}}]]))
158