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