From c1c32688a4afc43f6addb99b8b5fa878944a70e3 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 14 Jan 2019 15:42:48 -0800 Subject: Overhaul in progress, mostly working. Moved all the nomsu packages into lib/, including core/*. Changes to how nomsu environments and importing work. --- lib/core/metaprogramming.nom | 459 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 459 insertions(+) create mode 100644 lib/core/metaprogramming.nom (limited to 'lib/core/metaprogramming.nom') diff --git a/lib/core/metaprogramming.nom b/lib/core/metaprogramming.nom new file mode 100644 index 0000000..936f9bd --- /dev/null +++ b/lib/core/metaprogramming.nom @@ -0,0 +1,459 @@ +#!/usr/bin/env nomsu -V6.14 +# + This File contains actions for making actions and compile-time actions and some helper + functions to make that easier. + +lua> "NOMSU_CORE_VERSION = 14" +lua> "NOMSU_LIB_VERSION = 8" +lua> (" + do + local mangle_index = 0 + function mangler() + local my_mangle_index = mangle_index + mangle_index = mangle_index + 1 + return function(varname) + return (varname..(("\\3%X"):format(my_mangle_index))):as_lua_id() + end + end + end + COMPILE_RULES["define mangler"] = function(\(nomsu environment)) + return LuaCode("local mangle = mangler()") + end +") + +lua> (" + COMPILE_RULES["1 ->"] = function(\(nomsu environment), \$args, \$body) + if \$args and not \$body then \$args, \$body = {}, \$args end + local body_lua = SyntaxTree:is_instance(\$body) and \(nomsu environment):compile(\$body) or \$body + if SyntaxTree:is_instance(\$body) and \$body.type ~= "Block" then body_lua:prepend("\ + ..return ") end + local lua = LuaCode("(function(") + if SyntaxTree:is_instance(\$args) and (\$args.type == "Action" or \$args.type == "MethodCall") then + \$args = \$args:get_args() + elseif SyntaxTree:is_instance(\$args) and \$args.type == "Var" then \$args = {\$args} end + for i, arg in ipairs(\$args) do + local arg_lua = SyntaxTree:is_instance(arg) and \(nomsu environment):compile(arg):text() or arg + if arg_lua == "..." then + if i < #\$args then + compile_error_at(SyntaxTree:is_instance(arg) and arg or nil, + "Extra arguments must come last.", "Try removing any arguments after \ + ..(*extra arguments*)") + end + elseif not arg_lua:is_lua_id() then + compile_error_at(SyntaxTree:is_instance(arg) and arg or nil, + "This does not compile to a Lua identifier, so it can't be used as a function \ + ..argument.", + "This should probably be a Nomsu variable instead (like $x).") + end + lua:add(i > 1 and ", " or "", arg_lua) + body_lua:remove_free_vars({arg_lua}) + end + body_lua:declare_locals() + lua:add(")\\n ", body_lua, "\\nend)") + return lua + end + COMPILE_RULES["->"] = COMPILE_RULES["1 ->"] + COMPILE_RULES["for"] = COMPILE_RULES["1 ->"] +") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + (five) compiles to "5" + +test: + unless ((five) == 5): + fail "Compile to expression failed." + (loc x) compiles to "local x = 99;" + +test: + lua> "do" + loc x + unless ($x is 99): + fail "Compile to statements with locals failed." + lua> "end" + unless ($x is (nil)): + fail "Failed to properly localize a variable." + + (asdf) compiles to: + $tmp = "" + return (Lua $tmp) + +test: + asdf + unless ($tmp is (nil)): + fail "compile to is leaking variables" + +lua> (" + COMPILE_RULES["1 compiles to"] = function(\(nomsu environment), \$action, \$body) + local \$args = List{"\(nomsu environment)", unpack(\$action:get_args())} + if \$body.type == "Text" then + \$body = SyntaxTree{source=\$body.source, type="Action", "Lua", \$body} + end + return LuaCode("COMPILE_RULES[", \$action:get_stub():as_lua(), + "] = ", \(\($args -> $body) as lua)) + end +") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +($actions all compile to $body) compiles to: + lua> (" + if \$actions.type ~= "List" then + compile_error(\$actions, "This should be a list of actions.") + end + local lua = \(\($actions.1 compiles to $body) as lua) + local \$args = List{"\(nomsu environment)", unpack(\$actions[1]:get_args())} + local \$compiled_args = List{"\(nomsu environment)"}; + for i=2,#\$args do \$compiled_args[i] = \(nomsu environment):compile(\$args[i]) end + for i=2,#\$actions do + local alias = \$actions[i] + local \$alias_args = List{"\(nomsu environment)", unpack(alias:get_args())} + lua:add("\\nCOMPILE_RULES[", alias:get_stub():as_lua(), "] = ") + if \$alias_args == \$args then + lua:add("COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "]") + else + lua:add("function(") + local \$compiled_alias_args = List{"\(nomsu environment)"}; + for i=2,#\$alias_args do \$compiled_alias_args[i] = \(nomsu environment):compile(\$alias_args[i]) end + lua:concat_add(\$compiled_alias_args, ", ") + lua:add(") return COMPILE_RULES[", \$actions[1]:get_stub():as_lua(), "](") + lua:concat_add(\$compiled_args, ", ") + lua:add(") end") + end + end + return lua + ") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + (foo $x) means "outer" + with [$(foo $)]: + (foo $x) means: + $y = ($x + 1) + return $y + + unless ((foo 10) == 11): + fail "Action didn't work." + + unless ($y is (nil)): + fail "Action leaked a local into globals." + + (baz $) parses as (foo $) + assume ((foo 1) == "outer") + +($action means $body) compiles to: + lua> (" + + local lua = LuaCode() + if \$action.type == "MethodCall" then + lua:add(\(nomsu environment):compile(\$action[1]), ".", \$action[2]:get_stub():as_lua_id()) + elseif \$action.type == "Action" then + lua:add(\$action:get_stub():as_lua_id()) + lua:add_free_vars({\$action:get_stub():as_lua_id()}) + else + compile_error_at(\$action, "Expected an action or method call here") + end + lua:add(" = ", \(\($action -> $body) as lua), ";") + return lua + ") + +($actions all mean $body) compiles to: + lua> (" + local lua = \(\($actions.1 means $body) as lua) + local first_def = (\$actions[1].type == "MethodCall" + and LuaCode(\(nomsu environment):compile(\$actions[1][1]), ".", \$actions[1]:get_stub():as_lua_id()) + or LuaCode(\$actions[1]:get_stub():as_lua_id())) + local \$args = List(\$actions[1]:get_args()) + for i=2,#\$actions do + local alias = \$actions[i] + local \$alias_args = List(alias:get_args()) + lua:add("\\n") + if alias.type == "MethodCall" then + lua:add(\(nomsu environment):compile(alias[1]), ".", alias:get_stub():as_lua_id()) + else + lua:add(alias:get_stub():as_lua_id()) + lua:add_free_vars({alias_name}) + end + if \$args == \$alias_args then + lua:add(" = ", first_def, ";") + else + lua:add(" = ", \(\($alias_args -> $actions.1) as lua), ";") + end + end + return lua + ") + +test: + externally (baz1) means: + return "baz1" + externally (baz2) means "baz2" + +test: + assume ((baz1) == "baz1") + assume ((baz2) == "baz2") + +(externally $action means $body) compiles to: + lua> (" + local lua = \(\($action means $body) as lua) + lua:remove_free_vars({\$action:get_stub():as_lua_id()}) + return lua + ") + +(externally $actions all mean $body) compiles to: + lua> (" + local lua = \(\($actions all mean $body) as lua) + lua:remove_free_vars(table.map(\$actions, function(a) return a:get_stub():as_lua_id() end)) + return lua + ") + +test: + (swap $x and $y) parses as + do: + $tmp = $x + $x = $y + $y = $tmp + +test: + [$1, $2] = [1, 2] + swap $1 and $2 + unless (($1 == 2) and ($2 == 1)): + fail "'parse $ as $' failed on 'swap $ and $'" + [$tmp, $tmp2] = [1, 2] + swap $tmp and $tmp2 + unless (($tmp == 2) and ($tmp2 == 1)): + fail "'parse $ as $' variable mangling failed." + +($actions all parse as $body) compiles to: + lua> (" + local replacements = {} + if \$actions.type ~= "List" then + compile_error(\$actions, "This should be a list.") + end + for i,arg in ipairs(\$actions[1]:get_args()) do + replacements[arg[1]] = \(nomsu environment):compile(arg):text() + end + local function make_tree(t) + if SyntaxTree:is_instance(t) and t.type == "Var" then + if replacements[t:as_var()] then + return replacements[t:as_var()] + else + return "SyntaxTree{mangle("..t:as_var():as_lua().."), type="..t.type:as_lua()..", \ + ..source="..tostring(t.source):as_lua().."}" + end + elseif SyntaxTree:is_instance(t) then + local ret = {} + local i = 1 + for k, v in pairs(t) do + if k == i then + ret[#ret+1] = make_tree(t[i]) + i = i + 1 + elseif k == "source" then + ret[#ret+1] = k.."= "..tostring(v):as_lua() + elseif lua_type_of(k) == 'string' and k:is_a_lua_id() then + ret[#ret+1] = k.."= "..make_tree(v) + else + ret[#ret+1] = "["..make_tree(k).."]= "..make_tree(v) + end + end + return "SyntaxTree{"..table.concat(ret, ", ").."}" + elseif lua_type_of(t) == 'number' then + return tostring(t) + else + return t:as_lua() + end + end + local \$new_body = LuaCode:from(\$body.source, + "local mangle = mangler()", + "\\nreturn ", make_tree(\$body)) + local ret = \(\($actions all compile to $new_body) as lua) + return ret + ") + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +[$action parses as $body] all parse as ([$action] all parse as $body) +externally (in (nomsu environment) $tree as lua expr) means: + lua> (" + local tree_lua = \(nomsu environment):compile(\$tree) + if \$tree.type == 'Block' then + tree_lua = LuaCode:from(\$tree.source, '(function()\\n ', tree_lua, '\\nend)()') + elseif \$tree.type == 'MethodCall' and #\$tree > 2 then + compile_error_at(\$tree, "This must be a single value instead of "..(#\$tree - 1).."\ + .. method calls.", + "Replace this with a single method call.") + end + return tree_lua + ") + +# Need to make sure the proper environment is used for compilation (i.e. the caller's environment) +($tree as lua expr) compiles to (\(in \(nomsu environment) $tree as lua expr) as lua) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +externally [$var as lua identifier, $var as lua id] all mean: + lua> (" + local lua = \($var as lua) + if not lua:text():is_a_lua_id() then + compile_error(\$var, + "This is supposed to be something that compiles to a valid Lua identifier.", + "This should probably be a variable.") + end + return lua + ") + +test: + (num args (*extra arguments*)) means (select "#" (*extra arguments*)) + assume (num args 1 2 3) == 3 + (extra args (*extra arguments*)) means [*extra arguments*] + assume (extra args 1 2 3) == [1, 2, 3] + (third arg (*extra arguments*)) means (select 3 (*extra arguments*)) + assume (third arg 5 6 7 8) == 7 + +(*extra arguments*) compiles to "..." + +($ is syntax tree) compiles to "SyntaxTree:is_instance(\($ as lua expr))" + +externally ($ is $kind syntax tree) means + =lua "SyntaxTree:is_instance(\$) and \$.type == \$kind" + +($tree with $t -> $replacement) compiles to (" + \($tree as lua expr):map(function(\($t as lua expr)) + \( + =lua (" + \$replacement.type == 'Block' and \($replacement as lua) or 'return '..\ + ..\($replacement as lua expr):text() + ") + ) + end) +") + +externally ($tree with vars $replacements) means + =lua (" + \$tree:map(function(\$t) + if \$t.type == "Var" then + return \$replacements[\$t:as_var()] + end + end) + ") + +(tree $tree with vars $replacements) compiles to (" + \(=lua "(\$tree):as_lua()"):map(function(t) + if t.type == "Var" then + return \($replacements as lua expr)[t:as_var()] + end + end) +") + +($tree has subtree $match_tree) compiles to (" + (function() + local match_tree = \($match_tree as lua expr) + for subtree in coroutine_wrap(function() \($tree as lua expr):map(yield) end) do + if subtree == match_tree then return true end + end + end)() +") + +externally (match $tree with $patt) means: + lua> (" + if \$patt.type == "Var" then return Dict{[\$patt:as_var()]=\$tree} end + if \$patt.type == "Action" and \$patt:get_stub() ~= \$tree:get_stub() then return nil end + if #\$patt ~= #\$tree then return nil end + local matches = Dict{} + for \($i)=1,#\$patt do + if SyntaxTree:is_instance(\$tree[\$i]) then + local submatch = \(match $tree.$i with $patt.$i) + if not submatch then return nil end + for k,v in pairs(submatch) do + if matches[k] and matches[k] ~= v then return nil end + matches[k] = v + end + end + end + return matches + ") + +test: + assume + ( + quote (" + one + "two" + ") + ) == "\"one\\n\\\"two\\\"\"" + +(quote $s) compiles to "tostring(\($s as lua expr)):as_lua()" +test: + assume (lua type of {}) == "table" + assume (type of {}) == "Dict" + assume ({} is a "Dict") + assume ("" is text) + assume ("" isn't a "Dict") +externally ($ is text) means (=lua "\(lua type of $) == 'string'") +externally [$ is not text, $ isn't text] all mean + =lua "\(lua type of $) ~= 'string'" + +externally (type of $) means: + lua> (" + local lua_type = \(lua type of $) + if lua_type == 'string' then return 'Text' + elseif lua_type == 'table' or lua_type == 'userdata' then + local mt = getmetatable(\$) + if mt and mt.__type then return mt.__type end + end + return lua_type + ") + +[$ is a $type, $ is an $type] all parse as ((type of $) == $type) +[$ isn't a $type, $ isn't an $type, $ is not a $type, $ is not an $type] +..all parse as ((type of $) != $type) + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +test: + (foo) means: + return 100 200 300 + assume (select 2 (foo)) == 200 + +# 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. +(return (*extra arguments*)) compiles to: + lua> (" + local lua = \(Lua "do return ") + for i=1,select('#',...) do + if i > 1 then lua:add(", ") end + lua:add(\(nomsu environment):compile((select(i, ...)))) + end + lua:add(" end") + return lua + ") + +# Literals +(yes) compiles to "true" +(no) compiles to "false" +[nothing, nil, null] all compile to "nil" +(Nomsu syntax version) compiles to "NOMSU_SYNTAX_VERSION" +(Nomsu compiler version) compiles to "NOMSU_COMPILER_VERSION" +(core version) compiles to "NOMSU_CORE_VERSION" +(lib version) compiles to "NOMSU_LIB_VERSION" +(command line args) compiles to "COMMAND_LINE_ARGS" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# + (with local compile actions $body) compiles to (" + do + local OLD_RULES = COMPILE_RULES + local OLD_ENV = \(nomsu environment) + local \(nomsu environment) = setmetatable({ + COMPILE_RULES=setmetatable({}, {__index=OLD_RULES}) + }, {__index=OLD_ENV}) + \($body as lua) + end + ") + +externally (Nomsu version) means: + return (" + \(Nomsu syntax version).\(core version).\(Nomsu compiler version).\(lib version) + ") -- cgit v1.2.3