local insert, remove, concat
do
  local _obj_0 = table
  insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat
end
local Source
Source = require("code_obj").Source
local List, Dict
do
  local _obj_0 = require('containers')
  List, Dict = _obj_0.List, _obj_0.Dict
end
local Files = require('files')
local unpack = unpack or table.unpack
local as_lua
as_lua = function(self)
  if type(self) == 'number' then
    return tostring(self)
  end
  do
    local mt = getmetatable(self)
    if mt then
      do
        local _as_lua = mt.as_lua
        if _as_lua then
          return _as_lua(self)
        end
      end
    end
  end
  if self.as_lua then
    return self:as_lua()
  end
  return error("Not supported: " .. tostring(self))
end
local SyntaxTree
do
  local _class_0
  local _base_0 = {
    __tostring = function(self)
      local bits
      do
        local _accum_0 = { }
        local _len_0 = 1
        for _index_0 = 1, #self do
          local b = self[_index_0]
          _accum_0[_len_0] = type(b) == 'string' and b:as_lua() or tostring(b)
          _len_0 = _len_0 + 1
        end
        bits = _accum_0
      end
      for k, v in pairs(self) do
        if not (bits[k] or k == 'type' or k == 'source') then
          table.insert(bits, tostring(k) .. "=" .. tostring(type(v) == 'string' and v:as_lua() or v))
        end
      end
      return tostring(self.type) .. "{" .. tostring(table.concat(bits, ", ")) .. "}"
    end,
    __eq = function(self, other)
      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
        end
      end
      return true
    end,
    as_lua = function(self)
      local bits
      do
        local _accum_0 = { }
        local _len_0 = 1
        for _index_0 = 1, #self do
          local b = self[_index_0]
          _accum_0[_len_0] = as_lua(b)
          _len_0 = _len_0 + 1
        end
        bits = _accum_0
      end
      for k, v in pairs(self) do
        if not (bits[k]) then
          table.insert(bits, "[ " .. tostring(as_lua(k)) .. "]=" .. tostring(as_lua(v)))
        end
      end
      return "SyntaxTree{" .. tostring(table.concat(bits, ", ")) .. "}"
    end,
    get_source_file = function(self)
      return self.__class.source_code_for_tree[self]
    end,
    get_source_code = function(self)
      return self.__class.source_code_for_tree[self]:sub(self.source.start, self.source.stop - 1)
    end,
    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)
          if t.type == "Var" then
            do
              local r = replacements[t:as_var()]
              if r then
                return r
              end
            end
          end
        end
      end
      local replacement = fn(self)
      if replacement == false then
        return nil
      end
      if replacement then
        if SyntaxTree:is_instance(replacement) then
          do
            local _tbl_0 = { }
            for k, v in pairs(replacement) do
              _tbl_0[k] = v
            end
            replacement = _tbl_0
          end
          replacement.source = self.source
          if self.comments then
            replacement.comments = {
              unpack(self.comments)
            }
          end
          replacement = SyntaxTree(replacement)
        end
      else
        replacement = {
          source = self.source,
          comments = self.comments and {
            unpack(self.comments)
          }
        }
        local changes = false
        for k, v in pairs(self) do
          local _continue_0 = false
          repeat
            replacement[k] = v
            if SyntaxTree:is_instance(v) then
              local r = v:with(fn)
              if r == v or r == nil then
                _continue_0 = true
                break
              end
              changes = true
              replacement[k] = r
            end
            _continue_0 = true
          until true
          if not _continue_0 then
            break
          end
        end
        if not (changes) then
          return self
        end
        replacement = SyntaxTree(replacement)
      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 = { }
      if self.type == "MethodCall" then
        args[1] = self[1]
        for i = 2, #self do
          local _list_0 = self[i]
          for _index_0 = 1, #_list_0 do
            local tok = _list_0[_index_0]
            if type(tok) ~= 'string' then
              args[#args + 1] = tok
            end
          end
        end
      else
        for _index_0 = 1, #self do
          local tok = self[_index_0]
          if type(tok) ~= 'string' then
            args[#args + 1] = tok
          end
        end
      end
      return args
    end,
    get_stub = function(self)
      local _exp_0 = self.type
      if "Action" == _exp_0 then
        local stub_bits = { }
        local arg_i = 1
        for _index_0 = 1, #self do
          local a = self[_index_0]
          if type(a) == 'string' then
            stub_bits[#stub_bits + 1] = a
          else
            stub_bits[#stub_bits + 1] = arg_i
            arg_i = arg_i + 1
          end
        end
        while type(stub_bits[#stub_bits]) == 'number' do
          stub_bits[#stub_bits] = nil
        end
        return concat(stub_bits, " ")
      elseif "MethodCall" == _exp_0 then
        return "0, " .. table.concat((function()
          local _accum_0 = { }
          local _len_0 = 1
          for i = 2, #self do
            _accum_0[_len_0] = self[i]:get_stub()
            _len_0 = _len_0 + 1
          end
          return _accum_0
        end)(), "; ")
      else
        return error(tostring(self.type) .. "s do not have stubs")
      end
    end,
    as_var = function(self)
      assert(self.type == "Var")
      if type(self[1]) == 'string' then
        return self[1]
      else
        return self[1]:get_stub()
      end
    end,
    matching = function(self, patt)
      if patt.type == "Var" then
        return {
          [patt:as_var()] = self
        }
      end
      if patt.type ~= self.type then
        return nil
      end
      if patt.type == "Action" and patt:get_stub() ~= self:get_stub() then
        return nil
      end
      if #self ~= #patt then
        return nil
      end
      local match = { }
      for i = 1, #self do
        local v = self[i]
        local pv = patt[i]
        if type(v) ~= type(pv) then
          return nil
        end
        if type(v) ~= 'table' then
          if not (v == pv) then
            return nil
          end
        else
          local m = v:matching(pv)
          if not (m) then
            return nil
          end
          for mk, mv in pairs(m) do
            if match[mk] and match[mk] ~= mv then
              return nil
            end
            match[mk] = mv
          end
        end
      end
      return Dict(match)
    end,
    _breadth_first = function(self)
      coroutine.yield(self)
      for _index_0 = 1, #self do
        local child = self[_index_0]
        if getmetatable(child) == SyntaxTree.__base then
          child:_breadth_first()
        end
      end
    end,
    breadth_first = function(self)
      return coroutine.create(function()
        return self:_breadth_first()
      end)
    end,
    _depth_first = function(self)
      coroutine.yield(self)
      for _index_0 = 1, #self do
        local child = self[_index_0]
        if getmetatable(child) == SyntaxTree.__base then
          child:_depth_first()
        end
      end
    end,
    depth_first = function(self)
      return coroutine.create(function()
        return self:_depth_first()
      end)
    end
  }
  _base_0.__index = _base_0
  _class_0 = setmetatable({
    __init = function() end,
    __base = _base_0,
    __name = "SyntaxTree"
  }, {
    __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.source_code_for_tree = setmetatable({ }, {
    __index = function(self, t)
      local s = t.source
      local f = Files.read(s.filename)
      return f
    end,
    __mode = "k"
  })
  self.is_instance = function(self, t)
    return type(t) == 'table' and getmetatable(t) == self.__base
  end
  SyntaxTree = _class_0
end
SyntaxTree.__base.__type = "a Syntax Tree"
getmetatable(SyntaxTree).__call = function(self, t, ...)
  if type(t.source) == 'string' then
    t.source = Source:from_string(t.source)
  end
  setmetatable(t, self.__base)
  for i = 1, select("#", ...) do
    t[i] = select(i, ...)
  end
  if t.type == 'Action' then
    t.stub = t:get_stub()
  end
  return t
end
return SyntaxTree