# File lib/ruby_parser_extras.rb, line 404
  def logop(type, left, right) # TODO: rename logical_op
    left = value_expr left

    if left and left[0] == type and not left.paren then
      node, second = left, nil

      while (second = node[2]) && second[0] == type and not second.paren do
        node = second
      end

      node[2] = s(type, second, right)

      return left
    end

    return s(type, left, right)
  end

  def new_aref val
    val[2] ||= s(:arglist)
    val[2][0] = :arglist if val[2][0] == :array # REFACTOR
    if val[0].node_type == :self then
      result = new_call nil, "[]""[]", val[2]
    else
      result = new_call val[0], "[]""[]", val[2]
    end
    result
  end

  def new_body val
    result = val[0]

    if val[1] then
      result = s(:rescue)
      result << val[0] if val[0]

      resbody = val[1]

      while resbody do
        result << resbody
        resbody = resbody.resbody(true)
      end

      result << val[2] if val[2]

      result.line = (val[0] || val[1]).line
    elsif not val[2].nil? then
      warning("else without rescue is useless")
      result = block_append(result, val[2])
    end

    result = s(:ensure, result, val[3]).compact if val[3]
    return result
  end

  def new_call recv, meth, args = nil
    result = s(:call, recv, meth)
    result.line = recv.line if recv

    args ||= s(:arglist)
    args[0] = :arglist if args.first == :array
    args = s(:arglist, args) unless args.first == :arglist
    result << args
    result
  end

  def new_case expr, body
    result = s(:case, expr)
    line = (expr || body).line

    while body and body.node_type == :when
      result << body
      body = body.delete_at 3
    end

    # else
    body = nil if body == s(:block)
    result << body

    result.line = line
    result
  end

  def new_class val
    line, path, superclass, body = val[1], val[2], val[3], val[5]
    scope = s(:scope, body).compact
    result = s(:class, path, superclass, scope)
    result.line = line
    result.comments = self.comments.pop
    result
  end

  def new_compstmt val
    result = void_stmts(val[0])
    result = remove_begin(result) if result
    result
  end

  def new_defn val
    (line, bol), name, args, body = val[2], val[1], val[3], val[4]
    body ||= s(:nil)

    body ||= s(:block)
    body = s(:block, body) unless body.first == :block

    result = s(:defn, name.to_sym, args, s(:scope, body))
    result.line = line
    result.line -= 1 if bol
    result.comments = self.comments.pop
    result
  end

  def new_defs val
    recv, name, args, body = val[1], val[4], val[6], val[7]

    body ||= s(:block)
    body = s(:block, body) unless body.first == :block

    result = s(:defs, recv, name.to_sym, args, s(:scope, body))
    result.line = recv.line
    result.comments = self.comments.pop
    result
  end

  def new_for expr, var, body
    result = s(:for, expr, var).line(var.line)
    result << body if body
    result
  end

  def new_if c, t, f
    l = [c.line, t && t.line, f && f.line].compact.min
    c = cond c
    c, t, f = c.last, f, t if c[0] == :not
    s(:if, c, t, f).line(l)
  end

  def new_iter call, args, body
    result = s(:iter)
    result << call if call
    result << args
    result << body if body
    result
  end

  def new_masgn lhs, rhs, wrap = false
    rhs = value_expr(rhs)
    rhs = lhs[1] ? s(:to_ary, rhs) : s(:array, rhs) if wrap

    lhs.delete_at 1 if lhs[1].nil?
    lhs << rhs

    lhs
  end

  def new_module val
    line, path, body = val[1], val[2], val[4]
    body = s(:scope, body).compact
    result = s(:module, path, body)
    result.line = line
    result.comments = self.comments.pop
    result
  end

  def new_op_asgn val
    lhs, asgn_op, arg = val[0], val[1].to_sym, val[2]
    name = lhs.value
    arg = remove_begin(arg)
    result = case asgn_op # REFACTOR
             when "||""||" then
               lhs << arg
               s(:op_asgn_or, self.gettable(name), lhs)
             when "&&""&&" then
               lhs << arg
               s(:op_asgn_and, self.gettable(name), lhs)
             else
               # TODO: why [2] ?
               lhs[2] = new_call(self.gettable(name), asgn_op,
                                 s(:arglist, arg))
               lhs
             end
    result.line = lhs.line
    result
  end

  def new_regexp val
    node = val[1] || s(:str, '')
    options = val[2]

    o, k = 0, nil
    options.split(//).uniq.each do |c| # FIX: this has a better home
      v = {
        'x' => Regexp::EXTENDED,
        'i' => Regexp::IGNORECASE,
        'm' => Regexp::MULTILINE,
        'o' => Regexp::ONCE,
        'n' => Regexp::ENC_NONE,
        'e' => Regexp::ENC_EUC,
        's' => Regexp::ENC_SJIS,
        'u' => Regexp::ENC_UTF8,
      }[c]
      raise "unknown regexp option: #{c}" unless v
      o += v
      k = c if c =~ /[esu]/
    end

    case node[0]
    when :str then
      node[0] = :lit
      node[1] = if k then
                  Regexp.new(node[1], o, k)
                else
                  Regexp.new(node[1], o)
                end
    when :dstr then
      if options =~ /o/ then
        node[0] = :dregx_once
      else
        node[0] = :dregx
      end
      node << o if o and o != 0
    else
      node = s(:dregx, '', node);
      node[0] = :dregx_once if options =~ /o/
      node << o if o and o != 0
    end

    node
  end

  def new_sclass val
    recv, in_def, in_single, body = val[3], val[4], val[6], val[7]
    scope = s(:scope, body).compact
    result = s(:sclass, recv, scope)
    result.line = val[2]
    self.in_def = in_def
    self.in_single = in_single
    result
  end

  def new_super args
    if args && args.node_type == :block_pass then
      s(:super, args)
    else
      args ||= s(:arglist)
      s(:super, *args[1..-1])
    end
  end

  def new_undef n, m = nil
    if m then
      block_append(n, s(:undef, m))
    else
      s(:undef, n)
    end
  end

  def new_until block, expr, pre
    expr = (expr.first == :not ? expr.last : s(:not, expr)).line(expr.line)
    new_while block, expr, pre
  end

  def new_while block, expr, pre
    line = [block && block.line, expr.line].compact.min
    block, pre = block.last, false if block && block[0] == :begin

    expr = cond expr
    result = if expr.first == :not then
               s(:until, expr.last, block, pre)
             else
               s(:while, expr, block, pre)
             end

    result.line = line
    result
  end

  def new_xstring str
    if str then
      case str[0]
      when :str
        str[0] = :xstr
      when :dstr
        str[0] = :dxstr
      else
        str = s(:dxstr, '', str)
      end
      str
    else
      s(:xstr, '')
    end
  end

  def new_yield args = nil
    # TODO: raise args.inspect unless [:arglist].include? args.first # HACK
    raise SyntaxError, "Block argument should not be given." if
      args && args.node_type == :block_pass

    args ||= s(:arglist)

    # TODO: I can prolly clean this up
    args[0] = :arglist       if args.first == :array
    args = s(:arglist, args) unless args.first == :arglist

    return s(:yield, *args[1..-1])
  end

  def next_token
    if self.lexer.advance then
      return self.lexer.token, self.lexer.yacc_value
    else
      return [false, '$end']
    end
  end

  def node_assign(lhs, rhs) # TODO: rename new_assign
    return nil unless lhs

    rhs = value_expr rhs

    case lhs[0]
    when :gasgn, :iasgn, :lasgn, :dasgn, :dasgn_curr,
      :masgn, :cdecl, :cvdecl, :cvasgn then
      lhs << rhs
    when :attrasgn, :call then
      args = lhs.pop unless Symbol === lhs.last
      lhs << arg_add(args, rhs)
    when :const then
      lhs[0] = :cdecl
      lhs << rhs
    else
      raise "unknown lhs #{lhs.inspect}"
    end

    lhs
  end

  def process(str, file = "(string)")
    raise "bad val: #{str.inspect}" unless String === str

    self.file = file
    self.lexer.src = str

    @yydebug = ENV.has_key? 'DEBUG'

    do_parse
  end
  alias :parse :process

  def remove_begin node
    oldnode = node
    if node and :begin == node[0] and node.size == 2 then
      node = node[-1]
      node.line = oldnode.line
    end
    node
  end

  def reset
    lexer.reset
    self.in_def = false
    self.in_single = 0
    self.env.reset
    self.comments.clear
  end

  def ret_args node
    if node then
      raise SyntaxError, "block argument should not be given" if
        node[0] == :block_pass

      node = node.last if node[0] == :array && node.size == 2
      # HACK matz wraps ONE of the FOUR splats in a newline to
      # distinguish. I use paren for now. ugh
      node = s(:svalue, node) if node[0] == :splat and not node.paren
      node[0] = :svalue if node[0] == :arglist && node[1][0] == :splat
    end

    node
  end

  def s(*args)
    result = Sexp.new(*args)
    result.line ||= lexer.lineno if lexer.src          # otherwise...
    result.file = self.file
    result
  end

  def value_expr oldnode # HACK
    node = remove_begin oldnode
    node.line = oldnode.line if oldnode
    node[2] = value_expr(node[2]) if node and node[0] == :if
    node
  end

  def void_stmts node
    return nil unless node
    return node unless node[0] == :block

    node[1..-1] = node[1..-1].map { |n| remove_begin(n) }
    node
  end

  def warning s
    # do nothing for now
  end

  alias :old_yyerror :yyerror
  def yyerror msg
    # for now do nothing with the msg
    old_yyerror
  end
end