diff --git a/core/control_flow.nom b/core/control_flow.nom
index 315c4ef..4066a97 100644
--- a/core/control_flow.nom
+++ b/core/control_flow.nom
@@ -104,14 +104,14 @@ immediately:
         %body_lua <- (%body as lua)
         %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
         if %body has subtree % where:
-            ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repeat")
+            ((%'s "type") = "FunctionCall") and ((%'s stub) is "do next repeat")
         ..: %body_statments +<- "\n::continue_repeat::;"
         %code <- ".."
             while \(%condition as lua expr) do
             \%body_statements
             end --while-loop
         if %body has subtree % where:
-            ((%'s "type") = "FunctionCall") and ((%'s "stub") is "stop repeating")
+            ((%'s "type") = "FunctionCall") and ((%'s stub) is "stop repeating")
         ..:
             %code <- ".."
                 do -- scope of "stop repeating" label
@@ -128,14 +128,14 @@ immediately:
         %body_lua <- (%body as lua)
         %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
         if %body has subtree % where
-            ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repeat")
+            ((%'s "type") = "FunctionCall") and ((%'s stub) is "do next repeat")
         ..: %body_statements +<- "\n::continue_repeat::;"
         %code <- ".."
             for i=1,\(%n as lua expr) do
             \%body_statements
             end --numeric for-loop
         if %body has subtree % where:
-            ((%'s "type") = "FunctionCall") and ((%'s "stub") is "stop repeating")
+            ((%'s "type") = "FunctionCall") and ((%'s stub) is "stop repeating")
         ..:
             %code <- ".."
                 do -- scope of "stop repeating" label
@@ -161,7 +161,7 @@ immediately:
         %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
         if %body has subtree % where:
             ((%'s "type") = "FunctionCall") and
-                ((%'s "stub") is "do next %") and 
+                ((%'s stub) is "do next %") and 
                     ((3rd in (%'s "value"))'s "value") is (%var's "value")
         ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;"
 
@@ -174,7 +174,7 @@ immediately:
 
         if %body has subtree % where:
             ((%'s "type") = "FunctionCall") and:
-                ((%'s "stub") is "stop %") and:
+                ((%'s stub) is "stop %") and:
                     ((2nd in (%'s "value"))'s "value") is (%var's "value")
         ..:
             %code <- ".."
@@ -200,7 +200,7 @@ immediately:
         %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
         if %body has subtree % where:
             ((%'s "type") = "FunctionCall") and
-                ((%'s "stub") is "do next %") and
+                ((%'s stub) is "do next %") and
                     ((3rd in (%'s "value"))'s "value") is (%var's "value")
         ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;"
         # This uses Lua's approach of only allowing loop-scoped variables in a loop
@@ -211,7 +211,7 @@ immediately:
             end --foreach-loop
         if %body has subtree % where:
             ((%'s "type") = "FunctionCall") and
-                ((%'s "stub") is "stop %") and
+                ((%'s stub) is "stop %") and
                     ((2nd in (%'s "value"))'s "value") is (%var's "value")
         ..:
             %code <- ".."
@@ -230,13 +230,13 @@ immediately:
         %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");")
         if %body has subtree % where:
             ((%'s "type") = "FunctionCall") and
-                ((%'s "stub") is "do next %") and
+                ((%'s stub) is "do next %") and
                     ((3rd in (%'s "value"))'s "value") is (%key's "value")
         ..: %body_statements +<- "\n::continue_\(%key as lua identifier)::;"
 
         if %body has subtree % where:
             ((%'s "type") = "FunctionCall") and
-                ((%'s "stub") is "do next %") and
+                ((%'s stub) is "do next %") and
                     ((3rd in (%'s "value"))'s "value") is (%value's "value")
         ..: %body_statements +<- "\n::continue_\(%value as lua identifier)::;"
 
@@ -251,13 +251,13 @@ immediately:
         %stop_labels <- ""
         if %body has subtree % where:
             ((%'s "type") = "FunctionCall") and
-                ((%'s "stub") is "stop %") and
+                ((%'s stub) is "stop %") and
                     ((2nd in (%'s "value"))'s "value") is (%key's "value")
         ..: %stop_labels +<- "\n::stop_\(%key as lua identifier)::;"
 
         if %body has subtree % where:
             ((%'s "type") = "FunctionCall") and
-                ((%'s "stub") is "stop %") and
+                ((%'s stub) is "stop %") and
                     ((2nd in (%'s "value"))'s "value") is (%value's "value")
         ..: %stop_labels +<- "\n::stop_\(%value as lua identifier)::;"
 
diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom
index b56f0e9..fa589bd 100644
--- a/core/metaprogramming.nom
+++ b/core/metaprogramming.nom
@@ -6,12 +6,16 @@
 immediately:
     lua> ".."
         nomsu:define_compile_action("compile %actions to %lua", \(!! code location !!), function(\%actions, \%lua)
-            local signature = {};
-            for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end
-            local stubs = nomsu:get_stubs_from_signature(signature);
-            local stub_args = nomsu:get_args_from_signature(signature);
+            local stubs = {};
+            for i, action in ipairs(\%actions.value) do
+                stubs[i] = nomsu:tree_to_named_stub(action);
+            end
+            local args = {};
+            for i,tok in ipairs(\%actions.value[1].value) do
+                if tok.type == "Var" then args[#args+1] = nomsu:var_to_lua_identifier(tok.value); end
+            end
             local arg_set = {};
-            for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end
+            for i, arg in ipairs(args) do arg_set[arg] = true; end
             if \%lua.type == "Text" then
                 error("Invalid type for 'compile % to %', expected a dict with expr/statements, but got text.", 0);
             end
@@ -26,11 +30,11 @@ immediately:
             if #undeclared_locals > 0 then
                 body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code;
             end
-            local lua_fn_args = table.concat(stub_args[1], ", ");
-            local def_tree = nomsu.compilestack[#nomsu.compilestack];
-            local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop);
+            local lua_fn_args = table.concat(args, ", ");
+            local def_metadata = nomsu.tree_metadata[nomsu.compilestack[#nomsu.compilestack]];
+            local code_location = ("%s:%s,%s"):format(def_metadata.filename, def_metadata.start, def_metadata.stop);
             return {statements=([[
-        nomsu:define_compile_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
+        nomsu:define_compile_action(]]..repr(stubs)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
             ]]..body_code.."\\n"..[[
         end);
         ]])};
@@ -40,12 +44,16 @@ immediately:
 immediately:
     compile [action %actions %body] to:
         lua> ".."
-            local signature = {};
-            for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end
-            local stubs = nomsu:get_stubs_from_signature(signature);
-            local stub_args = nomsu:get_args_from_signature(signature);
+            local stubs = {};
+            for i, action in ipairs(\%actions.value) do
+                stubs[i] = nomsu:tree_to_named_stub(action);
+            end
+            local args = {};
+            for i,tok in ipairs(\%actions.value[1].value) do
+                if tok.type == "Var" then args[#args+1] = nomsu:var_to_lua_identifier(tok.value); end
+            end
             local arg_set = {};
-            for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end
+            for i, arg in ipairs(args) do arg_set[arg] = true; end
             local body_lua = nomsu:tree_to_lua(\%body);
             local body_code = body_lua.statements or ("return "..body_lua.expr..";");
             local undeclared_locals = {};
@@ -57,11 +65,12 @@ immediately:
             if #undeclared_locals > 0 then
                 body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code;
             end
-            local lua_fn_args = table.concat(stub_args[1], ", ");
-            local def_tree = nomsu.compilestack[#nomsu.compilestack];
-            local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop);
+            local lua_fn_args = table.concat(args, ", ");
+            local def_metadata = nomsu.tree_metadata[nomsu.compilestack[#nomsu.compilestack]];
+            assert(def_metadata, "No metadata found for: "..tostring(nomsu.compilestack[#nomsu.compilestack]));
+            local code_location = ("%s:%s,%s"):format(def_metadata.filename, def_metadata.start, def_metadata.stop);
             return {statements=[[
-            nomsu:define_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
+            nomsu:define_action(]]..repr(stubs)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
                 ]]..body_code.."\\n"..[[
             end);
             ]]};
@@ -70,27 +79,31 @@ immediately:
 immediately:
     compile [parse %shorthand as %longhand] to:
         lua> ".."
-            local signature = {};
-            for i, action in ipairs(\%shorthand.value) do signature[i] = action:get_src(); end
-            local stubs = nomsu:get_stubs_from_signature(signature);
-            local stub_args = nomsu:get_args_from_signature(signature);
-            local lua_fn_args = table.concat(stub_args[1], ", ");
+            local stubs = {};
+            for i, action in ipairs(\%shorthand.value) do
+                stubs[i] = nomsu:tree_to_named_stub(action);
+            end
+            local args = {};
+            for i,tok in ipairs(\%shorthand.value[1].value) do
+                if tok.type == "Var" then args[#args+1] = nomsu:var_to_lua_identifier(tok.value); end
+            end
+            local lua_fn_args = table.concat(args, ", ");
             local template;
             if \%longhand.type == "Block" then
                 local lines = {};
-                for i, line in ipairs(\%longhand.value) do lines[i] = nomsu:dedent(line:get_src()); end
+                for i, line in ipairs(\%longhand.value) do lines[i] = nomsu:get_source_code(line); end
                 template = repr(table.concat(lines, "\\n"));
             else
-                template = repr(nomsu:dedent(\%longhand:get_src()));
+                template = repr(nomsu:get_source_code(\%longhand));
             end
             local replacements = {};
-            for i, a in ipairs(stub_args[1]) do replacements[i] = a.."="..a; end
+            for i, a in ipairs(args) do replacements[i] = a.."="..a; end
             replacements = "{"..table.concat(replacements, ", ").."}";
-            local def_tree = nomsu.compilestack[#nomsu.compilestack];
-            local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop);
+            local def_metadata = nomsu.tree_metadata[nomsu.compilestack[#nomsu.compilestack]];
+            local code_location = ("%s:%s,%s"):format(def_metadata.filename, def_metadata.start, def_metadata.stop);
             return {statements=[[
-            nomsu:define_compile_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
-                local template = nomsu:parse(]]..template..[[, ]]..repr(def_tree.filename)..[[);
+            nomsu:define_compile_action(]]..repr(stubs)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[)
+                local template = nomsu:parse(]]..template..[[, ]]..repr(def_metadata.filename)..[[);
                 local replacement = nomsu:tree_with_replaced_vars(template, ]]..replacements..[[);
                 return nomsu:tree_to_lua(replacement);
             end);
@@ -99,7 +112,7 @@ immediately:
 action [remove action %stub]:
     lua> ".."
         local fn = ACTIONS[\%stub];
-        local metadata = ACTION_METADATA[fn];
+        local metadata = nomsu.action_metadata[fn];
         for i=#metadata.aliases,1,-1 do
             metadata.arg_orders[metadata.aliases[i]] = nil;
             table.remove(metadata.aliases, i);
@@ -114,7 +127,7 @@ immediately:
         lua> ".."
             local lua = nomsu:tree_to_lua(\%tree);
             if lua.locals or not lua.expr then
-                error("Invalid thing to convert to lua expr: "..\%tree:get_src());
+                error("Invalid thing to convert to lua expr: "..nomsu:get_source_code(\%tree));
             end
             return lua.expr;
 
@@ -130,8 +143,11 @@ immediately:
     action [%tree as value]:
         =lua "nomsu:tree_to_value(\%tree)"
     
+    action [%tree's stub]:
+        =lua "nomsu:tree_to_stub(\%tree)"
+    
 immediately:
-    compile [%tree's source code, %tree' source code] to {expr:"\(%tree as lua expr):get_src()"}
+    compile [%tree's source code, %tree' source code] to {expr:"nomsu:get_source_code(\(%tree as lua expr))"}
 
     compile [repr %obj] to {expr:"repr(\(%obj as lua expr))"}
     compile [type of %obj] to {expr:"type(\(%obj as lua expr))"}
@@ -141,7 +157,7 @@ immediately:
     compile [%var as lua identifier] to {expr:"nomsu:var_to_lua_identifier(\(%var as lua expr))"}
 
 action [action %names metadata]:
-    =lua "ACTION_METADATA[ACTIONS[\%names]]"
+    =lua "nomsu.action_metadata[ACTIONS[\%names]]"
 
 # Get the source code for a function
 action [help %action]:
@@ -155,7 +171,12 @@ action [help %action]:
 
 # Compiler tools
 immediately:
-    compile [run %code] to {expr: "nomsu:run(\(%code as lua expr), '\(!! code location !!)')"}
+    #local def_metadata = nomsu.tree_metadata[nomsu.compilestack[#nomsu.compilestack]];
+    compile [run %code] to {..}
+        expr: ".."
+            nomsu:run(\(%code as lua expr), '\
+                =lua "nomsu.tree_metadata[nomsu.compilestack[#nomsu.compilestack]].filename"
+            ..')
     parse [enable debugging] as: lua> "nomsu.debug = true;"
     parse [disable debugging] as: lua> "nomsu.debug = false;"
 
@@ -186,7 +207,7 @@ immediately:
     compile [barf] to {statements:"error(nil, 0);"}
     compile [barf %msg] to {statements:"error(\(%msg as lua expr), 0);"}
     compile [assume %condition] to:
-        lua> "local \%assumption = 'Assumption failed: '..\%condition:get_src();"
+        lua> "local \%assumption = 'Assumption failed: '..nomsu:get_source_code(\%condition);"
         return {..}
             statements:".."
                 if not \(%condition as lua expr) then
diff --git a/lib/object.nom b/lib/object.nom
index 2f2f3ff..ca664b5 100644
--- a/lib/object.nom
+++ b/lib/object.nom
@@ -33,17 +33,21 @@ compile [define object %classname %class_body] to:
     for %line in (%class_body's "value"):
         if: (%line's "type") is "Comment"
             do next %line
-        assume (((%line's "type") == "FunctionCall") and ((%line's "stub") == "action % %"))
+        assume (((%line's "type") == "FunctionCall") and ((%line's stub) == "action % %"))
         ..or barf "Only action definitions are supported inside 'define object % %', not \(%line's "src")"
         %actions <- (2nd in (%line's "value"))
         %body <- (3rd in (%line's "value"))
         lua> ".."
-            local signature = {};
-            for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end
-            local stubs = nomsu:get_stubs_from_signature(signature);
-            local stub_args = nomsu:get_args_from_signature(signature);
+            local stubs = {};
+            for i, action in ipairs(\%actions.value) do
+                stubs[i] = nomsu:tree_to_named_stub(action);
+            end
+            local args = {};
+            for i,tok in ipairs(\%actions.value[1].value) do
+                if tok.type == "Var" then args[#args+1] = nomsu:var_to_lua_identifier(tok.value); end
+            end
             local arg_set = {};
-            for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end
+            for i, arg in ipairs(args) do arg_set[arg] = true; end
             local body_lua = nomsu:tree_to_lua(\%body);
             local body_code = body_lua.statements or ("return "..body_lua.expr..";");
             local undeclared_locals = {};
@@ -55,12 +59,12 @@ compile [define object %classname %class_body] to:
             if #undeclared_locals > 0 then
                 body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code;
             end
-            local lua_fn_args = table.concat(stub_args[1], ", ");
+            local lua_fn_args = table.concat(args, ", ");
             local def_tree = nomsu.compilestack[#nomsu.compilestack];
             local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop);
 
             local compiled_args = {};
-            for i, arg in ipairs(stub_args[1]) do
+            for i, arg in ipairs(args) do
                 compiled_args[i] = "nomsu:tree_to_lua("..arg..").expr";
             end
             compiled_args = table.concat(compiled_args, "..', '..");
@@ -75,7 +79,7 @@ compile [define object %classname %class_body] to:
             ]==]):format(
                 \%class_identifier, repr(stubs[1]), lua_fn_args,
                     body_code,
-                repr(signature), repr(code_location), lua_fn_args,
+                repr(stubs), repr(code_location), lua_fn_args,
                     repr(repr(stubs[1])), compiled_args,
                 \%class_identifier, repr(stubs[1]), repr(stubs[1])));
 
diff --git a/lib/object2.nom b/lib/object2.nom
index d27bbc0..0e8c6fb 100644
--- a/lib/object2.nom
+++ b/lib/object2.nom
@@ -53,7 +53,7 @@ compile [define object %classname %class_body] to:
     for %line in (%class_body's "value"):
         if: (%line's "type") is "Comment"
             do next %line
-        if: ((%line's "type") is "FunctionCall") and ((%line's "stub") is "slots %")
+        if: ((%line's "type") is "FunctionCall") and ((%line's stub) is "slots %")
             %slot_index_clauses <- []
             %slot_newindex_clauses <- []
             %slots <- ("value" in (2nd in (%line's "value")))
@@ -78,17 +78,21 @@ compile [define object %classname %class_body] to:
                     error("Attempt to store data in "..repr(key)..", which is not a valid slot on "..tostring(self.class));
                 end
             do next %line
-        assume (((%line's "type") is "FunctionCall") and ((%line's "stub") is "action % %"))
+        assume (((%line's "type") is "FunctionCall") and ((%line's stub) is "action % %"))
         ..or barf "Only action definitions are supported inside 'define object % %', not \(%line's "src")"
         %actions <- (2nd in (%line's "value"))
         %body <- (3rd in (%line's "value"))
         lua> ".."
-            local signature = {};
-            for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end
-            local stubs = nomsu:get_stubs_from_signature(signature);
-            local stub_args = nomsu:get_args_from_signature(signature);
+            local stubs = {};
+            for i, action in ipairs(\%actions.value) do
+                stubs[i] = nomsu:tree_to_named_stub(action);
+            end
+            local args = {};
+            for i,tok in ipairs(\%actions.value[1].value) do
+                if tok.type == "Var" then args[#args+1] = nomsu:var_to_lua_identifier(tok.value); end
+            end
             local arg_set = {};
-            for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end
+            for i, arg in ipairs(args) do arg_set[arg] = true; end
             local body_lua = nomsu:tree_to_lua(\%body);
             local body_code = body_lua.statements or ("return "..body_lua.expr..";");
             local undeclared_locals = {};
@@ -100,12 +104,12 @@ compile [define object %classname %class_body] to:
             if #undeclared_locals > 0 then
                 body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code;
             end
-            local lua_fn_args = table.concat({"self", unpack(stub_args[1])}, ", ");
+            local lua_fn_args = table.concat({"self", unpack(args)}, ", ");
             local def_tree = nomsu.compilestack[#nomsu.compilestack];
             local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop);
 
             local compiled_args = {};
-            for i, arg in ipairs(stub_args[1]) do
+            for i, arg in ipairs(args) do
                 compiled_args[i] = "nomsu:tree_to_lua("..arg..").expr";
             end
             compiled_args = table.concat(compiled_args, "..', '..");
diff --git a/nomsu.lua b/nomsu.lua
index 82465d1..096bf9e 100644
--- a/nomsu.lua
+++ b/nomsu.lua
@@ -22,6 +22,18 @@ do
   local _obj_0 = table
   insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat
 end
+local cached
+cached = function(fn)
+  local cache = setmetatable({ }, {
+    __mode = "k"
+  })
+  return function(self, arg)
+    if not (cache[arg]) then
+      cache[arg] = fn(self, arg)
+    end
+    return cache[arg]
+  end
+end
 do
   local STRING_METATABLE = getmetatable("")
   STRING_METATABLE.__add = function(self, other)
@@ -119,44 +131,23 @@ do
     err_msg = err_msg .. "\n" .. tostring(prev_line) .. "\n" .. tostring(err_line) .. "\n" .. tostring(pointer) .. "\n" .. tostring(next_line) .. "\n"
     return error(err_msg)
   end
-  _with_0.FunctionCall = function(start, value, stop)
-    local stub = concat((function()
-      local _accum_0 = { }
-      local _len_0 = 1
-      for _index_0 = 1, #value do
-        local t = value[_index_0]
-        _accum_0[_len_0] = (t.type == "Word" and t.value or "%")
-        _len_0 = _len_0 + 1
-      end
-      return _accum_0
-    end)(), " ")
-    local src = lpeg.userdata.source_code:sub(start, stop - 1)
-    return {
-      type = "FunctionCall",
-      start = start,
-      stop = stop,
-      value = value,
-      stub = stub,
-      filename = lpeg.userdata.filename,
-      get_line_no = lpeg.userdata.get_line_no,
-      get_src = lpeg.userdata.get_src
-    }
-  end
   NOMSU_DEFS = _with_0
 end
 setmetatable(NOMSU_DEFS, {
   __index = function(self, key)
     local make_node
     make_node = function(start, value, stop)
-      return {
+      local node = {
         type = key,
+        value = value
+      }
+      lpeg.userdata.tree_metadata[node] = {
         start = start,
         stop = stop,
-        value = value,
         filename = lpeg.userdata.filename,
-        get_src = lpeg.userdata.get_src,
-        get_line_no = lpeg.userdata.get_line_no
+        source_code = lpeg.userdata.source_code
       }
+      return node
     end
     self[key] = make_node
     return make_node
@@ -233,7 +224,7 @@ do
           arg_orders[stub] = arg_positions
         end
       end
-      self.environment.ACTION_METADATA[fn] = {
+      self.action_metadata[fn] = {
         fn = fn,
         source = source,
         aliases = stubs,
@@ -244,7 +235,7 @@ do
     end,
     define_compile_action = function(self, signature, source, fn, src)
       self:define_action(signature, source, fn)
-      self.environment.ACTION_METADATA[fn].compile_time = true
+      self.action_metadata[fn].compile_time = true
     end,
     serialize_defs = function(self, scope, after)
       if scope == nil then
@@ -301,36 +292,53 @@ do
       end
       return code:gsub("\n", "\n" .. ("    "):rep(levels))
     end,
+    get_line_number = cached(function(self, tree)
+      local metadata = self.tree_metadata[tree]
+      if not (metadata) then
+        error("Failed to find metatdata for tree: " .. tostring(tree), 0)
+      end
+      if not (self.file_metadata[metadata.filename]) then
+        error("Failed to find file metatdata for file: " .. tostring(metadata.filename), 0)
+      end
+      local line_starts = self.file_metadata[metadata.filename].line_starts
+      local first_line = 1
+      while first_line < #line_starts and line_starts[first_line + 1] < metadata.start do
+        first_line = first_line + 1
+      end
+      local last_line = first_line
+      while last_line < #line_starts and line_starts[last_line + 1] < metadata.stop do
+        last_line = last_line + 1
+      end
+      return tostring(metadata.filename) .. ":" .. tostring(first_line)
+    end),
+    get_source_code = function(self, tree)
+      local metadata = self.tree_metadata[tree]
+      if not (metadata) then
+        return self:tree_to_nomsu(tree)
+      end
+      return self:dedent(metadata.source_code:sub(metadata.start, metadata.stop - 1))
+    end,
     parse = function(self, nomsu_code, filename)
       assert(type(filename) == "string", "Bad filename type: " .. tostring(type(filename)))
       if self.debug then
         print(tostring(colored.bright("PARSING:")) .. "\n" .. tostring(colored.yellow(nomsu_code)))
       end
-      local userdata
-      do
-        local _with_0 = {
+      if not (self.file_metadata[filename]) then
+        self.file_metadata[filename] = {
           source_code = nomsu_code,
           filename = filename,
-          indent_stack = {
-            ""
-          }
+          line_starts = line_counter:match(nomsu_code)
         }
-        _with_0.get_src = function(self)
-          return nomsu_code:sub(self.start, self.stop - 1)
-        end
-        _with_0.line_starts = line_counter:match(_with_0.source_code)
-        _with_0.get_line_no = function(self)
-          if not (self._line_no) then
-            local line_no = 1
-            while line_no < #_with_0.line_starts and _with_0.line_starts[line_no + 1] < self.start do
-              line_no = line_no + 1
-            end
-            self._line_no = tostring(_with_0.filename) .. ":" .. tostring(line_no)
-          end
-          return self._line_no
-        end
-        userdata = _with_0
       end
+      local userdata = {
+        source_code = nomsu_code,
+        filename = filename,
+        indent_stack = {
+          ""
+        },
+        tree_metadata = self.tree_metadata,
+        line_starts = self.file_metadata[filename].line_starts
+      }
       local old_userdata
       old_userdata, lpeg.userdata = lpeg.userdata, userdata
       local tree = NOMSU:match(nomsu_code)
@@ -789,7 +797,10 @@ do
           for _index_0 = 1, #_list_0 do
             local line = _list_0[_index_0]
             nomsu = expression(line)
-            assert(nomsu, "Failed to produce output for:\n" .. tostring(colored.yellow(line:get_src())))
+            if not (nomsu) then
+              local src = self:get_source_code(line)
+              error("Failed to produce output for:\n" .. tostring(colored.yellow(src)), 0)
+            end
             insert(lines, nomsu)
           end
           return concat(lines, "\n")
@@ -853,6 +864,9 @@ do
       if not tree.type then
         error("Invalid tree: " .. tostring(repr(tree)), 0)
       end
+      if not (self.tree_metadata[tree]) then
+        error("??? tree: " .. tostring(repr(tree)), 0)
+      end
       local _exp_0 = tree.type
       if "File" == _exp_0 then
         if #tree.value == 1 then
@@ -906,7 +920,7 @@ do
         }
       elseif "Nomsu" == _exp_0 then
         return {
-          expr = "nomsu:parse(" .. tostring(repr(tree.value:get_src())) .. ", " .. tostring(repr(tree:get_line_no())) .. ").value[1]"
+          expr = "nomsu:parse(" .. tostring(repr(self:get_source_code(tree.value))) .. ", " .. tostring(repr(self:get_line_number(tree.value))) .. ").value[1]"
         }
       elseif "Block" == _exp_0 then
         local lua_bits = { }
@@ -941,13 +955,14 @@ do
         }
       elseif "FunctionCall" == _exp_0 then
         insert(self.compilestack, tree)
+        local stub = self:tree_to_stub(tree)
         local ok, fn = pcall(function()
-          return self.environment.ACTIONS[tree.stub]
+          return self.environment.ACTIONS[stub]
         end)
         if not ok then
           fn = nil
         end
-        local metadata = self.environment.ACTION_METADATA[fn]
+        local metadata = self.action_metadata[fn]
         if metadata and metadata.compile_time then
           local args
           do
@@ -968,7 +983,7 @@ do
             do
               local _accum_0 = { }
               local _len_0 = 1
-              local _list_0 = metadata.arg_orders[tree.stub]
+              local _list_0 = metadata.arg_orders[stub]
               for _index_0 = 1, #_list_0 do
                 local p = _list_0[_index_0]
                 _accum_0[_len_0] = args[p]
@@ -979,22 +994,22 @@ do
             args = new_args
           end
           if self.debug then
-            print(tostring(colored.bright("RUNNING MACRO")) .. " " .. tostring(colored.underscore(colored.magenta(tree.stub))) .. " ")
-            print(tostring(colored.bright("WITH ARGS:")) .. " " .. tostring(colored.dim(repr((function()
+            print(tostring(colored.bright("RUNNING MACRO")) .. " " .. tostring(colored.underscore(colored.magenta(stub))) .. " ")
+            print(tostring(colored.bright("WITH ARGS:")) .. " " .. tostring(colored.dim(concat((function()
               local _accum_0 = { }
               local _len_0 = 1
               for _index_0 = 1, #args do
                 local a = args[_index_0]
-                _accum_0[_len_0] = (repr(a)):sub(1, 50)
+                _accum_0[_len_0] = (repr(a)):sub(1, 100)
                 _len_0 = _len_0 + 1
               end
               return _accum_0
-            end)()))))
+            end)(), ", "))))
           end
           local lua = fn(unpack(args))
           remove(self.compilestack)
           return lua
-        elseif not metadata and self.__class.math_patt:match(tree.stub) then
+        elseif not metadata and self.__class.math_patt:match(stub) then
           local bits = { }
           local _list_0 = tree.value
           for _index_0 = 1, #_list_0 do
@@ -1003,7 +1018,10 @@ do
               insert(bits, tok.value)
             else
               local lua = self:tree_to_lua(tok)
-              assert(lua.expr, "non-expression value inside math expression: " .. tostring(tok:get_src()))
+              if not (lua.expr) then
+                local src = self:get_source_code(tok)
+                error("non-expression value inside math expression: " .. tostring(colored.yellow(src)))
+              end
               insert(bits, lua.expr)
             end
           end
@@ -1023,7 +1041,11 @@ do
               break
             end
             local lua = self:tree_to_lua(tok)
-            assert(lua.expr, tostring(tree:get_line_no()) .. ": Cannot use:\n" .. tostring(colored.yellow(tok:get_src())) .. "\nas an argument to " .. tostring(tree.stub) .. ", since it's not an expression, it produces: " .. tostring(repr(lua)))
+            if not (lua.expr) then
+              local line = self:get_line_number(tok)
+              local src = self:get_source_code(tok)
+              error(tostring(line) .. ": Cannot use:\n" .. tostring(colored.yellow(src)) .. "\nas an argument to " .. tostring(stub) .. ", since it's not an expression, it produces: " .. tostring(repr(lua)), 0)
+            end
             insert(args, lua.expr)
             _continue_0 = true
           until true
@@ -1036,7 +1058,7 @@ do
           do
             local _accum_0 = { }
             local _len_0 = 1
-            local _list_1 = metadata.arg_orders[tree.stub]
+            local _list_1 = metadata.arg_orders[stub]
             for _index_0 = 1, #_list_1 do
               local p = _list_1[_index_0]
               _accum_0[_len_0] = args[p]
@@ -1048,7 +1070,7 @@ do
         end
         remove(self.compilestack)
         return {
-          expr = self.__class:comma_separated_items("ACTIONS[" .. tostring(repr(tree.stub)) .. "](", args, ")")
+          expr = self.__class:comma_separated_items("ACTIONS[" .. tostring(repr(stub)) .. "](", args, ")")
         }
       elseif "Text" == _exp_0 then
         local concat_parts = { }
@@ -1073,7 +1095,11 @@ do
               self:print_tree(bit)
               print(tostring(colored.bright("EXPR:")) .. " " .. tostring(lua.expr) .. ", " .. tostring(colored.bright("STATEMENT:")) .. " " .. tostring(lua.statements))
             end
-            assert(lua.expr, "Cannot use [[" .. tostring(bit:get_src()) .. "]] as a string interpolation value, since it's not an expression.")
+            if not (lua.expr) then
+              local line = self:get_line_number(bit)
+              local src = self:get_source_code(bit)
+              error(tostring(line) .. ": Cannot use " .. tostring(colored.yellow(bit)) .. " as a string interpolation value, since it's not an expression.", 0)
+            end
             insert(concat_parts, "stringify(" .. tostring(lua.expr) .. ")")
             _continue_0 = true
           until true
@@ -1103,7 +1129,11 @@ do
         for _index_0 = 1, #_list_0 do
           local item = _list_0[_index_0]
           local lua = self:tree_to_lua(item)
-          assert(lua.expr, "Cannot use [[" .. tostring(item:get_src()) .. "]] as a list item, since it's not an expression.")
+          if not (lua.expr) then
+            local line = self:get_line_number(item)
+            local src = self:get_source_code(item)
+            error(tostring(line) .. ": Cannot use " .. tostring(colored.yellow(src)) .. " as a list item, since it's not an expression.", 0)
+          end
           insert(items, lua.expr)
         end
         return {
@@ -1122,9 +1152,17 @@ do
           else
             key_lua = self:tree_to_lua(entry.dict_key)
           end
-          assert(key_lua.expr, "Cannot use [[" .. tostring(entry.dict_key:get_src()) .. "]] as a dict key, since it's not an expression.")
+          if not (key_lua.expr) then
+            local line = self:get_line_number(entry.dict_key)
+            local src = self:get_source_code(entry.dict_key)
+            error(tostring(line) .. ": Cannot use " .. tostring(colored.yellow(src)) .. " as a dict key, since it's not an expression.", 0)
+          end
           local value_lua = self:tree_to_lua(entry.dict_value)
-          assert(value_lua.expr, "Cannot use [[" .. tostring(entry.dict_value:get_src()) .. "]] as a dict value, since it's not an expression.")
+          if not (value_lua.expr) then
+            local line = self:get_line_number(entry.dict_value)
+            local src = self:get_source_code(entry.dict_value)
+            error(tostring(line) .. ": Cannot use " .. tostring(colored.yellow(src)) .. " as a dict value, since it's not an expression.", 0)
+          end
           local key_str = key_lua.expr:match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=])
           if key_str then
             insert(items, tostring(key_str) .. "=" .. tostring(value_lua.expr))
@@ -1215,13 +1253,16 @@ do
       elseif "File" == _exp_0 or "Nomsu" == _exp_0 or "Block" == _exp_0 or "List" == _exp_0 or "FunctionCall" == _exp_0 or "Text" == _exp_0 then
         local new_value = self:tree_with_replaced_vars(tree.value, replacements)
         if new_value ~= tree.value then
+          local new_tree
           do
             local _tbl_0 = { }
             for k, v in pairs(tree) do
               _tbl_0[k] = v
             end
-            tree = _tbl_0
+            new_tree = _tbl_0
           end
+          self.tree_metadata[new_tree] = self.tree_metadata[tree]
+          tree = new_tree
           tree.value = new_value
         end
       elseif "Dict" == _exp_0 then
@@ -1237,13 +1278,16 @@ do
           }
         end
         if dirty then
+          local new_tree
           do
             local _tbl_0 = { }
             for k, v in pairs(tree) do
               _tbl_0[k] = v
             end
-            tree = _tbl_0
+            new_tree = _tbl_0
           end
+          self.tree_metadata[new_tree] = self.tree_metadata[tree]
+          tree = new_tree
           tree.value = replacements
         end
       elseif nil == _exp_0 then
@@ -1259,6 +1303,38 @@ do
       end
       return tree
     end,
+    tree_to_stub = cached(function(self, tree)
+      if tree.type ~= "FunctionCall" then
+        error("Tried to get stub from non-functioncall tree: " .. tostring(tree.type), 0)
+      end
+      return concat((function()
+        local _accum_0 = { }
+        local _len_0 = 1
+        local _list_0 = tree.value
+        for _index_0 = 1, #_list_0 do
+          local t = _list_0[_index_0]
+          _accum_0[_len_0] = (t.type == "Word" and t.value or "%")
+          _len_0 = _len_0 + 1
+        end
+        return _accum_0
+      end)(), " ")
+    end),
+    tree_to_named_stub = cached(function(self, tree)
+      if tree.type ~= "FunctionCall" then
+        error("Tried to get stub from non-functioncall tree: " .. tostring(tree.type), 0)
+      end
+      return concat((function()
+        local _accum_0 = { }
+        local _len_0 = 1
+        local _list_0 = tree.value
+        for _index_0 = 1, #_list_0 do
+          local t = _list_0[_index_0]
+          _accum_0[_len_0] = (t.type == "Word" and t.value or "%" .. tostring(t.value))
+          _len_0 = _len_0 + 1
+        end
+        return _accum_0
+      end)(), " ")
+    end),
     get_stubs_from_signature = function(self, signature)
       if type(signature) ~= 'table' or signature.type then
         error("Invalid signature: " .. tostring(repr(signature)), 0)
@@ -1307,12 +1383,6 @@ do
         end
       end))
     end,
-    source_code = function(self, level)
-      if level == nil then
-        level = 0
-      end
-      return self:dedent(self.compilestack[#self.compilestack - level]:get_src())
-    end,
     initialize_core = function(self)
       local get_line_no
       get_line_no = function()
@@ -1330,7 +1400,9 @@ do
           else
             local lua = nomsu:tree_to_lua(bit)
             if not (lua.expr) then
-              error("Cannot use [[" .. tostring(bit:get_src()) .. "]] as a string interpolation value, since it's not an expression.", 0)
+              local line = self:get_line_number(bit)
+              local src = self:get_source_code(bit)
+              error(tostring(line) .. ": Cannot use " .. tostring(colored.yellow(src)) .. " as a string interpolation value, since it's not an expression.", 0)
             end
             insert(concat_parts, lua.expr)
           end
@@ -1369,8 +1441,9 @@ do
       end)
       self:define_compile_action("!! code location !!", get_line_no(), function()
         local tree = nomsu.compilestack[#nomsu.compilestack - 1]
+        local metadata = self.tree_metadata[tree]
         return {
-          expr = repr(tostring(tree.filename) .. ":" .. tostring(tree.start) .. "," .. tostring(tree.stop))
+          expr = repr(tostring(metadata.filename) .. ":" .. tostring(metadata.start) .. "," .. tostring(metadata.stop))
         }
       end)
       self:define_action("run file %filename", get_line_no(), function(_filename)
@@ -1405,6 +1478,15 @@ do
       })
       self.use_stack = { }
       self.compilestack = { }
+      self.file_metadata = setmetatable({ }, {
+        __mode = "k"
+      })
+      self.tree_metadata = setmetatable({ }, {
+        __mode = "k"
+      })
+      self.action_metadata = setmetatable({ }, {
+        __mode = "k"
+      })
       self.debug = false
       self.environment = {
         nomsu = self,
@@ -1455,9 +1537,6 @@ do
           return error("Attempt to run undefined action: " .. tostring(key), 0)
         end
       })
-      self.environment.ACTION_METADATA = setmetatable({ }, {
-        __mode = "k"
-      })
       self.environment.LOADED = { }
       return self:initialize_core()
     end,
@@ -1623,7 +1702,7 @@ if arg and debug.getinfo(2).func ~= require then
         end
         local line = nil
         do
-          local metadata = nomsu.environment.ACTION_METADATA[calling_fn.func]
+          local metadata = nomsu.action_metadata[calling_fn.func]
           if metadata then
             local filename, start, stop = metadata.source:match("([^:]*):([0-9]*),([0-9]*)")
             if filename then
diff --git a/nomsu.moon b/nomsu.moon
index ce4e5e2..10e57c4 100755
--- a/nomsu.moon
+++ b/nomsu.moon
@@ -20,6 +20,13 @@ colors = setmetatable({}, {__index:->""})
 colored = setmetatable({}, {__index:(_,color)-> ((msg)-> colors[color]..(msg or '')..colors.reset)})
 {:insert, :remove, :concat} = table
 
+cached = (fn)->
+    cache = setmetatable({}, {__mode:"k"})
+    return (self, arg)->
+        unless cache[arg]
+            cache[arg] = fn(self, arg)
+        return cache[arg]
+
 -- Use + operator for string coercive concatenation (note: "asdf" + 3 == "asdf3")
 -- Use [] for accessing string characters, or s[{3,4}] for s:sub(3,4)
 -- Note: This globally affects all strings in this instance of Lua!
@@ -107,20 +114,13 @@ NOMSU_DEFS = with {}
         err_msg ..="\n#{prev_line}\n#{err_line}\n#{pointer}\n#{next_line}\n"
         error(err_msg)
 
-    .FunctionCall = (start, value, stop)->
-        stub = concat([(t.type == "Word" and t.value or "%") for t in *value], " ")
-        src = lpeg.userdata.source_code\sub(start,stop-1)
-        return {
-            type: "FunctionCall", :start, :stop, :value, :stub, filename:lpeg.userdata.filename,
-            get_line_no:lpeg.userdata.get_line_no, get_src:lpeg.userdata.get_src,
-        }
-
 setmetatable(NOMSU_DEFS, {__index:(key)=>
     make_node = (start, value, stop)->
-        {
-            type: key, :start, :stop, :value, filename:lpeg.userdata.filename,
-            get_src:lpeg.userdata.get_src, get_line_no:lpeg.userdata.get_line_no,
+        node = {type: key, :value}
+        lpeg.userdata.tree_metadata[node] = {
+            :start,:stop,filename:lpeg.userdata.filename,source_code:lpeg.userdata.source_code
         }
+        return node
     self[key] = make_node
     return make_node
 })
@@ -157,6 +157,9 @@ class NomsuCompiler
         })
         @use_stack = {}
         @compilestack = {}
+        @file_metadata = setmetatable({}, {__mode:"k"})
+        @tree_metadata = setmetatable({}, {__mode:"k"})
+        @action_metadata = setmetatable({}, {__mode:"k"})
         @debug = false
 
         @environment = {
@@ -172,7 +175,6 @@ class NomsuCompiler
         @environment.ACTIONS = setmetatable({}, {__index:(key)=>
             error("Attempt to run undefined action: #{key}", 0)
         })
-        @environment.ACTION_METADATA = setmetatable({}, {__mode:"k"})
         @environment.LOADED = {}
         @initialize_core!
     
@@ -205,14 +207,14 @@ class NomsuCompiler
                     error("Mismatch in args between lua function's #{repr fn_arg_positions} and stub's #{repr args} for #{repr stub}", 0)
                 arg_orders[stub] = arg_positions
         
-        @environment.ACTION_METADATA[fn] = {
+        @action_metadata[fn] = {
             :fn, :source, aliases:stubs, :arg_orders,
             arg_positions:fn_arg_positions, def_number:@@def_number,
         }
 
     define_compile_action: (signature, source, fn, src)=>
         @define_action(signature, source, fn)
-        @environment.ACTION_METADATA[fn].compile_time = true
+        @action_metadata[fn].compile_time = true
 
     serialize_defs: (scope=nil, after=nil)=>
         -- TODO: repair
@@ -239,6 +241,30 @@ class NomsuCompiler
     indent: (code, levels=1)=>
         return code\gsub("\n","\n"..("    ")\rep(levels))
 
+    get_line_number: cached (tree)=>
+        metadata = @tree_metadata[tree]
+        unless metadata
+            error "Failed to find metatdata for tree: #{tree}", 0
+        unless @file_metadata[metadata.filename]
+            error "Failed to find file metatdata for file: #{metadata.filename}", 0
+        line_starts = @file_metadata[metadata.filename].line_starts
+        first_line = 1
+        while first_line < #line_starts and line_starts[first_line+1] < metadata.start
+            first_line += 1
+        last_line = first_line
+        while last_line < #line_starts and line_starts[last_line+1] < metadata.stop
+            last_line += 1
+        --return first_line == last_line and "#{metadata.filename}:#{first_line}" or "#{metadata.filename}:#{first_line}-#{last_line}"
+        return "#{metadata.filename}:#{first_line}"
+
+    get_source_code: (tree)=>
+        -- Return the (dedented) source code of a tree, or construct some if the tree was
+        -- dynamically generated.
+        metadata = @tree_metadata[tree]
+        unless metadata
+            return @tree_to_nomsu(tree)
+        return @dedent metadata.source_code\sub(metadata.start, metadata.stop-1)
+
     line_counter = re.compile([[
         lines <- {| line (%nl line)* |}
         line <- {} (!%nl .)*
@@ -248,16 +274,14 @@ class NomsuCompiler
         if @debug
             print "#{colored.bright "PARSING:"}\n#{colored.yellow nomsu_code}"
 
-        userdata = with {source_code:nomsu_code, :filename, indent_stack: {""}}
-            .get_src = => nomsu_code\sub(@start, @stop-1)
-            .line_starts = line_counter\match(.source_code)
-            .get_line_no = =>
-                unless @_line_no
-                    line_no = 1
-                    while line_no < #.line_starts and .line_starts[line_no+1] < @start
-                        line_no += 1
-                    @_line_no = "#{.filename}:#{line_no}"
-                return @_line_no
+        unless @file_metadata[filename]
+            @file_metadata[filename] = {
+                source_code:nomsu_code, :filename, line_starts:line_counter\match(nomsu_code)
+            }
+        userdata = {
+            source_code:nomsu_code, :filename, indent_stack: {""}, tree_metadata:@tree_metadata,
+            line_starts:@file_metadata[filename].line_starts,
+        }
 
         old_userdata, lpeg.userdata = lpeg.userdata, userdata
         tree = NOMSU\match(nomsu_code)
@@ -569,7 +593,9 @@ class NomsuCompiler
                     lines = {}
                     for line in *tree.value
                         nomsu = expression(line)
-                        assert nomsu, "Failed to produce output for:\n#{colored.yellow line\get_src!}"
+                        unless nomsu
+                            src = @get_source_code line
+                            error "Failed to produce output for:\n#{colored.yellow src}", 0
                         
                         insert lines, nomsu
                     return concat lines, "\n"
@@ -614,6 +640,8 @@ class NomsuCompiler
         assert tree, "No tree provided."
         if not tree.type
             error("Invalid tree: #{repr(tree)}", 0)
+        unless @tree_metadata[tree]
+            error "??? tree: #{repr tree}", 0
         switch tree.type
             when "File"
                 if #tree.value == 1
@@ -638,7 +666,7 @@ class NomsuCompiler
                 return statements:"--"..tree.value\gsub("\n","\n--")
             
             when "Nomsu"
-                return expr:"nomsu:parse(#{repr tree.value\get_src!}, #{repr tree\get_line_no!}).value[1]"
+                return expr:"nomsu:parse(#{repr @get_source_code(tree.value)}, #{repr @get_line_number(tree.value)}).value[1]"
 
             when "Block"
                 lua_bits = {}
@@ -657,22 +685,23 @@ class NomsuCompiler
             when "FunctionCall"
                 insert @compilestack, tree
 
-                ok, fn = pcall(-> @environment.ACTIONS[tree.stub])
+                stub = @tree_to_stub tree
+                ok, fn = pcall(-> @environment.ACTIONS[stub])
                 if not ok then fn = nil
 
-                metadata = @environment.ACTION_METADATA[fn]
+                metadata = @action_metadata[fn]
                 if metadata and metadata.compile_time
                     args = [arg for arg in *tree.value when arg.type != "Word"]
                     if metadata and metadata.arg_orders
-                        new_args = [args[p] for p in *metadata.arg_orders[tree.stub]]
+                        new_args = [args[p] for p in *metadata.arg_orders[stub]]
                         args = new_args
                     if @debug
-                        print "#{colored.bright "RUNNING MACRO"} #{colored.underscore colored.magenta(tree.stub)} "
-                        print "#{colored.bright "WITH ARGS:"} #{colored.dim repr [(repr a)\sub(1,50) for a in *args]}"
+                        print "#{colored.bright "RUNNING MACRO"} #{colored.underscore colored.magenta(stub)} "
+                        print "#{colored.bright "WITH ARGS:"} #{colored.dim concat([(repr a)\sub(1,100) for a in *args], ", ")}"
                     lua = fn(unpack(args))
                     remove @compilestack
                     return lua
-                elseif not metadata and @@math_patt\match(tree.stub)
+                elseif not metadata and @@math_patt\match(stub)
                     -- This is a bit of a hack, but this code handles arbitrarily complex
                     -- math expressions like 2*x + 3^2 without having to define a single
                     -- action for every possibility.
@@ -682,7 +711,9 @@ class NomsuCompiler
                             insert bits, tok.value
                         else
                             lua = @tree_to_lua(tok)
-                            assert(lua.expr, "non-expression value inside math expression: #{tok\get_src!}")
+                            unless lua.expr
+                                src = @get_source_code(tok)
+                                error("non-expression value inside math expression: #{colored.yellow src}")
                             insert bits, lua.expr
                     remove @compilestack
                     return expr:"(#{concat bits, " "})"
@@ -691,16 +722,18 @@ class NomsuCompiler
                 for tok in *tree.value
                     if tok.type == "Word" then continue
                     lua = @tree_to_lua(tok)
-                    assert lua.expr,
-                        "#{tree\get_line_no!}: Cannot use:\n#{colored.yellow tok\get_src!}\nas an argument to #{tree.stub}, since it's not an expression, it produces: #{repr lua}"
+                    unless lua.expr
+                        line = @get_line_number(tok)
+                        src = @get_source_code(tok)
+                        error "#{line}: Cannot use:\n#{colored.yellow src}\nas an argument to #{stub}, since it's not an expression, it produces: #{repr lua}", 0
                     insert args, lua.expr
 
                 if metadata and metadata.arg_orders
-                    new_args = [args[p] for p in *metadata.arg_orders[tree.stub]]
+                    new_args = [args[p] for p in *metadata.arg_orders[stub]]
                     args = new_args
                 
                 remove @compilestack
-                return expr:@@comma_separated_items("ACTIONS[#{repr tree.stub}](", args, ")")
+                return expr:@@comma_separated_items("ACTIONS[#{repr stub}](", args, ")")
 
             when "Text"
                 concat_parts = {}
@@ -717,8 +750,10 @@ class NomsuCompiler
                         print(colored.bright "INTERP:")
                         @print_tree bit
                         print "#{colored.bright "EXPR:"} #{lua.expr}, #{colored.bright "STATEMENT:"} #{lua.statements}"
-                    assert lua.expr,
-                        "Cannot use [[#{bit\get_src!}]] as a string interpolation value, since it's not an expression."
+                    unless lua.expr
+                        line = @get_line_number(bit)
+                        src = @get_source_code(bit)
+                        error "#{line}: Cannot use #{colored.yellow bit} as a string interpolation value, since it's not an expression.", 0
                     insert concat_parts, "stringify(#{lua.expr})"
 
                 if string_buffer ~= ""
@@ -734,8 +769,10 @@ class NomsuCompiler
                 items = {}
                 for item in *tree.value
                     lua = @tree_to_lua item
-                    assert lua.expr,
-                        "Cannot use [[#{item\get_src!}]] as a list item, since it's not an expression."
+                    unless lua.expr
+                        line = @get_line_number(item)
+                        src = @get_source_code(item)
+                        error "#{line}: Cannot use #{colored.yellow src} as a list item, since it's not an expression.", 0
                     insert items, lua.expr
                 return expr:@@comma_separated_items("{", items, "}")
 
@@ -746,11 +783,15 @@ class NomsuCompiler
                         {expr:repr(entry.dict_key.value)}
                     else
                         @tree_to_lua entry.dict_key
-                    assert key_lua.expr,
-                        "Cannot use [[#{entry.dict_key\get_src!}]] as a dict key, since it's not an expression."
+                    unless key_lua.expr
+                        line = @get_line_number(entry.dict_key)
+                        src = @get_source_code(entry.dict_key)
+                        error "#{line}: Cannot use #{colored.yellow src} as a dict key, since it's not an expression.", 0
                     value_lua = @tree_to_lua entry.dict_value
-                    assert value_lua.expr,
-                        "Cannot use [[#{entry.dict_value\get_src!}]] as a dict value, since it's not an expression."
+                    unless value_lua.expr
+                        line = @get_line_number(entry.dict_value)
+                        src = @get_source_code(entry.dict_value)
+                        error "#{line}: Cannot use #{colored.yellow src} as a dict value, since it's not an expression.", 0
                     key_str = key_lua.expr\match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=])
                     if key_str
                         insert items, "#{key_str}=#{value_lua.expr}"
@@ -828,7 +869,10 @@ class NomsuCompiler
             when "File", "Nomsu", "Block", "List", "FunctionCall", "Text"
                 new_value = @tree_with_replaced_vars tree.value, replacements
                 if new_value != tree.value
-                    tree = {k,v for k,v in pairs(tree)}
+                    new_tree = {k,v for k,v in pairs(tree)}
+                    -- TODO: Maybe generate new metadata?
+                    @tree_metadata[new_tree] = @tree_metadata[tree]
+                    tree = new_tree
                     tree.value = new_value
             when "Dict"
                 dirty = false
@@ -839,7 +883,10 @@ class NomsuCompiler
                     dirty or= new_key != e.dict_key or new_value != e.dict_value
                     replacements[i] = {dict_key:new_key, dict_value:new_value}
                 if dirty
-                    tree = {k,v for k,v in pairs(tree)}
+                    new_tree = {k,v for k,v in pairs(tree)}
+                    -- TODO: Maybe generate new metadata?
+                    @tree_metadata[new_tree] = @tree_metadata[tree]
+                    tree = new_tree
                     tree.value = replacements
             when nil -- Raw table, probably from one of the .value of a multi-value tree (e.g. List)
                 new_values = {}
@@ -851,6 +898,13 @@ class NomsuCompiler
                     tree = new_values
         return tree
 
+    tree_to_stub: cached (tree)=>
+        if tree.type != "FunctionCall" then error "Tried to get stub from non-functioncall tree: #{tree.type}", 0
+        return concat([(t.type == "Word" and t.value or "%") for t in *tree.value], " ")
+
+    tree_to_named_stub: cached (tree)=>
+        if tree.type != "FunctionCall" then error "Tried to get stub from non-functioncall tree: #{tree.type}", 0
+        return concat([(t.type == "Word" and t.value or "%#{t.value}") for t in *tree.value], " ")
 
     stub_defs = {
         space:(P(' ') + P('\n..'))^0
@@ -893,9 +947,6 @@ class NomsuCompiler
         "_"..(var\gsub "%W", (verboten)->
             if verboten == "_" then "__" else ("_%x")\format(verboten\byte!))
     
-    source_code: (level=0)=>
-        @dedent @compilestack[#@compilestack-level]\get_src!
-    
     initialize_core: =>
         -- Sets up some core functionality
         get_line_no = -> "nomsu.moon:#{debug.getinfo(2).currentline}"
@@ -908,7 +959,9 @@ class NomsuCompiler
                 else
                     lua = nomsu\tree_to_lua bit
                     unless lua.expr
-                        error("Cannot use [[#{bit\get_src!}]] as a string interpolation value, since it's not an expression.", 0)
+                        line = @get_line_number(bit)
+                        src = @get_source_code(bit)
+                        error "#{line}: Cannot use #{colored.yellow src} as a string interpolation value, since it's not an expression.", 0
                     insert concat_parts, lua.expr
             return concat(concat_parts)
 
@@ -933,7 +986,8 @@ class NomsuCompiler
 
         @define_compile_action "!! code location !!", get_line_no!, ->
             tree = nomsu.compilestack[#nomsu.compilestack-1]
-            return expr: repr("#{tree.filename}:#{tree.start},#{tree.stop}")
+            metadata = @tree_metadata[tree]
+            return expr: repr("#{metadata.filename}:#{metadata.start},#{metadata.stop}")
 
         @define_action "run file %filename", get_line_no!, (_filename)->
             return nomsu\run_file(_filename)
@@ -1035,7 +1089,7 @@ if arg and debug.getinfo(2).func != require
             name = calling_fn.name
             if name == "run_lua_fn" then continue
             line = nil
-            if metadata = nomsu.environment.ACTION_METADATA[calling_fn.func]
+            if metadata = nomsu.action_metadata[calling_fn.func]
                 filename, start, stop = metadata.source\match("([^:]*):([0-9]*),([0-9]*)")
                 if filename
                     file = io.open(filename)\read("*a")