require 'interp'

module Compiler

   # use eval and Value class below to compile
   # expression into bytecode
   def Compiler.compile(s)
     s.gsub!(/([0-9]+)/, 'Value.new(stack, \1)')
     stack = []
     eval(s)
     stack
   end

   class Value
     attr_reader :number  # constant value or nil for on stack
     ON_STACK = nil

     def initialize(stack, number)
       @number = number
       @stack = stack
     end

     # generate code for each binary operator (except -@)
     # algorithm:
     # push constants (or don't if already on stack)
     # swap if necessary
     # push bytecode
     # create stack item
     {'+' => Interpreter::Ops::ADD,
      '-' => Interpreter::Ops::SUB,
      '*' => Interpreter::Ops::MUL,
      '**'=> Interpreter::Ops::POW,
      '/' => Interpreter::Ops::DIV,
      '%' => Interpreter::Ops::MOD}.each do |operator, byte_code|
        Value.module_eval <<-FUNC
         def #{operator}(rhs)
           push_const(@number)
           push_const(rhs.number)
           # may need to swap integers on stack for all but plus
           #{
             if operator != "+"
               "@stack << Interpreter::Ops::SWAP if rhs.number == nil &&
                                                    @number != nil"
             end
           }
           @stack << #{byte_code}
           Value.new(@stack, ON_STACK)
         end
        FUNC
     end

     def -@
       if @number != ON_STACK
         @number = -@number
         push_const(@number)
       else
         push_const(@number)
         push_const(0)
         @stack << Interpreter::Ops::SWAP
         @stack << Interpreter::Ops::SUB
       end
       Value.new(@stack, ON_STACK)
     end

     def push_const(number)
       if number != ON_STACK
         if (-32768..32767).include?(number)
           @stack << Interpreter::Ops::CONST
         else
           @stack << Interpreter::Ops::LCONST
           @stack << ((number >> 24) & 0xff)
           @stack << ((number >> 16) & 0xff)
         end
         @stack << ((number >> 8) & 0xff)
         @stack << (number & 0xff)
       end
     end
   end
end