sexp = object.new

# Keep track of sexp types by name
sexp.types = [:]

sexp.init = { name |
  my.name = name
}

sexp.prototype.to_s = {
  "s#{[my.name] + my.nodes}"
}

sexp.prototype.== = { rhs |
  m = my
  true? rhs.has_method?(:name) && rhs.has_method?(:nodes)
    { m.name == rhs.name && { m.nodes == rhs.nodes } }
}

sexp.prototype.<< = { val |
  my.nodes << val
  my
}

sexp.prototype.concat = { val |
  my.nodes.concat val
  my
}

sexp.prototype.last = {
  my.nodes.last
}

# Shared initializer for sexp types
initializer = { nodes |
  my.nodes = nodes
  true? my.nodes.length == 1
    { my.value = nodes.last }
}

# Create new sexp with methods to access the given methods.
make = { name, *meths |
  new_thing = sexp.new(name)
  meths.each_with_index { name, i |
    new_thing.prototype.add_method name, {
      nodes[i]
    }
  }

  new_thing.init = ->initializer
  sexp.types[name] = new_thing
}

make :program

make :var_assign, :lhs, :rhs

make :local_var_assign, :lhs, :rhs

make :local_var_reassign, :lhs, :rhs

make :upvar_assign, :lhs, :rhs

make :number

make :array

make :hash

make :regex, :body, :opts

make :string

make :string_eval, :value

make :string_interp

make :simple_string

make :symbol

make :simple_symbol

make :function, :args, :body

make :arg, :id

make :def_arg, :id, :value

make :var_arg, :id

make :call, :target, :method, :args

make :simple_index_get, :target, :method, :args

make :simple_index_set, :target, :method, :args

make :get_value

make :get_local_value

make :get_up_value

make :invoke, :target, :args

make :invoke_local, :empty, :method, :args

make :invoke_up, :empty, :method, :args

make :invoke_self, :empty, :method, :args

make :invoke_numbers, :lhs, :op, :rhs

make :invoke_number, :lhs, :op, :rhs

make :invoke_number_rhs, :lhs, :op, :rhs

make :invoke_index_get, :target, :args

make :invoke_function, :target, :args

make :field_assign, :lhs, :rhs

make :field_access, :target, :field

make :binop, :lhs, :op, :rhs

make :meth_access, :target, :method

make :meth_access_local, :target, :method

make :meth_access_up, :target, :method

make :named_arg, :key, :value

# Little shortcut to make sexps via s[...]
s = object.new

s.get = { name, *args |
  sexp.types[name].new args
}

sexp? = { val |
  val.has_method?(:name) && { sexp.types[val.name] }
}

export ->sexp?, :sexp?
export s, :s