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 = {}