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. --- examples/tutorial.nom | 20 +- lib/collections.nom | 8 +- lib/control_flow.nom | 198 +++++++++++------- lib/metaprogramming.nom | 127 ++++------- lib/operators.nom | 2 + lib/testing.nom | 69 ++++++ lib/utils.nom | 20 +- nomsu.moon | 544 +++++++++++++++++++++--------------------------- utils.moon | 8 +- 9 files changed, 494 insertions(+), 502 deletions(-) diff --git a/examples/tutorial.nom b/examples/tutorial.nom index 7e9a14b..3608be2 100644 --- a/examples/tutorial.nom +++ b/examples/tutorial.nom @@ -54,24 +54,22 @@ if (1 > 10): # For longer conditionals, a "when" branch is the best option when: - ? 1 > 4: + * (1 > 4) + * (1 > 3): say "this won't print" - ? 1 > 3: + * (1 > 2): say "this won't print" - ? 1 > 2: - say "this won't print" - ?: + * else: say "this will print" # When "when" is given an argument, it works like a switch statement -when 3: - == 1: - say "this won't print" - == 2: +when 3 == ?: + * 1 + * 2: say "this won't print" - == 3: + * 3: say "this will print" - ==: + * else: say "this won't print" # Loops look like this: diff --git a/lib/collections.nom b/lib/collections.nom index 09fff10..c7cc10d 100644 --- a/lib/collections.nom +++ b/lib/collections.nom @@ -57,8 +57,8 @@ macro [..] %list doesn't have index %index, %list does not have index %index ..=: ".."|(\%list as lua\[\%index as lua\] ~= nil) -macro [length of %list, size of %list, number of %list, len %list] =: - ".."|#(\%list as lua\) +macro [length of %list, size of %list, size %list, number of %list, len %list] =: + ".."|nomsu.utils.size(\%list as lua\) # Chained lookup macro [%list ->* %indices] =: @@ -143,7 +143,7 @@ macro [%expression for all %iterable] =: ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua\) do - | vars.it = value + | vars[''] = value | comprehension[i] = \%expression as lua\ | end | return comprehension @@ -152,7 +152,7 @@ macro [%expression for all %iterable] =: # TODO: maybe make a generator/coroutine? #.. Dict comprehensions can be accomplished okay by doing: - dict ([new_key using (%it's "key"), new_value using (%it's "value")] for all (entries in %dict)) + dict ([new_key using (%'s "key"), new_value using (%'s "value")] for all (entries in %dict)) or something similar # TODO: fix compiler bugs pass diff --git a/lib/control_flow.nom b/lib/control_flow.nom index 8d5f8ab..b1df621 100644 --- a/lib/control_flow.nom +++ b/lib/control_flow.nom @@ -31,21 +31,15 @@ macro statement [go to %label] =: ".." |goto label_\nomsu "var_to_lua_identifier" [%label]\ # Loop control flow -macro statement [break] =: "break" -macro statement [break for] =: "goto break_for" -macro statement [break for-all] =: "goto break_for_all" -macro statement [break repeat] =: "goto break_repeat" -macro statement [break repeat-until] =: "goto break_repeat_until" -macro statement [break repeat-while] =: "goto break_repeat_while" -macro statement [break %var, stop getting %var, no more %var] =: ".." +macro statement [stop, stop loop, break] =: "break" +macro statement [stop for, stop for-loop, break for] =: "goto break_for" +macro statement [stop repeat, stop repeat-loop, break repeat] =: "goto break_repeat" +macro statement [stop %var, break %var] =: ".." |goto break_\nomsu "var_to_lua_identifier" [%var]\ -macro statement [continue] =: "continue" -macro statement [continue for] =: "goto continue_for" -macro statement [continue for-all] =: "goto continue_for_all" -macro statement [continue repeat] =: "goto continue_repeat" -macro statement [continue repeat-until] =: "goto continue_repeat_until" -macro statement [continue repeat-while] =: "goto continue_repeat_while" +macro statement [continue, continue loop] =: "continue" +macro statement [continue for, continue for-loop] =: "goto continue_for" +macro statement [continue repeat, continue repeat-loop] =: "goto continue_repeat" macro statement [continue %var, go to next %var, on to the next %var] =: ".." |goto continue_\nomsu "var_to_lua_identifier" [%var]\ @@ -59,17 +53,58 @@ macro block [repeat %body] =: macro block [repeat while %condition %body] =: ".."|while \%condition as lua\ do | \(lua expr "vars.body.value") as lua\ - | ::continue_repeat_while:: + | ::continue_repeat:: |end - |::break_repeat_while:: + |::break_repeat:: macro block [repeat until %condition %body] =: ".."|while not (\%condition as lua\) do | \(lua expr "vars.body.value") as lua\ - | ::continue_repeat_until:: + | ::continue_repeat:: + |end + |::break_repeat:: + +# Numeric range for loops +macro block [for %var from %start to %stop by %step %body] =: + %var-type =: lua expr "vars.var.type" + assert (%var-type == "Var") ".." + |For loop has the wrong type for the loop variable. Expected Var, but got: \%var-type\ + # This trashes the loop variables, just like in Python. + ".." + |for i=\%start as lua\,\%stop as lua\,\%step as lua\ do + | \%var as lua\ = i + | \(lua expr "vars.body.value") as lua\ + | ::continue_for:: + | ::continue_\nomsu "var_to_lua_identifier" [%var]\:: + |end + |::break_for:: + |::break_\nomsu "var_to_lua_identifier" [%var]\:: +macro block [for %var from %start to %stop %body] =: + %thunk =: :for %var from %start to %stop by 1 %body + lua block ".." + |for i,x in ipairs(vars.thunk.value) do + | if x.type == 'Var' then vars.thunk.type == vars[x.value] end |end - |::break_repeat_until:: + |return compiler:run_macro(vars.thunk, 'Statement') + +macro block [for all %start to %stop by %step %body] =: + # This trashes the loop variables, just like in Python. + ".." + |for i=\%start as lua\,\%stop as lua\,\%step as lua\ do + | vars[''] = i + | \(lua expr "vars.body.value") as lua\ + | ::continue_for:: + | ::continue_\nomsu "var_to_lua_identifier" [%]\:: + |end + |::break_for:: + |::break_\nomsu "var_to_lua_identifier" [%]\:: +macro block [for %var from %start to %stop %body] =: + %thunk =: :for %var from %start to %stop by 1 %body + lua block ".." + |for i,x in ipairs(vars.thunk.value) do + | if x.type == 'Var' then vars.thunk.type == vars[x.value] end + |end + |return compiler:run_macro(vars.thunk, 'Statement') -# For loops macro block [for %var in %iterable %body] =: %var-type =: lua expr "vars.var.type" assert (%var-type == "Var") ".." @@ -89,92 +124,93 @@ macro block [for all %iterable %body] =: pass # TODO: fix compiler bug # This trashes the loop variables, just like in Python. ".."|for i,value in ipairs(\%iterable as lua\) do - | vars.it = value + | vars[''] = value | \(lua expr "vars.body.value") as lua\ - | ::continue_for_all:: + | ::continue_for:: + | ::continue_\nomsu "var_to_lua_identifier" [%]\:: |end - |::break_for_all:: + |::break_for:: + |::break_\nomsu "var_to_lua_identifier" [%]\:: # Switch statement/multi-branch if macro block [when %body] =: %result =: "" + %fallthroughs =: [] for %statement in (lua expr "vars.body.value.value"): %func-call =: lua expr "vars.statement.value" assert ((lua expr "vars['func-call'].type") == "FunctionCall") ".." - |Invalid format for 'when' statement. Only '?' blocks are allowed. + |Invalid format for 'when' statement. Only '*' blocks are allowed. %tokens =: lua expr "vars['func-call'].value" - %q =: lua expr "vars.tokens[1]" - assert (((lua expr "vars.q.type") == "Word") and ((lua expr "vars.q.value") == "?")) ".." - |Invalid format for 'when' statement. Lines must begin with '?' - %thunk =: lua expr "vars.tokens[#vars.tokens]" - assert ((lua expr "vars.thunk.type") == "Thunk") ".." - |Invalid format for 'when' statement. Lines must have a body. - %condition_bits =: [] - for %i in (2 up to (lua expr "#vars.tokens")): - lua block "table.insert(vars['condition_bits'], vars.tokens[vars.i])" - - if (lua expr "#vars.condition_bits == 0"): - %result join=: ".." - | + + %star =: lua expr "vars.tokens[1]" + assert (lua expr "vars.star and vars.star.type == 'Word' and vars.star.value == '*'") ".." + |Invalid format for 'when' statement. Lines must begin with '*' + + %condition =: lua expr "vars.tokens[2]" + assert %condition ".." + |Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else" + + %thunk =: lua expr "vars.tokens[3]" + if (%thunk == (nil)): + lua block "table.insert(vars.fallthroughs, vars.condition)" + go to next %statement + + if (lua expr "vars.condition.type == 'Word' and vars.condition.value == 'else'"): + %result join=: ".."| |do - | \(lua expr "vars.thunk.value") as lua\ - | goto finished_when - |end ..else: - if (lua expr "#vars.condition_bits == 1 and vars.condition_bits[1].type ~= 'Word'"): - %condition =: lua expr "vars.condition_bits[1]" - ..else: - %condition =: dict [..] - ["type",lua expr "vars['func-call'].type"] - ["src",lua expr "vars['func-call'].src"] - ["value", %condition_bits] - %result join=: ".." - | - |if \%condition as lua\ then - | \(lua expr "vars.thunk.value") as lua\ - | goto finished_when - |end + %condition =: %condition as lua + for all %fallthroughs: %condition join=: ".."| or \% as lua\ + %result join=: ".."| + |if \%condition\ then + %result join=: ".."| + | \(lua expr "vars.thunk.value") as lua\ + | goto finished_when + |end + + %fallthroughs =: [] + %result join=: "\n::finished_when::" %result # Switch statement -macro block [when %branch-value %body] =: +macro block [when %branch-value == ? %body] =: %result =: ".."|local branch_value = \%branch-value as lua\ + %fallthroughs =: [] for %statement in (lua expr "vars.body.value.value"): %func-call =: lua expr "vars.statement.value" assert ((lua expr "vars['func-call'].type") == "FunctionCall") ".." - |Invalid format for 'when' statement. Only == blocks are allowed. + |Invalid format for 'when' statement. Only '*' blocks are allowed. %tokens =: lua expr "vars['func-call'].value" - %eq =: lua expr "vars.tokens[1]" - assert (((lua expr "vars.eq.type") == "Word") and ((lua expr "vars.eq.value") == "==")) ".." - |Invalid format for 'when' statement. Lines must begin with '==' - %thunk =: lua expr "vars.tokens[#vars.tokens]" - assert ((lua expr "vars.thunk.type") == "Thunk") ".." - |Invalid format for 'when' statement. Lines must have a body. - %condition_bits =: [] - for %i in (2 up to (lua expr "#vars.tokens")): - lua block "table.insert(vars.condition_bits, vars.tokens[vars.i])" - if (lua expr "#vars.condition_bits == 0"): - %result join=: ".." - | + + %star =: lua expr "vars.tokens[1]" + assert (lua expr "vars.star and vars.star.type == 'Word' and vars.star.value == '*'") ".." + |Invalid format for 'when' statement. Lines must begin with '*' + + %condition =: lua expr "vars.tokens[2]" + assert %condition ".." + |Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else" + + %thunk =: lua expr "vars.tokens[3]" + if (%thunk == (nil)): + lua block "table.insert(vars.fallthroughs, vars.condition)" + go to next %statement + + if (lua expr "vars.condition.type == 'Word' and vars.condition.value == 'else'"): + %result join=: ".."| |do - | \(lua expr "vars.thunk.value") as lua\ - | goto finished_when - |end ..else: - if (lua expr "#vars.condition_bits == 1 and vars.condition_bits[1].type ~= 'Word'"): - %condition =: (lua expr "vars.condition_bits[1]") - ..else: - %condition =: dict [..] - ["type",lua expr "vars['func-call'].type"] - ["src",lua expr "vars['func-call'].src"] - ["value", %condition_bits] - %result join=: ".." - | - |if nomsu.utils.equivalent(branch_condition, \%condition as lua\) then - | \(lua expr "vars.thunk.value") as lua\ - | goto finished_when - |end + %condition =: ".."|branch_value == (\%condition as lua\) + for all %fallthroughs: %condition join=: ".."| or (branch_value == \% as lua\) + %result join=: ".."| + |if \%condition\ then + %result join=: ".."| + | \(lua expr "vars.thunk.value") as lua\ + | goto finished_when + |end + + %fallthroughs =: [] + %result join=: "\n::finished_when::" %result diff --git a/lib/metaprogramming.nom b/lib/metaprogramming.nom index 72c50b5..9dcc9df 100644 --- a/lib/metaprogramming.nom +++ b/lib/metaprogramming.nom @@ -2,88 +2,55 @@ This File contains rules for making rules and macros and some helper functions to make that easier. -# Macros: - -lua block ".." - |local function make_fn(wrap_in_block) - | return function(nomsu, vars, kind) - # Do a minimal amount of pre-processing (get the aliases and the source) - | local aliases = nomsu:repr(nomsu:get_aliases(vars.macro_def)) - | local src = nomsu:repr(vars.user_macro.src) - | local user_macro = nomsu:tree_to_lua(vars.user_macro) - # Then produce a block of code that creates the macro at runtime - | local lua = [[ - |nomsu:defmacro(%s, (function(nomsu, vars, kind) - | if kind == "Expression" then - | nomsu:error("Macro "..%s.." was defined to be a block, but is being used as an expression") - | end - | local user_macro = %s - | local lua = user_macro(nomsu, vars) - | %s - | return lua, true - |end), %s)]] - | lua = lua:format(aliases, nomsu:repr(aliases), user_macro, - | wrap_in_block and [[lua = "do\\n "..lua.."\\nend"]] or "", src) - | return lua, true - | end - |end - |nomsu:defmacro("macro statement %macro_def = %user_macro", make_fn(false), "see:lib/metaprogramming.nom") - |nomsu:defmacro("macro block %macro_def = %user_macro", make_fn(true), "see:lib/metaprogramming.nom") +# Nil +lua code ".." + |nomsu:defmacro("nil", function(nomsu, vars) return "nil", nil end) +..with value 0 -macro statement [macro %macro_def = %user_macro] =: - ".."|nomsu:defmacro( - | \lua expr "nomsu:get_aliases(vars.macro_def)"\, - | \lua expr "nomsu:tree_to_lua(vars.user_macro)"\, - | \lua expr "nomsu:repr(vars.user_macro.src)"\) +# Macros: +lua code ".." + |nomsu:def("parse %shorthand as %longhand", function(nomsu, vars) + | local alias = nomsu:get_alias(vars.shorthand) + | local template = vars.longhand + | nomsu:defmacro(alias, function(nomsu, vars) + | return nomsu:tree_to_lua(nomsu:replaced_vars(template, vars)) + | end) + |end) +..with value (nil) + +parse \(lua code %code) as\: lua code %code with value (nil) +parse \(lua block %block) as\: + lua code ".." + |do + | \(%block) + |end + ..with value (nil) +parse \(lua value %value) as\: lua code (nil) with value %value -macro [nomsu] =: "nomsu" -macro [nomsu's %key] =: ".."|nomsu[\%key as lua\] -macro [nomsu %method %args] =: +parse \(nomsu) as\: lua value "nomsu" +parse \(nomsu's %key) as\: + lua value "nomsu[\(%key as lua)]" +parse \(nomsu %method %args) as\: lua block ".." |local args = {"nomsu"} |for _,arg in ipairs(vars.args.value) do | table.insert(args, nomsu:tree_to_lua(arg)) |end - |return "nomsu["..nomsu:repr(nomsu:tree_to_value(vars.method, vars)).."]("..table.concat(args, ", ")..")" -macro [nomsu utils %method %args] =: - lua block ".." - |local args = {} - |for i,arg in ipairs(vars.args.value) do - | args[i] = nomsu:tree_to_lua(arg) - |end - |return "nomsu.utils["..nomsu:repr(nomsu:tree_to_value(vars.method, vars)).."]("..table.concat(args, ", ")..")" - -# Macro that lets you make new rules -#.. - This is a macro instead of a rule because it needs to pre-empt processing the list of - function calls and convert it into a list of strings (rather than call a function that - is currently in the middle of being defined). Being a macro also allows us to snatch - the source code and store that -macro statement [rule %rule_def = %body] =: ".." - |nomsu:def( - | \nomsu "repr" [nomsu "get_aliases" [%rule_def]]\, - | \nomsu "tree_to_lua" [%body]\, - | \nomsu "repr" [lua expr "vars.body.src"]\) - -rule [fart]=: lua block "print('poot')" + |local method_name = nomsu:repr(nomsu:tree_to_value(vars.method, vars)) + ..with value ".." + |("nomsu[%s](%s)"):format(method_name, table.concat(args, ", ")) +parse \(repr %) as\: nomsu "repr" [%] -macro block [alias %aliases = %aliased] =: - lua block ".." - |local aliases = nomsu:get_aliases(vars.aliases) - |aliases = nomsu:repr(aliases) - |if vars.aliased.type ~= "Thunk" then - | nomsu:error("Right hand side of alias % = % should be a Thunk, but got "..vars.aliased.type - | ..". Maybe you used = instead of =: by mistake?") - |end - |local aliased = next(nomsu:get_aliases(vars.aliased.value)) - |aliased = nomsu:repr(aliased) - |local lua = ([[ - |nomsu:add_aliases(%s, nomsu.defs[%s]) - |]]):format(aliases, aliased) - |return lua +# Rule that lets you make new rules +lua block ".." + |nomsu:def("rule helper %rule_def = %body", function(nomsu, vars) + | nomsu:def(nomsu:get_alias(vars.rule_def), vars.body) + |end) +parse \(rule %rule_def = %body) as\: rule helper \(%rule_def) = \(%body) +parse \(rule %rule_name) as\: lua value "nomsu.defs[nomsu:get_alias(\(%rule_name))]" # Get the source code for a function -rule [help %rule] =: +rule (help %rule) =: lua block ".." |local fn_def = nomsu:get_fn_def(vars.rule) |if not fn_def then @@ -93,20 +60,16 @@ rule [help %rule] =: | .." ="..(fn_def.src or ":\\n ")) |end - # Macro helper functions -rule [%tree as value] =: +rule (%tree as value) =: lua expr "nomsu:tree_to_value(vars.tree, vars)" -rule [%tree as lua] =: +rule (%tree as lua) =: lua expr "nomsu:tree_to_lua(vars.tree)" -rule [test, foobar] =: lua expr "print('yup')" - # Compiler tools -rule [eval %code, run %code] =: nomsu "run" [%code] - -rule [source code from tree %tree] =: +rule (eval %code; run %code) =: nomsu "run" [%code] +rule (source code from tree %tree) =: lua block ".." |local _,_,leading_space = vars.tree.src:find("\\n(%s*)%S") |if leading_space then @@ -117,9 +80,9 @@ rule [source code from tree %tree] =: | return vars.tree.src:match(":%s*(%S.*)").."\\n" |end -macro [source code %body] =: +macro (source code %body) =: nomsu "repr" [nomsu "call" ["source code from tree %", %body]] -macro [parse tree %code] =: +macro (parse tree %code) =: nomsu "repr" [nomsu "stringify_tree" [lua expr "vars.code.value"]] diff --git a/lib/operators.nom b/lib/operators.nom index 6dadaf3..f2bb7c2 100644 --- a/lib/operators.nom +++ b/lib/operators.nom @@ -4,6 +4,8 @@ require "lib/metaprogramming.nom" macro [true, yes] =: "true" macro [false, no] =: "false" macro [nil, null] =: "nil" +macro [inf, infinity] =: "math.huge" +macro [nan, NaN, not a number] =: "(0/0)" macro statement [nop, pass] =: "" # Ternary operator diff --git a/lib/testing.nom b/lib/testing.nom index 87a7711..83d0108 100644 --- a/lib/testing.nom +++ b/lib/testing.nom @@ -2,6 +2,75 @@ require "lib/metaprogramming.nom" # For unit testing macro block [test %code yields %expected] =: + + + _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" + + 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}" + + + + + %generated =: repr (nomsu "stringify_tree" [%code's "value"]) %expected =: %expected as lua if (%generated != %expected): diff --git a/lib/utils.nom b/lib/utils.nom index b1e104b..1afc33a 100644 --- a/lib/utils.nom +++ b/lib/utils.nom @@ -44,22 +44,10 @@ macro [say %str] =: ".."|nomsu:writeln(nomsu.utils.repr_if_not_string(\%str as lua\)) # Number ranges -macro [%start up to %stop] =: ".." - |nomsu.utils.range(\%start as lua\, \%stop as lua\-1) -macro [%start thru %stop, %start through %stop] =: ".." - |nomsu.utils.range(\%start as lua\, \%stop as lua\) -macro [%start down to %stop] =: ".." - |nomsu.utils.range(\%start as lua\, \%stop as lua\+1,-1) -macro [%start down thru %stop, %start down through %stop] =: ".." - |nomsu.utils.range(\%start as lua\, \%stop as lua\,-1) -macro [%start up to %stop via %step] =: ".." - |nomsu.utils.range(\%start as lua\,\%stop as lua\-1,vars.step) -macro [%start thru %stop via %step, %start through %stop via %step] =: ".." - |nomsu.utils.range(\%start as lua\,\%stop as lua\,vars.step) -macro [%start down to %stop via %step] =: ".." - |nomsu.utils.range(\%start as lua\,\%stop as lua\+1,-vars.step) -macro [%start down thru %stop via %step, %start down through %stop via %step] =: ".." - |nomsu.utils.range(\%start as lua\,\%stop as lua\,-vars.step) +macro [%start to %stop] =: ".." + |nomsu.utils.range(\%start as lua\, \%stop as lua\) +macro [%start to %stop by %step, %start to %stop via %step] =: ".." + |nomsu.utils.range(\%start as lua\, \%stop as lua\, \%step as lua\) # Common utility functions macro [random number, random, rand] =: "math.random()" 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' ) diff --git a/utils.moon b/utils.moon index f267c7d..471b28e 100644 --- a/utils.moon +++ b/utils.moon @@ -7,6 +7,10 @@ utils = { i += 1 return true + size: (t)-> + with n = 0 + for _ in pairs(t) do n += 1 + repr: (x)-> switch type(x) when 'table' @@ -18,7 +22,9 @@ utils = { else "{#{table.concat(["[#{utils.repr(k)}]= #{utils.repr(v)}" for k,v in pairs x], ", ")}}" when 'string' - if not x\find[["]] and not x\find"\n" and not x\find"\\" + if x == "\n" + return "'\\n'" + elseif not x\find[["]] and not x\find"\n" and not x\find"\\" "\""..x.."\"" elseif not x\find[[']] and not x\find"\n" and not x\find"\\" "\'"..x.."\'" -- cgit v1.2.3