l-macro.lua /size: 9 Kb    last modification: 2020-07-01 14:35
1if not modules then modules = { } end modules ['l-macros'] = {
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-- This is actually rather old code that I made as a demo for Luigi but that
10-- now comes in handy when we switch to Lua 5.3. The reason for using it (in
11-- in transition) is that we cannot mix 5.3 bit operators in files that get
12-- loaded in 5.2 (parsing happens before conditional testing).
13
14local S, P, R, V, C, Cs, Cc, Ct, Carg = lpeg.S, lpeg.P, lpeg.R, lpeg.V, lpeg.C, lpeg.Cs, lpeg.Cc, lpeg.Ct, lpeg.Carg
15local lpegmatch = lpeg.match
16local concat = table.concat
17local format, sub, match = string.format, string.sub, string.match
18local next, load, type = next, load, type
19
20local newline       = S("\n\r")^1
21local continue      = P("\\") * newline
22local whitespace    = S(" \t\n\r")
23local spaces        = S(" \t") + continue
24local nametoken     = R("az","AZ","__","09")
25local name          = nametoken^1
26local body          = ((continue/"" + 1) - newline)^1
27local lparent       = P("(")
28local rparent       = P(")")
29local noparent      = 1 - (lparent  + rparent)
30local nested        = P { lparent  * (noparent  + V(1))^0 * rparent }
31local escaped       = P("\\") * P(1)
32local squote        = P("'")
33local dquote        = P('"')
34local quoted        = dquote * (escaped + (1-dquote))^0 * dquote
35                    + squote * (escaped + (1-squote))^0 * squote
36
37local arguments     = lparent * Ct((Cs((nested+(quoted + 1 - S("),")))^1) + S(", "))^0) * rparent
38
39local macros        = lua.macros or { }
40lua.macros          = macros
41
42local patterns      = { }
43local definitions   = { }
44local resolve
45local subparser
46
47local report_lua = function(...)
48    if logs and logs.reporter then
49        report_lua = logs.reporter("system","lua")
50        report_lua(...)
51    else
52        print(format(...))
53    end
54end
55
56-- todo: zero case
57
58local safeguard = P("local") * whitespace^1 * name * (whitespace + P("="))
59
60resolve = safeguard + C(C(name) * (arguments^-1)) / function(raw,s,a)
61    local d = definitions[s]
62    if d then
63        if a then
64            local n = #a
65            local p = patterns[s][n]
66            if p then
67                local d = d[n]
68                for i=1,n do
69                    a[i] = lpegmatch(subparser,a[i]) or a[i]
70                end
71                return lpegmatch(p,d,1,a) or d
72            else
73                return raw
74            end
75        else
76            return d[0] or raw
77        end
78    elseif a then
79        for i=1,#a do
80            a[i] = lpegmatch(subparser,a[i]) or a[i]
81        end
82        return s .. "(" .. concat(a,",") .. ")"
83    else
84        return raw
85    end
86end
87
88subparser = Cs((resolve + P(1))^1)
89
90local enddefine   = P("#enddefine") / ""
91
92local beginregister = (C(name) * (arguments + Cc(false)) * C((1-enddefine)^1) * enddefine) / function(k,a,v)
93    local n = 0
94    if a then
95        n = #a
96        local pattern = P(false)
97        for i=1,n do
98            pattern = pattern + (P(a[i]) * Carg(1)) / function(t) return t[i] end
99        end
100        pattern = Cs((pattern + P(1))^1)
101        local p = patterns[k]
102        if not p then
103            p = { [0] = false, false, false, false, false, false, false, false, false }
104            patterns[k] = p
105        end
106        p[n] = pattern
107    end
108    local d = definitions[k]
109    if not d then
110        d = { a = a, [0] = false, false, false, false, false, false, false, false, false }
111        definitions[k] = d
112    end
113    d[n] = lpegmatch(subparser,v) or v
114    return ""
115end
116
117local register = (Cs(name) * (arguments + Cc(false)) * spaces^0 * Cs(body)) / function(k,a,v)
118    local n = 0
119    if a then
120        n = #a
121        local pattern = P(false)
122        for i=1,n do
123            pattern = pattern + (P(a[i]) * Carg(1)) / function(t) return t[i] end
124        end
125        pattern = Cs((pattern + P(1))^1)
126        local p = patterns[k]
127        if not p then
128            p = { [0] = false, false, false, false, false, false, false, false, false }
129            patterns[k] = p
130        end
131        p[n] = pattern
132    end
133    local d = definitions[k]
134    if not d then
135        d = { a = a, [0] = false, false, false, false, false, false, false, false, false }
136        definitions[k] = d
137    end
138    d[n] = lpegmatch(subparser,v) or v
139    return ""
140end
141
142local unregister = (C(name) * spaces^0 * (arguments + Cc(false))) / function(k,a)
143    local n = 0
144    if a then
145        n = #a
146        local p = patterns[k]
147        if p then
148            p[n] = false
149        end
150    end
151    local d = definitions[k]
152    if d then
153        d[n] = false
154    end
155    return ""
156end
157
158local begindefine = (P("begindefine") * spaces^0 / "") * beginregister
159local define      = (P("define"     ) * spaces^0 / "") * register
160local undefine    = (P("undefine"   ) * spaces^0 / "") * unregister
161
162local parser = Cs( ( ( (P("#")/"") * (define + begindefine + undefine) * (newline^0/"") ) + resolve + P(1) )^0 )
163
164function macros.reset()
165    definitions = { }
166    patterns    = { }
167end
168
169function macros.showdefinitions()
170    -- no helpers loaded but not called early
171    for name, list in table.sortedhash(definitions) do
172        local arguments = list.a
173        if arguments then
174            arguments = "(" .. concat(arguments,",") .. ")"
175        else
176            arguments = ""
177        end
178        print("macro: " .. name .. arguments)
179        for i=0,#list do
180            local l = list[i]
181            if l then
182                print("  " .. l)
183            end
184        end
185    end
186end
187
188function macros.resolvestring(str)
189    return lpegmatch(parser,str) or str
190end
191
192function macros.resolving()
193    return next(patterns)
194end
195
196local function reload(path,name,data)
197    local only = match(name,".-([^/]+)%.lua")
198    if only and only ~= "" then
199        local name = path .. "/" .. only
200        local f = io.open(name,"wb")
201        f:write(data)
202        f:close()
203        local f = loadfile(name)
204        os.remove(name)
205        return f
206    end
207end
208
209-- local function reload(path,name,data)
210--     if path and path ~= "" then
211--         local only = file.nameonly(name) .. "-macro.lua"
212--         local name = file.join(path,only)
213--         io.savedata(name,data)
214--         local l = loadfile(name)
215--         os.remove(name)
216--         return l
217--     end
218--     return load(data,name)
219-- end
220--
221-- assumes no helpers
222
223local function reload(path,name,data)
224    if path and path ~= "" then
225        local only = string.match(name,".-([^/]+)%.lua")
226        if only and only ~= "" then
227            local name = path .. "/" .. only .. "-macro.lua"
228            local f = io.open(name,"wb")
229            if f then
230                f:write(data)
231                f:close()
232                local l = loadfile(name)
233                os.remove(name)
234                return l
235            end
236        end
237    end
238    return load(data,name)
239end
240
241local function loaded(name,trace,detail)
242 -- local c = io.loaddata(fullname) -- not yet available
243    local f = io.open(name,"rb")
244    if not f then
245        return false, format("file '%s' not found",name)
246    end
247    local c = f:read("*a")
248    if not c then
249        return false, format("file '%s' is invalid",name)
250    end
251    f:close()
252    local n = lpegmatch(parser,c)
253    if trace then
254        if #n ~= #c then
255            report_lua("macros expanded in '%s' (%i => %i bytes)",name,#c,#n)
256            if detail then
257                report_lua()
258                report_lua(n)
259                report_lua()
260            end
261        elseif detail then
262            report_lua("no macros expanded in '%s'",name)
263        end
264    end
265 -- if #name > 30 then
266 --     name = sub(name,-30)
267 -- end
268 -- n = "--[[" .. name .. "]]\n" .. n
269    return reload(lfs and lfs.currentdir(),name,n)
270end
271
272macros.loaded = loaded
273
274function required(name,trace)
275    local filename = file.addsuffix(name,"lua")
276    local fullname = resolvers and resolvers.findfile(filename) or filename
277    if not fullname or fullname == "" then
278        return false
279    end
280    local codeblob = package.loaded[fullname]
281    if codeblob then
282        return codeblob
283    end
284    local code, message = loaded(fullname,macros,trace,trace)
285    if type(code) == "function" then
286        code = code()
287    else
288        report_lua("error when loading '%s'",fullname)
289        return false, message
290    end
291    if code == nil then
292        code = false
293    end
294    package.loaded[fullname] = code
295    return code
296end
297
298macros.required = required
299
300-- local str = [[
301-- #define check(p,q) (p ~= 0) and (p > q)
302--
303-- #define oeps a > 10
304--
305-- #define whatever oeps
306--
307-- if whatever and check(1,2) then print("!") end
308-- if whatever and check(1,3) then print("!") end
309-- if whatever and check(1,4) then print("!") end
310-- if whatever and check(1,5) then print("!") end
311-- if whatever and check(1,6) then print("!") end
312-- if whatever and check(1,7) then print("!") end
313-- ]]
314--
315-- print(macros.resolvestring(str))
316--
317-- macros.resolvestring(io.loaddata("mymacros.lua"))
318-- loadstring(macros.resolvestring(io.loaddata("mytestcode.lua")))
319
320-- local luamacros = [[
321-- #begindefine setnodecodes
322-- local nodecodes  = nodes.codes
323-- local hlist_code = nodecodes.hlist
324-- local vlist_code = nodecodes.vlist
325-- local glyph_code = nodecodes.glyph
326-- #enddefine
327--
328-- #define hlist(id) id == hlist_code
329-- #define vlist(id) id == vlist_code
330-- #define glyph(id) id == glyph_code
331-- ]]
332--
333-- local luacode = [[
334-- setnodecodes
335--
336-- if hlist(id) or vlist(id) then
337--     print("we have a list")
338-- elseif glyph(id) then
339--     print("we have a glyph")
340-- else
341--     print("i'm stymied")
342-- end
343--
344-- local z = band(0x23,x)
345-- local z = btest(0x23,x)
346-- local z = rshift(0x23,x)
347-- local z = lshift(0x23,x)
348-- ]]
349--
350-- require("l-macros-test-001")
351--
352-- macros.resolvestring(luamacros)
353--
354-- local newcode = macros.resolvestring(luacode)
355--
356-- print(newcode)
357--
358-- macros.reset()
359
360-- local d = io.loaddata("t:/sources/font-otr.lua")
361-- local n = macros.resolvestring(d)
362-- io.savedata("r:/tmp/o.lua",n)
363