include 'parser/sexp'
sh = import('parser/string_helper', :string_helper)
to_id = { name | sh.escape_identifier name }

node = object.new
node.prototype [
    init: { name |
            my.node_name = name
            export my, name
          }
    to_s: { "<#{my.node_name}: #{my.matched}>" }
]

each_ast = { elements, block |
  elements.each { e |
    true? { e.has_method?(:ast)} { block e.ast }
          { true? e.has_method?(:elements) { each_ast e.elements, ->block } }
  }
}

add_ast = { node, list |
  each_ast node.elements, { ast |
    true? ast
      { list << ast }
  }

  list
}

ast = { name, block |
  n = node.new name
  n.ast = ->block
}

ast :program {
  out = s[:program]
  add_ast my, out
}

ast :simple_exp {
  a = null
  each_ast my.elements, { ast | a = ast }
  a
}

ast :bnumber {
  s[:number text.to_f]
}

ast :empty_array {
  s[:array]
}

ast :barray {
  s[:array].concat inner.ast
}

ast :array_inner {
  list = []

  add_ast my, list

  list
}

ast :empty_hash {
  s[:hash]
}

ast :bhash {
  s[:hash].concat inner.ast
}

ast :hash_arg {
  [my.key.ast, my.value.ast]
}

ast :hash_key_arg {
  [s[:simple_string my.key.text], my.value.ast]
}

ast :bregex {
  s[:regex my.body.text, my.opts.text]
}

ast :simple_symbol {
  s[:simple_symbol my.svalue.text]
}

ast :empty_symbol {
  s[:symbol]
}

ast :double_symbol {
  s[:symbol my.svalue.text]
}

ast :simple_string {
  s[:simple_string my.svalue.text]
}

ast :empty_string {
  s[:string]
}

ast :string_interp {
  list = []
  values.matched.each { e |
    true? e.has_method?(:node_name) && { e.node_name == :interp_value }
      { list << e.ast }
      {
        # Combine consecutive strings into one string
        true? sexp?(list.last) && { list.last.name == :string }
          { list.last.last << e.text }
          { list << s[:string e.text] }
      }
  }

  # If just one simple string, return that
  true? list.length == 1 && { list[0].name == :string }
    { list[0] }
    { true? list.length == 0
      { s[:string] }
      { s[:string_interp].concat list }
    }
}

ast :interp_value {
  value = s[:string_eval]

  true? first, {
    add_ast first, value
  }

  add_ast rest, value
}

ast :bfunction {
  list = s[:function]

  arg_list = []
  add_ast args, arg_list

  body_list = []
  add_ast body, body_list

  list << arg_list << body_list
}

ast :plain_arg {
  s[:arg to_id my.text]
}

ast :default_arg {
  s[:def_arg, to_id(my.arg_var.text), my.arg_val.ast]
}

ast :variable_args {
  s[:var_arg to_id(my.arg_var.text)]
}

ast :unary_op {
  list = s[:call]

  add_ast my, list

  list << to_id(my.elements[1].text)
}

ast :inner_arg_list {
  list = []

  add_ast my, list

  reg_args = []
  named_args = s[:hash]
  list.each { arg |
    true? arg.name == :named_arg
      { named_args << [arg.key, arg.value] }
      { reg_args << arg }
  }

  true? named_args.nodes.empty?
   { reg_args }
   { reg_args << named_args }
}

ast :named_argument {
  a = s[:named_arg]

  true? my.key.rule_name == :identifier
    { a << s[:string my.key.text] }
    { a << my.key.ast  }

  a << my.value.ast
}

ast :named_argument_call {
  s[:named_arg, s[:get_value, my.key.text], my.value.ast]
}

ast :simple_call {
  args = []

  add_ast my.alist, args

  var = to_id my.m_name.text
 
  true? args.empty?
  {
    true? my.alist.text.empty? # simple call with no args and no parens
      { s[:get_value, var] }
      { s[:call, null, var] }
  }
  { s[:call, null, var, args.first] }
}

ast :simpler_call {
  args = []

  add_ast my.alist, args

  var = to_id my.m_name.text

  true? args.empty?
    { s[:call, null, var] }
    { s[:call, null, var, args.first] }
}

ast :simplest_call {
  s[:get_value, to_id(my.elements.first.text)]
}

ast :chain_call {
  list = s[:call]

  list << my.elements.first.ast

  list << to_id(my.elements[1].text)

  add_ast my.elements.last, list
}

ast :simple_meth_lhs {
  args = []
  index_args = []

  add_ast my.args, args
  true? my.has_method?(:index_args) {
    add_ast my.index_args, index_args
  }

  var = to_id(my.elements.first.text)

  lhs_call = true? args.empty?
  {
    true? my.args.text.empty? # simple call with no args and no parens
    { s[:get_value, var] }
    { s[:call, null, var] }
  }
  { s[:call, null, var, args.first] }

  false? index_args.empty?
  {
    lhs_call = s[:call, lhs_call, :get, index_args]
  }

  lhs_call
}

ast :array_index_lhs {
  my.indexes.elements.reduce my.target.ast, { m, i |
    add_ast i, s[:call, m, :get]
  }
}

ast :access_meth_lhs {
  s[:meth_access, null, to_id(my.var.text)]
}

ast :method_chain {
  list = []

  add_ast my, list

  list.reduce { memo, call |
    true? call.name == :get_value
      { c = s[:call]; c.nodes = call.nodes; c.nodes.insert(0, memo); call = c }
      { null? call.nodes.first
          { call.nodes[0] = memo }
          { true? call.name == :call && { call.method == :get }
              {
                #Call is treated as target instead of a call on the target
                #and [] is on the result of the call instead of the call on
                #the target. So flip things around.
                args = call.nodes.pop
                target = call.target

                true? target.name == :call
                {
                  call.nodes[1] = target.method
                  call.nodes[2] = target.args
                }
                {
                  call.nodes[1] = target.value
                }
                call.nodes[0] = memo
                call = s[:call, call, :get, args]
              }
              {
                p "wut #{call}"
              }
          }
      }

    call
  }
}

ast :paren_call {
  list = s[:invoke]

  add_ast my, list
}

ast :func_lit_call {
  list = s[:invoke_function]
  add_ast my, list
}

ast :var_assign {
  s[:var_assign to_id(my.var.text), my.elements.last.ast]
}

ast :field_assign {
  list = s[:field_assign]
  add_ast my, list
}

ast :field_access {
  list = s[:field_access]

  add_ast my, list
  list << to_id(my.elements.last.text)
}

ast :binary_op {
  list = s[:binop].concat my.elements.first.ast

  list << my.elements.last.ast
}

ast :binary_op_chain {
  list = []

  my.elements.each { e |
    list << e.elements.first.ast
    list << to_id(e.elements[2].text)
  }

  list
}

ast :simple_meth_access {
  s[:meth_access, null, to_id(my.meth.text)]
}

ast :simple_paren_meth_access {
  list = s[:meth_access null]

  add_ast my, list
}

ast :paren_meth_access {
  list = s[:meth_access]

  add_ast my, list

  list << to_id(my.meth.text)
}

ast :meth_access {
  list = s[:meth_access]

  add_ast my, list

  list << to_id(my.meth.text)
}

ast :index_get {
  list = s[:call, my.elements.first.ast, :get]
  index_args = add_ast my.gindexes, []

  list << index_args.deq

  list = index_args.reduce(list, { memo, index |
    s[:call, memo, :get, index]
   })

  args = add_ast(my.iargs, []).first


  true? list.args.length == 1 && { list.args.first.name == :simple_symbol || { list.args.first.name == :simple_string } }
    { list.name = :simple_index_get }

  null? args
    { list }
    { s[:invoke_index_get, list, args] }
}

ast :index_set {
  list = s[:call, my.elements.first.ast]

  true? my.sindexes.elements.length > 1
    {
      list << :get

      index_args = add_ast my.sindexes, []

      list << [index_args.deq]
      final_arg = index_args.pop

      list = index_args.reduce(list, { memo, index |
        s[:call, memo, :get, [index]]
      })

      value = my.elements.last.ast

      s[:call, list, :set, [final_arg, value]]
    }
    {
      args = add_ast my.sindexes, []
      args << my.elements.last.ast

      true? args.first.name == :simple_symbol || { args.first.name == :simple_string }
        { list.name = :simple_index_set }

      list << :set << args
    }
}