#
# YASM: YARV Assembler
#

=begin

== Usage:

require 'yasm'
yasm = YARVCore::Assembler.new


#
# def m1(a, b)
#   p [a, b]
# end
# =>
m1 = yasm.method(:m1, :a, :b){|x|
  x.putself
  x.getlocal :a
  x.getlocal :b
  x.newarray 2
  x.send :p, 2
  x.putnil
  x.end
}


#
# def m2(a1, a2)
#   begin
#     t1()
#   rescue
#     t2()
#   ensure
#     t3()
#   end
# end
m2 = yasm.method(:m2, :a1, :a2){|x|
  x.begin{|y|
    y.putself
    y.send :t1, 0
  }.rescue{|y|
    y.putself
    y.send :t2, 0
  }.ensure{|y|
    y.putself
    y.send :t3, 0
  }
}


#
# def m3(a)   # abs
#   if a > 0
#     a
#   else
#     -a
#   end
# end
m3 = yasm.method(:m3, :a){|x|
  x.getlocal   :a
  x.putobject  0
  x.send :>,   1
  x.unless     :lelse
  x.getlocal   :a
  x.jump       :lend
x.label      :lelse ##
  x.getlocal   :a
  x.send       :+@, 0
x.label      :lend  ##
  x.end
}


#
# def m4(a)
#   3.times{|i|
#     p a
#   }
m4 = yasm.method(:m4, :a){|x|
  x.putobject 3
  x.send      :times, 0, x.block(:i){|y|
    y.getself
    y.getlocal :a
    y.send     :p, 1
    y.end
  }
  x.end
}


#
# def m5(a)
#   b = a * 2
# end
m5 = yasm.method(:a){|x|
  x.local_vars(:b) # you must declare local variables at first
  x.getlocal :a
  x.putobject 2
  x.send :*, 1
  x.dup
  x.setlocal :b
  x.end
}




#
# def m1(...); ...; end
#
# m1(1, 2)
# =>
top = yasm.top{|x|
  x.methoddef(:m1, m1)
  x.putself
  x.putobject 1
  x.putobject 2
  x.send :m1, 2
  x.end
}

#=> run
YARVCore::eval_parsed(top)

=end

require 'yarvcore'
require 'yasmdata'

module YARVCore
  class Assembler

    # from yarvcore.h
    ISeqType = {
      :top    => 1,
      :method => 2,
      :block  => 3,
      :class  => 4,
      :rescue => 5,
      :ensure => 6,
    }
    
    class ScopeAssember
      def initialize scope_type, id, file_name, parent, args = []
        @st     = scope_type
        @seq    = []
        @args   = args
        @locals = []
        @labels = {}
        @parent = parent || false
        @file_name = file_name

        # null iseq
        @iseq   = YARVCore::InstructionSequence.new(
          false, id.to_s, file_name,
          parent ? parent.iseq : nil, ISeqType[scope_type])
      end
      attr_reader :iseq
      
      def set_parent parent
        @parent = parent
      end

      def local_vars *vars
        @locals = vars
      end
      
      def assemble
        @iseq.assemble @args, @locals, @seq
      end

      def block *args
        sa = ScopeAssember.new(:block, 'block', @file_name, self, args)
        sa.set_parent self
        yield(sa)
        sa.assemble
      end

      def rescue
        sa = ScopeAssember.new(:rescue, 'rescue', args)
        sa.set_parent self
        yield(sa)
        sa.assemble
      end

      def ensure
        sa = ScopeAssember.new(:ensure, 'ensure', args)
        sa.set_parent self
        yield(sa)
        sa.assemble
      end

      ##
      def make_insn id, *args
        if insn_no = YARVCore::InstructionSequence::Instruction.id2insn_no(id)
          /\:(\d+)\Z/ =~ caller(1)[1]
          line = $1.to_i
          @seq << YARVCore::InstructionSequence::Instruction.make(insn_no, line, args)
          true
        end
      end
      
      def search_local sym
        @args.each_with_index{|e, i|
          return 1 + i              if e == sym
        }
        @locals.each_with_index{|e, i|
          return 1 + i + @args.size if e == sym
        }
        raise "unknown local variable: #{sym}"
      end

      def search_label label
        if lobj = @labels[label]
          lobj
        else
          @labels[label] = YARVCore::InstructionSequence::Label.new(false)
        end
      end

      def label l
        @seq << search_label(l)
      end
      
      ##
      
      def method_missing id, *args
        unless make_insn id, *args
          super
        end
      end

      def getlocal sym
        make_insn :getlocal, search_local(sym)
      end

      def setlocal sym
        make_insn :setlocal, search_local(sym)
      end

      def send sym, argc, block = 0, flag = 0
        make_insn :send, sym, argc, block, flag, 0
      end
      
      def end
        make_insn :end, @args.size + @locals.size + 1
      end

      def jump l
        make_insn :jump, search_label(l)
      end
      
      def if l
        make_insn :if, search_label(l)
      end
      
      def unless l
        make_insn :unless, search_label(l)
      end
      
    end
    
    def initialize file_name = '*YASM*'
      @file_name = file_name
    end

    attr_accessor :file_name
    
    def top id = 'top'
      # def initialize scope_type, id, file_name, parent, args = []
      sa = ScopeAssember.new(:top, id, @file_name,  nil)
      yield(sa)
      sa.assemble
    end

    def class id = 'Class'
      sa = ScopeAssember.new(:class, id)
      yield(sa)
      sa.assemble
    end

    def method id, *args
      sa = ScopeAssember.new(:method, id, @file_name, nil, args)
      yield(sa)
      sa.assemble
    end
  end
end

