Macro expansion

This commit is contained in:
Henri DF 2016-02-14 20:36:54 -08:00
parent b3f7b1d765
commit 6e2d8679c4
3 changed files with 130 additions and 16 deletions

View File

@ -15,12 +15,12 @@ function error_exit_bad
function good
{
lua test.lua "$1" 2> /dev/null || error_exit_good "$1"
lua test.lua "a: x.y=1; b: a and z.x exists; c: b; $1" 2> /dev/null || error_exit_good "$1"
}
function bad
{
lua test.lua "$1" 2> /dev/null && error_exit_bad "$1"
lua test.lua "a: x.y=1; b: a and z.x exists; c: b; $1" 2> /dev/null && error_exit_bad "$1"
}
# Filters
@ -41,7 +41,7 @@ good "not not a"
good "(not not a)"
good "not a.b=1"
good "not a.a exists"
good "notz"
good "notz: a and b; notz"
good "a.b = bla"
good "a.b = 'bla'"
good "a.b = not"

View File

@ -168,7 +168,7 @@ end
local G = {
V"Start", -- Entry rule
Start = (V"MacroDef" / macro + V"Filter" / filter) * -1 + report_error();
Start = V"Skip" * (V"MacroDef" / macro + V"Filter" / filter) * -1 + report_error();
-- Grammar
Filter = V"OrExpression";
@ -313,8 +313,94 @@ function expand_in(node)
end
end
--[[
Given a map of macro definitions, traverse AST and replace macro references
with their definitions.
The AST is changed in-place.
The return value is a boolean which is true if any macro was
substitued. This allows a caller to re-traverse until no more macros are
found, a simple strategy for recursive resoltuions (e.g. when a macro
definition uses another macro).
--]]
function expand_macros(node, defs, changed)
if node.type == "Filter" then
if (node.value.type == "Macro") then
if (defs[node.value.value] == nil) then
tostring = require 'ml'.tstring
error("Undefined macro '".. node.value.value .. "' used in filter.")
end
node.value = defs[node.value.value]
changed = true
end
return expand_macros(node.value, defs, changed)
elseif node.type == "BinaryBoolOp" then
if (node.left.type == "Macro") then
if (defs[node.left.value] == nil) then
error("Undefined macro '".. node.left.value .. "' used in filter.")
end
node.left = defs[node.left.value]
changed = true
end
if (node.right.type == "Macro") then
if (defs[node.right.value] == nil) then
error("Undefined macro ".. node.right.value .. "used in filter.")
end
node.right = defs[node.right.value]
changed = true
end
local changed_left = expand_macros(node.left, defs, false)
local changed_right = expand_macros(node.right, defs, false)
return changed or changed_left or changed_right
elseif node.type == "UnaryBoolOp" then
if (node.argument.type == "Macro") then
if (defs[node.argument.value] == nil) then
error("Undefined macro ".. node.argument.value .. "used in filter.")
end
node.argument = defs[node.argument.value]
changed = true
end
return expand_macros(node.argument, defs, changed)
end
return changed
end
function get_macros(node, set)
if (node.type == "Macro") then
set[node.value] = true
return set
end
if node.type == "Filter" then
return get_macros(node.value, set)
end
if node.type == "BinaryBoolOp" then
local left = get_macros(node.left, {})
local right = get_macros(node.right, {})
for m, _ in pairs(left) do set[m] = true end
for m, _ in pairs(right) do set[m] = true end
return set
end
if node.type == "UnaryBoolOp" then
return get_macros(node.argument, set)
end
return set
end
function print_ast(node, level)
local t = node.type
level = level or 0
local prefix = string.rep(" ", level*2)
level = level + 1
@ -345,13 +431,13 @@ function print_ast(node, level)
error ("Unexpected type: "..t)
end
end
compiler.parser.print_ast = print_ast
--[[
Parses a single line (which should be either a macro definition or a filter) and returns the AST.
--]]
function compiler.parser.parseline (subject)
function compiler.parser.parse_line (subject)
local errorinfo = { subject = subject }
lpeg.setmaxstack(1000)
local ast, error_msg = lpeg.match(G, subject, nil, errorinfo)
@ -369,22 +455,41 @@ end
to the line-oriented compiler.
--]]
function compiler.init()
return {}
return {macros={}}
end
--[[
Compiles a digwatch filter or macro
--]]
function compiler.compile_line(line, state)
ast, error_message = compiler.parser.parseline(line)
local ast, error_msg = compiler.parser.parse_line(line)
if (error_msg) then
return {}, state, error_msg
return nil, error_msg
end
expand_in(ast)
-- extract_macros(ast, state)
-- expand_macros(ast, state)
return ast, state, error_msg
local macros = get_macros(ast.value, {})
for m, _ in pairs(macros) do
if state.macros[m] == nil then
error ("Undefined macro '"..m.."' used in '"..line.."'")
end
end
if (ast.type == "MacroDef") then
state.macros[ast.name] = ast.value
return ast, error_msg
elseif (ast.type == "Filter") then
expand_in(ast)
repeat
expanded = expand_macros(ast, state.macros, false)
until expanded == false
else
error("Unexpected top-level AST type: "..ast.type)
end
return ast, error_msg
end

View File

@ -7,9 +7,18 @@ end
local state = compiler.init()
local ast, state, error_msg = compiler.compile_line(arg[1], state)
if not ast then
os.exit(1)
local function doit(line)
local ast, error_msg = compiler.compile_line(line, state)
if not ast then
print("error", error_msg)
os.exit(1)
end
compiler.parser.print_ast(ast)
end
for str in string.gmatch(arg[1], "([^;]+)") do
doit(str)
end
os.exit(0)