nomsu/lib/metaprogramming.nom
2018-01-26 15:02:32 -08:00

193 lines
8.1 KiB
Plaintext

#..
This File contains actions for making actions and compile-time actions and some helper
functions to make that easier.
# Compile-time action to make compile-time actions:
immediately
lua> ".."
nomsu:define_compile_action("compile %actions to %lua", \(!! code location !!), function(\%actions, \%lua)
local signature = {};
for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end
local stubs = nomsu:get_stubs_from_signature(signature);
local stub_args = nomsu:get_args_from_signature(signature);
local arg_set = {};
for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end
if \%lua.type == "Text" then
error("Invalid type for 'compile % to %', expected a dict with expr/statements, but got text.", 0);
end
local body_lua = nomsu:tree_to_lua(\%lua);
local body_code = body_lua.statements or ("return "..body_lua.expr..";");
local undeclared_locals = {};
for i, body_local in ipairs(body_lua.locals or {}) do
if not arg_set[body_local] then
table.insert(undeclared_locals, body_local);
end
end
if #undeclared_locals > 0 then
body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code;
end
local lua_fn_args = table.concat(stub_args[1], ", ");
local def_tree = nomsu.compilestack[#nomsu.compilestack];
local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop);
return {statements=([[
nomsu:define_compile_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
]]..body_code.."\\n"..[[
end);
]])};
end);
# Compile-time action to make actions
immediately
compile [action %actions %body] to
lua> ".."
local signature = {};
for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end
local stubs = nomsu:get_stubs_from_signature(signature);
local stub_args = nomsu:get_args_from_signature(signature);
local arg_set = {};
for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end
local body_lua = nomsu:tree_to_lua(\%body);
local body_code = body_lua.statements or ("return "..body_lua.expr..";");
local undeclared_locals = {};
for i, body_local in ipairs(body_lua.locals or {}) do
if not arg_set[body_local] then
table.insert(undeclared_locals, body_local);
end
end
if #undeclared_locals > 0 then
body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code;
end
local lua_fn_args = table.concat(stub_args[1], ", ");
local def_tree = nomsu.compilestack[#nomsu.compilestack];
local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop);
return {statements=[[
nomsu:define_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
]]..body_code.."\\n"..[[
end);
]]};
# Macro to make nomsu macros:
immediately
compile [parse %shorthand as %longhand] to
lua> ".."
local signature = {};
for i, action in ipairs(\%shorthand.value) do signature[i] = action:get_src(); end
local stubs = nomsu:get_stubs_from_signature(signature);
local stub_args = nomsu:get_args_from_signature(signature);
local lua_fn_args = table.concat(stub_args[1], ", ");
local template;
if \%longhand.type == "Block" then
local lines = {};
for i, line in ipairs(\%longhand.value) do lines[i] = nomsu:dedent(line:get_src()); end
template = repr(table.concat(lines, "\\n"));
else
template = repr(nomsu:dedent(\%longhand:get_src()));
end
local replacements = {};
for i, a in ipairs(stub_args[1]) do replacements[i] = a.."="..a; end
replacements = "{"..table.concat(replacements, ", ").."}";
local def_tree = nomsu.compilestack[#nomsu.compilestack];
local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop);
return {statements=[[
nomsu:define_compile_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
local template = nomsu:parse(]]..template..[[, ]]..repr(def_tree.filename)..[[);
local replacement = nomsu:tree_with_replaced_vars(template, ]]..replacements..[[);
return nomsu:tree_to_lua(replacement);
end);
]]};
action [remove action %stub]
lua> ".."
local fn = ACTIONS[\%stub];
local metadata = ACTION_METADATA[fn];
for i=#metadata.aliases,1,-1 do
metadata.arg_orders[metadata.aliases[i]] = nil;
table.remove(metadata.aliases, i);
end
ACTIONS[\%stub] = nil;
immediately
action [%tree as lua]
=lua "nomsu:tree_to_lua(\%tree)"
action [%tree as lua expr]
lua> ".."
local lua = nomsu:tree_to_lua(\%tree);
if lua.locals or not lua.expr then
error("Invalid thing to convert to lua expr: "..\%tree:get_src());
end
return lua.expr;
action [%tree as lua statements]
lua> ".."
local lua = nomsu:tree_to_lua(\%tree);
local code = lua.statements or (lua.expr..";");
if lua.locals then
code = "local "..table.concat(lua.locals, ", ")..";\\n"..code;
end
return code;
action [%tree as value]
=lua "nomsu:tree_to_value(\%tree)"
immediately
compile [%tree's source code, %tree' source code] to {expr:"\(%tree as lua expr):get_src()"}
compile [repr %obj] to {expr:"repr(\(%obj as lua expr))"}
compile [type of %obj] to {expr:"type(\(%obj as lua expr))"}
compile [nomsu] to {expr:"nomsu"}
compile [nomsu's %key] to {expr:"nomsu[\(%key as lua expr)]"}
compile [nomsu %method %args] to {expr:"nomsu[\(%method as lua expr)](nomsu, unpack(\(%args as lua expr)))"}
action [action %names metadata]
=lua "ACTION_METADATA[ACTIONS[\%names]]"
# Get the source code for a function
action [help %action]
lua> ".."
local metadata = \(action %action metadata);
if not metadata then
print("Action not found: "..repr(\%action));
else
print(metadata.src or "<unknown source code>");
end
# Compiler tools
parse [run %code] as: nomsu "run" [%code]
parse [enable debugging] as: lua> "nomsu.debug = true"
parse [disable debugging] as: lua> "nomsu.debug = false"
immediately
compile [say %message] to
lua> ".."
if \%message.type == "Text" then
return {statements="print("..\(%message as lua expr)..");"};
else
return {statements="print(stringify("..\(%message as lua expr).."));"};
end
# Return
immediately
#.. Return statement is wrapped in a do..end block because Lua is unhappy if you
put code after a return statement, unless you wrap it in a block.
compile [return] to {statements:"do return; end"}
compile [return %return_value] to {statements:"do return \(%return_value as lua expr); end"}
# Error functions
immediately
compile [barf!] to {statements:"error(nil, 0);"}
compile [barf %msg] to {statements:"error(\(%msg as lua expr), 0);"}
compile [assume %condition] to {..}
statements:"if not \(%condition as lua expr) then error('Assumption failed: '..\%condition:get_src(), 0); end"
compile [assume %condition or barf %msg] to {..}
statements:"if not \(%condition as lua expr) then error(\(%msg as lua expr), 0); end"
# Literals
immediately
compile [yes] to {expr:"true"}
compile [no] to {expr:"false"}
compile [nothing, nil, null] to {expr:"nil"}