diff --git a/README.md b/README.md
index 40c70aa..f1c615d 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ revolving around natural language rule-making and self modification.
 
 ## Dependencies
 
-Nomsu's dependencies are [Lua 5.2 or later](https://www.lua.org/) (tested with version 5.2.4) (or [Luajit](http://luajit.org/) (tested with version 2.1.0)), [LPEG](http://www.inf.puc-rio.br/~roberto/lpeg/) (`luarocks install lpeg`), and [Lua-Immutable](https://bitbucket.org/spilt/lua-immutable). Nomsu's compiler was written in [Moonscript](http://moonscript.org/), but all of the .moon files have been compiled into lua for convenience, so Moonscript is not a dependency.
+Nomsu's only dependencies are [Lua 5.2 or later](https://www.lua.org/) (tested with version 5.2.4) (or [Luajit](http://luajit.org/) (tested with version 2.1.0)) and [LPEG](http://www.inf.puc-rio.br/~roberto/lpeg/) (`luarocks install lpeg`). Nomsu's compiler was written in [Moonscript](http://moonscript.org/), but all of the .moon files have been compiled into lua for convenience, so Moonscript is not a dependency.
 
 ## Usage
 
diff --git a/code_obj.lua b/code_obj.lua
index fa36234..47e08cb 100644
--- a/code_obj.lua
+++ b/code_obj.lua
@@ -3,55 +3,76 @@ do
   local _obj_0 = table
   insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat
 end
-local immutable = require('immutable')
 local Lua, Source
-Source = immutable({
-  "filename",
-  "start",
-  "stop"
-}, {
-  name = "Source",
-  from_string = function(self, str)
+do
+  local _class_0
+  local _base_0 = {
+    __tostring = function(self)
+      if self.stop then
+        return "@" .. tostring(self.filename) .. "[" .. tostring(self.start) .. ":" .. tostring(self.stop) .. "]"
+      else
+        return "@" .. tostring(self.filename) .. "[" .. tostring(self.start) .. "]"
+      end
+    end,
+    __eq = function(self, other)
+      return getmetatable(self) == getmetatable(other) and self.filename == other.filename and self.start == other.start and self.stop == other.stop
+    end,
+    __lt = function(self, other)
+      assert(self.filename == other.filename, "Cannot compare sources from different files")
+      if self.start == other.start then
+        return (self.stop or self.start) < (other.stop or other.start)
+      else
+        return self.start < other.start
+      end
+    end,
+    __le = function(self, other)
+      assert(self.filename == other.filename, "Cannot compare sources from different files")
+      if self.start == other.start then
+        return (self.stop or self.start) <= (other.stop or other.start)
+      else
+        return self.start <= other.start
+      end
+    end,
+    __add = function(self, offset)
+      if type(self) == 'number' then
+        offset, self = self, offset
+      else
+        if type(offset) ~= 'number' then
+          error("Cannot add Source and " .. tostring(type(offset)))
+        end
+      end
+      return Source(self.filename, self.start + offset, self.stop)
+    end
+  }
+  _base_0.__index = _base_0
+  _class_0 = setmetatable({
+    __init = function(self, filename, start, stop)
+      self.filename, self.start, self.stop = filename, start, stop
+    end,
+    __base = _base_0,
+    __name = "Source"
+  }, {
+    __index = _base_0,
+    __call = function(cls, ...)
+      local _self_0 = setmetatable({}, _base_0)
+      cls.__init(_self_0, ...)
+      return _self_0
+    end
+  })
+  _base_0.__class = _class_0
+  local self = _class_0
+  self.from_string = function(self, str)
     local filename, start, stop = str:match("^@(.-)%[(%d+):(%d+)%]$")
     if not (filename) then
       filename, start = str:match("^@(.-)%[(%d+)%]$")
     end
-    return Source(filename or str, tonumber(start or 1), tonumber(stop))
-  end,
-  __tostring = function(self)
-    if self.stop then
-      return "@" .. tostring(self.filename) .. "[" .. tostring(self.start) .. ":" .. tostring(self.stop) .. "]"
-    else
-      return "@" .. tostring(self.filename) .. "[" .. tostring(self.start) .. "]"
-    end
-  end,
-  __lt = function(self, other)
-    assert(self.filename == other.filename, "Cannot compare sources from different files")
-    if self.start == other.start then
-      return (self.stop or self.start) < (other.stop or other.start)
-    else
-      return self.start < other.start
-    end
-  end,
-  __le = function(self, other)
-    assert(self.filename == other.filename, "Cannot compare sources from different files")
-    if self.start == other.start then
-      return (self.stop or self.start) <= (other.stop or other.start)
-    else
-      return self.start <= other.start
-    end
-  end,
-  __add = function(self, offset)
-    if type(self) == 'number' then
-      offset, self = self, offset
-    else
-      if type(offset) ~= 'number' then
-        error("Cannot add Source and " .. tostring(type(offset)))
-      end
-    end
-    return Source(self.filename, self.start + offset, self.stop)
+    return self(filename or str, tonumber(start or 1), tonumber(stop))
   end
-})
+  self.is_instance = function(self, x)
+    return type(x) == 'table' and x.__class == self
+  end
+  Source = _class_0
+end
 local Code
 do
   local _class_0
@@ -150,7 +171,7 @@ do
       end
       for _index_0 = 1, #vars do
         local var = vars[_index_0]
-        assert(type(var) == 'userdata' and var.type == "Var")
+        assert(var.type == "Var")
         if not (seen[var]) then
           self.free_vars[#self.free_vars + 1] = var
           seen[var] = true
@@ -165,7 +186,7 @@ do
       local removals = { }
       for _index_0 = 1, #vars do
         local var = vars[_index_0]
-        assert(type(var) == 'userdata' and var.type == "Var")
+        assert(var.type == "Var")
         removals[var.value] = true
       end
       local stack = {
diff --git a/code_obj.moon b/code_obj.moon
index a810dc7..45df615 100644
--- a/code_obj.moon
+++ b/code_obj.moon
@@ -2,38 +2,46 @@
 -- build up generated code, while keeping track of where it came from, and managing
 -- indentation levels.
 {:insert, :remove, :concat} = table
-immutable = require 'immutable'
 local Lua, Source
 export LINE_STARTS
 
-Source = immutable {"filename","start","stop"}, {
-    name:"Source"
-    from_string: (str)=>
+class Source
+    new: (@filename, @start, @stop)=>
+
+    @from_string: (str)=>
         filename,start,stop = str\match("^@(.-)%[(%d+):(%d+)%]$")
         unless filename
             filename,start = str\match("^@(.-)%[(%d+)%]$")
-        return Source(filename or str, tonumber(start or 1), tonumber(stop))
+        return @(filename or str, tonumber(start or 1), tonumber(stop))
+
+    @is_instance: (x)=> type(x) == 'table' and x.__class == @
+
     __tostring: =>
         if @stop
             "@#{@filename}[#{@start}:#{@stop}]"
         else
             "@#{@filename}[#{@start}]"
+    
+    __eq: (other)=>
+        getmetatable(@) == getmetatable(other) and @filename == other.filename and @start == other.start and @stop == other.stop
+
     __lt: (other)=>
         assert(@filename == other.filename, "Cannot compare sources from different files")
         return if @start == other.start
             (@stop or @start) < (other.stop or other.start)
         else @start < other.start
+
     __le: (other)=>
         assert(@filename == other.filename, "Cannot compare sources from different files")
         return if @start == other.start
             (@stop or @start) <= (other.stop or other.start)
         else @start <= other.start
+
     __add: (offset)=>
         if type(self) == 'number'
             offset, self = self, offset
         else if type(offset) != 'number' then error("Cannot add Source and #{type(offset)}")
         return Source(@filename, @start+offset, @stop)
-}
 
 class Code
     new: (@source, ...)=>
@@ -90,7 +98,7 @@ class Lua extends Code
         return unless #vars > 0
         seen = {[v]:true for v in *@free_vars}
         for var in *vars
-            assert(type(var) == 'userdata' and var.type == "Var")
+            assert(var.type == "Var")
             unless seen[var]
                 @free_vars[#@free_vars+1] = var
                 seen[var] = true
@@ -100,7 +108,7 @@ class Lua extends Code
         return unless #vars > 0
         removals = {}
         for var in *vars
-            assert(type(var) == 'userdata' and var.type == "Var")
+            assert(var.type == "Var")
             removals[var.value] = true
         
         stack = {self}
diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom
index 83293c1..ec65f3e 100644
--- a/core/metaprogramming.nom
+++ b/core/metaprogramming.nom
@@ -94,14 +94,14 @@ immediately
                     return replacements[t.value]
                 elseif t.type == 'Var' then
                     return t.type.."("..repr(tostring(t.source))..", "..repr(t.value.."#"..tostring(MANGLE_INDEX))..")"
-                elseif t.is_multi then
+                elseif t.value then
+                    return t.type.."("..repr(tostring(t.source))..", "..repr(t.value)..")"
+                else
                     local bits = {repr(tostring(t.source))}
                     for i, entry in ipairs(t) do
                         bits[#bits+1] = make_tree(entry)
                     end
                     return t.type.."("..table.concat(bits, ", ")..")"
-                else
-                    return t.type.."("..repr(tostring(t.source))..", "..repr(t.value)..")"
                 end
             end
             lua:append(")\n    local tree = ", make_tree(\%longhand), "\n    return nomsu:tree_to_lua(tree)\nend);")
diff --git a/nomsu.lua b/nomsu.lua
index 646b9af..e9d394f 100644
--- a/nomsu.lua
+++ b/nomsu.lua
@@ -34,10 +34,6 @@ local P, R, V, S, Cg, C, Cp, B, Cmt, Carg
 P, R, V, S, Cg, C, Cp, B, Cmt, Carg = lpeg.P, lpeg.R, lpeg.V, lpeg.S, lpeg.Cg, lpeg.C, lpeg.Cp, lpeg.B, lpeg.Cmt, lpeg.Carg
 local utils = require('utils')
 local new_uuid = require('uuid')
-local immutable = require('immutable')
-Tuple = immutable(nil, {
-  name = "Tuple"
-})
 local repr, stringify, min, max, equivalent, set, is_list, sum
 repr, stringify, min, max, equivalent, set, is_list, sum = utils.repr, utils.stringify, utils.min, utils.max, utils.equivalent, utils.set, utils.is_list, utils.sum
 local colors = setmetatable({ }, {
@@ -163,7 +159,7 @@ do
     end
   end
 end
-local Types = require("nomsu_tree")
+local AST = require("nomsu_tree")
 local NOMSU_DEFS
 do
   local _with_0 = { }
@@ -249,13 +245,12 @@ setmetatable(NOMSU_DEFS, {
         local _with_0 = userdata.source
         source = Source(_with_0.filename, _with_0.start + start - 1, _with_0.start + stop - 1)
       end
-      local tree
-      if Types[key].is_multi then
-        tree = Types[key](source, unpack(value))
-      else
-        tree = Types[key](source, value)
+      value.source = source
+      setmetatable(value, AST[key])
+      if value.__init then
+        value:__init()
       end
-      return tree
+      return value
     end
     self[key] = make_node
     return make_node
@@ -444,7 +439,7 @@ do
     run_lua = function(self, lua)
       assert(type(lua) ~= 'string', "Attempt to run lua string instead of Lua (object)")
       local lua_string = tostring(lua)
-      local run_lua_fn, err = load(lua_string, tostring(lua.source), "t", self.environment)
+      local run_lua_fn, err = load(lua_string, nil and tostring(lua.source), "t", self.environment)
       if not run_lua_fn then
         local n = 1
         local fn
@@ -592,10 +587,12 @@ do
       elseif "EscapedNomsu" == _exp_0 then
         local make_tree
         make_tree = function(t)
-          if type(t) ~= 'userdata' then
+          if not (AST.is_syntax_tree(t)) then
             return repr(t)
           end
-          if t.is_multi then
+          if t.value then
+            return t.type .. "(" .. repr(tostring(t.source)) .. ", " .. repr(t.value) .. ")"
+          else
             local bits
             do
               local _accum_0 = { }
@@ -608,8 +605,6 @@ do
               bits = _accum_0
             end
             return t.type .. "(" .. repr(tostring(t.source)) .. ", " .. table.concat(bits, ", ") .. ")"
-          else
-            return t.type .. "(" .. repr(tostring(t.source)) .. ", " .. repr(t.value) .. ")"
           end
         end
         return Lua.Value(tree.source, make_tree(tree[1]))
@@ -1327,10 +1322,9 @@ do
         end
         return _pairs(x)
       end
-      for k, v in pairs(Types) do
+      for k, v in pairs(AST) do
         self.environment[k] = v
       end
-      self.environment.Tuple = Tuple
       self.environment.Lua = Lua
       self.environment.Nomsu = Nomsu
       self.environment.Source = Source
@@ -1346,7 +1340,7 @@ do
         __mode = "k"
       })
       self.environment.LOADED = { }
-      self.environment.Types = Types
+      self.environment.AST = AST
       return self:initialize_core()
     end,
     __base = _base_0,
diff --git a/nomsu.moon b/nomsu.moon
index 1196b37..bef479b 100755
--- a/nomsu.moon
+++ b/nomsu.moon
@@ -40,9 +40,6 @@ lpeg.setmaxstack 10000
 {:P,:R,:V,:S,:Cg,:C,:Cp,:B,:Cmt,:Carg} = lpeg
 utils = require 'utils'
 new_uuid = require 'uuid'
-immutable = require 'immutable'
-export Tuple
-Tuple = immutable(nil, {name:"Tuple"})
 {:repr, :stringify, :min, :max, :equivalent, :set, :is_list, :sum} = utils
 colors = setmetatable({}, {__index:->""})
 export colored
@@ -134,7 +131,7 @@ do
         if type(i) == 'number' then return string.sub(@, i, i)
         elseif type(i) == 'table' then return string.sub(@, i[1], i[2])
 
-Types = require "nomsu_tree"
+AST = require "nomsu_tree"
 
 NOMSU_DEFS = with {}
     -- Newline supports either windows-style CR+LF or unix-style LF
@@ -205,10 +202,11 @@ setmetatable(NOMSU_DEFS, {__index:(key)=>
         local source
         with userdata.source
             source = Source(.filename, .start + start-1, .start + stop-1)
-        tree = if Types[key].is_multi
-            Types[key](source, unpack(value))
-        else Types[key](source, value)
-        return tree
+        value.source = source
+        setmetatable(value, AST[key])
+        if value.__init then value\__init!
+        return value
+
     self[key] = make_node
     return make_node
 })
@@ -254,6 +252,8 @@ class NomsuCompiler
                 @[key] = id
                 return id
         })
+        -- Mapping from source string (e.g. "@core/metaprogramming.nom[1:100]") to a mapping
+        -- from lua line number to nomsu line number
         @source_map = {}
 
         _list_mt =
@@ -304,8 +304,7 @@ class NomsuCompiler
                 if mt.__pairs
                     return mt.__pairs(x)
             return _pairs(x)
-        for k,v in pairs(Types) do @environment[k] = v
-        @environment.Tuple = Tuple
+        for k,v in pairs(AST) do @environment[k] = v
         @environment.Lua = Lua
         @environment.Nomsu = Nomsu
         @environment.Source = Source
@@ -316,7 +315,7 @@ class NomsuCompiler
         @environment.COMPILE_ACTIONS = {}
         @environment.ARG_ORDERS = setmetatable({}, {__mode:"k"})
         @environment.LOADED = {}
-        @environment.Types = Types
+        @environment.AST = AST
         @initialize_core!
     
     local stub_defs
@@ -427,7 +426,7 @@ class NomsuCompiler
     run_lua: (lua)=>
         assert(type(lua) != 'string', "Attempt to run lua string instead of Lua (object)")
         lua_string = tostring(lua)
-        run_lua_fn, err = load(lua_string, tostring(lua.source), "t", @environment)
+        run_lua_fn, err = load(lua_string, nil and tostring(lua.source), "t", @environment)
         if not run_lua_fn
             n = 1
             fn = ->
@@ -525,13 +524,13 @@ class NomsuCompiler
 
             when "EscapedNomsu"
                 make_tree = (t)->
-                    if type(t) != 'userdata'
+                    unless AST.is_syntax_tree(t)
                         return repr(t)
-                    if t.is_multi
+                    if t.value
+                        return t.type.."("..repr(tostring t.source)..", "..repr(t.value)..")"
+                    else
                         bits = [make_tree(bit) for bit in *t]
                         return t.type.."("..repr(tostring t.source)..", "..table.concat(bits, ", ")..")"
-                    else
-                        return t.type.."("..repr(tostring t.source)..", "..repr(t.value)..")"
                 Lua.Value tree.source, make_tree(tree[1])
             
             when "Block"
diff --git a/nomsu.peg b/nomsu.peg
index ec40211..2de5292 100644
--- a/nomsu.peg
+++ b/nomsu.peg
@@ -98,11 +98,11 @@ text_interpolation:
     inline_text_interpolation /
     ("\" indented_expression nodent "..")
 
-number (Number): (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)))-> tonumber)
+number (Number): {| {:value: (("-"? (([0-9]+ "." [0-9]+) / ("." [0-9]+) / ([0-9]+)))-> tonumber) :} |}
 
 -- Variables can be nameless (i.e. just %) and can't contain operators like apostrophe
 -- which is a hack to allow %'s to parse as "%" and "' s" separately
-variable (Var): "%" { (%ident_char+ ((!"'" %operator_char+) / %ident_char+)*)? }
+variable (Var): "%" {| {:value: (%ident_char+ ((!"'" %operator_char+) / %ident_char+)*)? :} |}
 
 inline_list (List):
     !('[..]')
diff --git a/nomsu_tree.lua b/nomsu_tree.lua
index f1b6489..5aeefe8 100644
--- a/nomsu_tree.lua
+++ b/nomsu_tree.lua
@@ -1,130 +1,105 @@
-local utils = require('utils')
-local repr, stringify, min, max, equivalent, set, is_list, sum
-repr, stringify, min, max, equivalent, set, is_list, sum = utils.repr, utils.stringify, utils.min, utils.max, utils.equivalent, utils.set, utils.is_list, utils.sum
-local immutable = require('immutable')
+local repr
+repr = require('utils').repr
 local insert, remove, concat
 do
   local _obj_0 = table
   insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat
 end
-local Lua, Nomsu, Source
-do
-  local _obj_0 = require("code_obj")
-  Lua, Nomsu, Source = _obj_0.Lua, _obj_0.Nomsu, _obj_0.Source
-end
-local MAX_LINE = 80
-local Types = { }
-Types.is_node = function(n)
-  return type(n) == 'userdata' and getmetatable(n) and Types[n.type] == getmetatable(n)
+local Source
+Source = require("code_obj").Source
+local AST = { }
+AST.is_syntax_tree = function(n)
+  return type(n) == 'table' and getmetatable(n) and AST[n.type] == getmetatable(n)
 end
 local Tree
-Tree = function(name, fields, methods)
-  methods = methods or { }
-  local is_multi = true
-  for _index_0 = 1, #fields do
-    local f = fields[_index_0]
-    is_multi = is_multi and (f ~= "value")
-  end
+Tree = function(name, leaf_or_branch, methods)
+  local cls = methods or { }
+  local is_multi = leaf_or_branch == 'branch'
   do
-    methods.type = name
-    methods.name = name
-    methods.__new = methods.__new or function(self, source, ...)
-      assert(source)
+    cls.type = name
+    cls.is_instance = function(self, x)
+      return getmetatable(x) == self
+    end
+    cls.__index = cls
+    cls.__tostring = function(self)
+      return tostring(self.name) .. "(#{@value and repr(@value) or table.concat([repr(v) for v in *@]), ', '})"
+    end
+    cls.map = function(self, fn)
+      do
+        local replacement = fn(self)
+        if replacement then
+          return replacement
+        end
+      end
+      if self.value then
+        return self
+      end
+      local new_vals
+      do
+        local _accum_0 = { }
+        local _len_0 = 1
+        for _index_0 = 1, #self do
+          local v = self[_index_0]
+          _accum_0[_len_0] = v.map and v:map(fn) or v
+          _len_0 = _len_0 + 1
+        end
+        new_vals = _accum_0
+      end
+      return getmetatable(self)(self.source, unpack(new_vals))
+    end
+  end
+  AST[name] = setmetatable(cls, {
+    __tostring = function(self)
+      return self.name
+    end,
+    __call = function(self, source, ...)
       if type(source) == 'string' then
         source = Source:from_string(source)
       end
-      return source, ...
+      assert(Source:is_instance(source))
+      local inst
+      if is_multi then
+        inst = {
+          source = source,
+          ...
+        }
+      else
+        inst = {
+          source = source,
+          value = ...
+        }
+      end
+      setmetatable(inst, self)
+      if inst.__init then
+        inst:__init()
+      end
+      return inst
     end
-    methods.is_multi = is_multi
-    if is_multi then
-      methods.__tostring = function(self)
-        return tostring(self.name) .. "(" .. tostring(table.concat((function()
-          local _accum_0 = { }
-          local _len_0 = 1
-          for _index_0 = 1, #self do
-            local v = self[_index_0]
-            _accum_0[_len_0] = repr(v)
-            _len_0 = _len_0 + 1
-          end
-          return _accum_0
-        end)(), ', ')) .. ")"
-      end
-      methods.map = function(self, fn)
-        do
-          local replacement = fn(self)
-          if replacement then
-            return replacement
-          end
-        end
-        local new_vals
-        do
-          local _accum_0 = { }
-          local _len_0 = 1
-          for _index_0 = 1, #self do
-            local v = self[_index_0]
-            _accum_0[_len_0] = v.map and v:map(fn) or v
-            _len_0 = _len_0 + 1
-          end
-          new_vals = _accum_0
-        end
-        return getmetatable(self)(self.source, unpack(new_vals))
-      end
-    else
-      methods.__tostring = function(self)
-        return tostring(self.name) .. "(" .. tostring(repr(self.value)) .. ")"
-      end
-      methods.map = function(self, fn)
-        return fn(self) or self
-      end
-    end
-  end
-  Types[name] = immutable(fields, methods)
+  })
 end
-Tree("Block", {
-  "source"
-})
-Tree("EscapedNomsu", {
-  "source"
-})
-Tree("Text", {
-  "source"
-})
-Tree("List", {
-  "source"
-})
-Tree("Dict", {
-  "source"
-})
-Tree("DictEntry", {
-  "source"
-})
-Tree("IndexChain", {
-  "source"
-})
-Tree("Number", {
-  "source",
-  "value"
-})
-Tree("Var", {
-  "source",
-  "value"
-})
-Tree("Action", {
-  "source",
-  "stub"
-}, {
-  __new = function(self, source, ...)
-    assert(source)
-    if type(source) == 'string' then
-      source = Source:from_string(source)
+Tree("Number", 'leaf')
+Tree("Var", 'leaf')
+Tree("Block", 'branch')
+Tree("EscapedNomsu", 'branch')
+Tree("Text", 'branch')
+Tree("List", 'branch')
+Tree("Dict", 'branch')
+Tree("DictEntry", 'branch')
+Tree("IndexChain", 'branch')
+Tree("Action", 'branch', {
+  __init = function(self)
+    local stub_bits
+    do
+      local _accum_0 = { }
+      local _len_0 = 1
+      for _index_0 = 1, #self do
+        local a = self[_index_0]
+        _accum_0[_len_0] = type(a) == 'string' and a or '%'
+        _len_0 = _len_0 + 1
+      end
+      stub_bits = _accum_0
     end
-    local stub_bits = { }
-    for i = 1, select("#", ...) do
-      local a = select(i, ...)
-      stub_bits[i] = type(a) == 'string' and a or "%"
-    end
-    local stub = concat(stub_bits, " ")
-    return source, stub, ...
+    self.stub = concat(stub_bits, " ")
   end,
   get_spec = function(self)
     return concat((function()
@@ -139,4 +114,4 @@ Tree("Action", {
     end)(), " ")
   end
 })
-return Types
+return AST
diff --git a/nomsu_tree.moon b/nomsu_tree.moon
index 3339606..fe1c4f4 100644
--- a/nomsu_tree.moon
+++ b/nomsu_tree.moon
@@ -1,69 +1,53 @@
 -- This file contains the datastructures used to represent parsed Nomsu syntax trees,
 -- as well as the logic for converting them to Lua code.
-utils = require 'utils'
-{:repr, :stringify, :min, :max, :equivalent, :set, :is_list, :sum} = utils
-immutable = require 'immutable'
+{:repr} = require 'utils'
 {:insert, :remove, :concat} = table
-{:Lua, :Nomsu, :Source} = require "code_obj"
+{:Source} = require "code_obj"
 
-MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value
-
-Types = {}
-Types.is_node = (n)->
-    type(n) == 'userdata' and getmetatable(n) and Types[n.type] == getmetatable(n)
+AST = {}
+AST.is_syntax_tree = (n)->
+    type(n) == 'table' and getmetatable(n) and AST[n.type] == getmetatable(n)
 
 -- Helper method:
-Tree = (name, fields, methods)->
-    methods or= {}
-    is_multi = true
-    for f in *fields do is_multi and= (f != "value")
-    with methods
+Tree = (name, leaf_or_branch, methods)->
+    cls = methods or {}
+    is_multi = leaf_or_branch == 'branch'
+    with cls
         .type = name
-        .name = name
-        .__new or= (source, ...)=>
-            assert source
+        .is_instance = (x)=> getmetatable(x) == @
+        .__index = cls
+        .__tostring = => "#{@name}(#{@value and repr(@value) or table.concat([repr(v) for v in *@]), ', '})"
+        .map = (fn)=>
+            if replacement = fn(@) then return replacement
+            if @value then return @
+            new_vals = [v.map and v\map(fn) or v for v in *@]
+            return getmetatable(self)(@source, unpack(new_vals))
+
+    AST[name] = setmetatable cls,
+        __tostring: => @name
+        __call: (source, ...)=>
             if type(source) == 'string'
                 source = Source\from_string(source)
-            --assert Source\is_instance(source)
-            return source, ...
-        .is_multi = is_multi
-        if is_multi
-            .__tostring = => "#{@name}(#{table.concat [repr(v) for v in *@], ', '})"
-            .map = (fn)=>
-                if replacement = fn(@)
-                    return replacement
-                new_vals = [v.map and v\map(fn) or v for v in *@]
-                return getmetatable(self)(@source, unpack(new_vals))
-        else
-            .__tostring = => "#{@name}(#{repr(@value)})"
-            .map = (fn)=>
-                fn(@) or @
+            assert(Source\is_instance(source))
+            inst = if is_multi then {:source, ...} else {:source, value:...}
+            setmetatable(inst, @)
+            if inst.__init then inst\__init!
+            return inst
 
-    Types[name] = immutable fields, methods
-
-Tree "Block", {"source"}
-Tree "EscapedNomsu", {"source"}
-Tree "Text", {"source"}
-Tree "List", {"source"}
-Tree "Dict", {"source"}
-Tree "DictEntry", {"source"}
-Tree "IndexChain", {"source"}
-Tree "Number", {"source", "value"}
-Tree "Var", {"source", "value"}
-
-Tree "Action", {"source", "stub"},
-    __new: (source, ...)=>
-        assert source
-        if type(source) == 'string'
-            source = Source\from_string(source)
-        --assert Source\is_instance(source)
-        stub_bits = {}
-        for i=1,select("#",...)
-            a = select(i, ...)
-            stub_bits[i] = type(a) == 'string' and a or "%"
-        stub = concat stub_bits, " "
-        return source, stub, ...
+Tree "Number", 'leaf'
+Tree "Var", 'leaf'
+Tree "Block", 'branch'
+Tree "EscapedNomsu", 'branch'
+Tree "Text", 'branch'
+Tree "List", 'branch'
+Tree "Dict", 'branch'
+Tree "DictEntry", 'branch'
+Tree "IndexChain", 'branch'
+Tree "Action", 'branch',
+    __init: =>
+        stub_bits = [type(a) == 'string' and a or '%' for a in *@]
+        @stub = concat stub_bits, " "
     get_spec: =>
         concat [type(a) == "string" and a or "%#{a.value}" for a in *@], " "
 
-return Types
+return AST
diff --git a/tests/metaprogramming.nom b/tests/metaprogramming.nom
index b9b97af..a31d030 100644
--- a/tests/metaprogramming.nom
+++ b/tests/metaprogramming.nom
@@ -55,7 +55,9 @@ try: foo 99
 
 assume ((\(5 + 5) as value) = 10) or barf "%tree as value failed."
 
-assume ("\(\(foo %x) as nomsu)" = "foo %x") or barf "source code failed."
+assume ("\(\(foo %x) as nomsu)" = "foo %x") or barf "action source code failed."
+
+assume ("\(\%x as nomsu)" = "%x") or barf "var source code failed."
 
 assume ((type of {}) = "table") or barf "type of failed."