From af3274ca9237a08093009e87955e30ab5f473f12 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Sun, 24 Sep 2017 20:20:27 -0700 Subject: massive overhaul, compiler kinda works. --- nomsu.moon | 544 +++++++++++++++++++++++++++---------------------------------- 1 file changed, 237 insertions(+), 307 deletions(-) (limited to 'nomsu.moon') diff --git a/nomsu.moon b/nomsu.moon index 8d5dd43..a3a0f30 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -1,4 +1,16 @@ #!/usr/bin/env moon +-- This file contains the source code of the Nomsu compiler. +-- Nomsu is a programming language that cross-compiles to Lua. It was designed to be good +-- at natural-language-like code that is highly self-modifying and flexible. +-- The only dependency is LPEG, which can be installed using "luarocks install lpeg" +-- File usage: +-- Either, in a lua/moonscript file: +-- Nomsu = require "nomsu" +-- nomsu = Nomsu() +-- nomsu:run(your_nomsu_code) +-- Or from the command line: +-- lua nomsu.lua [input_file [output_file or -]] + re = require 're' lpeg = require 'lpeg' utils = require 'utils' @@ -7,28 +19,20 @@ repr = utils.repr pcall = (fn,...)-> true, fn(...) -- TODO: +-- use actual variables instead of a vars table +-- have macros return (statements, expression) +-- consider non-linear codegen, like with moonscript's comprehensions, rather than doing thunks -- improve indentation of generated lua code --- provide way to run precompiled nomsu -> lua code +-- provide way to run precompiled nomsu -> lua code from nomsu -- better scoping? --- first-class rules -- better error reporting -- add line numbers of function calls --- versions of rules with auto-supplied arguments -- type checking? -INDENT = " " 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 = (x)-> setmetatable(x, parsetree_mt) - -functiondef_mt = {__tostring:=> "FunctionDef(#{repr(@aliases)}"} -FunctionDef = (fn, aliases, src, is_macro)-> - setmetatable({:fn, :aliases, :src, :is_macro}, functiondef_mt) - -- NOTE: this treats tabs as equivalent to 1 space indent_stack = {0} check_indent = (subject,end_pos,spaces)-> @@ -43,70 +47,81 @@ check_nodent = (subject,end_pos,spaces)-> if #spaces == indent_stack[#indent_stack] return end_pos +-- TYPES: +-- Number 1, "String", %Var, [List], (Block), \(Nomsu), FunctionCall, File + nomsu = [=[ - file <- ({ {| shebang? {:body: block :} %nl* (({.+} ("" -> "Unexpected end of file")) => error)? |} }) -> File + file <- ({ {| shebang? + (ignored_line %nl)* + statements (nodent statements)* + (%nl ignored_line)* %nl? + (({.+} ("" -> "Unexpected end of file")) => error)? |} }) -> File shebang <- "#!" [^%nl]* %nl - block <- ({ {| - (ignored_line %nl)* - line_of_statements (nodent line_of_statements)* - (%nl ignored_line)* |} }) -> Block - inline_block <- ({ {| inline_line_of_statements |} }) -> Block + inline_statements <- inline_statement (semicolon inline_statement)* + noeol_statements <- (inline_statement semicolon)* noeol_statement + statements <- (inline_statement semicolon)* statement - line_of_statements <- statement (%ws? ";" %ws? statement)* - inline_line_of_statements <- inline_statement (%ws? ";" %ws? inline_statement)* + statement <- functioncall / expression + noeol_statement <- noeol_functioncall / noeol_expression + inline_statement <- inline_functioncall / inline_expression - statement <- ({ functioncall / expression }) -> Statement - inline_statement <- ({ inline_functioncall / expression }) -> Statement + inline_block <- ({ {| "(" inline_statements ")" |} }) -> Block + eol_block <- ({ {| ":" %ws? noeol_statements eol |} }) -> Block + indented_block <- ({ {| (":" / "(..)") indent + statements + (dedent / (({.+} ("" -> "Error while parsing block")) => error)) + |} }) -> Block - expression <- ( - longstring / string / number / variable / list / thunk / block_functioncall - / ("(" %ws? (inline_thunk / inline_functioncall) %ws? ")")) + inline_nomsu <- ({ ("\" inline_block ) }) -> Nomsu + eol_nomsu <- ({ ("\" eol_block ) }) -> Nomsu + indented_nomsu <- ({ ("\" {indented_block} ) }) -> Nomsu + + inline_expression <- number / variable / inline_string / inline_list / inline_block / inline_nomsu + noeol_expression <- indented_string / indented_block / indented_nomsu / indented_list / inline_expression + expression <- eol_block / eol_nomsu / noeol_expression -- Function calls need at least one word in them + inline_functioncall <- ({ {| + (inline_expression tok_gap)* word (tok_gap (inline_expression / word))* + |} }) -> FunctionCall + noeol_functioncall <- ({ {| + (noeol_expression tok_gap)* word (tok_gap (noeol_expression / word))* + |} }) -> FunctionCall functioncall <- ({ {| (expression (dotdot / tok_gap))* word ((dotdot / tok_gap) (expression / word))* |} }) -> FunctionCall - inline_functioncall <- ({ {| - (expression tok_gap)* word (tok_gap (expression / word))* - |} }) -> FunctionCall - block_functioncall <- "(..)" indent - functioncall - (dedent / (({.+} ("" -> "Error while parsing block function call")) => error)) word <- ({ !number {%wordchar (!"'" %wordchar)*} }) -> Word - thunk <- ({ ":" ((indent block (dedent / (({.+} ("" -> "Error while parsing thunk")) => error))) - / (%ws? inline_block)) }) -> Thunk - inline_thunk <- ({ ":" %ws? inline_block }) -> Thunk - - string <- ({ (!longstring) '"' {(("\" [^%nl]) / [^"%nl])*} '"' }) -> String - - longstring <- ({ '".."' %ws? - {| (longstring_line (indent - longstring_line (nodent longstring_line)* - (dedent / longstring_error))?) - /(indent - longstring_line (nodent longstring_line)* - (dedent / longstring_error)) |} }) -> Longstring - longstring_line <- "|" {| ({("\\" / (!string_interpolation [^%nl]))+} / string_interpolation)* |} - longstring_error <- (({.+} ("" -> "Error while parsing Longstring")) => error) - string_interpolation <- "\" %ws? (((inline_functioncall / expression) dotdot?) / dotdot) %ws? "\" - - number <- ({ {"-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)) } }) -> Number - - -- Hack to allow %foo's to parse as "%foo" and "'s" separately - variable <- ({ ("%" {%wordchar (!"'" %wordchar)*}) }) -> Var - - list <- ({ {| + inline_string <- ({ '"' {| + ({~ (("\\" -> "\") / ('\"' -> '"') / (!string_interpolation [^%nl"]))+ ~} + / string_interpolation)* |} '"' }) -> String + indented_string <- ({ '".."' indent {| + indented_string_line (nodent {~ "" -> " +" ~} indented_string_line)* + |} (dedent / (({.+} ("" -> "Error while parsing String")) => error)) + }) -> String + indented_string_line <- "|" ({~ (("\\" -> "\") / (!string_interpolation [^%nl]))+ ~} / string_interpolation)* + string_interpolation <- "\" (inline_block / indented_block / dotdot) + + number <- ({ (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)))-> tonumber) }) -> Number + + -- Variables can be nameless (i.e. just %) and can't contain apostrophes + -- which is a hack to allow %foo's to parse as "%foo" and "'s" separately + variable <- ({ ("%" { (!"'" %wordchar)* }) }) -> Var + + inline_list <- ({ {| + ("[" %ws? ((inline_list_item comma)* inline_list_item comma?)? %ws? "]") + |} }) -> List + indented_list <- ({ {| ("[..]" indent list_line (nodent list_line)* (dedent / (({.+} ("" -> "Error while parsing list")) => error))) - /("[" %ws? (list_line %ws?)? "]") |} }) -> List - list_line <- list_bit (%ws? "," tok_gap list_bit)* (%ws? ",")? - list_bit <- inline_functioncall / expression + list_line <- (inline_list_item comma)* ((inline_list_item %ws? ",") / (functioncall / expression)) + inline_list_item <- inline_functioncall / inline_expression block_comment <- "#.." [^%nl]* indent [^%nl]* (%nl ((%ws? (!. / &%nl)) / (!%dedented [^%nl]*)))* line_comment <- "#" [^%nl]* @@ -116,18 +131,20 @@ nomsu = [=[ indent <- eol (%nl ignored_line)* %nl %indented nodent <- eol (%nl ignored_line)* %nl %nodented dedent <- eol (%nl ignored_line)* (((!.) &%dedented) / (&(%nl %dedented))) - tok_gap <- %ws / %prev_edge / &("[" / [.,:;{("#%']) + tok_gap <- %ws / %prev_edge / &("[" / "\" / [.,:;{("#%']) + comma <- %ws? "," %ws? + semicolon <- %ws? ";" %ws? dotdot <- nodent ".." %ws? ]=] whitespace = S(" \t")^1 defs = - ws:whitespace, nl: P("\n") + ws:whitespace, nl: P("\n"), :tonumber wordchar: P(1)-S(' \t\n\r%#:;,.{}[]()"\\') indented: Cmt(S(" \t")^0 * (#(P(1)-S(" \t\n") + (-P(1)))), check_indent) nodented: Cmt(S(" \t")^0 * (#(P(1)-S(" \t\n") + (-P(1)))), check_nodent) dedented: Cmt(S(" \t")^0 * (#(P(1)-S(" \t\n") + (-P(1)))), check_dedent) - prev_edge: B(S(" \t\n.,:;}])\"")) + prev_edge: B(S(" \t\n.,:;}])\"\\")) error: (src,pos,errors,err_msg)-> line_no = 1 for _ in src\sub(1,-#errors)\gmatch("\n") do line_no += 1 @@ -150,8 +167,6 @@ defs = setmetatable(defs, { __index: (t,key)-> - -- Disabled for performance - --with t[key] = (src, value, errors)-> ParseTree({type: key, :src, :value, :errors}) do nil with t[key] = (src, value, errors)-> {type: key, :src, :value, :errors} do nil }) nomsu = re.compile(nomsu, defs) @@ -162,84 +177,54 @@ class NomsuCompiler @defs = setmetatable({}, {__index:parent and parent.defs}) @callstack = {} @debug = false - @initialize_core! @utils = utils @repr = (...)=> repr(...) @loaded_files = {} + @initialize_core! writeln:(...)=> @write(...) @write("\n") - def: (aliases, fn, src, is_macro=false)=> - if type(aliases) == 'string' - aliases = @get_aliases aliases - if @debug - @writeln "Defining rule: #{repr 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] + def: (invocation, thunk, src)=> + if type(invocation) != 'string' then @error "Invocation should be string, not: #{repr invocation}" + if @debug then @writeln "Defining rule: #{repr invocation}" + stub = invocation\gsub("'"," '")\gsub("%%%S+","%%")\gsub("%s+"," ") + args = [arg for arg in invocation\gmatch("%%(%S[^%s']*)")] + for i=1,#args-1 do for j=i+1,#args + if args[i] == args[j] then @error "Duplicate argument in function def: #{args[i]}" + with @defs[invocation] = {:thunk, :invocation, :args, :src, is_macro:false} do nil + + defmacro: (invocation, thunk, src)=> + with @def(invocation, thunk, src) do .is_macro = true call: (alias,...)=> - fn_def = @defs[alias] - if fn_def == nil + def = @defs[alias] + if def == nil @error "Attempt to call undefined function: #{alias}" -- This is a little bit hacky, but having this check is handy for catching mistakes - if fn_def.is_macro and @callstack[#@callstack] != "__macro__" + -- I use a hash sign in "#macro" so it's guaranteed to not be a valid function name + if 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(fn_def) + unless @check_permission(def) @error "You do not have the authority to call: #{alias}" - {:fn, :aliases} = fn_def - args = {name, select(i,...) for i,name in ipairs(aliases[alias])} + {:thunk, :args} = def + args = {name, select(i,...) for i,name in ipairs(args)} if @debug @writeln "Calling #{repr alias} with args: #{repr(args)}" insert @callstack, alias -- TODO: optimize, but still allow multiple return values? - rets = {fn(self,args)} + rets = {thunk(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)) + local args, alias + alias,args = @get_alias tree + insert @callstack, "#macro" + expr, statement = @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 + return expr, statement check_permission: (fn_def)=> if getmetatable(fn_def) != functiondef_mt @@ -258,18 +243,56 @@ class NomsuCompiler parse: (str, filename)=> if @debug @writeln("PARSING:\n#{str}") - - str = str\gsub("\r","").."\n" + str = str\gsub("\r","") export indent_stack old_indent_stack, indent_stack = indent_stack, {0} tree = nomsu\match(str) indent_stack = old_indent_stack -- Put it back, just in case. - if @debug - @writeln("\nPARSE TREE:") - @print_tree(tree) assert tree, "Failed to parse: #{str}" + if @debug + @writeln "PARSE TREE:" + @print_tree tree, " " return tree + run: (src, filename)=> + tree = @parse(src, filename) + assert tree, "Tree failed to compile: #{src}" + assert tree.type == "File" + + buffer = {} + vars = {} + return_value = nil + for statement in *tree.value + ok,expr,statements = pcall(@tree_to_lua, self, statement) + if not ok + @writeln "Error occurred in statement:\n#{statement.src}" + @error(expr) + code_for_statement = ([[ + return (function(nomsu, vars) + %s + return %s + end)]])\format(statements or "", expr or "") + if @debug + @writeln "RUNNING LUA:\n#{code_for_statement}" + lua_thunk, err = load(code_for_statement) + if not lua_thunk + error("Failed to compile generated code:\n#{code_for_statement}\n\n#{err}\n\nProduced by statement:\n#{statement.src}") + run_statement = lua_thunk! + ok,ret = pcall(run_statement, self, vars) + if expr then return_value = ret + if not ok + @writeln "Error occurred in statement:\n#{statement.src}" + @error(return_value) + insert buffer, "#{statements or ''}\n#{expr and "ret = #{expr}" or ''}" + + lua_code = ([[ + return function(nomsu, vars) + local ret + %s + return ret + end]])\format(concat(buffer, "\n")) + return return_value, lua_code + tree_to_value: (tree, vars)=> code = " return (function(nomsu, vars)\nreturn #{@tree_to_lua(tree)}\nend)" @@ -279,119 +302,94 @@ class NomsuCompiler return (lua_thunk!)(self, vars or {}) tree_to_lua: (tree)=> + -- Return , assert tree, "No tree provided." if not tree.type @error "Invalid tree: #{repr(tree)}" switch tree.type when "File" - buffer = {[[return (function(nomsu, vars) - local ret]]} - vars = {} - for statement in *tree.value.body.value - ok,code = pcall(@tree_to_lua, self, statement) - if not ok - @writeln "Error occurred in statement:\n#{statement.src}" - error(code) - -- Run the fuckers as we go - lua_code = " - return (function(nomsu, 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#{repr(statement)}") - value = lua_thunk! - ok,return_value = pcall(value, self, vars) - if not ok - @writeln "Error occurred in statement:\n#{statement.src}" - error(return_value) - insert buffer, code - insert buffer, [[ - return ret - end) - ]] - return concat(buffer, "\n"), return_value + error("Should not be converting File to lua through this function.") + + when "Nomsu" + return repr(tree.value), nil when "Block" - buffer = {} - for statement in *tree.value - insert buffer, @tree_to_lua(statement) - return concat(buffer, "\n") - - when "Thunk" - assert tree.value.type == "Block", "Non-block value in Thunk" - lua = @tree_to_lua(tree.value) - if #tree.value.value == 1 - if ret_value = lua\match("^%s*ret = (.*)") - return ([[ - (function(nomsu, vars) - return %s - end)]])\format(ret_value) + lua_bits = {} + for arg in *tree.value + expr,statement = @tree_to_lua arg + -- Optimization for common case + if expr and not statement and #tree.value == 1 + return expr, nil + if statement then insert lua_bits, statement + if expr then insert lua_bits, "ret = #{expr}" return ([[ - (function(nomsu, vars) + function(nomsu, vars) local ret %s return ret - end)]])\format(lua) - - when "Statement" - -- This case here is to prevent "ret =" from getting prepended when the macro might not want it - if tree.value.type == "FunctionCall" - 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)) + end]])\format(concat lua_bits, "\n") when "FunctionCall" 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, repr(alias) - return @@comma_separated_items("nomsu:call(", args, ")") + args = {repr(alias)} + for arg in *tree.value + if arg.type == 'Word' then continue + expr,statement = @tree_to_lua arg + if statement + @error "Cannot use [[#{arg.src}]] as a function argument, since it's not an expression." + insert args, expr + return @@comma_separated_items("nomsu:call(", args, ")"), nil when "String" - return repr(@@unescape_string(tree.value)) - - when "Longstring" concat_parts = {} string_buffer = "" - for i,line in ipairs(tree.value) - if i > 1 then string_buffer ..= "\n" - for bit in *line - if type(bit) == "string" - string_buffer ..= bit\gsub("\\\\","\\") - else - if string_buffer ~= "" - insert concat_parts, repr(string_buffer) - string_buffer = "" - insert concat_parts, "nomsu.utils.repr_if_not_string(#{@tree_to_lua(bit)})" + for bit in *tree.value + if type(bit) == "string" + string_buffer ..= bit + continue + if string_buffer ~= "" + insert concat_parts, repr(string_buffer) + string_buffer = "" + expr, statement = @tree_to_lua bit + if statement + @error "Cannot use [[#{bit.src}]] as a string interpolation value, since it's not an expression." + insert concat_parts, "nomsu.utils.repr_if_not_string(#{expr})" if string_buffer ~= "" insert concat_parts, repr(string_buffer) - if #concat_parts == 0 - return "''" - elseif #concat_parts == 1 - return concat_parts[1] - else - return "(#{concat(concat_parts, "..")})" - - when "Number" - return tree.value + return "(#{concat(concat_parts, "..")})", nil when "List" - if #tree.value == 0 - return "{}" - elseif #tree.value == 1 - return "{#{@tree_to_lua(tree.value[1])}}" - else - return @@comma_separated_items("{", [@tree_to_lua(item) for item in *tree.value], "}") + items = {} + for item in *tree.value + expr,statement = @tree_to_lua item + if statement + @error "Cannot use [[#{item.src}]] as a list item, since it's not an expression." + insert items, expr + return @@comma_separated_items("{", items, "}"), nil + + when "Number" + return repr(tree.value) when "Var" - return "vars[#{repr(tree.value)}]" + return "vars[#{repr tree.value}]" else @error("Unknown/unimplemented thingy: #{tree.type}") + + print_tree: (tree, ind="")=> + if type(tree) ~= 'table' or not tree.type + @writeln "#{ind}#{repr tree}" + return + @writeln "#{ind}#{tree.type}:" + switch tree.type + when "List", "File", "Block", "FunctionCall", "String" + for v in *tree.value + @print_tree(v, ind.." ") + else @print_tree(tree.value, ind.." ") @unescape_string: (str)=> str\gsub("\\(.)", ((c)-> STRING_ESCAPES[c] or c)) @@ -409,6 +407,28 @@ class NomsuCompiler insert bits, close return concat(bits) + replaced_vars: (tree, vars)=> + -- TODO: consider making a pure function version of this that copies instead of modifying + if type(tree) != 'table' then return tree + switch tree.type + when "Var" + if vars[tree.value] + tree = vars[tree.value] + when "File", "Thunk", "Statement", "Block", "List", "FunctionCall", "String" + new_value = @replaced_vars tree.value + if new_value != tree.value + tree = {k,v for k,v in pairs(tree)} + tree.value = new_value + when nil -- Raw table, probably from one of the .value of a multi-value tree (e.g. List) + new_values = {} + any_different = false + for k,v in pairs tree + new_values[k] = @replaced_vars v + any_different or= (new_values[k] != tree[k]) + if any_different + tree = new_values + return tree + 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 @@ -453,68 +473,11 @@ class NomsuCompiler 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 type(var) == 'table' and var.type == "Var" + var = var.value + (var\gsub "%W", (verboten)-> if verboten == "_" then "__" else ("_%x")\format(verboten\byte!)) - _yield_tree: (tree, indent_level=0)=> - ind = (s) -> INDENT\rep(indent_level)..s - switch tree.type - when "File" - coroutine.yield(ind"File:") - @_yield_tree(tree.value.body, indent_level+1) - 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" then @_yield_tree(tree.value, indent_level) - when "FunctionCall" - alias = @get_alias tree - args = [a for a in *tree.value when a.type != "Word"] - if #args == 0 - coroutine.yield(ind"Call [#{alias}]!") - else - coroutine.yield(ind"Call [#{alias}]:") - for a in *args - @_yield_tree(a, indent_level+1) - 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("")) - else - coroutine.yield(ind"List:") - for item in *tree.value - @_yield_tree(item, indent_level+1) - else error("Unknown/unimplemented thingy: #{tree.type}") - - print_tree:(tree)=> - for line in coroutine.wrap(-> @_yield_tree(tree)) - @writeln(line) - - stringify_tree:(tree)=> - result = {} - for line in coroutine.wrap(-> @_yield_tree(tree)) - insert(result, line) - return concat result, "\n" - - run: (src, filename, output_file=nil)=> - if @debug - @writeln "COMPILING:\n#{src}" - tree = @parse(src, filename) - assert tree, "Tree failed to compile: #{src}" - code, retval = @tree_to_lua(tree) - if output_file - output = io.open(output_file, "w") - output\write(code) - return retval, code - error: (...)=> @writeln "ERROR!" @writeln(...) @@ -525,40 +488,14 @@ class NomsuCompiler @callstack = {} error! - test: (src, filename, expected)=> - i = 1 - while i != nil - start,stop = src\find("\n\n", i) - - test = src\sub(i,start) - i = stop - start,stop = test\find"===" - if not start or not stop then - @error("WHERE'S THE ===? in:\n#{test}") - test_src, expected = test\sub(1,start-1), test\sub(stop+1,-1) - expected = expected\match'[\n]*(.*[^\n])' - tree = @parse(test_src, filename) - got = @stringify_tree(tree.value.body) - if got != expected - @error"TEST FAILED!\nSource:\n#{test_src}\nExpected:\n#{expected}\n\nGot:\n#{got}" - - initialize_core: => -- Sets up some core functionality - @defmacro "lua block %lua_code", (vars, kind)=> - if kind == "Expression" then error("Expected to be in statement.") - inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{repr(key)}]"}) - lua = @tree_to_value(vars.lua_code, inner_vars) - if not lua\match("^do\n.*\nend$") - lua = "do\n#{lua}\nend" - return lua, true - - @defmacro "lua expr %lua_code", (vars, kind)=> - lua_code = vars.lua_code.value + @defmacro "lua code %statements with value %value", (vars)=> inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{repr(key)}]"}) - lua = @tree_to_value(vars.lua_code, inner_vars) - return lua - + statements = @tree_to_value(vars.statements, inner_vars) + value = @tree_to_value(vars.value, inner_vars) + return value, statements + @def "require %filename", (vars)=> if not @loaded_files[vars.filename] file = io.open(vars.filename) @@ -574,12 +511,11 @@ class NomsuCompiler return @run(file\read('*a'), vars.filename) --- Run on the command line via "./nomsu.moon input_file.nom" to execute --- and "./nomsu.moon input_file.nom output_file.lua" to compile (use "-" to compile to stdout) if arg and arg[1] --ProFi = require 'ProFi' --ProFi\start() c = NomsuCompiler() + c.debug = true input = io.open(arg[1])\read("*a") -- If run via "./nomsu.moon file.nom -", then silence output and print generated -- source code instead. @@ -592,18 +528,12 @@ if arg and arg[1] output = if arg[2] == "-" io.output() else io.open(arg[2], 'w') - - output\write [[ - local load = function() - ]] - output\write(code) - output\write [[ - - end + output\write ([[ local NomsuCompiler = require('nomsu') local c = NomsuCompiler() - return load()(c, {}) - ]] + local run = %s + return run(c, {}) + ]])\format(code) --ProFi\stop() --ProFi\writeReport( 'MyProfilingReport.txt' ) -- cgit v1.2.3