diff --git a/lib/collections.nom b/lib/collections.nom index dbe2afa..41bf173 100644 --- a/lib/collections.nom +++ b/lib/collections.nom @@ -112,6 +112,22 @@ rule [entries in %dict] =: |end |return items +rule [keys in %dict] =: + lua block ".." + |local items = {} + |for k,v in pairs(vars.dict) do + | table.insert(items, k) + |end + |return items + +rule [values in %dict] =: + lua block ".." + |local items = {} + |for k,v in pairs(vars.dict) do + | table.insert(items, v) + |end + |return items + # List Comprehension macro [%expression for %var in %iterable] =: assert ((%var's "type") == "Var") ".." diff --git a/lib/metaprogramming.nom b/lib/metaprogramming.nom index 9b76620..fa04232 100644 --- a/lib/metaprogramming.nom +++ b/lib/metaprogramming.nom @@ -1,4 +1,3 @@ - #.. This File contains rules for making rules and macros and some helper functions to make that easier. @@ -8,10 +7,9 @@ lua block ".." |local function make_fn(wrap_in_block) | return function(compiler, vars, kind) - # Do a minimal amount of pre-processing (get the spec and the source) - | local spec = compiler:get_invocations_from_definition(vars.spec, vars) - | spec = compiler.utils.repr(spec) - | local src = compiler.utils.repr(vars.user_macro.src) + # Do a minimal amount of pre-processing (get the aliases and the source) + | local aliases = compiler:repr(compiler:get_aliases(vars.macro_def)) + | local src = compiler:repr(vars.user_macro.src) | local user_macro = compiler:tree_to_lua(vars.user_macro) # Then produce a block of code that creates the macro at runtime | local lua = [[ @@ -25,57 +23,75 @@ lua block ".." | return lua, true | end), %s) | ]] - | lua = lua:format(spec, compiler.utils.repr(spec), user_macro, + | lua = lua:format(aliases, compiler:repr(aliases), user_macro, | wrap_in_block and [[lua = "do\\n "..lua.."\\nend"]] or "", src) | return lua, true | end |end - |compiler:defmacro("macro statement %spec = %user_macro", make_fn(false), "N/A") - |compiler:defmacro("macro block %spec = %user_macro", make_fn(true), "N/A") + |compiler:defmacro("macro statement %macro_def = %user_macro", make_fn(false), "see:lib/metaprogramming.nom") + |compiler:defmacro("macro block %macro_def = %user_macro", make_fn(true), "see:lib/metaprogramming.nom") -macro block [macro %spec = %user_macro] =: +macro block [macro %macro_def = %user_macro] =: ".."|compiler:defmacro( - | \lua expr "compiler:get_invocations_from_definition(vars.spec, vars)"\, + | \lua expr "compiler:get_aliases(vars.macro_def)"\, | \lua expr "compiler:tree_to_lua(vars.user_macro)"\, - | \lua expr "compiler.utils.repr(vars.user_macro.src)"\) + | \lua expr "compiler:repr(vars.user_macro.src)"\) macro [compiler] =: "compiler" macro [compiler's %key] =: ".."|compiler[\%key as lua\] macro [compiler %method %args] =: lua block ".." - |local args = {} - |for i,arg in ipairs(vars.args.value) do - | args[i] = compiler:tree_to_lua(arg) + |local args = {"compiler"} + |for _,arg in ipairs(vars.args.value) do + | table.insert(args, compiler:tree_to_lua(arg)) |end - |return "compiler:"..compiler:tree_to_value(vars.method, vars).."("..table.concat(args, ", ")..")" + |return "compiler["..compiler:repr(compiler:tree_to_value(vars.method, vars)).."]("..table.concat(args, ", ")..")" macro [compiler utils %method %args] =: lua block ".." |local args = {} |for i,arg in ipairs(vars.args.value) do | args[i] = compiler:tree_to_lua(arg) |end - |return "compiler.utils."..compiler:tree_to_value(vars.method, vars).."("..table.concat(args, ", ")..")" + |return "compiler.utils["..compiler:repr(compiler:tree_to_value(vars.method, vars)).."]("..table.concat(args, ", ")..")" # Macro that lets you make new rules #.. This is a macro instead of a rule because it needs to pre-empt processing the list of - invocations and convert it into a list of strings (rather than call a function that + function calls and convert it into a list of strings (rather than call a function that is currently in the middle of being defined). Being a macro also allows us to snatch the source code and store that -macro block [rule %spec = %body] =: ".." +macro block [rule %rule_def = %body] =: ".." |compiler:def( - | \compiler utils "repr" [compiler "get_invocations_from_definition" [%spec, lua expr "vars"]]\, + | \compiler "repr" [compiler "get_aliases" [%rule_def]]\, | \compiler "tree_to_lua" [%body]\, - | \compiler utils "repr" [lua expr "vars.body.src"]\) + | \compiler "repr" [lua expr "vars.body.src"]\) + +rule [fart]=: lua block "print('poot')" + +macro block [alias %aliases = %aliased] =: + lua block ".." + |local aliases = compiler:get_aliases(vars.aliases) + |aliases = compiler:repr(aliases) + |if vars.aliased.type ~= "Thunk" then + | compiler:error("Right hand side of alias % = % should be a Thunk, but got "..vars.aliased.type + | ..". Maybe you used = instead of =: by mistake?") + |end + |local aliased = next(compiler:get_aliases(vars.aliased.value)) + |aliased = compiler:repr(aliased) + |local lua = ([[ + |compiler:add_aliases(%s, compiler.defs[%s]) + |]]):format(aliases, aliased) + |return lua # Get the source code for a function -rule [help %invocation] =: +rule [help %rule] =: lua block ".." - |local fn_info = compiler.defs[vars.invocation] - |if not fn_info then - | compiler:writeln("Function not found: "..compiler.utils.repr(vars.invocation)) + |local fn_def = compiler:get_fn_def(vars.rule) + |if not fn_def then + | compiler:writeln("Rule not found: "..compiler:repr(vars.rule)) |else - | compiler:writeln("rule "..compiler.utils.repr(fn_info.invocations).." ="..(fn_info.src or ":\\n ")) + | compiler:writeln("rule "..compiler:repr(compiler.utils.keys(fn_def.aliases)) + | .." ="..(fn_def.src or ":\\n ")) |end @@ -103,8 +119,8 @@ rule [source code from tree %tree] =: |end macro [source code %body] =: - compiler utils "repr" [compiler "call" ["source code from tree %", %body]] + compiler "repr" [compiler "call" ["source code from tree %", %body]] macro [parse tree %code] =: - compiler utils "repr" [compiler "stringify_tree" [lua expr "vars.code.value"]] + compiler "repr" [compiler "stringify_tree" [lua expr "vars.code.value"]] diff --git a/lib/permissions.nom b/lib/permissions.nom index fd55ec8..41d559e 100644 --- a/lib/permissions.nom +++ b/lib/permissions.nom @@ -6,8 +6,8 @@ require "lib/collections.nom" # Permission functions rule [restrict %rules to within %elite-rules] =: say ".."|Restricting \%rules\ to within \%elite-rules\ - %rules =: compiler "get_invocations" [%rules] - %elite-rules =: compiler "get_invocations" [%elite-rules] + %rules =: keys in (compiler "get_aliases" [%rules]) + %elite-rules =: keys in (compiler "get_aliases" [%elite-rules]) for all (flatten [%elite-rules, %rules]): assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\ for all %rules: @@ -20,8 +20,8 @@ rule [restrict %rules to within %elite-rules] =: rule [allow %elite-rules to use %rules] =: say ".."|Allowing \%elite-rules\ to use \%rules\ - %rules =: compiler "get_invocations" [%rules] - %elite-rules =: compiler "get_invocations" [%elite-rules] + %rules =: keys in (compiler "get_aliases" [%rules]) + %elite-rules =: keys in (compiler "get_aliases" [%elite-rules]) for all (flatten [%elite-rules, %rules]): assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\ for %fn in %rules: @@ -33,8 +33,8 @@ rule [allow %elite-rules to use %rules] =: rule [forbid %pleb-rules to use %rules] =: say ".."|Forbidding \%pleb-rules\ to use \%rules\ - %rules =: compiler "get_invocations" [%rules] - %pleb-rules =: compiler "get_invocations" [%pleb-rules] + %rules =: keys in (compiler "get_aliases" [%rules]) + %pleb-rules =: keys in (compiler "get_aliases" [%pleb-rules]) for all (flatten [%pleb-rules, %used]): assert ((compiler's "defs") has key %it) ".."|Undefined function: \%it\ for all %rules: diff --git a/lib/utils.nom b/lib/utils.nom index ec35c15..ff261f7 100644 --- a/lib/utils.nom +++ b/lib/utils.nom @@ -15,7 +15,7 @@ macro block [assert %condition %msg] =: ".." |end macro block [show generated lua %block] =: ".." - |compiler:writeln(\lua expr "compiler.utils.repr(compiler:tree_to_lua(vars.block.value))"\) + |compiler:writeln(\lua expr "compiler:repr(compiler:tree_to_lua(vars.block.value))"\) # String functions @@ -35,7 +35,7 @@ macro [capitalize %str, %str capitalized] =: ".." |(\%str as lua\):gsub("%l", string.upper, 1) macro [repr %obj] =: - ".."|compiler.utils.repr(\%obj as lua\) + ".."|compiler:repr(\%obj as lua\) macro [%obj as string] =: ".."|compiler.utils.repr_if_not_string(\%obj as lua\) diff --git a/nomsu.lua b/nomsu.lua index 11ae3ae..9b1856c 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -1,7 +1,16 @@ local re = require('re') local lpeg = require('lpeg') local utils = require('utils') -local insert = table.insert +local repr = utils.repr +local insert, remove, concat +do + local _obj_0 = table + insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat +end +local pcall +pcall = function(fn, ...) + return true, fn(...) +end local INDENT = " " lpeg.setmaxstack(10000) local P, V, S, Cg, C, Cp, B, Cmt @@ -15,6 +24,34 @@ local STRING_ESCAPES = { f = "\f", r = "\r" } +local parsetree_mt = { + __tostring = function(self) + return tostring(self.type) .. "(" .. tostring(repr(self.value)) .. ")" + end +} +local ParseTree +ParseTree = function(type, src, value, errors) + return setmetatable({ + type = type, + src = src, + value = value, + errors = errors + }, parsetree_mt) +end +local functiondef_mt = { + __tostring = function(self) + return "FunctionDef(" .. tostring(repr(self.aliases)) + end +} +local FunctionDef +FunctionDef = function(fn, aliases, src, is_macro) + return setmetatable({ + fn = fn, + aliases = aliases, + src = src, + is_macro = is_macro + }, functiondef_mt) +end local NomsuCompiler do local _class_0 @@ -23,169 +60,150 @@ do self:write(...) return self:write("\n") end, - call = function(self, fn_name, ...) - local fn_info = self.defs[fn_name] - if fn_info == nil then - self:error("Attempt to call undefined function: " .. tostring(fn_name)) - end - if fn_info.is_macro then - self:error("Attempt to call macro at runtime: " .. tostring(fn_name) .. "\nThis can be caused by using a macro in a function that is defined before the macro.") - end - if not (self:check_permission(fn_name)) then - self:error("You do not have the authority to call: " .. tostring(fn_name)) - end - insert(self.callstack, fn_name) - local fn, arg_names - fn, arg_names = fn_info.fn, fn_info.arg_names - local args - do - local _tbl_0 = { } - for i, name in ipairs(arg_names[fn_name]) do - _tbl_0[name] = select(i, ...) - end - args = _tbl_0 - end - if self.debug then - self:writeln("Calling " .. tostring(fn_name) .. " with args: " .. tostring(utils.repr(args))) - end - local ret = fn(self, args) - table.remove(self.callstack) - return ret - end, - check_permission = function(self, fn_name) - local fn_info = self.defs[fn_name] - if fn_info == nil then - self:error("Undefined function: " .. tostring(fn_name)) - end - if fn_info.whiteset == nil then - return true - end - local _list_0 = self.callstack - for _index_0 = 1, #_list_0 do - local caller = _list_0[_index_0] - if fn_info.whiteset[caller] then - return true - end - end - return false - end, - def = function(self, spec, fn, src) - if self.debug then - self:writeln("Defining rule: " .. tostring(spec)) - end - local invocations, arg_names = self:get_invocations(spec) - for i = 2, #invocations do - if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]])) then - self:error("Conflicting argument names " .. tostring(utils.repr(invocations[1])) .. " and " .. tostring(utils.repr(invocations[i])) .. " for " .. tostring(utils.repr(spec))) - end - end - local fn_info = { - fn = fn, - arg_names = arg_names, - invocations = invocations, - src = src, + def = function(self, aliases, fn, src, is_macro) + if is_macro == nil then is_macro = false - } - for _index_0 = 1, #invocations do - local invocation = invocations[_index_0] - self.defs[invocation] = fn_info end + if type(aliases) == 'string' then + aliases = self:get_aliases(aliases) + end + if self.debug then + self:writeln("Defining rule: " .. tostring(aliases)) + end + local fn_def = FunctionDef(fn, { }, src, is_macro) + return self:add_aliases(aliases, fn_def) end, - get_invocations_from_definition = function(self, def, vars) - if def.type == "String" then - return self.__class:unescape_string(def.value) + defmacro = function(self, aliases, fn, src) + return self:def(aliases, fn, src, true) + end, + add_aliases = function(self, aliases, fn_def) + local first_alias, first_args = next(fn_def.aliases) + if not first_alias then + first_alias, first_args = next(aliases) end - if def.type ~= "List" then - self:error("Trying to get invocations from " .. tostring(def.type) .. ", but expected List or String.") - end - local invocations = { } - local _list_0 = def.value - for _index_0 = 1, #_list_0 do + for alias, args in pairs(aliases) do local _continue_0 = false repeat - local item = _list_0[_index_0] - if item.type == "String" then - insert(invocations, item.value) + if fn_def[alias] then _continue_0 = true break end - if item.type ~= "FunctionCall" then - self:error("Invalid list item: " .. tostring(item.type) .. ", expected FunctionCall or String") + if self.defs[alias] then + self:remove_alias(alias) end - local name_bits = { } - local _list_1 = item.value - for _index_1 = 1, #_list_1 do - local token = _list_1[_index_1] - if token.type == "Word" then - insert(name_bits, token.value) - elseif token.type == "Var" then - insert(name_bits, token.src) - else - self:error("Unexpected token type in definition: " .. tostring(token.type) .. " (expected Word or Var)") - end + if alias ~= first_alias and not utils.equivalent(utils.set(args), utils.set(first_args)) then + self:error("Conflicting argument names between " .. tostring(first_alias) .. " and " .. tostring(alias)) end - insert(invocations, table.concat(name_bits, " ")) + fn_def.aliases[alias] = args + self.defs[alias] = fn_def _continue_0 = true until true if not _continue_0 then break end end - return invocations end, - get_invocations = function(self, text) - if not text then - self:error("No text provided!") + remove_alias = function(self, alias) + local fn_def = self.defs[alias] + if not fn_def then + return end - if type(text) == 'function' then - error("Function passed to get_invocations") + fn_def.aliases[alias] = nil + self.defs[alias] = nil + end, + remove_aliases = function(self, aliases) + for alias in pairs(aliases) do + self:remove_alias(alias) end - if type(text) == 'string' then - text = { - text - } + end, + get_fn_def = function(self, x) + if not x then + self:error("Nothing to get function def from") end - local invocations = { } - local arg_names = { } - for _index_0 = 1, #text do - local _text = text[_index_0] - local invocation = _text:gsub("'", " '"):gsub("%%%S+", "%%"):gsub("%s+", " ") - local _arg_names - do - local _accum_0 = { } - local _len_0 = 1 - for arg in _text:gmatch("%%(%S[^%s']*)") do - _accum_0[_len_0] = arg + local aliases = self:get_aliases(x) + local alias, _ = next(aliases) + return self.defs[alias] + end, + call = function(self, alias, ...) + local fn_def = self.defs[alias] + if fn_def == nil then + self:error("Attempt to call undefined function: " .. tostring(alias)) + end + if fn_def.is_macro and self.callstack[#self.callstack] ~= "__macro__" then + self:error("Attempt to call macro at runtime: " .. tostring(alias) .. "\nThis can be caused by using a macro in a function that is defined before the macro.") + end + if not (self:check_permission(alias)) then + self:error("You do not have the authority to call: " .. tostring(alias)) + end + insert(self.callstack, alias) + local fn, aliases + fn, aliases = fn_def.fn, fn_def.aliases + local args + do + local _tbl_0 = { } + for i, name in ipairs(aliases[alias]) do + _tbl_0[name] = select(i, ...) + end + args = _tbl_0 + end + if self.debug then + self:writeln("Calling " .. tostring(alias) .. " with args: " .. tostring(repr(args))) + end + local rets = { + fn(self, args) + } + remove(self.callstack) + return unpack(rets) + end, + run_macro = function(self, tree, kind) + if kind == nil then + kind = "Expression" + end + local args + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = tree.value + for _index_0 = 1, #_list_0 do + local a = _list_0[_index_0] + if a.type ~= "Word" then + _accum_0[_len_0] = a _len_0 = _len_0 + 1 end - _arg_names = _accum_0 end - insert(invocations, invocation) - arg_names[invocation] = _arg_names + args = _accum_0 end - return invocations, arg_names + local alias, _ = self:get_alias(tree) + insert(self.callstack, "__macro__") + local ret, manual_mode = self:call(alias, unpack(args)) + remove(self.callstack) + if not ret then + self:error("No return value for macro: " .. tostring(name)) + end + if kind == "Statement" and not manual_mode then + if ret:match("^do\n") then + error("Attempting to use macro return value as an expression, when it looks like a block:\n" .. tostring(ret)) + end + ret = "ret = " .. ret + end + return ret end, - defmacro = function(self, spec, lua_gen_fn, src) - if self.debug then - self:writeln("DEFINING MACRO: " .. tostring(spec) .. tostring(src or "")) + check_permission = function(self, fn_name) + local fn_def = self.defs[fn_name] + if fn_def == nil then + self:error("Undefined function: " .. tostring(fn_name)) end - local invocations, arg_names = self:get_invocations(spec) - for i = 2, #invocations do - if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]])) then - self:error("Conflicting argument names " .. tostring(utils.repr(invocations[1])) .. " and " .. tostring(utils.repr(invocations[i])) .. " for " .. tostring(utils.repr(spec))) + if fn_def.whiteset == nil then + return true + end + local _list_0 = self.callstack + for _index_0 = 1, #_list_0 do + local caller = _list_0[_index_0] + if fn_def.whiteset[caller] then + return true end end - local fn_info = { - fn = lua_gen_fn, - arg_names = arg_names, - invocations = invocations, - src = src, - is_macro = true - } - for _index_0 = 1, #invocations do - local invocation = invocations[_index_0] - self.defs[invocation] = fn_info - end + return false end, parse = function(self, str, filename) if self.debug then @@ -204,7 +222,7 @@ do local check_dedent check_dedent = function(subject, end_pos, spaces) if #spaces < indent_stack[#indent_stack] then - table.remove(indent_stack) + remove(indent_stack) return end_pos end end @@ -319,17 +337,21 @@ do return error("\n" .. tostring(err_msg or "Parse error") .. " in " .. tostring(filename) .. " on line " .. tostring(line_no) .. ":\n\n" .. tostring(prev_line) .. "\n" .. tostring(err_line) .. "\n" .. tostring(pointer) .. "\n" .. tostring(next_line) .. "\n") end } + local tree_mt = { + __tostring = function(self) + return tostring(self.type) .. "(" .. tostring(repr(self.value)) .. ")" + end + } setmetatable(defs, { __index = function(t, key) local fn fn = function(src, value, errors) - local token = { + return setmetatable({ type = key, src = src, value = value, errors = errors - } - return token + }, tree_mt) end t[key] = fn return fn @@ -355,7 +377,7 @@ do tree_to_lua = function(self, tree) assert(tree, "No tree provided.") if not tree.type then - self:error("Invalid tree: " .. tostring(utils.repr(tree))) + self:error("Invalid tree: " .. tostring(repr(tree))) end local _exp_0 = tree.type if "File" == _exp_0 then @@ -375,7 +397,7 @@ do local lua_code = "\n return (function(compiler, vars)\n" .. tostring(code) .. "\nend)" local lua_thunk, err = load(lua_code) if not lua_thunk then - error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err) .. "\n\nProduced by statement:\n" .. tostring(utils.repr(statement))) + error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err) .. "\n\nProduced by statement:\n" .. tostring(repr(statement))) end local value = lua_thunk() local return_value @@ -389,7 +411,7 @@ do insert(buffer, [[ return ret end) ]]) - return table.concat(buffer, "\n"), return_value + return concat(buffer, "\n"), return_value elseif "Block" == _exp_0 then local buffer = { } local _list_0 = tree.value @@ -397,7 +419,7 @@ do local statement = _list_0[_index_0] insert(buffer, self:tree_to_lua(statement)) end - return table.concat(buffer, "\n") + return concat(buffer, "\n") elseif "Thunk" == _exp_0 then assert(tree.value.type == "Block", "Non-block value in Thunk") return [[ (function(compiler, vars) @@ -407,15 +429,15 @@ do ]] elseif "Statement" == _exp_0 then if tree.value.type == "FunctionCall" then - local name = self:fn_name_from_tree(tree.value) - if self.defs[name] and self.defs[name].is_macro then + local alias = self:get_alias(tree.value) + if self.defs[alias] and self.defs[alias].is_macro then return self:run_macro(tree.value, "Statement") end end return "ret = " .. (self:tree_to_lua(tree.value)) elseif "FunctionCall" == _exp_0 then - local name = self:fn_name_from_tree(tree) - if self.defs[name] and self.defs[name].is_macro then + local alias = self:get_alias(tree) + if self.defs[alias] and self.defs[alias].is_macro then return self:run_macro(tree, "Expression") else local args @@ -432,11 +454,11 @@ do end args = _accum_0 end - insert(args, 1, utils.repr(name)) + insert(args, 1, repr(alias)) return self.__class:comma_separated_items("compiler:call(", args, ")") end elseif "String" == _exp_0 then - return utils.repr(self.__class:unescape_string(tree.value)) + return repr(self.__class:unescape_string(tree.value)) elseif "Longstring" == _exp_0 then local concat_parts = { } local string_buffer = "" @@ -450,7 +472,7 @@ do string_buffer = string_buffer .. bit:gsub("\\\\", "\\") else if string_buffer ~= "" then - insert(concat_parts, utils.repr(string_buffer)) + insert(concat_parts, repr(string_buffer)) string_buffer = "" end insert(concat_parts, "compiler.utils.repr_if_not_string(" .. tostring(self:tree_to_lua(bit)) .. ")") @@ -458,14 +480,14 @@ do end end if string_buffer ~= "" then - insert(concat_parts, utils.repr(string_buffer)) + insert(concat_parts, repr(string_buffer)) end if #concat_parts == 0 then return "''" elseif #concat_parts == 1 then return concat_parts[1] else - return "(" .. tostring(table.concat(concat_parts, "..")) .. ")" + return "(" .. tostring(concat(concat_parts, "..")) .. ")" end elseif "Number" == _exp_0 then return tree.value @@ -488,26 +510,94 @@ do end)(), "}") end elseif "Var" == _exp_0 then - return "vars[" .. tostring(utils.repr(tree.value)) .. "]" + return "vars[" .. tostring(repr(tree.value)) .. "]" else return self:error("Unknown/unimplemented thingy: " .. tostring(tree.type)) end end, - fn_name_from_tree = function(self, tree) - assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: " .. tostring(tree.type)) - local name_bits = { } - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local token = _list_0[_index_0] - insert(name_bits, (function() - if token.type == "Word" then - return token.value - else - return "%" - end - end)()) + get_alias = function(self, x) + if not x then + self:error("Nothing to get alias from") + end + if type(x) == 'string' then + local alias = x:gsub("'", " '"):gsub("%%%S+", "%%"):gsub("%s+", " ") + local args + do + local _accum_0 = { } + local _len_0 = 1 + for arg in x:gmatch("%%(%S[^%s']*)") do + _accum_0[_len_0] = arg + _len_0 = _len_0 + 1 + end + args = _accum_0 + end + return alias, args + end + local _exp_0 = x.type + if "String" == _exp_0 then + return self:get_alias(x.value) + elseif "Statement" == _exp_0 then + return self:get_alias(x.value) + elseif "FunctionCall" == _exp_0 then + local alias, args = { }, { }, { } + local _list_0 = x.value + for _index_0 = 1, #_list_0 do + local token = _list_0[_index_0] + local _exp_1 = token.type + if "Word" == _exp_1 then + insert(alias, token.value) + elseif "Var" == _exp_1 then + insert(alias, "%") + insert(args, token.value) + else + insert(alias, "%") + insert(args, token) + end + end + return concat(alias, " "), args + end + end, + get_aliases = function(self, x) + if self.value then + print(self) + error("WTF") + end + if not x then + self:error("Nothing to get aliases from") + end + if type(x) == 'string' then + local alias, args = self:get_alias(x) + return { + [alias] = args + } + end + local _exp_0 = x.type + if "String" == _exp_0 then + return self:get_aliases({ + x.value + }) + elseif "Statement" == _exp_0 then + return self:get_aliases({ + x.value + }) + elseif "FunctionCall" == _exp_0 then + return self:get_aliases({ + x + }) + elseif "List" == _exp_0 then + x = x.value + elseif "Block" == _exp_0 then + x = x.value + end + do + local _with_0 = { } + for _index_0 = 1, #x do + local y = x[_index_0] + local alias, args = self:get_alias(y) + _with_0[alias] = args + end + return _with_0 end - return table.concat(name_bits, " ") end, var_to_lua_identifier = function(self, var) if var.type ~= "Var" then @@ -521,54 +611,6 @@ do end end)) end, - run_macro = function(self, tree, kind) - if kind == nil then - kind = "Expression" - end - local name = self:fn_name_from_tree(tree) - if not (self.defs[name] and self.defs[name].is_macro) then - self:error("Macro not found: " .. tostring(name)) - end - if not (self:check_permission(name)) then - self:error("You do not have the authority to call: " .. tostring(name)) - end - local fn, arg_names - do - local _obj_0 = self.defs[name] - fn, arg_names = _obj_0.fn, _obj_0.arg_names - end - local args - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local a = _list_0[_index_0] - if a.type ~= "Word" then - _accum_0[_len_0] = a - _len_0 = _len_0 + 1 - end - end - args = _accum_0 - end - do - local _tbl_0 = { } - for i, name in ipairs(arg_names[name]) do - _tbl_0[name] = args[i] - end - args = _tbl_0 - end - insert(self.callstack, name) - local ret, manual_mode = fn(self, args, kind) - table.remove(self.callstack) - if not ret then - self:error("No return value for macro: " .. tostring(name)) - end - if kind == "Statement" and not manual_mode then - ret = "ret = " .. ret - end - return ret - end, _yield_tree = function(self, tree, indent_level) if indent_level == nil then indent_level = 0 @@ -580,9 +622,9 @@ do local _exp_0 = tree.type if "File" == _exp_0 then coroutine.yield(ind("File:")) - self:_yield_tree(tree.value.body, indent_level + 1) + return self:_yield_tree(tree.value.body, indent_level + 1) elseif "Errors" == _exp_0 then - coroutine.yield(ind("Error:\n" .. tostring(tree.value))) + return coroutine.yield(ind("Error:\n" .. tostring(tree.value))) elseif "Block" == _exp_0 then local _list_0 = tree.value for _index_0 = 1, #_list_0 do @@ -591,11 +633,11 @@ do end elseif "Thunk" == _exp_0 then coroutine.yield(ind("Thunk:")) - self:_yield_tree(tree.value, indent_level + 1) + return self:_yield_tree(tree.value, indent_level + 1) elseif "Statement" == _exp_0 then - self:_yield_tree(tree.value, indent_level) + return self:_yield_tree(tree.value, indent_level) elseif "FunctionCall" == _exp_0 then - local name = self:fn_name_from_tree(tree) + local alias = self:get_alias(tree) local args do local _accum_0 = { } @@ -611,23 +653,25 @@ do args = _accum_0 end if #args == 0 then - coroutine.yield(ind("Call [" .. tostring(name) .. "]!")) + return coroutine.yield(ind("Call [" .. tostring(alias) .. "]!")) else - coroutine.yield(ind("Call [" .. tostring(name) .. "]:")) + coroutine.yield(ind("Call [" .. tostring(alias) .. "]:")) for _index_0 = 1, #args do local a = args[_index_0] self:_yield_tree(a, indent_level + 1) end end elseif "String" == _exp_0 then - coroutine.yield(ind(utils.repr(tree.value))) + return coroutine.yield(ind(repr(tree.value))) elseif "Longstring" == _exp_0 then - coroutine.yield(ind(utils.repr(tree.value))) + return coroutine.yield(ind(repr(tree.value))) elseif "Number" == _exp_0 then - coroutine.yield(ind(tree.value)) + return coroutine.yield(ind(tree.value)) + elseif "Var" == _exp_0 then + return coroutine.yield(ind("Var[" .. tostring(repr(tree.value)) .. "]")) elseif "List" == _exp_0 then if #tree.value == 0 then - coroutine.yield(ind("")) + return coroutine.yield(ind("")) else coroutine.yield(ind("List:")) local _list_0 = tree.value @@ -636,12 +680,9 @@ do self:_yield_tree(item, indent_level + 1) end end - elseif "Var" == _exp_0 then - coroutine.yield(ind("Var[" .. tostring(utils.repr(tree.value)) .. "]")) else - error("Unknown/unimplemented thingy: " .. tostring(tree.type)) + return error("Unknown/unimplemented thingy: " .. tostring(tree.type)) end - return nil end, print_tree = function(self, tree) for line in coroutine.wrap(function() @@ -657,7 +698,7 @@ do end) do insert(result, line) end - return table.concat(result, "\n") + return concat(result, "\n") end, run = function(self, src, filename, output_file) if output_file == nil then @@ -707,22 +748,22 @@ do end end, initialize_core = function(self) - self:defmacro([[lua block %lua_code]], function(self, vars, kind) + self:defmacro("lua block %lua_code", function(self, vars, kind) if kind == "Expression" then error("Expected to be in statement.") end local inner_vars = setmetatable({ }, { __index = function(_, key) - return error("vars[" .. tostring(utils.repr(key)) .. "]") + return error("vars[" .. tostring(repr(key)) .. "]") end }) return "do\n" .. self:tree_to_value(vars.lua_code, inner_vars) .. "\nend", true end) - self:defmacro([[lua expr %lua_code]], function(self, vars, kind) + self:defmacro("lua expr %lua_code", function(self, vars, kind) local lua_code = vars.lua_code.value local inner_vars = setmetatable({ }, { __index = function(_, key) - return error("vars[" .. tostring(utils.repr(key)) .. "]") + return error("vars[" .. tostring(repr(key)) .. "]") end }) return self:tree_to_value(vars.lua_code, inner_vars) @@ -759,6 +800,9 @@ do self.debug = false self:initialize_core() self.utils = utils + self.repr = function(self, ...) + return repr(...) + end self.loaded_files = { } end, __base = _base_0, @@ -795,7 +839,7 @@ do end end insert(bits, close) - return table.concat(bits) + return concat(bits) end NomsuCompiler = _class_0 end @@ -845,7 +889,7 @@ elseif arg then return c:run(buff) end) if ok and ret ~= nil then - print("= " .. utils.repr(ret)) + print("= " .. repr(ret)) end end end diff --git a/nomsu.moon b/nomsu.moon index 6dce00b..ed4b955 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -2,7 +2,9 @@ re = require 're' lpeg = require 'lpeg' utils = require 'utils' -insert = table.insert +repr = utils.repr +{:insert, :remove, :concat} = table +pcall = (fn,...)-> true, fn(...) -- TODO: -- improve indentation of generated lua code @@ -19,6 +21,14 @@ lpeg.setmaxstack 10000 -- whoa {:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg STRING_ESCAPES = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r" +-- Helper "classes" +parsetree_mt = {__tostring:=> "#{@type}(#{repr(@value)})"} +ParseTree = (type, src, value, errors)-> + setmetatable({:type, :src, :value, :errors}, parsetree_mt) + +functiondef_mt = {__tostring:=> "FunctionDef(#{repr(@aliases)}"} +FunctionDef = (fn, aliases, src, is_macro)-> + setmetatable({:fn, :aliases, :src, :is_macro}, functiondef_mt) class NomsuCompiler new:(parent)=> @@ -28,102 +38,93 @@ class NomsuCompiler @debug = false @initialize_core! @utils = utils + @repr = (...)=> repr(...) @loaded_files = {} writeln:(...)=> @write(...) @write("\n") - - call: (fn_name,...)=> - fn_info = @defs[fn_name] - if fn_info == nil - @error "Attempt to call undefined function: #{fn_name}" - if fn_info.is_macro - @error "Attempt to call macro at runtime: #{fn_name}\nThis can be caused by using a macro in a function that is defined before the macro." - unless @check_permission(fn_name) - @error "You do not have the authority to call: #{fn_name}" - insert @callstack, fn_name - {:fn, :arg_names} = fn_info - args = {name, select(i,...) for i,name in ipairs(arg_names[fn_name])} + + def: (aliases, fn, src, is_macro=false)=> + if type(aliases) == 'string' + aliases = @get_aliases aliases if @debug - @writeln "Calling #{fn_name} with args: #{utils.repr(args)}" - ret = fn(self, args) - table.remove @callstack + @writeln "Defining rule: #{aliases}" + fn_def = FunctionDef(fn, {}, src, is_macro) + @add_aliases aliases, fn_def + + defmacro: (aliases, fn, src)=> @def(aliases, fn, src, true) + + add_aliases: (aliases, fn_def)=> + first_alias,first_args = next(fn_def.aliases) + if not first_alias + first_alias,first_args = next(aliases) + for alias,args in pairs(aliases) + if fn_def[alias] then continue + if @defs[alias] then @remove_alias(alias) + if alias != first_alias and not utils.equivalent(utils.set(args), utils.set(first_args)) + @error "Conflicting argument names between #{first_alias} and #{alias}" + fn_def.aliases[alias] = args + @defs[alias] = fn_def + + remove_alias: (alias)=> + fn_def = @defs[alias] + if not fn_def then return + fn_def.aliases[alias] = nil + @defs[alias] = nil + + remove_aliases: (aliases)=> + for alias in pairs(aliases) do @remove_alias(alias) + + get_fn_def: (x)=> + if not x then @error "Nothing to get function def from" + aliases = @get_aliases x + alias,_ = next(aliases) + return @defs[alias] + + call: (alias,...)=> + fn_def = @defs[alias] + if fn_def == nil + @error "Attempt to call undefined function: #{alias}" + if fn_def.is_macro and @callstack[#@callstack] != "__macro__" + @error "Attempt to call macro at runtime: #{alias}\nThis can be caused by using a macro in a function that is defined before the macro." + unless @check_permission(alias) + @error "You do not have the authority to call: #{alias}" + insert @callstack, alias + {:fn, :aliases} = fn_def + args = {name, select(i,...) for i,name in ipairs(aliases[alias])} + if @debug + @writeln "Calling #{alias} with args: #{repr(args)}" + -- TODO: optimize, but still allow multiple return values? + rets = {fn(self,args)} + remove @callstack + return unpack(rets) + + run_macro: (tree, kind="Expression")=> + args = [a for a in *tree.value when a.type != "Word"] + alias,_ = @get_alias tree + insert @callstack, "__macro__" + ret, manual_mode = @call(alias, unpack(args)) + remove @callstack + if not ret + @error("No return value for macro: #{name}") + if kind == "Statement" and not manual_mode + if ret\match("^do\n") + error "Attempting to use macro return value as an expression, when it looks like a block:\n#{ret}" + ret = "ret = "..ret return ret check_permission: (fn_name)=> - fn_info = @defs[fn_name] - if fn_info == nil + fn_def = @defs[fn_name] + if fn_def == nil @error "Undefined function: #{fn_name}" - if fn_info.whiteset == nil then return true + if fn_def.whiteset == nil then return true -- TODO: optimize this, maybe by making the callstack a Counter and having a move-to-front optimization on the whitelist for caller in *@callstack - if fn_info.whiteset[caller] + if fn_def.whiteset[caller] return true return false - def: (spec, fn, src)=> - if @debug - @writeln "Defining rule: #{spec}" - invocations,arg_names = @get_invocations spec - for i=2,#invocations - if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]])) - @error("Conflicting argument names #{utils.repr(invocations[1])} and #{utils.repr(invocations[i])} for #{utils.repr(spec)}") - fn_info = {:fn, :arg_names, :invocations, :src, is_macro:false} - for invocation in *invocations - @defs[invocation] = fn_info - - get_invocations_from_definition:(def, vars)=> - if def.type == "String" - return @@unescape_string(def.value) - - if def.type != "List" - @error "Trying to get invocations from #{def.type}, but expected List or String." - - invocations = {} - for item in *def.value - if item.type == "String" - insert invocations, item.value - continue - if item.type != "FunctionCall" - @error "Invalid list item: #{item.type}, expected FunctionCall or String" - name_bits = {} - for token in *item.value - if token.type == "Word" - insert name_bits, token.value - elseif token.type == "Var" - insert name_bits, token.src - else - @error "Unexpected token type in definition: #{token.type} (expected Word or Var)" - insert invocations, table.concat(name_bits, " ") - return invocations - - get_invocations:(text)=> - if not text - @error "No text provided!" - if type(text) == 'function' - error "Function passed to get_invocations" - if type(text) == 'string' then text = {text} - invocations = {} - arg_names = {} - for _text in *text - invocation = _text\gsub("'"," '")\gsub("%%%S+","%%")\gsub("%s+"," ") - _arg_names = [arg for arg in _text\gmatch("%%(%S[^%s']*)")] - insert invocations, invocation - arg_names[invocation] = _arg_names - return invocations, arg_names - - defmacro: (spec, lua_gen_fn, src)=> - if @debug - @writeln("DEFINING MACRO: #{spec}#{src or ""}") - invocations,arg_names = @get_invocations spec - for i=2,#invocations - if not utils.equivalent(utils.set(arg_names[invocations[1]]), utils.set(arg_names[invocations[i]])) - @error("Conflicting argument names #{utils.repr(invocations[1])} and #{utils.repr(invocations[i])} for #{utils.repr(spec)}") - fn_info = {fn:lua_gen_fn, :arg_names, :invocations, :src, is_macro:true} - for invocation in *invocations - @defs[invocation] = fn_info - parse: (str, filename)=> if @debug @writeln("PARSING:\n#{str}") @@ -136,7 +137,7 @@ class NomsuCompiler return end_pos check_dedent = (subject,end_pos,spaces)-> if #spaces < indent_stack[#indent_stack] - table.remove(indent_stack) + remove(indent_stack) return end_pos check_nodent = (subject,end_pos,spaces)-> if #spaces == indent_stack[#indent_stack] @@ -245,11 +246,10 @@ class NomsuCompiler pointer = ("-")\rep(err_pos - start_of_err_line + 0) .. "^" error("\n#{err_msg or "Parse error"} in #{filename} on line #{line_no}:\n\n#{prev_line}\n#{err_line}\n#{pointer}\n#{next_line}\n") + tree_mt = {__tostring:=> "#{@type}(#{repr(@value)})"} setmetatable(defs, { __index: (t,key)-> - fn = (src, value, errors)-> - token = {type: key, :src, :value, :errors} - return token + fn = (src, value, errors)-> setmetatable({type: key, :src, :value, :errors}, tree_mt) t[key] = fn return fn }) @@ -272,7 +272,7 @@ class NomsuCompiler tree_to_lua: (tree)=> assert tree, "No tree provided." if not tree.type - @error "Invalid tree: #{utils.repr(tree)}" + @error "Invalid tree: #{repr(tree)}" switch tree.type when "File" buffer = {[[return (function(compiler, vars) @@ -288,7 +288,7 @@ class NomsuCompiler return (function(compiler, vars)\n#{code}\nend)" lua_thunk, err = load(lua_code) if not lua_thunk - error("Failed to compile generated code:\n#{code}\n\n#{err}\n\nProduced by statement:\n#{utils.repr(statement)}") + error("Failed to compile generated code:\n#{code}\n\n#{err}\n\nProduced by statement:\n#{repr(statement)}") value = lua_thunk! ok,return_value = pcall(value, self, vars) if not ok @@ -299,13 +299,13 @@ class NomsuCompiler return ret end) ]] - return table.concat(buffer, "\n"), return_value + return concat(buffer, "\n"), return_value when "Block" buffer = {} for statement in *tree.value insert buffer, @tree_to_lua(statement) - return table.concat(buffer, "\n") + return concat(buffer, "\n") when "Thunk" assert tree.value.type == "Block", "Non-block value in Thunk" @@ -320,22 +320,22 @@ class NomsuCompiler when "Statement" -- This case here is to prevent "ret =" from getting prepended when the macro might not want it if tree.value.type == "FunctionCall" - name = @fn_name_from_tree(tree.value) - if @defs[name] and @defs[name].is_macro + alias = @get_alias(tree.value) + if @defs[alias] and @defs[alias].is_macro return @run_macro(tree.value, "Statement") return "ret = "..(@tree_to_lua(tree.value)) when "FunctionCall" - name = @fn_name_from_tree(tree) - if @defs[name] and @defs[name].is_macro + alias = @get_alias(tree) + if @defs[alias] and @defs[alias].is_macro return @run_macro(tree, "Expression") else args = [@tree_to_lua(a) for a in *tree.value when a.type != "Word"] - insert args, 1, utils.repr(name) + insert args, 1, repr(alias) return @@comma_separated_items("compiler:call(", args, ")") when "String" - return utils.repr(@@unescape_string(tree.value)) + return repr(@@unescape_string(tree.value)) when "Longstring" concat_parts = {} @@ -347,19 +347,19 @@ class NomsuCompiler string_buffer ..= bit\gsub("\\\\","\\") else if string_buffer ~= "" - insert concat_parts, utils.repr(string_buffer) + insert concat_parts, repr(string_buffer) string_buffer = "" insert concat_parts, "compiler.utils.repr_if_not_string(#{@tree_to_lua(bit)})" if string_buffer ~= "" - insert concat_parts, utils.repr(string_buffer) + insert concat_parts, repr(string_buffer) if #concat_parts == 0 return "''" elseif #concat_parts == 1 return concat_parts[1] else - return "(#{table.concat(concat_parts, "..")})" + return "(#{concat(concat_parts, "..")})" when "Number" return tree.value @@ -373,7 +373,7 @@ class NomsuCompiler return @@comma_separated_items("{", [@tree_to_lua(item) for item in *tree.value], "}") when "Var" - return "vars[#{utils.repr(tree.value)}]" + return "vars[#{repr(tree.value)}]" else @error("Unknown/unimplemented thingy: #{tree.type}") @@ -392,38 +392,60 @@ class NomsuCompiler insert bits, "\n" so_far = 0 insert bits, close - return table.concat(bits) - - fn_name_from_tree: (tree)=> - assert(tree.type == "FunctionCall", "Attempt to get fn name from non-functioncall tree: #{tree.type}") - name_bits = {} - for token in *tree.value - insert name_bits, if token.type == "Word" then token.value else "%" - table.concat(name_bits, " ") + return concat(bits) + + get_alias: (x)=> + if not x then @error "Nothing to get alias from" + -- Returns a single alias ("say %"), and list of args ({msg}) from a single rule def + -- (e.g. "say %msg") or function call (e.g. FunctionCall({Word("say"), Var("msg"))) + if type(x) == 'string' + -- TODO + alias = x\gsub("'"," '")\gsub("%%%S+","%%")\gsub("%s+"," ") + args = [arg for arg in x\gmatch("%%(%S[^%s']*)")] + return alias, args + switch x.type + when "String" then return @get_alias(x.value) + when "Statement" then return @get_alias(x.value) + when "FunctionCall" + alias, args = {}, {}, {} + for token in *x.value + switch token.type + when "Word" + insert alias, token.value + when "Var" + insert alias, "%" + insert args, token.value + else + insert alias, "%" + insert args, token + return concat(alias," "), args + + get_aliases:(x)=> + if self.value + print self + error "WTF" + if not x then @error "Nothing to get aliases from" + if type(x) == 'string' + alias, args = @get_alias(x) + return {[alias]: args} + switch x.type + when "String" then return @get_aliases({x.value}) + when "Statement" then return @get_aliases({x.value}) + when "FunctionCall" then return @get_aliases({x}) + when "List" then x = x.value + when "Block" then x = x.value + with {} + for y in *x + alias,args = @get_alias(y) + [alias] = args var_to_lua_identifier: (var)=> + -- Converts arbitrary nomsu vars to valid lua identifiers by replacing illegal + -- characters with escape sequences if var.type != "Var" @error("Tried to convert something that wasn't a Var into a lua identifier: it was not a Var, it was: "..label.type) "var"..(var.value\gsub "%W", (verboten)-> if verboten == "_" then "__" else ("_%x")\format(verboten\byte!)) - - run_macro: (tree, kind="Expression")=> - name = @fn_name_from_tree(tree) - unless @defs[name] and @defs[name].is_macro - @error("Macro not found: #{name}") - unless @check_permission(name) - @error "You do not have the authority to call: #{name}" - {:fn, :arg_names} = @defs[name] - args = [a for a in *tree.value when a.type != "Word"] - args = {name,args[i] for i,name in ipairs(arg_names[name])} - insert @callstack, name - ret, manual_mode = fn(self, args, kind) - table.remove @callstack - if not ret - @error("No return value for macro: #{name}") - if kind == "Statement" and not manual_mode - ret = "ret = "..ret - return ret _yield_tree: (tree, indent_level=0)=> ind = (s) -> INDENT\rep(indent_level)..s @@ -431,42 +453,27 @@ class NomsuCompiler when "File" coroutine.yield(ind"File:") @_yield_tree(tree.value.body, indent_level+1) - - when "Errors" - coroutine.yield(ind"Error:\n#{tree.value}") - + when "Errors" then coroutine.yield(ind"Error:\n#{tree.value}") when "Block" for chunk in *tree.value @_yield_tree(chunk, indent_level) - when "Thunk" coroutine.yield(ind"Thunk:") @_yield_tree(tree.value, indent_level+1) - - when "Statement" - @_yield_tree(tree.value, indent_level) - + when "Statement" then @_yield_tree(tree.value, indent_level) when "FunctionCall" - name = @fn_name_from_tree(tree) + alias = @get_alias tree args = [a for a in *tree.value when a.type != "Word"] if #args == 0 - coroutine.yield(ind"Call [#{name}]!") + coroutine.yield(ind"Call [#{alias}]!") else - coroutine.yield(ind"Call [#{name}]:") + coroutine.yield(ind"Call [#{alias}]:") for a in *args @_yield_tree(a, indent_level+1) - - when "String" - -- TODO: Better implement - coroutine.yield(ind(utils.repr(tree.value))) - - when "Longstring" - -- TODO: Better implement - coroutine.yield(ind(utils.repr(tree.value))) - - when "Number" - coroutine.yield(ind(tree.value)) - + when "String" then coroutine.yield(ind(repr(tree.value))) + when "Longstring" then coroutine.yield(ind(repr(tree.value))) + when "Number" then coroutine.yield(ind(tree.value)) + when "Var" then coroutine.yield ind"Var[#{repr(tree.value)}]" when "List" if #tree.value == 0 coroutine.yield(ind("")) @@ -474,13 +481,7 @@ class NomsuCompiler coroutine.yield(ind"List:") for item in *tree.value @_yield_tree(item, indent_level+1) - - when "Var" - coroutine.yield ind"Var[#{utils.repr(tree.value)}]" - - else - error("Unknown/unimplemented thingy: #{tree.type}") - return nil -- to prevent tail calls + else error("Unknown/unimplemented thingy: #{tree.type}") print_tree:(tree)=> for line in coroutine.wrap(-> @_yield_tree(tree)) @@ -490,7 +491,7 @@ class NomsuCompiler result = {} for line in coroutine.wrap(-> @_yield_tree(tree)) insert(result, line) - return table.concat result, "\n" + return concat result, "\n" run: (src, filename, output_file=nil)=> if @debug @@ -533,14 +534,14 @@ class NomsuCompiler initialize_core: => -- Sets up some core functionality - @defmacro [[lua block %lua_code]], (vars, kind)=> + @defmacro "lua block %lua_code", (vars, kind)=> if kind == "Expression" then error("Expected to be in statement.") - inner_vars = setmetatable({}, {__index:(_,key)-> error"vars[#{utils.repr(key)}]"}) + inner_vars = setmetatable({}, {__index:(_,key)-> error"vars[#{repr(key)}]"}) return "do\n"..@tree_to_value(vars.lua_code, inner_vars).."\nend", true - @defmacro [[lua expr %lua_code]], (vars, kind)=> + @defmacro "lua expr %lua_code", (vars, kind)=> lua_code = vars.lua_code.value - inner_vars = setmetatable({}, {__index:(_,key)-> error"vars[#{utils.repr(key)}]"}) + inner_vars = setmetatable({}, {__index:(_,key)-> error"vars[#{repr(key)}]"}) return @tree_to_value(vars.lua_code, inner_vars) @def "require %filename", (vars)=> @@ -607,6 +608,6 @@ elseif arg break ok, ret = pcall(-> c\run(buff)) if ok and ret != nil - print "= "..utils.repr(ret) + print "= "..repr(ret) return NomsuCompiler diff --git a/utils.lua b/utils.lua index ddd2c3c..e058645 100644 --- a/utils.lua +++ b/utils.lua @@ -78,6 +78,14 @@ utils = { end return _accum_0 end, + remove_from_list = function(list, item) + for i, list_item in ipairs(list) do + if list_item == item then + table.remove(list, i) + return + end + end + end, accumulate = function(glue, co) if co == nil then glue, co = "", glue diff --git a/utils.moon b/utils.moon index 3b3573c..f267c7d 100644 --- a/utils.moon +++ b/utils.moon @@ -41,6 +41,12 @@ utils = { split: (str, sep="%s")-> [chunk for chunk in str\gmatch("[^#{sep}]+")] + remove_from_list: (list, item)-> + for i,list_item in ipairs(list) + if list_item == item + table.remove list, i + return + accumulate: (glue, co)-> if co == nil then glue, co = "", glue bits = {}