1
0
Fork 0
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:
Étienne Fildadut 2021-06-07 17:04:26 +02:00
parent 66f1a5a3c2
commit ebd36d7103
6 changed files with 5304 additions and 4763 deletions

View file

@ -47,10 +47,15 @@ end
-- @tparam input string input code -- @tparam input string input code
-- @tparam options table arguments for the preprocessor. They will be inserted into the preprocessor environement. -- @tparam options table arguments for the preprocessor. They will be inserted into the preprocessor environement.
-- @treturn[1] output string output code -- @treturn[1] output string output code
-- @treturn[1] macros registered macros
-- @treturn[2] nil nil if error -- @treturn[2] nil nil if error
-- @treturn[2] error string error message -- @treturn[2] error string error message
function candran.preprocess(input, options={}) function candran.preprocess(input, options={})
options = util.merge(candran.default, options) options = util.merge(candran.default, options)
local macros = {
functions = {},
variables = {}
}
-- generate preprocessor code -- generate preprocessor code
local preprocessor = "" 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 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) 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() f:close()
-- get module name (ex: module name of path.to.module is module) -- 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]) env.write(env[name])
end end
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 -- compile & load preprocessor
local preprocess, err = candran.compile(preprocessor, options) local preprocess, err = candran.compile(preprocessor, options)
@ -158,16 +188,17 @@ function candran.preprocess(input, options={})
return nil, "in preprocessor: "..output return nil, "in preprocessor: "..output
end end
return output return output, macros
end end
--- Run the compiler --- Run the compiler
-- @tparam input string input code -- @tparam input string input code
-- @tparam options table options for the compiler -- @tparam options table options for the compiler
-- @tparam macros table defined macros, as returned by the preprocessor
-- @treturn[1] output string output code -- @treturn[1] output string output code
-- @treturn[2] nil nil if error -- @treturn[2] nil nil if error
-- @treturn[2] error string error message -- @treturn[2] error string error message
function candran.compile(input, options={}) function candran.compile(input, options={}, macros)
options = util.merge(candran.default, options) options = util.merge(candran.default, options)
local ast, errmsg = parser.parse(input, options.chunkname) local ast, errmsg = parser.parse(input, options.chunkname)
@ -176,7 +207,7 @@ function candran.compile(input, options={})
return nil, errmsg return nil, errmsg
end end
return require("compiler."..options.target)(input, ast, options) return require("compiler."..options.target)(input, ast, options, macros)
end end
--- Preprocess & compile code --- Preprocess & compile code
@ -188,7 +219,7 @@ end
function candran.make(code, options) function candran.make(code, options)
local r, err = candran.preprocess(code, options) local r, err = candran.preprocess(code, options)
if r then if r then
r, err = candran.compile(r, options) r, err = candran.compile(r, options, err)
if r then if r then
return r return r
end end

File diff suppressed because it is too large Load diff

View file

@ -306,6 +306,11 @@ local function fixShortFunc (t)
return t return t
end end
local function markImplicit (t)
t.implicit = true
return t
end
local function statToExpr (t) -- tag a StatExpr local function statToExpr (t) -- tag a StatExpr
t.tag = t.tag .. "Expr" t.tag = t.tag .. "Expr"
return t return t
@ -577,7 +582,7 @@ local G = { V"Lua",
RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1); RetStat = tagC("Return", kw("return") * commaSep(V"Expr", "RetList")^-1);
PushStat = tagC("Push", kw("push") * 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")); NameList = tagC("NameList", commaSep(V"Id"));
DestructuringNameList = tagC("NameList", commaSep(V"DestructuringId")), 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"; 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 parser = {}
local validator = require("candran.can-parser.validator") local validator = require("candran.can-parser.validator")
@ -774,4 +793,15 @@ function parser.parse (subject, filename)
return validate(ast, errorinfo) return validate(ast, errorinfo)
end 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 return parser

View file

@ -27,6 +27,20 @@ function util.load(str, name, env)
end end
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(...) function util.merge(...)
local r = {} local r = {}
for _, t in ipairs({...}) do for _, t in ipairs({...}) do

View file

@ -1,6 +1,8 @@
local util = require("candran.util")
local targetName = "Lua 5.4" local targetName = "Lua 5.4"
return function(code, ast, options) return function(code, ast, options, macros={functions={}, variables={}})
--- Line mapping --- Line mapping
local lastInputPos = 1 -- last token position in the input code 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 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 = { local states = {
push = {}, -- push stack variable names push = {}, -- push stack variable names
destructuring = {}, -- list of variable that need to be assigned from a destructure {id = "parent variable", "field1", "field2"...} 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. -- Push a new value on top of the stack "name". Returns an empty string for chaining.
local function push(name, state) local function push(name, state)
@ -83,6 +86,9 @@ return function(code, ast, options)
return var return var
end end
-- indicate if currently processing a macro, so it cannot be applied recursively
local nomacro = { variables = {}, functions = {} }
--- Module management --- Module management
local required = {} -- { ["full require expression"] = true, ... } local required = {} -- { ["full require expression"] = true, ... }
local requireStr = "" local requireStr = ""
@ -536,7 +542,15 @@ return function(code, ast, options)
end, end,
-- Dots -- Dots
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, end,
-- Boolean{ <boolean> } -- Boolean{ <boolean> }
Boolean = (t) Boolean = (t)
@ -723,6 +737,28 @@ return function(code, ast, options)
Call = (t) Call = (t)
if t[1].tag == "String" or t[1].tag == "Table" then if t[1].tag == "String" or t[1].tag == "Table" then
return "("..lua(t[1])..")("..lua(t, "_lhs", 2)..")" 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 elseif t[1].tag == "MethodStub" then -- method call
if t[1][1].tag == "String" or t[1][1].tag == "Table" then 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)..")" return "("..lua(t[1][1]).."):"..lua(t[1][2], "Id").."("..lua(t, "_lhs", 2)..")"
@ -757,6 +793,20 @@ return function(code, ast, options)
end, end,
-- Id{ <string> } -- Id{ <string> }
Id = (t) 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] return t[1]
end, end,
-- AttributeId{ <string> <string>? } -- AttributeId{ <string> <string>? }

View file

@ -151,6 +151,54 @@ return a
#error("preprocessor should ignore long strings") #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 -- -- SYNTAX ADDITIONS --
---------------------- ----------------------