diff options
| author | Bruce Hill <bitbucket@bruce-hill.com> | 2018-01-08 18:53:57 -0800 |
|---|---|---|
| committer | Bruce Hill <bitbucket@bruce-hill.com> | 2018-01-08 18:53:57 -0800 |
| commit | f97ab858edae5495f8ebfef46656e86150665888 (patch) | |
| tree | ed47620eba2365192ba54950647d6307223cac29 /nomsu.moon | |
| parent | 568a44ef191e1f4072d379700e3b6f599150a92b (diff) | |
Modernized the codebase a bit to include "immediately:" for immediately
running code before further parsing takes place. That means that in the
default case, whole files can be run at once, which makes all but the
weirdest edge cases make a lot more sense and operate smoothly.
Diffstat (limited to 'nomsu.moon')
| -rwxr-xr-x | nomsu.moon | 276 |
1 files changed, 135 insertions, 141 deletions
@@ -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) + + @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)" - -- Uses named local functions to help out callstack readability - lua_code = (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 nil, lua - @defmacro "lua> %code", lua_code + return statements:lua - lua_value = (vars)=> + @defmacro "=lua %code", (vars)=> lua = nomsu_string_as_lua(@, vars.code) - return lua, nil - @defmacro "=lua %code", lua_value + 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 |
