diff --git a/lib/control_flow.nom b/lib/control_flow.nom index 33f72ab..5169ac2 100644 --- a/lib/control_flow.nom +++ b/lib/control_flow.nom @@ -43,7 +43,7 @@ rule [tree %tree has function call %call] =: return true; end end - do return false; end + return false; # While loops compile [do next repeat-loop] to code: "goto continue_repeat;" @@ -100,12 +100,14 @@ compile [..] %stop_labels join= "\n::stop_for::;" if (tree %body has function call (tree \(stop %) with {""=%var})): %stop_labels join= "\n::stop_\(nomsu "var_to_lua_identifier" [%var])::;" - if (%stop_labels != ""): ".." - do --for-loop label scope - \%code\ - ..\%stop_labels - end --for-loop label scope - ..else: %code + return (..) + ".." + do --for-loop label scope + \%code\ + ..\%stop_labels + end --for-loop label scope + ..if %stop_labels != "" else %code + parse [for %var from %start to %stop %body] as: for %var from %start to %stop via 1 %body parse [..] for all %start to %stop by %step %body @@ -131,11 +133,12 @@ compile [for %var in %iterable %body] to code: %stop_labels join= "\n::stop_for::;" if (tree %body has function call (tree \(stop %) with {""=%var})): %stop_labels join= "\n::stop_\(nomsu "var_to_lua_identifier" [%var])::;" - if (%stop_labels != ""): ".." - do --for-loop label scope - \%code\%stop_labels - end --for-loop label scope - ..else: %code + return (..) + ".." + do --for-loop label scope + \%code\%stop_labels + end --for-loop label scope + ..if %stop_labels != "" else %code parse [for all %iterable %body] as: for % in %iterable %body # Dict iteration (lua's "pairs()") @@ -161,12 +164,13 @@ compile [for %key = %value in %iterable %body] to code: %stop_labels join= "\n::stop_\(nomsu "var_to_lua_identifier" [%key])::;" if (tree %body has function call (tree \(stop %) with {""=%value})): %stop_labels join= "\n::stop_\(nomsu "var_to_lua_identifier" [%value])::;" - if (%stop_labels != ""): ".." - do --for-loop label scope - \%code\ - ..\%stop_labels - end --for-loop label scope - ..else: %code + return (..) + ".." + do --for-loop label scope + \%code\ + ..\%stop_labels + end --for-loop label scope + ..if %stop_labels != "" else %code # Switch statement/multi-branch if compile [when %body] to code: diff --git a/lib/metaprogramming.nom b/lib/metaprogramming.nom index 325f102..f59ce04 100644 --- a/lib/metaprogramming.nom +++ b/lib/metaprogramming.nom @@ -3,74 +3,93 @@ that easier. # Rule to make rules: -lua> ".." - nomsu:defmacro("rule %signature = %body", (function(nomsu, vars) - nomsu:assert(\%signature.type == "List", - "Invalid type for rule definition signature. Expected List, but got: "..tostring(\%signature.type)); - nomsu:assert(\%body.type == "Thunk", - "Invalid type for rule definition body. Expected Thunk, but got: "..tostring(\%body.type)); - local signature = {}; - for i, alias in ipairs(\%signature.value) do - signature[i] = alias.src; - end - local src = nomsu:source_code(0); - return nil, ([[ - nomsu:def(%s, %s, %s) - ]]):format(nomsu:repr(signature), nomsu:tree_to_lua(\%body), nomsu:repr(nomsu:dedent(src))); - end), \(__src__ 1)); - -# Rule to make lua macros: -rule [compile \%macro_def to \%body] =: +immediately: lua> ".." - nomsu:assert(\%macro_def.type == "List", - "Invalid type for compile definition signature. Expected List, but got: "..tostring(\%macro_def.type)); - nomsu:assert(\%body.type == "Thunk", - "Invalid type for compile definition body. Expected Thunk, but got: "..tostring(\%body.type)); - local signature = {}; - for i, alias in ipairs(\%macro_def.value) do - signature[i] = alias.src; - end - local thunk = nomsu:tree_to_value(\%body); - nomsu:defmacro(signature, thunk, ("compile %s\\n..to %s"):format(\%macro_def.src, \%body.src)); + nomsu:defmacro("rule %signature = %body", (function(nomsu, vars) + nomsu:assert(\%signature.type == "List", + "Invalid type for rule definition signature. Expected List, but got: "..tostring(\%signature.type)); + nomsu:assert(\%body.type == "Block", + "Invalid type for rule definition body. Expected Block, but got: "..tostring(\%body.type)); + local signature = {}; + for i, alias in ipairs(\%signature.value) do + signature[i] = alias.src; + end + local src = nomsu:source_code(0); + local body_lua = nomsu:tree_to_lua(\%body); + local fn_src = ([[ + function(nomsu, vars) + %s + end]]):format(body_lua.statements or ("return "..body_lua.expr..";")); + return {statements=([[ + nomsu:def(%s, %s, %s) + ]]):format(nomsu:repr(signature), fn_src, nomsu:repr(nomsu:dedent(src)))}; + end), \(__src__ 1)); -rule [compile \%macro_def to code \%body] =: - lua> ".." - nomsu:assert(\%macro_def.type == "List", - "Invalid type for compile definition signature. Expected List, but got: "..tostring(\%macro_def.type)); - nomsu:assert(\%body.type == "Thunk", - "Invalid type for compile definition body. Expected Thunk, but got: "..tostring(\%body.type)); - local signature = {}; - for i, alias in ipairs(\%macro_def.value) do - signature[i] = alias.src; - end - local thunk = nomsu:tree_to_value(\%body); - local thunk_wrapper = function(...) return nil, thunk(...); end - nomsu:defmacro(signature, thunk_wrapper, ("compile %s\\n..to code %s"):format(\%macro_def.src, \%body.src)); +# Rules to make lua macros: +immediately: + rule [compile \%macro_def to \%body] =: + lua> ".." + nomsu:assert(\%macro_def.type == "List", + "Invalid type for compile definition signature. Expected List, but got: "..tostring(\%macro_def.type)); + nomsu:assert(\%body.type == "Block", + "Invalid type for compile definition body. Expected Block, but got: "..tostring(\%body.type)); + local signature = {}; + for i, alias in ipairs(\%macro_def.value) do + signature[i] = alias.src; + end + local body_lua = nomsu:tree_to_lua(\%body); + local fn_src = ([[ + function(nomsu, vars) + %s + end]]):format(body_lua.statements or ("return "..body_lua.expr..";")); + local fn = nomsu:run_lua("return "..fn_src..";"); + local fn_wrapper = function(...) return {expr=fn(...)}; end; + nomsu:defmacro(signature, fn_wrapper, ("compile %s\\n..to %s"):format(\%macro_def.src, \%body.src)); + + rule [compile \%macro_def to code \%body] =: + lua> ".." + nomsu:assert(\%macro_def.type == "List", + "Invalid type for compile definition signature. Expected List, but got: "..tostring(\%macro_def.type)); + nomsu:assert(\%body.type == "Block", + "Invalid type for compile definition body. Expected Block, but got: "..tostring(\%body.type)); + local signature = {}; + for i, alias in ipairs(\%macro_def.value) do + signature[i] = alias.src; + end + local body_lua = nomsu:tree_to_lua(\%body); + local fn_src = ([[ + function(nomsu, vars) + %s + end]]):format(body_lua.statements or ("return "..body_lua.expr..";")); + local fn = nomsu:run_lua("return "..fn_src..";"); + local fn_wrapper = function(...) return {statements=fn(...)}; end; + nomsu:defmacro(signature, fn_wrapper, ("compile %s\\n..to code %s"):format(\%macro_def.src, \%body.src)); # Rule to make nomsu macros: -lua> ".." - nomsu:defmacro("parse %shorthand as %longhand", (function(nomsu, vars) - nomsu:assert(\%shorthand.type == "List", - "Invalid type for parse definition signature. Expected List, but got: "..tostring(\%shorthand.type)); - nomsu:assert(\%longhand.type == "Thunk", - "Invalid type for parse definition body. Expected Thunk, but got: "..tostring(\%longhand.type)); - local signature = {}; - for i, alias in ipairs(\%shorthand.value) do - signature[i] = alias.src; - end - local template = {}; - for i, line in ipairs(\%longhand.value) do - template[i] = nomsu:dedent(line.src); - end - signature, template = nomsu:repr(signature), nomsu:repr(table.concat(template, "\\n")); - return nil, ([[ - nomsu:defmacro(%s, (function(nomsu, vars) - local template = nomsu:parse(%s, %s); - if #template.value == 1 then template = template.value[1]; end - local replacement = nomsu:replaced_vars(template, vars); - return nomsu:tree_to_lua(replacement); - end), %s)]]):format(signature, template, nomsu:repr(\%shorthand.line_no), nomsu:repr(nomsu:source_code(0))); - end), \(__src__ 1)); +immediately: + lua> ".." + nomsu:defmacro("parse %shorthand as %longhand", (function(nomsu, vars) + nomsu:assert(\%shorthand.type == "List", + "Invalid type for parse definition signature. Expected List, but got: "..tostring(\%shorthand.type)); + nomsu:assert(\%longhand.type == "Block", + "Invalid type for parse definition body. Expected Block, but got: "..tostring(\%longhand.type)); + local signature = {}; + for i, alias in ipairs(\%shorthand.value) do + signature[i] = alias.src; + end + local template = {}; + for i, line in ipairs(\%longhand.value) do + template[i] = nomsu:dedent(line.src); + end + signature, template = nomsu:repr(signature), nomsu:repr(table.concat(template, "\\n")); + return {expr=([[ + nomsu:defmacro(%s, (function(nomsu, vars) + local template = nomsu:parse(%s, %s); + if #template.value == 1 then template = template.value[1]; end + local replacement = nomsu:replaced_vars(template, vars); + return nomsu:tree_to_lua(replacement); + end), %s)]]):format(signature, template, nomsu:repr(\%shorthand.line_no), nomsu:repr(nomsu:source_code(0)))}; + end), \(__src__ 1)); rule [remove rule %stub] =: lua> ".." @@ -79,40 +98,29 @@ rule [remove rule %stub] =: nomsu.defs[alias] = false; end -rule [%tree as lua] =: - =lua "nomsu:tree_to_lua(\%tree)" -rule [%tree as value] =: - =lua "nomsu:tree_to_value(\%tree, vars)" -compile [repr %obj] to: - "nomsu:repr(\(%obj as lua))" -compile [indented %obj] to: - "nomsu:indent(\(%obj as lua))" -compile [dedented %obj] to: - "nomsu:dedent(\(%obj as lua))" -compile [type %obj, type of %obj] to: - "type(\(%obj as lua))" +immediately: + rule [%tree as lua] =: + =lua "nomsu:tree_to_lua(\%tree).expr" + rule [%tree as lua statements] =: + lua> ".." + local lua = nomsu:tree_to_lua(\%tree); + return lua.statements or (lua.expr..";"); + rule [%tree as value] =: + =lua "nomsu:tree_to_value(\%tree, vars)" + compile [repr %obj] to: + "nomsu:repr(\(%obj as lua))" + compile [indented %obj] to: + "nomsu:indent(\(%obj as lua))" + compile [dedented %obj] to: + "nomsu:dedent(\(%obj as lua))" + compile [type %obj, type of %obj] to: + "type(\(%obj as lua))" parse [lua do> %block] as: lua> "do" lua> %block lua> "end" -rule [%tree as lua statement] =: - lua do> ".." - local _,statement = nomsu:tree_to_lua(\%tree); - return statement; -rule [%tree as lua statements] =: - lua do> ".." - local lua_bits = {}; - nomsu:assert(type(\%tree) == 'table' and \%tree.type == "Thunk", - "Invalid type. Expected Thunk."); - for _,bit in ipairs(\%tree.value) do - local expr, statement = nomsu:tree_to_lua(bit); - if statement then table.insert(lua_bits, statement); end - if expr then table.insert(lua_bits, "ret = "..expr..";"); end - end - return table.concat(lua_bits, "\\n"); - compile [nomsu] to: "nomsu" compile [nomsu's %key] to: "nomsu[\(%key as lua)]" compile [nomsu %method %args] to: "nomsu[\(%method as lua)](nomsu, unpack(\(%args as lua)))" @@ -124,7 +132,7 @@ parse [rule %signature] as: # Get the source code for a function rule [help %rule] =: - lua do> ".." + lua> ".." local fn_def = nomsu.defs[nomsu:get_stub(\%rule)] if not fn_def then nomsu:writeln("Rule not found: "..nomsu:repr(\%rule)); @@ -135,7 +143,7 @@ rule [help %rule] =: # Compiler tools parse [eval %code, run %code] as: nomsu "run" [%code] rule [source code from tree %tree] =: - lua do> ".." + lua> ".." local _,_,leading_space = \%tree.src:find("\\n(%s*)%S"); if leading_space then local chunk1, chunk2 = \%tree.src:match(":%s*([^\\n]*)(\\n.*)"); diff --git a/lib/operators.nom b/lib/operators.nom index 59a9ad2..2f08089 100644 --- a/lib/operators.nom +++ b/lib/operators.nom @@ -51,9 +51,9 @@ compile [%var = %val] to code: lua> ".." if \%var.type == 'List' and \%val.type == 'List' then local lhs = {}; - for i,x in ipairs(\%var.value) do lhs[i] = nomsu:tree_to_lua(x); end + for i,x in ipairs(\%var.value) do lhs[i] = nomsu:tree_to_lua(x).expr; end local rhs = {}; - for i,x in ipairs(\%val.value) do rhs[i] = nomsu:tree_to_lua(x); end + for i,x in ipairs(\%val.value) do rhs[i] = nomsu:tree_to_lua(x).expr; end return table.concat(lhs, ", ").." = "..table.concat(rhs, ", ")..";"; else return \(%var as lua).." = "..\(%val as lua)..";"; diff --git a/nomsu.lua b/nomsu.lua index 730ab13..39e83fc 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -194,7 +194,7 @@ do self:write_err(...) return self:write_err("\n") end, - def = function(self, signature, thunk, src, is_macro) + def = function(self, signature, fn, src, is_macro) if is_macro == nil then is_macro = false end @@ -205,13 +205,13 @@ do elseif type(signature) == 'table' and type(signature[1]) == 'string' then signature = self:get_stubs(signature) end - self:assert(type(thunk) == 'function', "Bad thunk: " .. tostring(repr(thunk))) + self:assert(type(fn) == 'function', "Bad fn: " .. tostring(repr(fn))) local canonical_args = nil local canonical_escaped_args = nil local aliases = { } self.__class.def_number = self.__class.def_number + 1 local def = { - thunk = thunk, + fn = fn, src = src, is_macro = is_macro, aliases = { }, @@ -256,8 +256,8 @@ do rawset(where_defs_go, stub, stub_def) end end, - defmacro = function(self, signature, thunk, src) - return self:def(signature, thunk, src, true) + defmacro = function(self, signature, fn, src) + return self:def(signature, fn, src, true) end, scoped = function(self, thunk) local old_defs = self.defs @@ -363,8 +363,8 @@ do if not (def.is_macro) then self:assert_permission(stub) end - local thunk, arg_names - thunk, arg_names = def.thunk, def.arg_names + local fn, arg_names + fn, arg_names = def.fn, def.arg_names local args do local _tbl_0 = { } @@ -375,12 +375,15 @@ do end if self.debug then self:write(tostring(colored.bright("CALLING")) .. " " .. tostring(colored.magenta(colored.underscore(stub))) .. " ") - self:writeln(tostring(colored.bright("WITH ARGS:")) .. " " .. tostring(colored.dim(repr(args)))) + self:writeln(tostring(colored.bright("WITH ARGS:"))) + for name, value in pairs(args) do + self:writeln(" " .. tostring(colored.bright("* " .. tostring(name))) .. " = " .. tostring(colored.dim(repr(value)))) + end end local old_defs old_defs, self.defs = self.defs, def.defs local rets = { - thunk(self, args) + fn(self, args) } self.defs = old_defs remove(self.callstack) @@ -406,9 +409,9 @@ do self:writeln(tostring(colored.bright("WITH ARGS:")) .. " " .. tostring(colored.dim(repr(args)))) end insert(self.callstack, "#macro") - local expr, statement = self:call(tree.stub, tree.line_no, unpack(args)) + local ret = self:call(tree.stub, tree.line_no, unpack(args)) remove(self.callstack) - return expr, statement + return ret end, dedent = function(self, code) if not (code:find("\n")) then @@ -448,8 +451,11 @@ do return (code:gsub("\n" .. (" "):rep(indent_spaces), "\n ")) end end, - indent = function(self, code) - return (code:gsub("\n", "\n ")) + indent = function(self, code, levels) + if levels == nil then + levels = 1 + end + return code:gsub("\n", "\n" .. (" "):rep(levels)) end, assert_permission = function(self, stub) local fn_def = self.defs[stub] @@ -525,72 +531,46 @@ do debug.sethook(timeout, "", max_operations) end local tree = self:parse(src, filename) - self:assert(tree, "Tree failed to compile: " .. tostring(src)) + self:assert(tree, "Failed to parse: " .. tostring(src)) self:assert(tree.type == "File", "Attempt to run non-file: " .. tostring(tree.type)) - local buffer = { } - local _list_0 = tree.value - for _index_0 = 1, #_list_0 do - local statement = _list_0[_index_0] - if self.debug then - self:writeln(tostring(colored.bright("RUNNING NOMSU:")) .. "\n" .. tostring(colored.bright(colored.yellow(statement.src)))) - self:writeln(colored.bright("PARSED TO TREE:")) - self:print_tree(statement) - end - local ok, expr, statements = pcall(self.tree_to_lua, self, statement, filename) - if not ok then - self:errorln(tostring(colored.red("Error occurred in statement:")) .. "\n" .. tostring(colored.bright(colored.yellow(statement.src)))) - error(expr) - end - local code_for_statement = ([[return (function(nomsu, vars) -%s -end);]]):format(statements or ("return " .. expr .. ";")) - if output_file then - if statements and #statements > 0 then - output_file:write("lua> \"..\"\n " .. tostring(self:indent(statements:gsub("\\", "\\\\"))) .. "\n") - end - if expr and #expr > 0 then - output_file:write("=lua \"..\"\n " .. tostring(self:indent(expr:gsub("\\", "\\\\"))) .. "\n") - end - end - if self.debug then - self:writeln(tostring(colored.bright("RUNNING LUA:")) .. "\n" .. tostring(colored.blue(colored.bright(code_for_statement)))) - end - local lua_thunk, err = load(code_for_statement) - if not lua_thunk then - local n = 1 - local fn - fn = function() - n = n + 1 - return ("\n%-3d|"):format(n) - end - local code = "1 |" .. code_for_statement:gsub("\n", fn) - error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(code)))) .. "\n\n" .. tostring(err) .. "\n\nProduced by statement:\n" .. tostring(colored.bright(colored.yellow(statement.src)))) - end - local run_statement = lua_thunk() - local ret - ok, ret = pcall(run_statement, self, vars) - if not ok then - self:errorln(tostring(colored.red("Error occurred in statement:")) .. "\n" .. tostring(colored.yellow(statement.src))) - self:errorln(debug.traceback()) - error(ret) - end - if statements then - insert(buffer, statements) - end - if expr then - insert(buffer, tostring(expr) .. ";") - end - end + local lua = self:tree_to_lua(tree) + local lua_code = lua.statements or (lua.expr .. ";") + local ret = self:run_lua(lua_code, vars) if max_operations then debug.sethook() end - local lua_code = ([[return (function(nomsu, vars) -%s -end);]]):format(concat(buffer, "\n")) - return nil, lua_code, vars + if output_file then + output_file:write(lua_code) + end + return ret, lua_code, vars + end, + run_lua = function(self, lua_code, vars) + if vars == nil then + vars = { } + end + local load_lua_fn, err = load(([[return function(nomsu, vars) + %s +end]]):format(lua_code)) + if not load_lua_fn then + local n = 1 + local fn + fn = function() + n = n + 1 + return ("\n%-3d|"):format(n) + end + local code = "1 |" .. lua_code:gsub("\n", fn) + self:error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(code)))) .. "\n\n" .. tostring(err)) + end + local run_lua_fn = load_lua_fn() + local ok, ret = pcall(run_lua_fn, self, vars) + if not ok then + self:errorln(debug.traceback()) + self:error(ret) + end + return ret end, tree_to_value = function(self, tree, vars, filename) - local code = "return (function(nomsu, vars)\nreturn " .. tostring(self:tree_to_lua(tree, filename)) .. ";\nend);" + local code = "return (function(nomsu, vars)\nreturn " .. tostring(self:tree_to_lua(tree, filename).expr) .. ";\nend);" if self.debug then self:writeln(tostring(colored.bright("RUNNING LUA TO GET VALUE:")) .. "\n" .. tostring(colored.blue(colored.bright(code)))) end @@ -625,9 +605,9 @@ end);]]):format(concat(buffer, "\n")) elseif "Nomsu" == _exp_0 then local inside, inline = self:tree_to_nomsu(tree.value, force_inline) return "\\" .. tostring(inside), inline - elseif "Thunk" == _exp_0 then + elseif "Block" == _exp_0 then if force_inline then - return "{" .. tostring(concat((function() + return "(:" .. tostring(concat((function() local _accum_0 = { } local _len_0 = 1 local _list_0 = tree.value @@ -637,7 +617,7 @@ end);]]):format(concat(buffer, "\n")) _len_0 = _len_0 + 1 end return _accum_0 - end)(), "; ")), true + end)(), "; ")) .. ")", true else return ":" .. self:indent("\n" .. concat((function() local _accum_0 = { } @@ -739,7 +719,7 @@ end);]]):format(concat(buffer, "\n")) return longbuff, false end elseif "Dict" == _exp_0 then - return error("Sorry, not yet implemented.") + return self:error("Sorry, not yet implemented.") elseif "Number" == _exp_0 then return repr(tree.value), true elseif "Var" == _exp_0 then @@ -771,7 +751,7 @@ end);]]):format(concat(buffer, "\n")) return _accum_0 end)(), ", ")) .. "]" else - return "(d{" .. tostring(concat((function() + return "{" .. tostring(concat((function() local _accum_0 = { } local _len_0 = 1 for k, v in pairs(value) do @@ -779,7 +759,7 @@ end);]]):format(concat(buffer, "\n")) _len_0 = _len_0 + 1 end return _accum_0 - end)(), "; ")) .. "})" + end)(), ", ")) .. "}" end elseif "string" == _exp_0 then if value == "\n" then @@ -801,49 +781,59 @@ end);]]):format(concat(buffer, "\n")) end local _exp_0 = tree.type if "File" == _exp_0 then + if #tree.value == 1 then + return self:tree_to_lua(tree.value[1], filename) + end local lua_bits = { } local _list_0 = tree.value for _index_0 = 1, #_list_0 do local line = _list_0[_index_0] - local expr, statement = self:tree_to_lua(line, filename) - if statement then - insert(lua_bits, statement) + local lua = self:tree_to_lua(line, filename) + if not lua then + self:error("No lua produced by " .. tostring(repr(line))) end - if expr then - insert(lua_bits, tostring(expr) .. ";") + if lua.statements then + insert(lua_bits, lua.statements) + end + if lua.expr then + insert(lua_bits, tostring(lua.expr) .. ";") end end - return nil, concat(lua_bits, "\n") + return { + statements = concat(lua_bits, "\n") + } elseif "Nomsu" == _exp_0 then - return "nomsu:parse(" .. tostring(repr(tree.value.src)) .. ", " .. tostring(repr(tree.line_no)) .. ").value[1]", nil - elseif "Thunk" == _exp_0 then + return { + expr = "nomsu:parse(" .. tostring(repr(tree.value.src)) .. ", " .. tostring(repr(tree.line_no)) .. ").value[1]" + } + elseif "Block" == _exp_0 then local lua_bits = { } local _list_0 = tree.value for _index_0 = 1, #_list_0 do local arg = _list_0[_index_0] - local expr, statement = self:tree_to_lua(arg, filename) - if #tree.value == 1 and expr and not statement then - return ([[(function(nomsu, vars) - return %s; -end)]]):format(expr) + local lua = self:tree_to_lua(arg, filename) + if #tree.value == 1 and lua.expr and not lua.statements then + return { + expr = lua.expr + } end - if statement then - insert(lua_bits, statement) + if lua.statements then + insert(lua_bits, lua.statements) end - if expr then - insert(lua_bits, tostring(expr) .. ";") + if lua.expr then + insert(lua_bits, tostring(lua.expr) .. ";") end end - return ([[(function(nomsu, vars) -%s -end)]]):format(concat(lua_bits, "\n")) + return { + statements = concat(lua_bits, "\n") + } elseif "FunctionCall" == _exp_0 then insert(self.compilestack, tree) local def = self.defs[tree.stub] if def and def.is_macro then - local expr, statement = self:run_macro(tree) + local lua = self:run_macro(tree) remove(self.compilestack) - return expr, statement + return lua elseif not def and self.__class.math_patt:match(tree.stub) then local bits = { } local _list_0 = tree.value @@ -852,12 +842,15 @@ end)]]):format(concat(lua_bits, "\n")) if tok.type == "Word" then insert(bits, tok.value) else - local expr, statement = self:tree_to_lua(tok, filename) - self:assert(statement == nil, "non-expression value inside math expression") - insert(bits, expr) + local lua = self:tree_to_lua(tok, filename) + self:assert(lua.statements == nil, "non-expression value inside math expression") + insert(bits, lua.expr) end end - return "(" .. tostring(concat(bits, " ")) .. ")" + remove(self.compilestack) + return { + expr = "(" .. tostring(concat(bits, " ")) .. ")" + } end local args = { repr(tree.stub), @@ -894,11 +887,11 @@ end)]]):format(concat(lua_bits, "\n")) if escaped_args[arg_names[arg_num]] then insert(args, "nomsu:parse(" .. tostring(repr(arg.src)) .. ", " .. tostring(repr(tree.line_no)) .. ").value[1]") else - local expr, statement = self:tree_to_lua(arg, filename) - if statement then + local lua = self:tree_to_lua(arg, filename) + if lua.statements then self:error("Cannot use [[" .. tostring(arg.src) .. "]] as a function argument, since it's not an expression.") end - insert(args, expr) + insert(args, lua.expr) end arg_num = arg_num + 1 _continue_0 = true @@ -908,7 +901,9 @@ end)]]):format(concat(lua_bits, "\n")) end end remove(self.compilestack) - return self.__class:comma_separated_items("nomsu:call(", args, ")"), nil + return { + expr = self.__class:comma_separated_items("nomsu:call(", args, ")") + } elseif "String" == _exp_0 then local concat_parts = { } local string_buffer = "" @@ -926,16 +921,16 @@ end)]]):format(concat(lua_bits, "\n")) insert(concat_parts, repr(string_buffer)) string_buffer = "" end - local expr, statement = self:tree_to_lua(bit, filename) + local lua = self:tree_to_lua(bit, filename) if self.debug then self:writeln((colored.bright("INTERP:"))) self:print_tree(bit) - self:writeln(tostring(colored.bright("EXPR:")) .. " " .. tostring(expr) .. ", " .. tostring(colored.bright("STATEMENT:")) .. " " .. tostring(statement)) + self:writeln(tostring(colored.bright("EXPR:")) .. " " .. tostring(lua.expr) .. ", " .. tostring(colored.bright("STATEMENT:")) .. " " .. tostring(lua.statements)) end - if statement then + if lua.statements then self:error("Cannot use [[" .. tostring(bit.src) .. "]] as a string interpolation value, since it's not an expression.") end - insert(concat_parts, "nomsu:stringify(" .. tostring(expr) .. ")") + insert(concat_parts, "nomsu:stringify(" .. tostring(lua.expr) .. ")") _continue_0 = true until true if not _continue_0 then @@ -946,57 +941,75 @@ end)]]):format(concat(lua_bits, "\n")) insert(concat_parts, repr(string_buffer)) end if #concat_parts == 0 then - return "''", nil + return { + expr = "''" + } elseif #concat_parts == 1 then - return concat_parts[1], nil + return { + expr = concat_parts[1] + } else - return "(" .. tostring(concat(concat_parts, "..")) .. ")", nil + return { + expr = "(" .. tostring(concat(concat_parts, "..")) .. ")" + } end elseif "List" == _exp_0 then local items = { } local _list_0 = tree.value for _index_0 = 1, #_list_0 do local item = _list_0[_index_0] - local expr, statement = self:tree_to_lua(item, filename) - if statement then + local lua = self:tree_to_lua(item, filename) + if lua.statements then self:error("Cannot use [[" .. tostring(item.src) .. "]] as a list item, since it's not an expression.") end - insert(items, expr) + insert(items, lua.expr) end - return self.__class:comma_separated_items("{", items, "}"), nil + return { + expr = self.__class:comma_separated_items("{", items, "}") + } elseif "Dict" == _exp_0 then local items = { } local _list_0 = tree.value for _index_0 = 1, #_list_0 do local entry = _list_0[_index_0] - local key_expr, key_statement + local key_lua if entry.dict_key.type == "Word" then - key_expr, key_statement = repr(entry.dict_key.value), nil + key_lua = { + expr = repr(entry.dict_key.value) + } else - key_expr, key_statement = self:tree_to_lua(entry.dict_key, filename) + key_lua = self:tree_to_lua(entry.dict_key, filename) end - if key_statement then + if key_lua.statements then self:error("Cannot use [[" .. tostring(entry.dict_key.src) .. "]] as a dict key, since it's not an expression.") end - local value_expr, value_statement = self:tree_to_lua(entry.dict_value, filename) - if value_statement then + local value_lua = self:tree_to_lua(entry.dict_value, filename) + if value_lua.statements then self:error("Cannot use [[" .. tostring(entry.dict_value.src) .. "]] as a dict value, since it's not an expression.") end - local key_str = key_expr:match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) + local key_str = key_lua.expr:match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) if key_str then - insert(items, tostring(key_str) .. "=" .. tostring(value_expr)) + insert(items, tostring(key_str) .. "=" .. tostring(value_lua.expr)) else - insert(items, "[" .. tostring(key_expr) .. "]=" .. tostring(value_expr)) + insert(items, "[" .. tostring(key_lua.expr) .. "]=" .. tostring(value_lua.expr)) end end - return self.__class:comma_separated_items("{", items, "}"), nil + return { + expr = self.__class:comma_separated_items("{", items, "}") + } elseif "Number" == _exp_0 then - return repr(tree.value), nil + return { + expr = repr(tree.value) + } elseif "Var" == _exp_0 then if tree.value:match("^[a-zA-Z_][a-zA-Z0-9_]*$") then - return "vars." .. tostring(tree.value), nil + return { + expr = "vars." .. tostring(tree.value) + } else - return "vars[" .. tostring(repr(tree.value)) .. "]", nil + return { + expr = "vars[" .. tostring(repr(tree.value)) .. "]" + } end else return self:error("Unknown/unimplemented thingy: " .. tostring(tree.type)) @@ -1011,7 +1024,7 @@ end)]]):format(concat(lua_bits, "\n")) return end local _exp_0 = tree.type - if "List" == _exp_0 or "File" == _exp_0 or "Thunk" == _exp_0 or "FunctionCall" == _exp_0 or "String" == _exp_0 then + if "List" == _exp_0 or "File" == _exp_0 or "Block" == _exp_0 or "FunctionCall" == _exp_0 or "String" == _exp_0 then local _list_0 = tree.value for _index_0 = 1, #_list_0 do local v = _list_0[_index_0] @@ -1064,7 +1077,7 @@ end)]]):format(concat(lua_bits, "\n")) if vars[tree.value] ~= nil then tree = vars[tree.value] end - elseif "File" == _exp_0 or "Nomsu" == _exp_0 or "Thunk" == _exp_0 or "List" == _exp_0 or "FunctionCall" == _exp_0 or "String" == _exp_0 then + elseif "File" == _exp_0 or "Nomsu" == _exp_0 or "Block" == _exp_0 or "List" == _exp_0 or "FunctionCall" == _exp_0 or "String" == _exp_0 then local new_value = self:replaced_vars(tree.value, vars) if new_value ~= tree.value then do @@ -1281,43 +1294,52 @@ end)]]):format(concat(lua_bits, "\n")) if type(bit) == "string" then insert(concat_parts, bit) else - local expr, statement = self:tree_to_lua(bit, filename) - if statement then + local lua = self:tree_to_lua(bit, filename) + if lua.statements then self:error("Cannot use [[" .. tostring(bit.src) .. "]] as a string interpolation value, since it's not an expression.") end - insert(concat_parts, expr) + insert(concat_parts, lua.expr) end end return concat(concat_parts) end - local lua_code - lua_code = function(self, vars) - local lua = nomsu_string_as_lua(self, vars.code) - return nil, lua - end - self:defmacro("lua> %code", lua_code) - local lua_value - lua_value = function(self, vars) - local lua = nomsu_string_as_lua(self, vars.code) - return lua, nil - end - self:defmacro("=lua %code", lua_value) - self:defmacro("__src__ %level", function(self, vars) - return self:repr(self:source_code(self:tree_to_value(vars.level))) + self:defmacro("do %block", function(self, vars) + local make_line + make_line = function(lua) + return lua.expr and (lua.expr .. ";") or lua.statements + end + if vars.block.type == "Block" then + return self:tree_to_lua(vars.block) + else + return { + expr = tostring(self:tree_to_lua(vars.block)) .. "(nomsu, vars)" + } + end end) - self:def("derp \\%foo derp \\%bar", function(self, vars) - local lua = "local x = " .. repr((function() - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = vars.foo.value - for _index_0 = 1, #_list_0 do - local t = _list_0[_index_0] - _accum_0[_len_0] = t.stub - _len_0 = _len_0 + 1 - end - return _accum_0 - end)()) .. ";\nlocal y = " .. self:tree_to_lua(vars.bar) - return print(colored.green(lua)) + self:defmacro("immediately %block", function(self, vars) + local lua = self:tree_to_lua(vars.block) + local lua_code = lua.statements or (lua.expr .. ";") + self:run_lua(lua_code, vars) + return { + statements = lua_code + } + end) + self:defmacro("lua> %code", function(self, vars) + local lua = nomsu_string_as_lua(self, vars.code) + return { + statements = lua + } + end) + self:defmacro("=lua %code", function(self, vars) + local lua = nomsu_string_as_lua(self, vars.code) + return { + expr = lua + } + end) + self:defmacro("__src__ %level", function(self, vars) + return { + expr = repr(self:source_code(self:tree_to_value(vars.level))) + } end) local run_file run_file = function(self, vars) @@ -1340,17 +1362,19 @@ end)]]):format(concat(lua_bits, "\n")) end end self:def("run file %filename", run_file) - local _require - _require = function(self, vars) + return self:defmacro("require %filename", function(self, vars) + local filename = self:tree_to_value(vars.filename) local loaded = self.defs["#loaded_files"] - if not loaded[vars.filename] then - loaded[vars.filename] = run_file(self, { - filename = vars.filename + if not loaded[filename] then + loaded[filename] = run_file(self, { + filename = filename }) or true end - return loaded[vars.filename] - end - return self:def("require %filename", _require) + local _ = loaded[filename] + return { + statements = "" + } + end) end } _base_0.__index = _base_0 @@ -1476,7 +1500,10 @@ if arg then input = io.open(args.input):read("*a") end local vars = { } - local retval, code = c:run(input, args.input, vars, nil, compiled_output) + local retval, code = c:run(input, args.input, vars) + if args.output then + compiled_output:write("lua> \"..\"\n " .. c:indent(code:gsub("\\", "\\\\"), 1)) + end end if args.flags["-p"] then c.write = _write diff --git a/nomsu.moon b/nomsu.moon index 8f77c87..03005bb 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -36,6 +36,7 @@ if _VERSION == "Lua 5.1" -- type checking? -- Fix compiler bug that breaks when file ends with a block comment -- Add compiler options for optimization level (compile-fast vs. run-fast, etc.) +-- Do a pass on all rules to enforce parameters-are-nouns heuristic lpeg.setmaxstack 10000 -- whoa {:P,:R,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg @@ -164,17 +165,17 @@ class NomsuCompiler @write_err(...) @write_err("\n") - def: (signature, thunk, src, is_macro=false)=> + def: (signature, fn, src, is_macro=false)=> if type(signature) == 'string' signature = @get_stubs {signature} elseif type(signature) == 'table' and type(signature[1]) == 'string' signature = @get_stubs signature - @assert type(thunk) == 'function', "Bad thunk: #{repr thunk}" + @assert type(fn) == 'function', "Bad fn: #{repr fn}" canonical_args = nil canonical_escaped_args = nil aliases = {} @@def_number += 1 - def = {:thunk, :src, :is_macro, aliases:{}, def_number:@@def_number, defs:@defs} + def = {:fn, :src, :is_macro, aliases:{}, def_number:@@def_number, defs:@defs} where_defs_go = (getmetatable(@defs) or {}).__newindex or @defs for {stub, arg_names, escaped_args} in *signature @assert stub, "NO STUB FOUND: #{repr signature}" @@ -193,8 +194,8 @@ class NomsuCompiler stub_def = setmetatable({:stub, :arg_names, :escaped_args}, {__index:def}) rawset(where_defs_go, stub, stub_def) - defmacro: (signature, thunk, src)=> - @def(signature, thunk, src, true) + defmacro: (signature, fn, src)=> + @def(signature, fn, src, true) scoped: (thunk)=> old_defs = @defs @@ -253,13 +254,15 @@ class NomsuCompiler @error "Attempt to call undefined function: #{stub}" unless def.is_macro @assert_permission(stub) - {:thunk, :arg_names} = def + {:fn, :arg_names} = def args = {name, select(i,...) for i,name in ipairs(arg_names)} if @debug @write "#{colored.bright "CALLING"} #{colored.magenta(colored.underscore stub)} " - @writeln "#{colored.bright "WITH ARGS:"} #{colored.dim repr(args)}" + @writeln "#{colored.bright "WITH ARGS:"}" + for name, value in pairs(args) + @writeln " #{colored.bright "* #{name}"} = #{colored.dim repr(value)}" old_defs, @defs = @defs, def.defs - rets = {thunk(self,args)} + rets = {fn(self,args)} @defs = old_defs remove @callstack return unpack(rets) @@ -270,9 +273,9 @@ class NomsuCompiler @write "#{colored.bright "RUNNING MACRO"} #{colored.underscore colored.magenta(tree.stub)} " @writeln "#{colored.bright "WITH ARGS:"} #{colored.dim repr args}" insert @callstack, "#macro" - expr, statement = @call(tree.stub, tree.line_no, unpack(args)) + ret = @call(tree.stub, tree.line_no, unpack(args)) remove @callstack - return expr, statement + return ret dedent: (code)=> unless code\find("\n") @@ -290,8 +293,8 @@ class NomsuCompiler else return (code\gsub("\n"..(" ")\rep(indent_spaces), "\n ")) - indent: (code)=> - (code\gsub("\n","\n ")) + indent: (code, levels=1)=> + return code\gsub("\n","\n"..(" ")\rep(levels)) assert_permission: (stub)=> fn_def = @defs[stub] @@ -338,60 +341,40 @@ class NomsuCompiler @error "Execution quota exceeded. Your code took too long." debug.sethook timeout, "", max_operations tree = @parse(src, filename) - @assert tree, "Tree failed to compile: #{src}" + @assert tree, "Failed to parse: #{src}" @assert tree.type == "File", "Attempt to run non-file: #{tree.type}" - buffer = {} - -- TODO: handle return statements in a file - for statement in *tree.value - if @debug - @writeln "#{colored.bright "RUNNING NOMSU:"}\n#{colored.bright colored.yellow statement.src}" - @writeln colored.bright("PARSED TO TREE:") - @print_tree statement - ok,expr,statements = pcall(@tree_to_lua, self, statement, filename) - if not ok - @errorln "#{colored.red "Error occurred in statement:"}\n#{colored.bright colored.yellow statement.src}" - error(expr) - code_for_statement = ([[ -return (function(nomsu, vars) -%s -end);]])\format(statements or ("return "..expr..";")) - if output_file - if statements and #statements > 0 - output_file\write "lua> \"..\"\n #{@indent statements\gsub("\\","\\\\")}\n" - if expr and #expr > 0 - output_file\write "=lua \"..\"\n #{@indent expr\gsub("\\","\\\\")}\n" - if @debug - @writeln "#{colored.bright "RUNNING LUA:"}\n#{colored.blue colored.bright(code_for_statement)}" - lua_thunk, err = load(code_for_statement) - if not lua_thunk - n = 1 - fn = -> - n = n + 1 - ("\n%-3d|")\format(n) - code = "1 |"..code_for_statement\gsub("\n", fn) - error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{err}\n\nProduced by statement:\n#{colored.bright colored.yellow statement.src}") - run_statement = lua_thunk! - ok,ret = pcall(run_statement, self, vars) - if not ok - @errorln "#{colored.red "Error occurred in statement:"}\n#{colored.yellow statement.src}" - @errorln debug.traceback! - error(ret) - if statements - insert buffer, statements - if expr - insert buffer, "#{expr};" - + lua = @tree_to_lua(tree) + lua_code = lua.statements or (lua.expr..";") + ret = @run_lua(lua_code, vars) if max_operations debug.sethook! - lua_code = ([[ -return (function(nomsu, vars) -%s -end);]])\format(concat(buffer, "\n")) - return nil, lua_code, vars + if output_file + output_file\write(lua_code) + return ret, lua_code, vars + + run_lua: (lua_code, vars={})=> + load_lua_fn, err = load([[ +return function(nomsu, vars) + %s +end]]\format(lua_code)) + if not load_lua_fn + n = 1 + fn = -> + n = n + 1 + ("\n%-3d|")\format(n) + code = "1 |"..lua_code\gsub("\n", fn) + @error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{err}") + run_lua_fn = load_lua_fn! + ok,ret = pcall(run_lua_fn, self, vars) + if not ok + --@errorln "#{colored.red "Error occurred in statement:"}\n#{colored.yellow tree.src}" + @errorln debug.traceback! + @error(ret) + return ret tree_to_value: (tree, vars, filename)=> - code = "return (function(nomsu, vars)\nreturn #{@tree_to_lua(tree, filename)};\nend);" + code = "return (function(nomsu, vars)\nreturn #{@tree_to_lua(tree, filename).expr};\nend);" if @debug @writeln "#{colored.bright "RUNNING LUA TO GET VALUE:"}\n#{colored.blue colored.bright(code)}" lua_thunk, err = load(code) @@ -413,9 +396,9 @@ end);]])\format(concat(buffer, "\n")) inside, inline = @tree_to_nomsu(tree.value, force_inline) return "\\#{inside}", inline - when "Thunk" + when "Block" if force_inline - return "{#{concat([@tree_to_nomsu(v, true) for v in *tree.value], "; ")}", true + return "(:#{concat([@tree_to_nomsu(v, true) for v in *tree.value], "; ")})", true else return ":"..@indent("\n"..concat([@tree_to_nomsu v for v in *tree.value], "\n")), false @@ -491,7 +474,8 @@ end);]])\format(concat(buffer, "\n")) return longbuff, false when "Dict" - error("Sorry, not yet implemented.") + -- TODO: Implement + @error("Sorry, not yet implemented.") when "Number" return repr(tree.value), true @@ -517,7 +501,7 @@ end);]])\format(concat(buffer, "\n")) if is_list(value) return "[#{concat [@value_to_nomsu(v) for v in *value], ", "}]" else - return "(d{#{concat ["#{@value_to_nomsu(k)}=#{@value_to_nomsu(v)}" for k,v in pairs(value)], "; "}})" + return "{#{concat ["#{@value_to_nomsu(k)}=#{@value_to_nomsu(v)}" for k,v in pairs(value)], ", "}}" when "string" if value == "\n" return "'\\n'" @@ -538,50 +522,52 @@ end);]])\format(concat(buffer, "\n")) @error "Invalid tree: #{repr(tree)}" switch tree.type when "File" + if #tree.value == 1 + return @tree_to_lua(tree.value[1], filename) lua_bits = {} for line in *tree.value - expr,statement = @tree_to_lua line, filename - if statement then insert lua_bits, statement - if expr then insert lua_bits, "#{expr};" - return nil, concat(lua_bits, "\n") + lua = @tree_to_lua line, filename + if not lua + @error "No lua produced by #{repr line}" + if lua.statements then insert lua_bits, lua.statements + if lua.expr then insert lua_bits, "#{lua.expr};" + return statements:concat(lua_bits, "\n") when "Nomsu" - return "nomsu:parse(#{repr tree.value.src}, #{repr tree.line_no}).value[1]", nil + return expr:"nomsu:parse(#{repr tree.value.src}, #{repr tree.line_no}).value[1]" - when "Thunk" + when "Block" lua_bits = {} for arg in *tree.value - expr,statement = @tree_to_lua arg, filename - if #tree.value == 1 and expr and not statement - return ([[ -(function(nomsu, vars) - return %s; -end)]])\format(expr) - if statement then insert lua_bits, statement - if expr then insert lua_bits, "#{expr};" - return ([[ -(function(nomsu, vars) -%s -end)]])\format(concat(lua_bits, "\n")) + lua = @tree_to_lua arg, filename + if #tree.value == 1 and lua.expr and not lua.statements + return expr:lua.expr + if lua.statements then insert lua_bits, lua.statements + if lua.expr then insert lua_bits, "#{lua.expr};" + return statements:concat(lua_bits, "\n") when "FunctionCall" insert @compilestack, tree def = @defs[tree.stub] if def and def.is_macro - expr, statement = @run_macro(tree) + lua = @run_macro(tree) remove @compilestack - return expr, statement + return lua elseif not def and @@math_patt\match(tree.stub) + -- This is a bit of a hack, but this code handles arbitrarily complex + -- math expressions like 2*x + 3^2 without having to define a single + -- rule for every possibility. bits = {} for tok in *tree.value if tok.type == "Word" insert bits, tok.value else - expr, statement = @tree_to_lua(tok, filename) - @assert(statement == nil, "non-expression value inside math expression") - insert bits, expr - return "(#{concat bits, " "})" + lua = @tree_to_lua(tok, filename) + @assert(lua.statements == nil, "non-expression value inside math expression") + insert bits, lua.expr + remove @compilestack + return expr:"(#{concat bits, " "})" args = {repr(tree.stub), repr(tree.line_no)} local arg_names, escaped_args @@ -595,14 +581,14 @@ end)]])\format(concat(lua_bits, "\n")) if escaped_args[arg_names[arg_num]] insert args, "nomsu:parse(#{repr arg.src}, #{repr tree.line_no}).value[1]" else - expr,statement = @tree_to_lua arg, filename - if statement + lua = @tree_to_lua arg, filename + if lua.statements @error "Cannot use [[#{arg.src}]] as a function argument, since it's not an expression." - insert args, expr + insert args, lua.expr arg_num += 1 remove @compilestack - return @@comma_separated_items("nomsu:call(", args, ")"), nil + return expr:@@comma_separated_items("nomsu:call(", args, ")") when "String" concat_parts = {} @@ -614,61 +600,60 @@ end)]])\format(concat(lua_bits, "\n")) if string_buffer ~= "" insert concat_parts, repr(string_buffer) string_buffer = "" - expr, statement = @tree_to_lua bit, filename + lua = @tree_to_lua bit, filename if @debug @writeln (colored.bright "INTERP:") @print_tree bit - @writeln "#{colored.bright "EXPR:"} #{expr}, #{colored.bright "STATEMENT:"} #{statement}" - if statement + @writeln "#{colored.bright "EXPR:"} #{lua.expr}, #{colored.bright "STATEMENT:"} #{lua.statements}" + if lua.statements @error "Cannot use [[#{bit.src}]] as a string interpolation value, since it's not an expression." - insert concat_parts, "nomsu:stringify(#{expr})" + insert concat_parts, "nomsu:stringify(#{lua.expr})" if string_buffer ~= "" insert concat_parts, repr(string_buffer) if #concat_parts == 0 - return "''", nil + return expr:"''" elseif #concat_parts == 1 - return concat_parts[1], nil - else return "(#{concat(concat_parts, "..")})", nil + return expr:concat_parts[1] + else return expr:"(#{concat(concat_parts, "..")})" when "List" items = {} for item in *tree.value - expr,statement = @tree_to_lua item, filename - if statement + lua = @tree_to_lua item, filename + if lua.statements @error "Cannot use [[#{item.src}]] as a list item, since it's not an expression." - insert items, expr - return @@comma_separated_items("{", items, "}"), nil + insert items, lua.expr + return expr:@@comma_separated_items("{", items, "}") when "Dict" items = {} for entry in *tree.value - local key_expr,key_statement - if entry.dict_key.type == "Word" - key_expr,key_statement = repr(entry.dict_key.value),nil + key_lua = if entry.dict_key.type == "Word" + {expr:repr(entry.dict_key.value)} else - key_expr,key_statement = @tree_to_lua entry.dict_key, filename - if key_statement + @tree_to_lua entry.dict_key, filename + if key_lua.statements @error "Cannot use [[#{entry.dict_key.src}]] as a dict key, since it's not an expression." - value_expr,value_statement = @tree_to_lua entry.dict_value, filename - if value_statement + value_lua = @tree_to_lua entry.dict_value, filename + if value_lua.statements @error "Cannot use [[#{entry.dict_value.src}]] as a dict value, since it's not an expression." - key_str = key_expr\match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) + key_str = key_lua.expr\match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) if key_str - insert items, "#{key_str}=#{value_expr}" + insert items, "#{key_str}=#{value_lua.expr}" else - insert items, "[#{key_expr}]=#{value_expr}" - return @@comma_separated_items("{", items, "}"), nil + insert items, "[#{key_lua.expr}]=#{value_lua.expr}" + return expr:@@comma_separated_items("{", items, "}") when "Number" - return repr(tree.value), nil + return expr:repr(tree.value) when "Var" if tree.value\match("^[a-zA-Z_][a-zA-Z0-9_]*$") - return "vars.#{tree.value}", nil + return expr:"vars.#{tree.value}" else - return "vars[#{repr tree.value}]", nil + return expr:"vars[#{repr tree.value}]" else @error("Unknown/unimplemented thingy: #{tree.type}") @@ -678,7 +663,7 @@ end)]])\format(concat(lua_bits, "\n")) if type(tree) != 'table' or not tree.type return switch tree.type - when "List", "File", "Thunk", "FunctionCall", "String" + when "List", "File", "Block", "FunctionCall", "String" for v in *tree.value @walk_tree(v, depth+1) when "Dict" @@ -728,7 +713,7 @@ end)]])\format(concat(lua_bits, "\n")) when "Var" if vars[tree.value] ~= nil tree = vars[tree.value] - when "File", "Nomsu", "Thunk", "List", "FunctionCall", "String" + when "File", "Nomsu", "Block", "List", "FunctionCall", "String" new_value = @replaced_vars tree.value, vars if new_value != tree.value tree = {k,v for k,v in pairs(tree)} @@ -834,29 +819,35 @@ end)]])\format(concat(lua_bits, "\n")) if type(bit) == "string" insert concat_parts, bit else - expr, statement = @tree_to_lua bit, filename - if statement + lua = @tree_to_lua bit, filename + if lua.statements @error "Cannot use [[#{bit.src}]] as a string interpolation value, since it's not an expression." - insert concat_parts, expr + insert concat_parts, lua.expr return concat(concat_parts) - - -- Uses named local functions to help out callstack readability - lua_code = (vars)=> - lua = nomsu_string_as_lua(@, vars.code) - return nil, lua - @defmacro "lua> %code", lua_code - lua_value = (vars)=> + @defmacro "do %block", (vars)=> + make_line = (lua)-> lua.expr and (lua.expr..";") or lua.statements + if vars.block.type == "Block" + return @tree_to_lua(vars.block) + else + return expr:"#{@tree_to_lua vars.block}(nomsu, vars)" + + @defmacro "immediately %block", (vars)=> + lua = @tree_to_lua(vars.block) + lua_code = lua.statements or (lua.expr..";") + @run_lua(lua_code, vars) + return statements:lua_code + + @defmacro "lua> %code", (vars)=> lua = nomsu_string_as_lua(@, vars.code) - return lua, nil - @defmacro "=lua %code", lua_value + return statements:lua + + @defmacro "=lua %code", (vars)=> + lua = nomsu_string_as_lua(@, vars.code) + return expr:lua @defmacro "__src__ %level", (vars)=> - @repr @source_code @tree_to_value vars.level - - @def "derp \\%foo derp \\%bar", (vars)=> - lua = "local x = "..repr([t.stub for t in *vars.foo.value])..";\nlocal y = "..@tree_to_lua(vars.bar) - print(colored.green lua) + expr: repr(@source_code(@tree_to_value(vars.level))) run_file = (vars)=> if vars.filename\match(".*%.lua") @@ -873,13 +864,13 @@ end)]])\format(concat(lua_bits, "\n")) else @error "Invalid filetype for #{vars.filename}" @def "run file %filename", run_file - - _require = (vars)=> + @defmacro "require %filename", (vars)=> + filename = @tree_to_value(vars.filename) loaded = @defs["#loaded_files"] - if not loaded[vars.filename] - loaded[vars.filename] = run_file(self, {filename:vars.filename}) or true - return loaded[vars.filename] - @def "require %filename", _require + if not loaded[filename] + loaded[filename] = run_file(self, {:filename}) or true + loaded[filename] + return statements:"" if arg @@ -923,7 +914,10 @@ if arg io.read('*a') else io.open(args.input)\read("*a") vars = {} - retval, code = c\run(input, args.input, vars, nil, compiled_output) + retval, code = c\run(input, args.input, vars) + if args.output + compiled_output\write("lua> \"..\"\n "..c\indent(code\gsub("\\","\\\\"), 1)) + if args.flags["-p"] c.write = _write diff --git a/nomsu.peg b/nomsu.peg index 2946891..0bd51df 100644 --- a/nomsu.peg +++ b/nomsu.peg @@ -11,12 +11,12 @@ statement: functioncall / expression noeol_statement: noeol_functioncall / noeol_expression inline_statement: inline_functioncall / inline_expression -inline_thunk (Thunk): {| ":" %ws* inline_statement |} -eol_thunk (Thunk): {| ":" %ws* noeol_statement eol |} -indented_thunk (Thunk): +inline_block (Block): {| ":" %ws* inline_statement |} +eol_block (Block): {| ":" %ws* noeol_statement eol |} +indented_block (Block): {| ":" indent statement (nodent statement)* - (dedent / (("" -> "Error while parsing thunk") => error)) + (dedent / (("" -> "Error while parsing block") => error)) |} inline_nomsu (Nomsu): "\" inline_expression @@ -25,18 +25,18 @@ indented_nomsu (Nomsu): "\" expression inline_expression: number / variable / inline_string / inline_list / inline_dict / inline_nomsu - / ("(" %ws* (inline_thunk / inline_statement) %ws* ")") + / ("(" %ws* (inline_block / inline_statement) %ws* ")") noeol_expression: - indented_string / indented_nomsu / indented_list / indented_dict / indented_thunk + indented_string / indented_nomsu / indented_list / indented_dict / indented_block / ("(..)" indent statement (dedent / (("" -> "Error while parsing indented expression") => error)) ) / inline_expression -expression: eol_thunk / eol_nomsu / noeol_expression +expression: eol_block / eol_nomsu / noeol_expression -- Function calls need at least one word in them inline_functioncall (FunctionCall): - {| (inline_expression %ws*)* word (%ws* (inline_expression / word))* (%ws* inline_thunk)?|} + {| (inline_expression %ws*)* word (%ws* (inline_expression / word))* (%ws* inline_block)?|} noeol_functioncall (FunctionCall): {| (noeol_expression %ws*)* word (%ws* (noeol_expression / word))* |} functioncall (FunctionCall): @@ -57,7 +57,7 @@ indented_string (String): ~} / string_interpolation)* |} ((!.) / (&(%nl+ !%gt_nodented)) / (("" -> "Error while parsing String") => error)) string_interpolation: - "\" (variable / inline_list / inline_dict / inline_string / ("(" %ws* (inline_thunk / inline_statement) %ws* ")")) + "\" (variable / inline_list / inline_dict / inline_string / ("(" %ws* (inline_block / inline_statement) %ws* ")")) number (Number): (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)))-> tonumber)