# -*- encoding: us-ascii -*-

module CodeTools
  module AST

    class TypeConstant < Node
      def initialize(line)
        @line = line
      end

      def bytecode(g)
        pos(g)
        g.push_type
      end
    end

    class MirrorConstant < Node
      def initialize(line)
        @line = line
      end

      def bytecode(g)
        pos(g)
        g.push_mirror
      end
    end

    class ScopedConstant < Node
      attr_accessor :parent, :name

      def initialize(line, parent, name)
        @line = line
        @parent = parent
        @name = name
      end

      def bytecode(g)
        pos(g)

        @parent.bytecode(g)
        g.find_const @name
      end

      def assign_bytecode(g, value)
        pos(g)

        value.bytecode(g)
        g.push_literal @name
        @parent.bytecode(g)
        g.rotate 3
      end

      def masgn_bytecode(g)
        pos(g)

        @parent.bytecode(g)
        g.swap
        g.push_literal @name
      end

      def defined(g)
        f = g.new_label
        done = g.new_label

        value_defined(g, f, false)

        g.pop
        g.push_literal "constant"
        g.goto done

        f.set!
        g.push_nil

        done.set!
      end

      def value_defined(g, f, const_missing=true)
        # Save the current exception into a stack local
        g.push_exception_state
        outer_exc_state = g.new_stack_local
        g.set_stack_local outer_exc_state
        g.pop

        ex = g.new_label
        ok = g.new_label
        g.setup_unwind ex, RescueType

        @parent.bytecode(g)
        g.push_literal @name
        if const_missing
          g.push_true
        else
          g.push_false
        end
        g.invoke_primitive :vm_const_defined_under, 3

        g.pop_unwind
        g.goto ok

        ex.set!
        g.clear_exception
        g.push_stack_local outer_exc_state
        g.restore_exception_state
        g.goto f

        ok.set!
      end

      def to_sexp
        [:colon2, @parent.to_sexp, @name]
      end

      alias_method :assign_sexp, :to_sexp
    end

    class ToplevelConstant < Node
      attr_accessor :name

      def initialize(line, name)
        @line = line
        @name = name
      end

      def bytecode(g)
        pos(g)

        g.push_cpath_top
        g.find_const @name
      end

      def assign_bytecode(g, value)
        pos(g)

        g.push_cpath_top
        g.push_literal @name
        value.bytecode(g)
      end

      def masgn_bytecode(g)
        pos(g)

        g.push_cpath_top
        g.swap
        g.push_literal @name
      end

      def defined(g)
        f = g.new_label
        done = g.new_label

        value_defined(g, f)

        g.pop
        g.push_literal "constant"
        g.goto done

        f.set!
        g.push_nil

        done.set!
      end

      def value_defined(g, f)
        # Save the current exception into a stack local
        g.push_exception_state
        outer_exc_state = g.new_stack_local
        g.set_stack_local outer_exc_state
        g.pop

        ex = g.new_label
        ok = g.new_label
        g.setup_unwind ex, RescueType

        g.push_cpath_top
        g.push_literal @name
        g.push_false
        g.invoke_primitive :vm_const_defined_under, 3

        g.pop_unwind
        g.goto ok

        ex.set!
        g.clear_exception
        g.push_stack_local outer_exc_state
        g.restore_exception_state
        g.goto f

        ok.set!
      end

      def to_sexp
        [:colon3, @name]
      end

      alias_method :assign_sexp, :to_sexp
    end

    class ConstantAccess < Node
      attr_accessor :name

      def initialize(line, name, top_level = false)
        @line = line
        @name = name
        @top_level = top_level
      end

      def bytecode(g)
        pos(g)

        if g.state.op_asgn?
          g.push_rubinius
          g.find_const :Runtime
          g.push_literal @name
          g.push_scope
          g.send :find_constant_for_op_asign_or, 2
        else
          if @top_level
            g.push_cpath_top
            g.find_const @name
          else
            g.push_const @name
          end
        end
      end

      def assign_bytecode(g, value)
        pos(g)

        g.push_scope
        g.push_literal @name
        value.bytecode(g)
      end

      def masgn_bytecode(g)
        pos(g)

        g.push_scope
        g.swap
        g.push_literal @name
      end

      def defined(g)
        f = g.new_label
        done = g.new_label

        value_defined(g, f)

        g.pop
        g.push_literal "constant"
        g.goto done

        f.set!
        g.push_nil

        done.set!
      end

      def value_defined(g, f)
        # Save the current exception into a stack local
        g.push_exception_state
        outer_exc_state = g.new_stack_local
        g.set_stack_local outer_exc_state
        g.pop

        ex = g.new_label
        ok = g.new_label
        g.setup_unwind ex, RescueType

        g.push_literal @name
        g.invoke_primitive :vm_const_defined, 1

        g.pop_unwind
        g.goto ok

        ex.set!
        g.clear_exception
        g.push_stack_local outer_exc_state
        g.restore_exception_state
        g.goto f

        ok.set!
      end

      def assign_sexp
        @name
      end

      def to_sexp
        [:const, @name]
      end
    end

    class ConstantAssignment < Node
      attr_accessor :constant, :value

      def initialize(line, expr, value)
        @line = line
        @value = value

        if expr.kind_of? Symbol
          @constant = ConstantAccess.new line, expr
        else
          @constant = expr
        end
      end

      def masgn_bytecode(g)
        @constant.masgn_bytecode(g)
        g.swap
        g.send :const_set, 2
      end

      def bytecode(g)
        pos(g)

        return masgn_bytecode(g) if g.state.masgn?

        @constant.assign_bytecode(g, @value)
        g.send :const_set, 2
      end

      def to_sexp
        sexp = [:cdecl, @constant.assign_sexp]
        sexp << @value.to_sexp if @value
        sexp
      end
    end
  end
end
