mirror of
https://github.com/Reuh/candran.git
synced 2025-10-27 09:59:29 +00:00
Add macro support in preprocessor
This commit is contained in:
parent
66f1a5a3c2
commit
ebd36d7103
6 changed files with 5304 additions and 4763 deletions
41
candran.can
41
candran.can
|
|
@ -47,10 +47,15 @@ end
|
|||
-- @tparam input string input code
|
||||
-- @tparam options table arguments for the preprocessor. They will be inserted into the preprocessor environement.
|
||||
-- @treturn[1] output string output code
|
||||
-- @treturn[1] macros registered macros
|
||||
-- @treturn[2] nil nil if error
|
||||
-- @treturn[2] error string error message
|
||||
function candran.preprocess(input, options={})
|
||||
options = util.merge(candran.default, options)
|
||||
local macros = {
|
||||
functions = {},
|
||||
variables = {}
|
||||
}
|
||||
|
||||
-- generate preprocessor code
|
||||
local preprocessor = ""
|
||||
|
|
@ -104,7 +109,8 @@ function candran.preprocess(input, options={})
|
|||
if not f then error("can't open the module file to import") end
|
||||
|
||||
margs = util.merge(options, { chunkname = filepath, loadLocal = true, loadPackage = true }, margs)
|
||||
local modcontent = assert(candran.preprocess(f:read("*a"), margs))
|
||||
local modcontent, modmacros = assert(candran.preprocess(f:read("*a"), margs))
|
||||
macros = util.recmerge(macros, modmacros)
|
||||
f:close()
|
||||
|
||||
-- get module name (ex: module name of path.to.module is module)
|
||||
|
|
@ -140,6 +146,30 @@ function candran.preprocess(input, options={})
|
|||
env.write(env[name])
|
||||
end
|
||||
end
|
||||
env.define = function(identifier, replacement)
|
||||
-- parse identifier
|
||||
local iast, ierr = parser.parsemacroidentifier(identifier, options.chunkname)
|
||||
if not iast then
|
||||
return error("in macro identifier: %s":format(ierr))
|
||||
end
|
||||
-- parse replacement value
|
||||
local rast, rerr = parser.parse(replacement, options.chunkname)
|
||||
if not rast then
|
||||
return error("in macro replacement: %s":format(rerr))
|
||||
end
|
||||
-- when giving a single value as a replacement, bypass the implicit push
|
||||
if #rast == 1 and rast[1].tag == "Push" and rast[1].implicit then
|
||||
rast = rast[1][1]
|
||||
end
|
||||
-- add macros
|
||||
if iast.tag == "MacroFunction" then
|
||||
macros.functions[iast[1][1]] = { args = iast[2], replacement = rast }
|
||||
elseif iast.tag == "Id" then
|
||||
macros.variables[iast[1]] = rast
|
||||
else
|
||||
error("invalid macro type %s":format(iast.tag))
|
||||
end
|
||||
end
|
||||
|
||||
-- compile & load preprocessor
|
||||
local preprocess, err = candran.compile(preprocessor, options)
|
||||
|
|
@ -158,16 +188,17 @@ function candran.preprocess(input, options={})
|
|||
return nil, "in preprocessor: "..output
|
||||
end
|
||||
|
||||
return output
|
||||
return output, macros
|
||||
end
|
||||
|
||||
--- Run the compiler
|
||||
-- @tparam input string input code
|
||||
-- @tparam options table options for the compiler
|
||||
-- @tparam macros table defined macros, as returned by the preprocessor
|
||||
-- @treturn[1] output string output code
|
||||
-- @treturn[2] nil nil if error
|
||||
-- @treturn[2] error string error message
|
||||
function candran.compile(input, options={})
|
||||
function candran.compile(input, options={}, macros)
|
||||
options = util.merge(candran.default, options)
|
||||
|
||||
local ast, errmsg = parser.parse(input, options.chunkname)
|
||||
|
|
@ -176,7 +207,7 @@ function candran.compile(input, options={})
|
|||
return nil, errmsg
|
||||
end
|
||||
|
||||
return require("compiler."..options.target)(input, ast, options)
|
||||
return require("compiler."..options.target)(input, ast, options, macros)
|
||||
end
|
||||
|
||||
--- Preprocess & compile code
|
||||
|
|
@ -188,7 +219,7 @@ end
|
|||
function candran.make(code, options)
|
||||
local r, err = candran.preprocess(code, options)
|
||||
if r then
|
||||
r, err = candran.compile(r, options)
|
||||
r, err = candran.compile(r, options, err)
|
||||
if r then
|
||||
return r
|
||||
end
|
||||
|
|
|
|||
9876
candran.lua
9876
candran.lua
File diff suppressed because it is too large
Load diff
|
|
@ -306,6 +306,11 @@ local function fixShortFunc (t)
|
|||
return t
|
||||
end
|
||||
|
||||
local function markImplicit (t)
|
||||
t.implicit = true
|
||||
return t
|
||||
end
|
||||
|
||||
local function statToExpr (t) -- tag a StatExpr
|
||||
t.tag = t.tag .. "Expr"
|
||||
return t
|
||||
|
|
@ -577,7 +582,7 @@ local G = { V"Lua",
|
|||
RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1);
|
||||
|
||||
PushStat = tagC("Push", kw("push") * commaSep(V"Expr", "RetList")^-1);
|
||||
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList"));
|
||||
ImplicitPushStat = tagC("Push", commaSep(V"Expr", "RetList")) / markImplicit;
|
||||
|
||||
NameList = tagC("NameList", commaSep(V"Id"));
|
||||
DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId")),
|
||||
|
|
@ -757,6 +762,20 @@ local G = { V"Lua",
|
|||
BinOp = V"OrOp" + V"AndOp" + V"BOrOp" + V"BXorOp" + V"BAndOp" + V"ShiftOp" + V"ConcatOp" + V"AddOp" + V"MulOp" + V"PowOp";
|
||||
}
|
||||
|
||||
-- used to parse macro indentifier in define() preprocessor function
|
||||
local macroidentifier = {
|
||||
expect(V"MacroIdentifier", "InvalidStat") * expect(P(-1), "Extra"),
|
||||
|
||||
MacroIdentifier = tagC("MacroFunction", V"Id" * sym("(") * V"MacroFunctionArgs" * expect(sym(")"), "CParenPList"))
|
||||
+ V"Id";
|
||||
|
||||
MacroFunctionArgs = V"NameList" * (sym(",") * expect(tagC("Dots", sym("...")), "ParList"))^-1 / addDots
|
||||
+ Ct(tagC("Dots", sym("...")))
|
||||
+ Ct(Cc());
|
||||
|
||||
}
|
||||
for k,v in pairs(G) do if macroidentifier[k] == nil then macroidentifier[k] = v end end -- copy other rules from main syntax
|
||||
|
||||
local parser = {}
|
||||
|
||||
local validator = require("candran.can-parser.validator")
|
||||
|
|
@ -774,4 +793,15 @@ function parser.parse (subject, filename)
|
|||
return validate(ast, errorinfo)
|
||||
end
|
||||
|
||||
function parser.parsemacroidentifier (subject, filename)
|
||||
local errorinfo = { subject = subject, filename = filename }
|
||||
lpeg.setmaxstack(1000)
|
||||
local ast, label, errpos = lpeg.match(macroidentifier, subject, nil, errorinfo)
|
||||
if not ast then
|
||||
local errmsg = labels[label][2]
|
||||
return ast, syntaxerror(errorinfo, errpos, errmsg)
|
||||
end
|
||||
return ast
|
||||
end
|
||||
|
||||
return parser
|
||||
|
|
|
|||
|
|
@ -27,6 +27,20 @@ function util.load(str, name, env)
|
|||
end
|
||||
end
|
||||
|
||||
function util.recmerge(...)
|
||||
local r = {}
|
||||
for _, t in ipairs({...}) do
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == "table" then
|
||||
r[k] = util.merge(v, r[k])
|
||||
else
|
||||
r[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
function util.merge(...)
|
||||
local r = {}
|
||||
for _, t in ipairs({...}) do
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
local util = require("candran.util")
|
||||
|
||||
local targetName = "Lua 5.4"
|
||||
|
||||
return function(code, ast, options)
|
||||
return function(code, ast, options, macros={functions={}, variables={}})
|
||||
--- Line mapping
|
||||
local lastInputPos = 1 -- last token position in the input code
|
||||
local prevLinePos = 1 -- last token position in the previous line of code in the input code
|
||||
|
|
@ -47,7 +49,8 @@ return function(code, ast, options)
|
|||
local states = {
|
||||
push = {}, -- push stack variable names
|
||||
destructuring = {}, -- list of variable that need to be assigned from a destructure {id = "parent variable", "field1", "field2"...}
|
||||
scope = {} -- list of variables defined in the current scope
|
||||
scope = {}, -- list of variables defined in the current scope
|
||||
macroargs = {} -- currently defined arguemnts from a macro function
|
||||
}
|
||||
-- Push a new value on top of the stack "name". Returns an empty string for chaining.
|
||||
local function push(name, state)
|
||||
|
|
@ -83,6 +86,9 @@ return function(code, ast, options)
|
|||
return var
|
||||
end
|
||||
|
||||
-- indicate if currently processing a macro, so it cannot be applied recursively
|
||||
local nomacro = { variables = {}, functions = {} }
|
||||
|
||||
--- Module management
|
||||
local required = {} -- { ["full require expression"] = true, ... }
|
||||
local requireStr = ""
|
||||
|
|
@ -536,7 +542,15 @@ return function(code, ast, options)
|
|||
end,
|
||||
-- Dots
|
||||
Dots = ()
|
||||
return "..."
|
||||
local macroargs = peek("macroargs")
|
||||
if macroargs and not nomacro.variables["..."] and macroargs["..."] then
|
||||
nomacro.variables["..."] = true
|
||||
local r = lua(macroargs["..."], "_lhs")
|
||||
nomacro.variables["..."] = nil
|
||||
return r
|
||||
else
|
||||
return "..."
|
||||
end
|
||||
end,
|
||||
-- Boolean{ <boolean> }
|
||||
Boolean = (t)
|
||||
|
|
@ -723,6 +737,28 @@ return function(code, ast, options)
|
|||
Call = (t)
|
||||
if t[1].tag == "String" or t[1].tag == "Table" then
|
||||
return "("..lua(t[1])..")("..lua(t, "_lhs", 2)..")"
|
||||
elseif t[1].tag == "Id" and not nomacro.functions[t[1][1]] and macros.functions[t[1][1]] then
|
||||
local macro = macros.functions[t[1][1]]
|
||||
local replacement = macro.replacement
|
||||
local macroargs = util.merge(peek("macroargs"))
|
||||
for i, arg in ipairs(macro.args) do
|
||||
if arg.tag == "Dots" then
|
||||
macroargs["..."] = [for j=i+1, #t do t[j] end]
|
||||
elseif arg.tag == "Id" then
|
||||
if t[i+1] == nil then
|
||||
error("bad argument #%s to macro %s (value expected)":format(i, t[1][1]))
|
||||
end
|
||||
macroargs[arg[1]] = t[i+1]
|
||||
else
|
||||
error("unexpected argument type %s in macro %s":format(arg.tag, t[1][1]))
|
||||
end
|
||||
end
|
||||
push("macroargs", macroargs)
|
||||
nomacro.functions[t[1][1]] = true
|
||||
local r = lua(replacement)
|
||||
nomacro.functions[t[1][1]] = nil
|
||||
pop("macroargs")
|
||||
return r
|
||||
elseif t[1].tag == "MethodStub" then -- method call
|
||||
if t[1][1].tag == "String" or t[1][1].tag == "Table" then
|
||||
return "("..lua(t[1][1]).."):"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
|
||||
|
|
@ -757,6 +793,20 @@ return function(code, ast, options)
|
|||
end,
|
||||
-- Id{ <string> }
|
||||
Id = (t)
|
||||
local macroargs = peek("macroargs")
|
||||
if not nomacro.variables[t[1]] then
|
||||
if macroargs and macroargs[t[1]] then -- replace with macro argument
|
||||
nomacro.variables[t[1]] = true
|
||||
local r = lua(macroargs[t[1]])
|
||||
nomacro.variables[t[1]] = nil
|
||||
return r
|
||||
elseif macros.variables[t[1]] ~= nil then -- replace with macro variable
|
||||
nomacro.variables[t[1]] = true
|
||||
local r = lua(macros.variables[t[1]])
|
||||
nomacro.variables[t[1]] = nil
|
||||
return r
|
||||
end
|
||||
end
|
||||
return t[1]
|
||||
end,
|
||||
-- AttributeId{ <string> <string>? }
|
||||
|
|
|
|||
|
|
@ -151,6 +151,54 @@ return a
|
|||
#error("preprocessor should ignore long strings")
|
||||
]])
|
||||
|
||||
test("preprocessor macro remove function", [[
|
||||
#define("log(...)", "")
|
||||
log("test")
|
||||
return true
|
||||
]], true)
|
||||
|
||||
test("preprocessor macro replace function", [[
|
||||
#define("log(x)", "a = x")
|
||||
log("test")
|
||||
return a
|
||||
]], "test")
|
||||
|
||||
test("preprocessor macro identifier replace function", [[
|
||||
#define("test(x)", "x = 42")
|
||||
test(hello)
|
||||
return hello
|
||||
]], 42)
|
||||
|
||||
test("preprocessor macro replace function with vararg", [[
|
||||
#define("log(...)", "a, b, c = ...")
|
||||
log(1, 2, 3)
|
||||
assert(a == 1)
|
||||
assert(b == 2)
|
||||
assert(c == 3)
|
||||
return true
|
||||
]], true)
|
||||
|
||||
test("preprocessor macro replace function with vararg and arg", [[
|
||||
#define("log(x, ...)", "a, b, c = x, ...")
|
||||
log(1, 2, 3)
|
||||
assert(a == 1)
|
||||
assert(b == 2)
|
||||
assert(c == 3)
|
||||
return true
|
||||
]], true)
|
||||
|
||||
test("preprocessor macro replace variable", [[
|
||||
#define("a", "42")
|
||||
return a
|
||||
]], 42)
|
||||
|
||||
test("preprocessor macro prevent recursive macro", [[
|
||||
#define("f(x)", "x")
|
||||
local x = 42
|
||||
x = f(x)
|
||||
return x
|
||||
]], 42)
|
||||
|
||||
----------------------
|
||||
-- SYNTAX ADDITIONS --
|
||||
----------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue