aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2019-02-05 15:45:27 -0800
committerBruce Hill <bruce@bruce-hill.com>2019-02-05 15:47:01 -0800
commit72d699fe86ddb34473b54a0df27d21b4a9159284 (patch)
treebdb4036181b5c15d920d1500f8f67407a44d2014
parent0ff3219f355b4307783288800724213636920912 (diff)
Bunch of changes:
- Added shebangs to generated code output - SyntaxTree:map() -> SyntaxTree:with(), and corresponding changes to metaprogramming API - Added (return Lua 1) shorthand for (return (Lua 1)) - (1 and 2 and 3) compile rule mapping to -> (1 and (*extra arguments*)) - Don't scan for errors, just report them when compiling - Syntax changes: - Added prefix actions (e.g. #$foo) - Operator chars now include utf8 chars - Ditch "escaped nomsu" type (use (\ 1) compile action instead)
-rw-r--r--lib/core/control_flow.nom205
-rw-r--r--lib/core/errors.nom137
-rw-r--r--lib/core/metaprogramming.nom105
-rw-r--r--lib/core/text.nom19
-rw-r--r--lib/core/things.nom23
-rwxr-xr-xlib/tools/repl.nom7
-rwxr-xr-xlib/tools/replace.nom2
-rw-r--r--nomsu.7.peg273
-rw-r--r--nomsu.lua3
-rwxr-xr-xnomsu.moon2
-rw-r--r--nomsu_compiler.lua58
-rw-r--r--nomsu_compiler.moon46
-rw-r--r--nomsu_decompiler.lua63
-rw-r--r--nomsu_decompiler.moon61
-rw-r--r--nomsu_environment.lua71
-rw-r--r--nomsu_environment.moon31
-rw-r--r--parser.lua2
-rw-r--r--parser.moon2
-rw-r--r--syntax_tree.lua37
-rw-r--r--syntax_tree.moon25
20 files changed, 780 insertions, 392 deletions
diff --git a/lib/core/control_flow.nom b/lib/core/control_flow.nom
index 415611b..ca67394 100644
--- a/lib/core/control_flow.nom
+++ b/lib/core/control_flow.nom
@@ -64,25 +64,23 @@ test:
# If $when_true_expr is guaranteed to be truthy, we can use Lua's idiomatic
equivalent of a conditional expression: (cond and if_true or if_false)
if {.Text, .List, .Dict, .Number}.($when_true_expr.type):
- return
- Lua ("
- (\($condition as lua expr) and \($when_true_expr as lua expr) or \
- ..\($when_false_expr as lua expr))
- ")
+ return Lua ("
+ (\($condition as lua expr) and \($when_true_expr as lua expr) or \
+ ..\($when_false_expr as lua expr))
+ ")
..else:
# Otherwise, need to do an anonymous inline function (yuck, too bad lua
doesn't have a proper ternary operator!)
To see why this is necessary consider: (random()<.5 and false or 99)
- return
- Lua ("
- ((function()
- if \($condition as lua expr) then
- return \($when_true_expr as lua expr)
- else
- return \($when_false_expr as lua expr)
- end
- end)())
- ")
+ return Lua ("
+ ((function()
+ if \($condition as lua expr) then
+ return \($when_true_expr as lua expr)
+ else
+ return \($when_false_expr as lua expr)
+ end
+ end)())
+ ")
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -117,15 +115,15 @@ test:
# Basic loop control
(stop $var) compiles to:
if $var:
- return (Lua "goto stop_\($var as lua identifier)")
+ return Lua "goto stop_\($var as lua identifier)"
..else:
- return (Lua "break")
+ return Lua "break"
(do next $var) compiles to:
if $var:
- return (Lua "goto continue_\($var as lua identifier)")
+ return Lua "goto continue_\($var as lua identifier)"
..else:
- return (Lua "goto continue")
+ return Lua "goto continue"
(---stop $var ---) compiles to "::stop_\($var as lua identifier)::"
(---next $var ---) compiles to "::continue_\($var as lua identifier)::"
@@ -162,18 +160,75 @@ test:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+# For-each loop (lua's "ipairs()")
+(for $var in $iterable at $i $body) compiles to:
+ # This uses Lua's approach of only allowing loop-scoped variables in a loop
+ if (($iterable.type == "Action") and (($iterable, get stub) == "1 to")):
+ [$start, $stop] = [$iterable.1, $iterable.3]
+ $loop =
+ Lua ("
+ local _start = \($start as lua expr)
+ for \($var as lua identifier)=_start,\($stop as lua expr) do
+ \($i as lua identifier) = \($var as lua identifier) - _start + 1
+ ")
+ if (($iterable.type == "Action") and (($iterable, get stub) == "1 to 2 by")):
+ [$start, $stop, $step] = [$iterable.1, $iterable.3, $iterable.5]
+ $loop =
+ Lua ("
+ local _start, _step = \($start as lua expr), \($step as lua expr)
+ for \($var as lua identifier)=_start,\($stop as lua expr),_step do
+ \($i as lua identifier) = (\($var as lua identifier) - _start)/_step + 1
+ ")
+ unless $loop:
+ $loop =
+ Lua ("
+ local _iterating = _1_as_list(\($iterable as lua expr))
+ for \($i as lua identifier)=1,#_iterating do
+ \($var as lua identifier) = _iterating[\($i as lua identifier)]
+ ")
+ $lua =
+ Lua ("
+ do -- for-loop
+ \$loop
+ \;
+ ")
+ $lua, add ($body as lua)
+ if ($body has subtree \(do next)):
+ $lua, add "\n ::continue::"
+
+ if ($body has subtree \(do next $var)):
+ $lua, add "\n " (\(---next $var ---) as lua)
+
+ $lua, add "\n end"
+ if ($body has subtree \(stop $var)):
+ $lua, add "\n " (\(---stop $var ---) as lua)
+ $lua, add "\nend -- for-loop"
+ return $lua
+
+(for $var in $iterable $body) parses as
+ for $var in $iterable at (=lua "_i") $body
+
test:
+ $d = {.a = 10, .b = 20, .c = 30, .d = 40, .e = 50}
+ $result = []
+ for $k = $v in $d:
+ if ($k == "a"):
+ do next $k
+
+ if ($v == 20):
+ do next $v
+
+ $result, add "\$k = \$v"
+ assume (($result sorted) == ["c = 30", "d = 40", "e = 50"])
+
+# Numeric range for loops
+test:
+ assume ([: for $ in (1 to 5): add $] == [1, 2, 3, 4, 5])
+ assume ([: for $ in (1 to 5 by 2): add $] == [1, 3, 5])
+ assume ([: for $ in (5 to 1): add $] == [])
$nums = []
- for $x in 1 to 5:
- $nums, add $x
- assume ($nums == [1, 2, 3, 4, 5])
- $nums = []
- for $x in 1 to 5 via 2:
- $nums, add $x
- assume ($nums == [1, 3, 5])
- $nums = []
- for $outer in 1 to 100:
- for $inner in $outer to ($outer + 2):
+ for $outer in (1 to 100):
+ for $inner in ($outer to ($outer + 2)):
if ($inner == 2):
$nums, add -2
do next $inner
@@ -182,47 +237,25 @@ test:
stop $outer
assume ($nums == [1, -2, 3, -2, 3, 4, 3, 4, 5])
-# Numeric range for loops
+# These are shims, and should be phased out:
[
for $var in $start to $stop by $step $body
for $var in $start to $stop via $step $body
-] all compile to:
- # This uses Lua's approach of only allowing loop-scoped variables in a loop
- $lua =
- Lua ("
- for \($var as lua identifier)=\($start as lua expr),\($stop as lua expr),\
- ..\($step as lua expr) do
- ")
- $lua, add "\n " ($body as lua)
- if ($body has subtree \(do next)):
- $lua, add "\n ::continue::"
-
- if ($body has subtree \(do next $var)):
- $lua, add "\n " (\(---next $var ---) as lua)
-
- $lua, add "\nend -- numeric for " ($var as lua identifier) " loop"
- if ($body has subtree \(stop $var)):
- $lua =
- Lua ("
- do -- scope for (stop \($var as lua identifier))
- \$lua
- \(\(---stop $var ---) as lua)
- end -- scope for (stop \($var as lua identifier))
- ")
- return $lua
-
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+] all parse as (for $var in ($start to $stop by $step) $body)
(for $var in $start to $stop $body) parses as
- for $var in $start to $stop via 1 $body
+ for $var in ($start to $stop) $body
+# repeat $n times is a shorthand:
test:
$x = 0
repeat 5 times:
$x += 1
assume $x == 5
-(repeat $n times $body) parses as (for (=lua "_XXX_") in 1 to $n $body)
+(repeat $n times $body) parses as (for (=lua "_XXX_") in (1 to $n by 1) $body)
+
+# Dict iteration (lua's "pairs()")
test:
$a = [10, 20, 30, 40, 50]
$b = []
@@ -239,48 +272,9 @@ test:
$b, add $x
assume ($b == [20, 30, 40])
+ # Small memory footprint:
+ assume (1 to (1 << 31))
-# For-each loop (lua's "ipairs()")
-(for $var in $iterable at $i $body) compiles to:
- # This uses Lua's approach of only allowing loop-scoped variables in a loop
- $lua =
- Lua ("
- for \($i as lua identifier),\($var as lua identifier) in ipairs(\($iterable as lua expr)) do
- \;
- ")
- $lua, add ($body as lua)
- if ($body has subtree \(do next)):
- $lua, add "\n ::continue::"
-
- if ($body has subtree \(do next $var)):
- $lua, add "\n " (\(---next $var ---) as lua)
-
- $lua, add "\nend --for \($var as lua identifier) loop"
- if ($body has subtree \(stop $var)):
- $inner_lua = $lua
- $lua = (Lua "do -- scope for stopping for-loop\n ")
- $lua, add $inner_lua "\n "
- $lua, add (\(---stop $var ---) as lua)
- $lua, add "\nend -- end of scope for stopping for-loop"
- return $lua
-
-(for $var in $iterable $body) parses as
- for $var in $iterable at (=lua "__") $body
-
-test:
- $d = {.a = 10, .b = 20, .c = 30, .d = 40, .e = 50}
- $result = []
- for $k = $v in $d:
- if ($k == "a"):
- do next $k
-
- if ($v == 20):
- do next $v
-
- $result, add "\$k = \$v"
- assume (($result sorted) == ["c = 30", "d = 40", "e = 50"])
-
-# Dict iteration (lua's "pairs()")
[for $key = $value in $iterable $body, for $key $value in $iterable $body]
..all compile to:
$lua =
@@ -453,13 +447,12 @@ test:
")
$code, add "\nend --when"
- return
- Lua ("
- do --if $ is...
- local \(mangle "branch value") = \($branch_value as lua expr)
- \$code
- end -- if $ is...
- ")
+ return Lua ("
+ do --if $ is...
+ local \(mangle "branch value") = \($branch_value as lua expr)
+ \$code
+ end -- if $ is...
+ ")
# Do/finally
(do $action) compiles to ("
diff --git a/lib/core/errors.nom b/lib/core/errors.nom
index 6be7e7f..a05c7d4 100644
--- a/lib/core/errors.nom
+++ b/lib/core/errors.nom
@@ -18,65 +18,79 @@ use "core/control_flow"
if ($condition.type == "Action"):
when $condition.stub is:
"1 ==":
- return
- LuaCode ("
- do
- local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
- if _a ~= _b then
- _a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
- _b = type_of(_b) == 'Text' and _b:as_lua() or _1_as_text(_b)
- at_1_fail(\(quote "\($condition.1.source)"),
- "Assumption failed: This value was ".._a.." but it was expected to be ".._b..".")
- end
+ return Lua ("
+ do -- Assumption:
+ local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
+ if _a ~= _b then
+ _a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
+ _b = type_of(_b) == 'Text' and _b:as_lua() or _1_as_text(_b)
+ at_1_fail(\(quote "\($condition.1.source)"),
+ "Assumption failed: This value was ".._a.." but it was expected to be ".._b..".")
end
- ")
+ end
+ ")
+
"1 !=":
- return
- LuaCode ("
- do
- local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
- if _a == _b then
- _a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
- at_1_fail(\(quote "\($condition.1.source)"),
- "Assumption failed: This value was ".._a.." but it wasn't expected to be.")
- end
+ return Lua ("
+ do -- Assumption:
+ local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
+ if _a == _b then
+ _a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
+ at_1_fail(\(quote "\($condition.1.source)"),
+ "Assumption failed: This value was ".._a.." but it wasn't expected to be.")
end
- ")
+ end
+ ")
+
"1 >" "1 <" "1 >=" "1 <=":
- return
- LuaCode ("
- do
- local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
- if _a ~= _b then
- _a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
- _b = type_of(_b) == 'Text' and _b:as_lua() or _1_as_text(_b)
- at_1_fail(\(quote "\($condition.1.source)"),
- "Assumption failed: This value was ".._a..", but it was expected to be \($condition.3)".._b..".")
- end
+ return Lua ("
+ do -- Assumption:
+ local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
+ if _a ~= _b then
+ _a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
+ _b = type_of(_b) == 'Text' and _b:as_lua() or _1_as_text(_b)
+ at_1_fail(\(quote "\($condition.1.source)"),
+ "Assumption failed: This value was ".._a..", but it was expected to be \
+ ..\($condition.3)".._b..".")
end
- ")
+ end
+ ")
+
"1 is":
- return
- LuaCode ("
- do
- local _ta, _tb = type_of(\($condition.1 as lua expr)), \($condition.3 as lua expr)
- if _ta ~= _tb then
- at_1_fail(\(quote "\($condition.1.source)"),
- "Assumption failed: This value was ".._ta.." but it was expected to be ".._tb..".")
- end
+ return Lua ("
+ do -- Assumption:
+ local _a, _b = \($condition.1 as lua expr), \($condition.3 as lua expr)
+ if not _1_is(_a, _b) then
+ _a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
+ at_1_fail(\(quote "\($condition.1.source)"),
+ "Assumption failed: This value (".._a..") was expected to be "..\
+ .._b..", but wasn't.")
end
- ")
- return
- LuaCode ("
- if not \($condition as lua expr) then
- at_1_fail(\(quote "\($condition.source)"), "Assumption failed: This assumption did not hold.")
- end
- ")
+ end
+ ")
+
+ "1 isn ' t" "1 is not":
+ return Lua ("
+ do -- Assumption:
+ local _a, _b = \($condition.1 as lua expr), \($condition.(#$condition) as lua expr)
+ if _1_is(_a, _b) then
+ _a = type_of(_a) == 'Text' and _a:as_lua() or _1_as_text(_a)
+ at_1_fail(\(quote "\($condition.1.source)"),
+ "Assumption failed: This value (".._a..") was expected to not be \
+ ..".._b..", but it was.")
+ end
+ end
+ ")
+
+ return Lua ("
+ if not \($condition as lua expr) then
+ at_1_fail(\(quote "\($condition.source)"), "Assumption failed: This assumption did not hold.")
+ end
+ ")
(assume $a == $b) parses as (assume ($a == $b))
(assume $a != $b) parses as (assume ($a != $b))
(test that $condition) parses as (assume $condition)
-
test:
try: fail
$worked = (no)
@@ -109,21 +123,20 @@ test:
if ($msg_lua, text, is lua id):
$fallback_lua, add free vars [($msg_lua, text)]
$fallback_lua, prepend "-- Failure:\n"
- return
- Lua ("
- do
- local _fell_through = false
- local _result = {xpcall(function()
- \($action as lua)
- _fell_through = true
- end, enhance_error)}
- if _result[1] then
- \$success_lua
- else
- \$fallback_lua
- end
+ return Lua ("
+ do
+ local _fell_through = false
+ local _result = {xpcall(function()
+ \($action as lua)
+ _fell_through = true
+ end, enhance_error)}
+ if _result[1] then
+ \$success_lua
+ else
+ \$fallback_lua
end
- ")
+ end
+ ")
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/lib/core/metaprogramming.nom b/lib/core/metaprogramming.nom
index 3ac8c4a..c310fdd 100644
--- a/lib/core/metaprogramming.nom
+++ b/lib/core/metaprogramming.nom
@@ -44,8 +44,8 @@ lua> ("
end
elseif not arg_lua:is_lua_id() then
at_1_fail(SyntaxTree:is_instance(arg) and arg or nil,
- "Compile error: This does not compile to a Lua identifier, so it "..
- "can't be used as a function argument. "..
+ "Compile error: This does not compile to a Lua identifier ("..arg_lua.."),"..
+ "so it can't be used as a function argument. "..
"Hint: This should probably be a Nomsu variable instead (like $x).")
end
lua:add(i > 1 and ", " or "", arg_lua)
@@ -97,7 +97,7 @@ lua> ("
(\$action.type == "EscapedNomsu" and \$action[1].type == "Action") or
\$action.type == "MethodCall") then
at_1_fail(\$action.source, "Compile error: "..
- "This is neither an action nor an escaped action. "..
+ "This first argument to (* compiles to *) is neither an action nor an escaped action (it's a "..\$action.type.."). "..
"Hint: This should probably be an action like:\\n"
.."(foo $x) compiles to \\"(\\\\($x as lua) + 1)\\"")
end
@@ -117,6 +117,8 @@ lua> ("
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+(` $) compiles to (Lua (=lua "\$ or SyntaxTree{type='Action'}", as lua))
+
($actions all compile to $body) compiles to:
lua> ("
if \$actions.type ~= "List" then
@@ -308,22 +310,19 @@ test:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[$action parses as $body] all parse as ([$action] all parse as $body)
-external:
- (in (nomsu environment) $tree as lua expr) means:
- lua> ("
- local tree_lua = \(nomsu environment):compile(\$tree)
- if \$tree.type == 'Block' then
- tree_lua = LuaCode:from(\$tree.source, '(function()\\n ', tree_lua, '\\nend)()')
- elseif \$tree.type == 'MethodCall' and #\$tree > 2 then
- at_1_fail(\$tree, "Compile error: This must be a single value instead of "..
- (#\$tree - 1).." method calls. Hint: Replace this with a single method call.")
- end
- return tree_lua
- ")
+
+((nomsu environment), $tree as lua expr) means:
+ lua> ("
+ local tree_lua = \(nomsu environment):compile(\$tree)
+ if \$tree.type == 'Block' and #\$tree > 1 then
+ tree_lua = LuaCode:from(\$tree.source, '(function()\\n ', tree_lua, '\\nend)()')
+ end
+ return tree_lua
+ ")
# Need to make sure the proper environment is used for compilation (i.e. the caller's environment)
($tree as lua expr) compiles to
- \(in \(nomsu environment) $tree as lua expr) as lua
+ =lua "SyntaxTree{type='MethodCall', \(\(nomsu environment)), \(\($tree as lua expr))}"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -340,11 +339,11 @@ external:
")
test:
- (num args (*extra arguments*)) means (select "#" (*extra arguments*))
+ (num args (*extra arguments*)) means (#(*extra arguments*))
assume (num args 1 2 3) == 3
(extra args (*extra arguments*)) means [*extra arguments*]
assume (extra args 1 2 3) == [1, 2, 3]
- (third arg (*extra arguments*)) means (select 3 (*extra arguments*))
+ (third arg (*extra arguments*)) means ((*extra arguments*).3)
assume (third arg 5 6 7 8) == 7
(*extra arguments*) compiles to "..."
@@ -353,44 +352,6 @@ external:
($ is $kind syntax tree) means
=lua "SyntaxTree:is_instance(\$) and \$.type == \$kind"
-($tree with $t -> $replacement) compiles to ("
- \($tree as lua expr):map(function(\($t as lua expr))
- \(
- =lua ("
- \$replacement.type == 'Block' and \($replacement as lua) or 'return '..\
- ..\($replacement as lua expr):text()
- ")
- )
- end)
-")
-
-external:
- ($tree with vars $replacements) means
- =lua ("
- \$tree:map(function(\$t)
- if \$t.type == "Var" then
- return \$replacements[\$t:as_var()]
- end
- end)
- ")
-
-(tree $tree with vars $replacements) compiles to ("
- \(=lua "(\$tree):as_lua()"):map(function(t)
- if t.type == "Var" then
- return \($replacements as lua expr)[t:as_var()]
- end
- end)
-")
-
-($tree has subtree $match_tree) compiles to ("
- (function()
- local match_tree = \($match_tree as lua expr)
- for subtree in coroutine_wrap(function() \($tree as lua expr):map(yield) end) do
- if subtree == match_tree then return true end
- end
- end)()
-")
-
external:
(match $tree with $patt) means:
lua> ("
@@ -444,12 +405,24 @@ external:
if mt and mt.__type then return mt.__type end
if \$ == nil then return 'nil' end
local lua_type = \(lua type of $)
- if lua_type == 'function' then return "an Action" end
return 'a '..lua_type:capitalized()
")
-($ is $type) parses as ((type of $) == $type)
-[$ isn't $type, $ is not $type] all parse as ((type of $) != $type)
+ ($ is $type) means:
+ lua> ("
+ local class = getmetatable(\$)
+ ::check_parent::
+ if not class or not class.__type then return 'a '..lua_type:capitalized() == \$type end
+ if class.__type == \$type then return true end
+ local class_mt = getmetatable(class)
+ if class_mt.__index and class_mt.__index ~= class then
+ class = class_mt.__index
+ goto check_parent
+ end
+ return false
+ ")
+
+[$ isn't $type, $ is not $type] all parse as (not ($ is $type))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -482,6 +455,9 @@ test:
return lua
")
+# Convenience helper:
+(return Lua (*extra arguments*)) compiles to \(return \(Lua (*extra arguments*)))
+
# Literals
(yes) compiles to "(true)"
(no) compiles to "(false)"
@@ -525,3 +501,14 @@ external:
return ("
\(Nomsu syntax version).\(core version).\(Nomsu compiler version).\(lib version)
")
+
+~~~~
+# TODO: Remove shim
+($tree with $t -> $replacement) parses as
+ $tree, with ($t -> $replacement)
+
+[tree $tree with vars $replacements, $tree with vars $replacements] all parse as
+ ($tree, with $replacements)
+
+($tree has subtree $match_tree) parses as
+ $tree, contains $match_tree
diff --git a/lib/core/text.nom b/lib/core/text.nom
index 1401cf2..d7719b3 100644
--- a/lib/core/text.nom
+++ b/lib/core/text.nom
@@ -43,16 +43,15 @@ test:
($expr for $match in $text matching $patt) compiles to:
define mangler
- return
- Lua ("
- (function()
- local \(mangle "comprehension") = a_List{}
- for \($match as lua expr) in (\($text as lua expr)):gmatch(\($patt as lua expr)) do
- \(mangle "comprehension")[#\(mangle "comprehension")+1] = \($expr as lua)
- end
- return \(mangle "comprehension")
- end)()
- ")
+ return Lua ("
+ (function()
+ local \(mangle "comprehension") = a_List{}
+ for \($match as lua expr) in (\($text as lua expr)):gmatch(\($patt as lua expr)) do
+ \(mangle "comprehension")[#\(mangle "comprehension")+1] = \($expr as lua)
+ end
+ return \(mangle "comprehension")
+ end)()
+ ")
test:
assume "\n" == (newline)
diff --git a/lib/core/things.nom b/lib/core/things.nom
index 5f74b1a..6ba21cb 100644
--- a/lib/core/things.nom
+++ b/lib/core/things.nom
@@ -37,6 +37,8 @@ test:
($self, number of commas) means ((#$bits) - 1)
$csv = (a Comma Buffer)
assume $csv.is_a_buffer
+ assume ($csv is "a Comma Buffer")
+ assume ($csv is "a Buffer")
assume "\$csv" == ""
$csv, add "x"
$csv, add "y"
@@ -96,17 +98,20 @@ external:
Compile error: This is not a list of variables.
")
$class_id = ($classname.stub, as lua id)
- if $class_body:
- $class_body =
- $class_body with vars {
- : for $v in $vars:
- add ($v as lua expr, text) =
- "IndexChain" tree with ("Var" tree with "self")
- "Index" tree with ("Text" tree with $v.1)
- }
+ $class_body and=
+ $class_body, with
+ $t ->:
+ for $v in $vars:
+ if ($t == $v):
+ return
+ "IndexChain" tree with ("Var" tree with "self")
+ "Index" tree with ("Text" tree with $v.1)
+
+ if ($parent.type == "Action"):
+ $parent = ("Var" tree with $parent)
$lua =
Lua ("
- \$class_id = _1_class_named(\($parent as lua), \(quote $classname.stub)\(
+ \$class_id = _1_class_named(\($parent as lua id), \(quote $classname.stub)\(
(
Lua ("
, function(\$class_id)
diff --git a/lib/tools/repl.nom b/lib/tools/repl.nom
index 029df4a..5e8336f 100755
--- a/lib/tools/repl.nom
+++ b/lib/tools/repl.nom
@@ -70,6 +70,13 @@ command line program with $args:
unless $tree:
do next
+ if ($tree.type == "Comment"):
+ say (dim "Comment:\($tree.1)")
+ do next
+
+ if ($tree.type != "FileChunks"):
+ $tree = [$tree]
+
for $chunk in $tree:
try:
$lua = ($chunk as lua)
diff --git a/lib/tools/replace.nom b/lib/tools/replace.nom
index 7337447..314834e 100755
--- a/lib/tools/replace.nom
+++ b/lib/tools/replace.nom
@@ -107,7 +107,7 @@ command line program with $args:
$matched = {}
$user_answers = {}
($tree with replacements) means
- $tree, map
+ $tree, with
for $t:
$values = ($t matches $pattern_tree with {})
if $values:
diff --git a/nomsu.7.peg b/nomsu.7.peg
new file mode 100644
index 0000000..22bcd46
--- /dev/null
+++ b/nomsu.7.peg
@@ -0,0 +1,273 @@
+-- Nomsu version 7
+file <-
+ {:curr_indent: ' '* :}
+ (((comment / methodchain / action / expression / inline_block) eol !.)
+ / file_chunks / comment? blank_lines?)
+ {:curr_indent: %nil :}
+ !.
+
+shebang <- "#!" [^%nl]*
+
+file_chunks (FileChunks) <-
+ {:shebang: shebang :}?
+ (top_block (nl_nodent section_division top_block)*)
+ blank_lines?
+ unexpected_indent? unexpected_chunk?
+
+section_division <- ("~")^+3 eol
+
+eof <- !.
+eol <- ws* (&%nl / !.)
+nodent <- (unexpected_indent [^%nl]* / =curr_indent)
+indent <- { =curr_indent " " }
+blank_lines <- %nl ((tab_error / nodent comment / ws*) %nl)*
+nl_nodent <- blank_lines nodent
+nl_indent <- blank_lines {:curr_indent: indent :} (comment nl_nodent)*
+
+
+comment (Comment) <-
+ "###" {~ [^%nl]* (%nl+ (indent -> '') [^%nl]*)* (%nl &%nl)* ~}
+
+
+top_block (Block) <-
+ ((blank_lines nodent) / (comment nl_nodent))? statement (nl_nodent statement)*
+
+inline_block (Block) <-
+ ":" ws* (inline_statement (ws* ";" ws* inline_statement)*)?
+ (&eol !nl_indent / &(ws* ([)},;] / "]")))
+
+indented_block (Block) <-
+ ":" eol nl_indent statement (nl_nodent statement)*
+ (%nl (ws* %nl)* nodent (comment / eol / unexpected_code))*
+ {:curr_indent: %nil :}
+
+statement_block (Block) <-
+ ":" ws* (methodchain / action / expression)
+
+
+statement <-
+ (methodchain / action / expression / statement_block) (eol / unexpected_code)
+
+inline_statement <-
+ inline_methodchain / inline_action / inline_expression
+
+noindex_inline_expression <-
+ number / variable / inline_text / inline_list / inline_dict / inline_unary_action /
+ "("
+ ws* (inline_block / inline_methodchain / inline_action / inline_expression) ws*
+ (")" / eof / missing_paren_err / unexpected_code)
+
+inline_expression <- inline_index_chain / noindex_inline_expression / inline_index / inline_block
+
+indented_expression <-
+ indented_text / indented_list / indented_dict / indented_block /
+ indented_parens / unary_action
+
+indented_parens <-
+ "(" indented_naked_expression (nl_nodent ")" / missing_paren_err / unexpected_code)
+
+indented_naked_expression <-
+ ({| nl_indent
+ (methodchain / action / expression) (eol / unexpected_code)
+ (%nl (ws* %nl)* nodent (comment / eol / unexpected_code))*
+ {:curr_indent: %nil :}
+ |} -> unpack)
+
+expression <-
+ inline_expression / indented_index_chain / indented_expression / inline_index / indented_index
+
+
+inline_index (Index) <-
+ "." (hex_integer / integer / text_word / noindex_inline_expression)
+_inline_index (IndexChain) <- inline_index
+inline_index_chain <-
+ (noindex_inline_expression _inline_index+) -> foldr
+
+indented_index (Index) <- "." indented_expression
+_indented_index (IndexChain) <- indented_index
+indented_index_chain <-
+ (noindex_inline_expression inline_index* (inline_index / _indented_index)) -> foldr
+
+
+-- Actions need 1 argument and either another argument or a word.
+inline_action (Action) <-
+ !section_division
+ ( inline_expression ((ws* inline_arg)+ / "(" ws* ")")
+ / word (ws* inline_arg)*)
+inline_arg <- inline_expression / word
+
+inline_unary_action (Action) <-
+ !section_division %at_break operator !":" inline_expression
+
+action (Action) <-
+ !section_division
+ ( !statement_block (expression / indented_naked_expression) (((linesplit / ws*) arg)+ / "(" ws* ")")
+ / word ((linesplit / ws*) arg)*)
+arg <- expression / indented_naked_expression / word
+linesplit <- eol nl_nodent ".." ws*
+
+unary_action (Action) <-
+ !section_division %at_break operator !":" !eol (expression / indented_naked_expression)
+
+
+inline_methodsuffix (MethodCall) <-
+ inline_action
+ / "(" ws* inline_action (ws* ";" ws* inline_action)* ")"
+inline_methodchain <-
+ ((inline_action / inline_expression) (ws* "," ws* inline_methodsuffix)+) -> foldr
+
+methodsuffix (MethodCall) <-
+ action
+ / "(" ws* inline_action (ws* ";" ws* inline_action)* ws* ")"
+ / eol ({| nl_indent
+ (action eol) (nl_nodent action eol)*
+ (%nl (ws* %nl)* nodent (comment / eol / unexpected_code))*
+ {:curr_indent: %nil :}
+ |} -> unpack)
+methodchain <-
+ ((unary_action / inline_action / expression) ((linesplit / ws*) "," ws* methodsuffix)+) -> foldr
+
+word <- !number { %operator_char+ / ident_char+ }
+operator <- !"###" {%operator_char+}
+
+
+text_word (Text) <- word
+
+inline_text (Text) <-
+ '"' !eol _inline_text* ('"' / eof / missing_quote_err / unexpected_code)
+_inline_text <-
+ {~ (('\"' -> '"') / ('\\' -> '\') / escaped_char / text_char+)+ ~}
+ / inline_text_interpolation / illegal_char
+inline_text_interpolation <-
+ "\" (
+ variable / inline_list / inline_dict
+ / ("("
+ ws* ((inline_methodchain / inline_action / inline_expression) ws*)?
+ (")" / eof / missing_paren_err / unexpected_code))
+ )
+
+text_char <- %utf8_char / !["\] %print / %tab
+
+indented_text (Text) <-
+ '("' %nl {%nl*} ({|
+ {:curr_indent: indent :}
+ (indented_plain_text / text_interpolation / illegal_char / blank_text_lines)*
+ {:curr_indent: %nil :}
+ |} -> unpack)
+ (nl_nodent '")' / eof / missing_indented_quote_err)
+
+-- Tracking text-lines-within-indented-text as separate objects allows for better debugging line info
+indented_plain_text (Text) <-
+ {~
+ ((("\" blank_lines =curr_indent "..") -> "") / ('\\' -> '\') / ('\"' -> '"') / ('\;' -> '')
+ / (!text_interpolation ((!("\n") escaped_char) / '\'))
+ / ('"' / text_char)+)+
+ blank_text_lines?
+ ~}
+blank_text_lines <-
+ {~ (%nl ((ws* -> '') (&%nl / !.) / (=curr_indent -> '') &[^%nl]))+ ~}
+
+text_interpolation <-
+ ({|
+ -- %indentation will backtrack and match the actual indentation of the current line
+ "\" {:curr_indent: %indentation :}
+ (indented_block (blank_lines =curr_indent "..")? / indented_expression)
+ |} -> unpack)
+ / inline_text_interpolation
+
+
+number <-
+ hex_integer / real_number / integer
+
+integer (Number) <-
+ (((%at_break "-")? [0-9]+)-> tonumber)
+
+hex_integer (Number) <-
+ (((%at_break "-")? "0x" [0-9a-fA-F]+)-> tonumber)
+ {:hex: '' -> 'yes' :}
+
+real_number (Number) <-
+ (((%at_break "-")? [0-9]+ "." [0-9]+)-> tonumber)
+
+
+variable (Var) <- "$" ({ident_char+} / "(" ws* (inline_action / variable) ws* ")" / {''})
+
+
+inline_list (List) <-
+ "[" ws* !eol
+ (inline_list_item (ws* ',' ws* inline_list_item)* (ws* ',')?)? ws*
+ ("]" / eof / (","? (missing_bracket_error / unexpected_code)))
+inline_list_item <- inline_action / inline_expression
+
+indented_list (List) <-
+ ({|
+ "[" eol nl_indent
+ list_line (nl_nodent list_line)*
+ {:curr_indent: %nil :}
+ |} -> unpack)
+ (nl_nodent "]" / eof / missing_bracket_error / unexpected_code)
+list_line <-
+ (inline_list_item ws* "," ws*)+ eol
+ / (inline_list_item ws* "," ws*)* (action / statement_block / expression) eol
+
+
+inline_dict (Dict) <-
+ "{" ws* !eol
+ ((inline_action / inline_expression) (ws* ',' ws* (inline_action / inline_expression))*)? ws*
+ ("}" / eof / (","? (missing_brace_error / unexpected_code)))
+
+indented_dict (Dict) <-
+ ({|
+ "{" eol nl_indent
+ dict_line (nl_nodent dict_line)*
+ (%nl (ws* %nl)* nodent (comment / eol / unexpected_code))*
+ {:curr_indent: %nil :}
+ |} -> unpack)
+ (nl_nodent "}" / eof / missing_brace_error / unexpected_code)
+dict_line <-
+ ((inline_action / inline_expression) ws* "," ws*)+ eol
+ / ((inline_action / inline_expression) ws* "," ws*)* (action / statement_block / expression) eol
+
+ident_char <- [a-zA-Z0-9_] / (!%operator_char %utf8_char)
+ws <- " "
+
+escaped_char <-
+ ("\"->'') (
+ (([xX]->'') ((({[0-9a-fA-F]^2} %number_16) -> tonumber) -> tochar))
+ / ((([0-9] [0-9]^-2) -> tonumber) -> tochar)
+ / ("a"->ascii_7) / ("b"->ascii_8) / ("t"->ascii_9) / ("n"->ascii_10)
+ / ("v"->ascii_11) / ("f"->ascii_12) / ("r"->ascii_13)
+ )
+
+
+-- Errors
+unexpected_code <- ws* _unexpected_code
+_unexpected_code (Error) <-
+ {:error: {~ [^%nl]+ -> "Couldn't parse this code." ~} :}
+unexpected_chunk (Error) <-
+ {:error: {~ .+ -> "Couldn't parse this chunk of code." ~} :}
+unexpected_indent (Error) <-
+ {:error: {~ (=curr_indent ws+) -> "This indentation is messed up." ~} :}
+ {:hint: {~ '' -> 'This line should either have the same indentation as the line above it, or exactly 4 spaces more.' ~} :}
+missing_paren_err (Error) <-
+ {:error: {~ eol -> 'This expression is missing a closing )-parenthesis.' ~} :}
+ {:hint: {~ '' -> 'Put a ")" here' ~} :}
+missing_quote_err (Error) <-
+ {:error: {~ eol -> "This text is missing a closing quotation mark." ~} :}
+ {:hint: {~ "" -> "Put a quotation mark here." ~} :}
+missing_indented_quote_err (Error) <-
+ {:error: {~ '' -> 'This text is missing a closing ")-quotation mark.' ~} :}
+ {:hint: {~ "" -> 'Put a ") after this line, at the same level of indentation as the opening (".' ~} :}
+missing_bracket_error (Error) <-
+ {:error: {~ eol -> "This list is missing a closing ]-bracket" ~} :}
+ {:hint: {~ '' -> 'Put a "]" here' ~} :}
+missing_brace_error (Error) <-
+ {:error: {~ eol -> "This dict is missing a closing }-brace" ~} :}
+ {:hint: {~ '' -> 'Put a "}" here' ~} :}
+tab_error <- ws* _tab_error [^%nl]*
+_tab_error (Error) <-
+ {:error: {~ %tab+ -> 'Tabs are not allowed for indentation.' ~} :}
+ {:hint: {~ '' -> 'Use 4-space indentation instead of tabs.' ~} :}
+illegal_char (Error) <-
+ {:error: {~ (!(%nl / %tab / %print) .) -> "Illegal unprintable character here (it may not be visible, but it's there)" ~} :}
+ {:hint: {~ '' -> "This sort of thing can happen when copying and pasting code. Try deleting and retyping the code." ~} :}
diff --git a/nomsu.lua b/nomsu.lua
index 21e4af2..86172c4 100644
--- a/nomsu.lua
+++ b/nomsu.lua
@@ -199,6 +199,9 @@ run = function()
local env = nomsu_environment.new_environment()
env.MODULE_NAME = filename
local tree = env._1_parsed(code)
+ if tree.shebang then
+ output:write(tree.shebang)
+ end
if not (tree.type == 'FileChunks') then
tree = {
tree
diff --git a/nomsu.moon b/nomsu.moon
index 96053eb..94b5388 100755
--- a/nomsu.moon
+++ b/nomsu.moon
@@ -162,6 +162,8 @@ run = ->
env = nomsu_environment.new_environment!
env.MODULE_NAME = filename
tree = env._1_parsed(code)
+ if tree.shebang
+ output\write tree.shebang
tree = {tree} unless tree.type == 'FileChunks'
for chunk_no, chunk in ipairs tree
lua = env\compile(chunk)
diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua
index c9a15db..2521ce1 100644
--- a/nomsu_compiler.lua
+++ b/nomsu_compiler.lua
@@ -21,7 +21,7 @@ fail_at = function(source, msg)
source = source.source
elseif type(source) == 'string' then
source = Source:from_string(source)
- else
+ elseif not Source:is_instance(source) then
assert(source.short_src and source.currentline)
file = Files.read(source.short_src)
assert(file, "Could not find " .. tostring(source.short_src))
@@ -86,6 +86,26 @@ compile = function(self, tree)
end
return lua
end
+ if not compile_action then
+ local seen_words = { }
+ local words = { }
+ for word in stub:gmatch("[^0-9 ][^ ]*") do
+ if not (seen_words[word]) then
+ seen_words[word] = true
+ table.insert(words, word)
+ end
+ end
+ table.sort(words)
+ local stub2 = table.concat(words, " ")
+ compile_action = self.COMPILE_RULES[stub2]
+ if compile_action then
+ if debug.getinfo(compile_action, 'u').isvararg then
+ stub = stub2
+ else
+ compile_action = nil
+ end
+ end
+ end
if compile_action then
local args
do
@@ -118,7 +138,7 @@ compile = function(self, tree)
lua:add((stub):as_lua_id(), "(")
for argnum, arg in ipairs(tree:get_args()) do
local arg_lua = self:compile(arg)
- if arg.type == "Block" then
+ if arg.type == "Block" and #arg > 1 then
arg_lua = LuaCode:from(arg.source, "(function()\n ", arg_lua, "\nend)()")
end
if lua:trailing_line_len() + #arg_lua:text() > MAX_LINE then
@@ -155,15 +175,22 @@ compile = function(self, tree)
if not (target_text:match("^%(.*%)$") or target_text:match("^[_a-zA-Z][_a-zA-Z0-9.]*$") or tree[1].type == "IndexChain") then
target_lua:parenthesize()
end
+ local self_lua = #tree > 2 and "_self" or target_lua
+ if #tree > 2 then
+ lua:add("(function(", self_lua, ")\n ")
+ end
for i = 2, #tree do
if i > 2 then
- lua:add("\n")
+ lua:add("\n ")
end
- lua:add(target_lua, ":")
+ if i > 2 and i == #tree then
+ lua:add("return ")
+ end
+ lua:add(self_lua, ":")
lua:add((tree[i].stub):as_lua_id(), "(")
for argnum, arg in ipairs(tree[i]:get_args()) do
local arg_lua = self:compile(arg)
- if arg.type == "Block" then
+ if arg.type == "Block" and #arg > 1 then
arg_lua = LuaCode:from(arg.source, "(function()\n ", arg_lua, "\nend)()")
end
if lua:trailing_line_len() + #arg_lua:text() > MAX_LINE then
@@ -175,6 +202,9 @@ compile = function(self, tree)
end
lua:add(")")
end
+ if #tree > 2 then
+ lua:add("\nend)(", target_lua, ")")
+ end
return lua
elseif "EscapedNomsu" == _exp_0 then
local lua = LuaCode:from(tree.source, "SyntaxTree{")
@@ -320,9 +350,9 @@ compile = function(self, tree)
items_lua:add("\n")
sep = ''
elseif items_lua:trailing_line_len() > MAX_LINE then
- sep = ',\n '
+ sep = items_lua:text():sub(-1) == ";" and "\n " or ",\n "
else
- sep = ', '
+ sep = items_lua:text():sub(-1) == ";" and " " or ", "
end
i = i + 1
end
@@ -360,6 +390,9 @@ compile = function(self, tree)
if lua:text():match("['\"}]$") or lua:text():match("]=*]$") then
lua:parenthesize()
end
+ if lua:text() == "..." then
+ return LuaCode:from(tree.source, "select(", self:compile(tree[2][1]), ", ...)")
+ end
for i = 2, #tree do
local key = tree[i]
if key.type ~= "Index" then
@@ -381,7 +414,16 @@ compile = function(self, tree)
elseif "Comment" == _exp_0 then
return LuaCode:from(tree.source, "-- ", (tree[1]:gsub('\n', '\n-- ')))
elseif "Error" == _exp_0 then
- return error("Can't compile errors")
+ local err_msg = pretty_error({
+ title = "Parse error",
+ error = tree.error,
+ hint = tree.hint,
+ source = tree:get_source_file(),
+ start = tree.source.start,
+ stop = tree.source.stop,
+ filename = tree.source.filename
+ })
+ return error(err_msg)
else
return error("Unknown type: " .. tostring(tree.type))
end
diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon
index 73cc5a8..8190abe 100644
--- a/nomsu_compiler.moon
+++ b/nomsu_compiler.moon
@@ -16,7 +16,7 @@ fail_at = (source, msg)->
source = source.source
elseif type(source) == 'string'
source = Source\from_string(source)
- else
+ elseif not Source\is_instance(source)
-- debug.getinfo() output:
assert(source.short_src and source.currentline)
file = Files.read(source.short_src)
@@ -73,6 +73,21 @@ compile = (tree)=>
lua\add " " if i < #tree
return lua
+ if not compile_action
+ seen_words = {}
+ words = {}
+ for word in stub\gmatch("[^0-9 ][^ ]*")
+ unless seen_words[word]
+ seen_words[word] = true
+ table.insert words, word
+ table.sort(words)
+ stub2 = table.concat(words, " ")
+ compile_action = @COMPILE_RULES[stub2]
+ if compile_action
+ if debug.getinfo(compile_action, 'u').isvararg
+ stub = stub2
+ else compile_action = nil
+
if compile_action
args = [arg for arg in *tree when type(arg) != "string"]
ret = compile_action(@, tree, unpack(args))
@@ -92,7 +107,7 @@ compile = (tree)=>
lua\add((stub)\as_lua_id!,"(")
for argnum, arg in ipairs tree\get_args!
arg_lua = @compile(arg)
- if arg.type == "Block"
+ if arg.type == "Block" and #arg > 1
arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE
lua\add(argnum > 1 and ",\n " or "\n ")
@@ -129,13 +144,18 @@ compile = (tree)=>
tree[1].type == "IndexChain")
target_lua\parenthesize!
+ self_lua = #tree > 2 and "_self" or target_lua
+ if #tree > 2
+ lua\add "(function(", self_lua, ")\n "
for i=2,#tree
- lua\add "\n" if i > 2
- lua\add target_lua, ":"
+ lua\add "\n " if i > 2
+ if i > 2 and i == #tree
+ lua\add "return "
+ lua\add self_lua, ":"
lua\add((tree[i].stub)\as_lua_id!,"(")
for argnum, arg in ipairs tree[i]\get_args!
arg_lua = @compile(arg)
- if arg.type == "Block"
+ if arg.type == "Block" and #arg > 1
arg_lua = LuaCode\from(arg.source, "(function()\n ", arg_lua, "\nend)()")
if lua\trailing_line_len! + #arg_lua\text! > MAX_LINE
lua\add(argnum > 1 and ",\n " or "\n ")
@@ -143,6 +163,8 @@ compile = (tree)=>
lua\add ", "
lua\add arg_lua
lua\add ")"
+ if #tree > 2
+ lua\add "\nend)(", target_lua, ")"
return lua
when "EscapedNomsu"
@@ -258,9 +280,9 @@ compile = (tree)=>
items_lua\add "\n"
sep = ''
elseif items_lua\trailing_line_len! > MAX_LINE
- sep = ',\n '
+ sep = items_lua\text!\sub(-1) == ";" and "\n " or ",\n "
else
- sep = ', '
+ sep = items_lua\text!\sub(-1) == ";" and " " or ", "
i += 1
if items_lua\is_multiline!
lua\add LuaCode\from items_lua.source, typename, "{\n ", items_lua, "\n}"
@@ -293,6 +315,8 @@ compile = (tree)=>
lua = @compile(tree[1])
if lua\text!\match("['\"}]$") or lua\text!\match("]=*]$")
lua\parenthesize!
+ if lua\text! == "..."
+ return LuaCode\from(tree.source, "select(", @compile(tree[2][1]), ", ...)")
for i=2,#tree
key = tree[i]
-- TODO: remove this shim
@@ -315,7 +339,13 @@ compile = (tree)=>
return LuaCode\from(tree.source, "-- ", (tree[1]\gsub('\n', '\n-- ')))
when "Error"
- error("Can't compile errors")
+ err_msg = pretty_error{
+ title:"Parse error"
+ error:tree.error, hint:tree.hint, source:tree\get_source_file!
+ start:tree.source.start, stop:tree.source.stop, filename:tree.source.filename
+ }
+ -- Coroutine yield here?
+ error(err_msg)
else
error("Unknown type: #{tree.type}")
diff --git a/nomsu_decompiler.lua b/nomsu_decompiler.lua
index 3f3702c..4b1622e 100644
--- a/nomsu_decompiler.lua
+++ b/nomsu_decompiler.lua
@@ -14,15 +14,20 @@ local re = require('re')
local MAX_LINE = 80
local GOLDEN_RATIO = ((math.sqrt(5) - 1) / 2)
local utf8_char_patt = (R("\194\223") * R("\128\191") + R("\224\239") * R("\128\191") * R("\128\191") + R("\240\244") * R("\128\191") * R("\128\191") * R("\128\191"))
-local operator_patt = S("'`~!@%#^&*+=|<>?/-") ^ 1 * -1
-local identifier_patt = (R("az", "AZ", "09") + P("_") + utf8_char_patt) ^ 1 * -1
+local operator_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B") + R("\xA8\xAB")) * R("\128\191"))
+local operator_patt = operator_char ^ 1 * -1
+local identifier_patt = (R("az", "AZ", "09") + P("_") + (-operator_char * utf8_char_patt)) ^ 1 * -1
local is_operator
is_operator = function(s)
- return not not operator_patt:match(s)
+ return type(s) == 'string' and operator_patt:match(s)
end
local is_identifier
is_identifier = function(s)
- return not not identifier_patt:match(s)
+ return type(s) == 'string' and identifier_patt:match(s)
+end
+local can_be_unary
+can_be_unary = function(t)
+ return t.type == "Action" and #t == 2 and is_operator(t[1]) and type(t[2]) ~= 'string' and t[2].type ~= "Block"
end
local inline_escaper = re.compile("{~ (%utf8_char / ('\"' -> '\\\"') / ('\n' -> '\\n') / ('\t' -> '\\t') / ('\b' -> '\\b') / ('\a' -> '\\a') / ('\v' -> '\\v') / ('\f' -> '\\f') / ('\r' -> '\\r') / ('\\' -> '\\\\') / ([^ -~] -> escape) / .)* ~}", {
utf8_char = utf8_char_patt,
@@ -49,6 +54,15 @@ tree_to_inline_nomsu = function(tree)
local _exp_0 = tree.type
if "Action" == _exp_0 then
local nomsu = NomsuCode:from(tree.source)
+ if can_be_unary(tree) then
+ nomsu:add(tree[1])
+ local arg_nomsu = tree_to_inline_nomsu(tree[2])
+ if tree[2].type == "MethodCall" or tree[2].type == "Action" then
+ arg_nomsu:parenthesize()
+ end
+ nomsu:add(arg_nomsu)
+ return nomsu
+ end
local num_args, num_words = 0, 0
for i, bit in ipairs(tree) do
if type(bit) == "string" then
@@ -67,17 +81,19 @@ tree_to_inline_nomsu = function(tree)
num_args = num_args + 1
local arg_nomsu = tree_to_inline_nomsu(bit)
if bit.type == "Block" then
- if i > 1 and i < #tree then
- nomsu:add(" ")
- end
- if not (i == #tree) then
+ if i ~= #tree then
+ if i > 1 then
+ nomsu:add(" ")
+ end
arg_nomsu:parenthesize()
end
else
- if i > 1 and tree[i - 1] ~= "#" then
+ if i > 1 then
nomsu:add(" ")
end
- if bit.type == "Action" or bit.type == "MethodCall" then
+ if bit.type == "MethodCall" then
+ arg_nomsu:parenthesize()
+ elseif bit.type == "Action" and not can_be_unary(bit) then
arg_nomsu:parenthesize()
end
end
@@ -153,7 +169,7 @@ tree_to_inline_nomsu = function(tree)
nomsu:add(", ")
end
local item_nomsu = tree_to_inline_nomsu(item, true)
- if item.type == "MethodCall" then
+ if item.type == "MethodCall" or (item.type == "Block" and i < #tree) then
item_nomsu:parenthesize()
end
nomsu:add(item_nomsu)
@@ -256,7 +272,7 @@ tree_to_nomsu = function(tree)
local space = MAX_LINE - nomsu:trailing_line_len()
local try_inline = true
for subtree in coroutine.wrap(function()
- return (t:map(coroutine.yield) and nil)
+ return (t:with(coroutine.yield) and nil)
end) do
local _exp_0 = subtree.type
if "Comment" == _exp_0 then
@@ -294,7 +310,9 @@ tree_to_nomsu = function(tree)
local inline_nomsu
if try_inline then
inline_nomsu = tree_to_inline_nomsu(t)
- if (t.type == "Action" or t.type == "MethodCall") then
+ if t.type == "MethodCall" then
+ inline_nomsu:parenthesize()
+ elseif t.type == "Action" and not can_be_unary(t) then
inline_nomsu:parenthesize()
end
if #inline_nomsu:text() <= space or #inline_nomsu:text() <= 8 then
@@ -306,10 +324,12 @@ tree_to_nomsu = function(tree)
local indented = tree_to_nomsu(t)
if t.type == "Action" or t.type == "MethodCall" then
if indented:is_multiline() then
- if argnum == nil or argnum == 1 then
- return NomsuCode:from(t.source, "(\n ", indented, "\n)")
- else
- return NomsuCode:from(t.source, "\n ", indented)
+ if not (indented:text():match("\n%S[^\n ]*$")) then
+ if argnum == nil or argnum == 1 then
+ return NomsuCode:from(t.source, "(\n ", indented, "\n)")
+ else
+ return NomsuCode:from(t.source, "\n ", indented)
+ end
end
elseif argnum and argnum > 1 then
return NomsuCode:from(t.source, "\n ", indented)
@@ -346,7 +366,7 @@ tree_to_nomsu = function(tree)
local _exp_0 = tree.type
if "FileChunks" == _exp_0 then
if tree.shebang then
- nomsu:add(tree.shebang)
+ nomsu:add(tree.shebang, "\n")
end
for chunk_no, chunk in ipairs(tree) do
if chunk_no > 1 then
@@ -360,6 +380,11 @@ tree_to_nomsu = function(tree)
end
return nomsu
elseif "Action" == _exp_0 then
+ if can_be_unary(tree) and not can_be_unary(tree[2]) then
+ nomsu:add(tree[1])
+ nomsu:add(recurse(tree[2]))
+ return nomsu
+ end
local next_space = ""
local word_buffer = { }
local num_args, num_words = 0, 0
@@ -636,7 +661,7 @@ tree_to_nomsu = function(tree)
end
return nomsu
elseif "Comment" == _exp_0 then
- nomsu:add("#", (tree[1]:gsub("\n", "\n ")))
+ nomsu:add("###", (tree[1]:gsub("\n", "\n ")))
return nomsu
elseif "IndexChain" == _exp_0 or "Index" == _exp_0 or "Number" == _exp_0 or "Var" == _exp_0 or "Comment" == _exp_0 or "Error" == _exp_0 then
return tree_to_inline_nomsu(tree)
diff --git a/nomsu_decompiler.moon b/nomsu_decompiler.moon
index b588e1e..5ba583b 100644
--- a/nomsu_decompiler.moon
+++ b/nomsu_decompiler.moon
@@ -11,14 +11,18 @@ utf8_char_patt = (
R("\194\223")*R("\128\191") +
R("\224\239")*R("\128\191")*R("\128\191") +
R("\240\244")*R("\128\191")*R("\128\191")*R("\128\191"))
-operator_patt = S("'`~!@%#^&*+=|<>?/-")^1 * -1
-identifier_patt = (R("az","AZ","09") + P("_") + utf8_char_patt)^1 * -1
+operator_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B")+R("\xA8\xAB")) * R("\128\191"))
+operator_patt = operator_char^1 * -1
+identifier_patt = (R("az","AZ","09") + P("_") + (-operator_char*utf8_char_patt))^1 * -1
is_operator = (s)->
- return not not operator_patt\match(s)
+ return type(s) == 'string' and operator_patt\match(s)
is_identifier = (s)->
- return not not identifier_patt\match(s)
+ return type(s) == 'string' and identifier_patt\match(s)
+
+can_be_unary = (t)->
+ t.type == "Action" and #t == 2 and is_operator(t[1]) and type(t[2]) != 'string' and t[2].type != "Block"
inline_escaper = re.compile("{~ (%utf8_char / ('\"' -> '\\\"') / ('\n' -> '\\n') / ('\t' -> '\\t') / ('\b' -> '\\b') / ('\a' -> '\\a') / ('\v' -> '\\v') / ('\f' -> '\\f') / ('\r' -> '\\r') / ('\\' -> '\\\\') / ([^ -~] -> escape) / .)* ~}", {utf8_char: utf8_char_patt, escape:(=> ("\\%03d")\format(@byte!))})
inline_escape = (s)->
@@ -33,6 +37,14 @@ tree_to_inline_nomsu = (tree)->
switch tree.type
when "Action"
nomsu = NomsuCode\from(tree.source)
+ if can_be_unary(tree)
+ nomsu\add tree[1]
+ arg_nomsu = tree_to_inline_nomsu(tree[2])
+ if tree[2].type == "MethodCall" or tree[2].type == "Action"
+ arg_nomsu\parenthesize!
+ nomsu\add arg_nomsu
+ return nomsu
+
num_args, num_words = 0, 0
for i,bit in ipairs tree
if type(bit) == "string"
@@ -46,13 +58,14 @@ tree_to_inline_nomsu = (tree)->
num_args += 1
arg_nomsu = tree_to_inline_nomsu(bit)
if bit.type == "Block"
- if i > 1 and i < #tree
- nomsu\add " "
- unless i == #tree
+ if i != #tree
+ nomsu\add " " if i > 1
arg_nomsu\parenthesize!
else
- nomsu\add " " if i > 1 and tree[i-1] != "#"
- if bit.type == "Action" or bit.type == "MethodCall"
+ nomsu\add " " if i > 1
+ if bit.type == "MethodCall"
+ arg_nomsu\parenthesize!
+ elseif bit.type == "Action" and not can_be_unary(bit)
arg_nomsu\parenthesize!
nomsu\add arg_nomsu
if num_args == 1 and num_words == 0
@@ -112,7 +125,7 @@ tree_to_inline_nomsu = (tree)->
item_nomsu = tree_to_inline_nomsu(item, true)
--if item.type == "Block" or item.type == "Action" or item.type == "MethodCall"
-- item_nomsu\parenthesize!
- if item.type == "MethodCall"
+ if item.type == "MethodCall" or (item.type == "Block" and i < #tree)
item_nomsu\parenthesize!
nomsu\add item_nomsu
nomsu\add(tree.type == "List" and "]" or "}")
@@ -196,7 +209,7 @@ tree_to_nomsu = (tree)->
recurse = (t, argnum=nil)->
space = MAX_LINE - nomsu\trailing_line_len!
try_inline = true
- for subtree in coroutine.wrap(-> (t\map(coroutine.yield) and nil))
+ for subtree in coroutine.wrap(-> (t\with(coroutine.yield) and nil))
switch subtree.type
when "Comment"
try_inline = false
@@ -215,7 +228,9 @@ tree_to_nomsu = (tree)->
local inline_nomsu
if try_inline
inline_nomsu = tree_to_inline_nomsu(t)
- if (t.type == "Action" or t.type == "MethodCall")
+ if t.type == "MethodCall"
+ inline_nomsu\parenthesize!
+ elseif t.type == "Action" and not can_be_unary(t)
inline_nomsu\parenthesize!
if #inline_nomsu\text! <= space or #inline_nomsu\text! <= 8
if t.type != "Text"
@@ -223,10 +238,11 @@ tree_to_nomsu = (tree)->
indented = tree_to_nomsu(t)
if t.type == "Action" or t.type == "MethodCall"
if indented\is_multiline!
- if argnum == nil or argnum == 1
- return NomsuCode\from(t.source, "(\n ", indented, "\n)")
- else
- return NomsuCode\from(t.source, "\n ", indented)
+ unless indented\text!\match("\n%S[^\n ]*$")
+ if argnum == nil or argnum == 1
+ return NomsuCode\from(t.source, "(\n ", indented, "\n)")
+ else
+ return NomsuCode\from(t.source, "\n ", indented)
elseif argnum and argnum > 1
return NomsuCode\from(t.source, "\n ", indented)
else
@@ -244,7 +260,7 @@ tree_to_nomsu = (tree)->
switch tree.type
when "FileChunks"
if tree.shebang
- nomsu\add tree.shebang
+ nomsu\add tree.shebang, "\n"
for chunk_no, chunk in ipairs tree
nomsu\add "\n\n#{("~")\rep(80)}\n\n" if chunk_no > 1
@@ -256,6 +272,11 @@ tree_to_nomsu = (tree)->
return nomsu
when "Action"
+ if can_be_unary(tree) and not can_be_unary(tree[2])
+ nomsu\add tree[1]
+ nomsu\add recurse(tree[2])
+ return nomsu
+
next_space = ""
word_buffer = {}
num_args, num_words = 0, 0
@@ -429,10 +450,6 @@ tree_to_nomsu = (tree)->
nomsu\add "\\;"
return NomsuCode\from(tree.source, '("\n ', nomsu, '\n")')
-
-
-
-
when "List", "Dict"
if #tree == 0
nomsu\add(tree.type == "List" and "[]" or "{}")
@@ -490,7 +507,7 @@ tree_to_nomsu = (tree)->
return nomsu
when "Comment"
- nomsu\add "#", (tree[1]\gsub("\n", "\n "))
+ nomsu\add "###", (tree[1]\gsub("\n", "\n "))
return nomsu
when "IndexChain", "Index", "Number", "Var", "Comment", "Error"
diff --git a/nomsu_environment.lua b/nomsu_environment.lua
index 5ea6a62..8573179 100644
--- a/nomsu_environment.lua
+++ b/nomsu_environment.lua
@@ -22,17 +22,6 @@ make_tree = function(tree, userdata)
tree = SyntaxTree(tree)
return tree
end
-table.map = function(t, fn)
- return setmetatable((function()
- local _accum_0 = { }
- local _len_0 = 1
- for _, v in ipairs(t) do
- _accum_0[_len_0] = fn(v)
- _len_0 = _len_0 + 1
- end
- return _accum_0
- end)(), getmetatable(t))
-end
local Parsers = { }
local max_parser_version = 0
for version = 1, 999 do
@@ -88,6 +77,14 @@ _1_as_text = function(x)
end
return tostring(x)
end
+local _1_as_list
+_1_as_list = function(x)
+ local mt = getmetatable(x)
+ if mt.as_list then
+ return mt.as_list(x)
+ end
+ return x
+end
local nomsu_environment
nomsu_environment = Importer({
NOMSU_COMPILER_VERSION = 13,
@@ -160,6 +157,7 @@ nomsu_environment = Importer({
compile = compile,
at_1_fail = fail_at,
_1_as_text = _1_as_text,
+ _1_as_list = _1_as_list,
exit = os.exit,
quit = os.exit,
_1_parsed = function(nomsu_code, syntax_version)
@@ -179,56 +177,6 @@ nomsu_environment = Importer({
if tree.shebang then
tree.version = tree.version or tree.shebang:match("nomsu %-V[ ]*([%d.]*)")
end
- local errs = { }
- local find_errors
- find_errors = function(t)
- if t.type == "Error" then
- errs[#errs + 1] = t
- else
- for k, v in pairs(t) do
- local _continue_0 = false
- repeat
- if not (SyntaxTree:is_instance(v)) then
- _continue_0 = true
- break
- end
- find_errors(v)
- _continue_0 = true
- until true
- if not _continue_0 then
- break
- end
- end
- end
- end
- find_errors(tree)
- local num_errs = #errs
- if num_errs > 0 then
- local err_strings
- do
- local _accum_0 = { }
- local _len_0 = 1
- for i, e in ipairs(errs) do
- if i <= 3 then
- _accum_0[_len_0] = pretty_error({
- title = "Parse error",
- error = e.error,
- hint = e.hint,
- source = e:get_source_file(),
- start = e.source.start,
- stop = e.source.stop,
- filename = e.source.filename
- })
- _len_0 = _len_0 + 1
- end
- end
- err_strings = _accum_0
- end
- if num_errs > #err_strings then
- table.insert(err_strings, C("bright red", " +" .. tostring(num_errs - #err_strings) .. " additional errors...\n"))
- end
- error(table.concat(err_strings, '\n\n'), 0)
- end
return tree
end,
Module = function(self, package_name)
@@ -361,6 +309,7 @@ nomsu_environment = Importer({
elseif LuaCode:is_instance(to_run) then
local source = to_run.source
local lua_string = to_run:text()
+ lua_string = lua_string:gsub("^#![^\n]*\n", "")
local run_lua_fn, err = load(lua_string, tostring(source), "t", self)
if not run_lua_fn then
local lines
diff --git a/nomsu_environment.moon b/nomsu_environment.moon
index d572f8c..3b589dd 100644
--- a/nomsu_environment.moon
+++ b/nomsu_environment.moon
@@ -16,8 +16,6 @@ make_tree = (tree, userdata)->
tree = SyntaxTree(tree)
return tree
-table.map = (t, fn)-> setmetatable([fn(v) for _,v in ipairs(t)], getmetatable(t))
-
Parsers = {}
max_parser_version = 0
for version=1,999
@@ -48,6 +46,11 @@ _1_as_text = (x)->
if x == false then return "no"
return tostring(x)
+_1_as_list = (x)->
+ mt = getmetatable(x)
+ if mt.as_list then return mt.as_list(x)
+ return x
+
local nomsu_environment
nomsu_environment = Importer{
NOMSU_COMPILER_VERSION: 13, NOMSU_SYNTAX_VERSION: max_parser_version
@@ -74,7 +77,7 @@ nomsu_environment = Importer{
-- Nomsu functions:
_1_as_nomsu:tree_to_nomsu, _1_as_inline_nomsu:tree_to_inline_nomsu,
- compile: compile, at_1_fail:fail_at, _1_as_text:_1_as_text,
+ compile: compile, at_1_fail:fail_at, :_1_as_text, :_1_as_list,
exit:os.exit, quit:os.exit,
_1_parsed: (nomsu_code, syntax_version)->
@@ -91,26 +94,6 @@ nomsu_environment = Importer{
tree = parse(nomsu_code, source.filename)
if tree.shebang
tree.version or= tree.shebang\match("nomsu %-V[ ]*([%d.]*)")
- errs = {}
- find_errors = (t)->
- if t.type == "Error"
- errs[#errs+1] = t
- else
- for k,v in pairs(t)
- continue unless SyntaxTree\is_instance(v)
- find_errors(v)
- find_errors(tree)
- num_errs = #errs
- if num_errs > 0
- err_strings = [pretty_error{
- title:"Parse error"
- error:e.error, hint:e.hint, source:e\get_source_file!
- start:e.source.start, stop:e.source.stop, filename:e.source.filename
- } for i, e in ipairs(errs) when i <= 3]
- if num_errs > #err_strings
- table.insert(err_strings, C("bright red", " +#{num_errs-#err_strings} additional errors...\n"))
- error(table.concat(err_strings, '\n\n'), 0)
-
return tree
Module: (package_name)=>
@@ -218,6 +201,8 @@ nomsu_environment = Importer{
elseif LuaCode\is_instance(to_run)
source = to_run.source
lua_string = to_run\text!
+ -- For some reason, Lua doesn't strip shebangs from Lua files
+ lua_string = lua_string\gsub("^#![^\n]*\n","")
-- If you replace tostring(source) with "nil", source mapping won't happen
run_lua_fn, err = load(lua_string, tostring(source), "t", @)
if not run_lua_fn
diff --git a/parser.lua b/parser.lua
index 339cf30..5f36ee0 100644
--- a/parser.lua
+++ b/parser.lua
@@ -21,6 +21,7 @@ do
local _with_0 = { }
_with_0.nl = P("\r") ^ -1 * P("\n")
_with_0.tab = P("\t")
+ _with_0.at_break = lpeg.B(lpeg.S(";,. \r\n\t({[")) + -lpeg.B(1)
_with_0.tonumber = tonumber
_with_0.tochar = string.char
_with_0.unpack = unpack or table.unpack
@@ -34,6 +35,7 @@ do
return true, (s:match("^ *", i))
end)
_with_0.utf8_char = (R("\194\223") * R("\128\191") + R("\224\239") * R("\128\191") * R("\128\191") + R("\240\244") * R("\128\191") * R("\128\191") * R("\128\191"))
+ _with_0.operator_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B") + R("\xA8\xAB")) * R("\128\191"))
_with_0.Tree = function(t, userdata)
return userdata.make_tree(t, userdata)
end
diff --git a/parser.moon b/parser.moon
index 47459b2..ce81ff4 100644
--- a/parser.moon
+++ b/parser.moon
@@ -20,6 +20,7 @@ DEFS = with {}
-- Newline supports either windows-style CR+LF or unix-style LF
.nl = P("\r")^-1 * P("\n")
.tab = P("\t")
+ .at_break = lpeg.B(lpeg.S(";,. \r\n\t({[")) + -lpeg.B(1)
.tonumber = tonumber
.tochar = string.char
.unpack = unpack or table.unpack
@@ -36,6 +37,7 @@ DEFS = with {}
R("\194\223")*R("\128\191") +
R("\224\239")*R("\128\191")*R("\128\191") +
R("\240\244")*R("\128\191")*R("\128\191")*R("\128\191"))
+ .operator_char = S("#'`~@^&*+=<>?/%!|\\-") + (P("\xE2") * (R("\x88\x8B")+R("\xA8\xAB")) * R("\128\191"))
.Tree = (t, userdata)-> userdata.make_tree(t, userdata)
.foldr = foldr
diff --git a/syntax_tree.lua b/syntax_tree.lua
index b7b7f83..aa15ef3 100644
--- a/syntax_tree.lua
+++ b/syntax_tree.lua
@@ -55,6 +55,9 @@ do
if type(self) ~= type(other) or #self ~= #other or getmetatable(self) ~= getmetatable(other) then
return false
end
+ if self.type ~= other.type then
+ return false
+ end
for i = 1, #self do
if self[i] ~= other[i] then
return false
@@ -87,7 +90,24 @@ do
get_source_code = function(self)
return self.__class.source_code_for_tree[self]:sub(self.source.start, self.source.stop - 1)
end,
- map = function(self, fn)
+ add = function(self, ...)
+ local n = #self
+ for i = 1, select('#', ...) do
+ self[n + i] = select(i, ...)
+ end
+ self.stub = nil
+ end,
+ with = function(self, fn)
+ if type(fn) == 'table' then
+ local replacements = fn
+ fn = function(t)
+ for k, v in pairs(replacements) do
+ if k == t then
+ return v
+ end
+ end
+ end
+ end
local replacement = fn(self)
if replacement == false then
return nil
@@ -122,7 +142,7 @@ do
repeat
replacement[k] = v
if SyntaxTree:is_instance(v) then
- local r = v:map(fn)
+ local r = v:with(fn)
if r == v or r == nil then
_continue_0 = true
break
@@ -143,6 +163,19 @@ do
end
return replacement
end,
+ contains = function(self, subtree)
+ if subtree == self then
+ return true
+ end
+ for k, v in pairs(self) do
+ if SyntaxTree:is_instance(v) then
+ if v:contains(subtree) then
+ return true
+ end
+ end
+ end
+ return false
+ end,
get_args = function(self)
assert(self.type == "Action" or self.type == "MethodCall", "Only actions and method calls have arguments")
local args = { }
diff --git a/syntax_tree.moon b/syntax_tree.moon
index 278e22b..1800fcb 100644
--- a/syntax_tree.moon
+++ b/syntax_tree.moon
@@ -24,6 +24,7 @@ class SyntaxTree
__eq: (other)=>
return false if type(@) != type(other) or #@ != #other or getmetatable(@) != getmetatable(other)
+ return false if @type != other.type
for i=1,#@
return false if @[i] != other[i]
return true
@@ -44,7 +45,20 @@ class SyntaxTree
})
get_source_file: => @@source_code_for_tree[@]
get_source_code: => @@source_code_for_tree[@]\sub(@source.start, @source.stop-1)
- map: (fn)=>
+
+ add: (...)=>
+ n = #@
+ for i=1,select('#', ...)
+ @[n+i] = select(i, ...)
+ @stub = nil
+
+ with: (fn)=>
+ if type(fn) == 'table'
+ replacements = fn
+ fn = (t)->
+ for k,v in pairs(replacements)
+ if k == t then return v
+
replacement = fn(@)
if replacement == false then return nil
if replacement
@@ -60,7 +74,7 @@ class SyntaxTree
for k,v in pairs(@)
replacement[k] = v
if SyntaxTree\is_instance(v)
- r = v\map(fn)
+ r = v\with(fn)
continue if r == v or r == nil
changes = true
replacement[k] = r
@@ -68,6 +82,13 @@ class SyntaxTree
replacement = SyntaxTree(replacement)
return replacement
+ contains: (subtree)=>
+ if subtree == @ then return true
+ for k,v in pairs(@)
+ if SyntaxTree\is_instance(v)
+ return true if v\contains(subtree)
+ return false
+
get_args: =>
assert(@type == "Action" or @type == "MethodCall", "Only actions and method calls have arguments")
args = {}