aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2017-09-24 20:20:27 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2017-09-24 20:20:27 -0700
commitaf3274ca9237a08093009e87955e30ab5f473f12 (patch)
tree46c249d6b42ac51111c7e198a10b8dad09b17923
parente4660b169c14d24c3ec373b197e8b9469d200d50 (diff)
massive overhaul, compiler kinda works.
-rw-r--r--examples/tutorial.nom20
-rw-r--r--lib/collections.nom8
-rw-r--r--lib/control_flow.nom198
-rw-r--r--lib/metaprogramming.nom127
-rw-r--r--lib/operators.nom2
-rw-r--r--lib/testing.nom69
-rw-r--r--lib/utils.nom20
-rwxr-xr-xnomsu.moon544
-rw-r--r--utils.moon8
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 <unknown source code>"))
|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("<Empty List>"))
+ 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 <lua code for value>, <additional lua code>
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("<Empty List>"))
- 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.."\'"