require_relative "eventable" module Interpreter class Interpreter # fire events for changed pc and register contents include Eventable attr_reader :instruction # current instruction or pc attr_reader :clock # current instruction or pc # an (arm style) link register. store the return address to return to attr_reader :link attr_reader :registers # the registers, 16 (a hash, sym -> contents) attr_reader :stdout # collect the output attr_reader :state # running etc attr_reader :flags # somewhat like the lags on a cpu, hash sym => bool (zero .. . ) def initialize @state = :stopped @stdout = "" @registers = {} @flags = { :zero => false , :positive => false , :negative=> false , :overflow => false } @clock = 0 (0...12).each do |r| set_register "r#{r}".to_sym , "r#{r}:unknown" end end def start instruction @clock = 0 set_state(:running) set_instruction instruction end def set_state state old = @state return if state == old @state = state trigger(:state_changed , old , state ) end def set_instruction i return if @instruction == i old = @instruction @instruction = i trigger(:instruction_changed, old , i) set_state( :exited) unless i end def get_register( reg ) reg = reg.symbol if reg.is_a? Register::RegisterValue raise "Not a register #{reg}" unless Register::RegisterValue.look_like_reg(reg) @registers[reg] end def set_register reg , val old = get_register( reg ) # also ensures format unless val.is_a? String @flags[:zero] = (val == 0) @flags[:positive] = (val > 0) @flags[:negative] = (val < 0) #puts "Set_flags #{val} :#{@flags.inspect}" end return if old === val reg = reg.symbol if reg.is_a? Register::RegisterValue @registers[reg] = val trigger(:register_changed, reg , old , val) end def tick return unless @instruction @clock += 1 name = @instruction.class.name.split("::").last fetch = send "execute_#{name}" return unless fetch fetch_next_intruction end def fetch_next_intruction set_instruction @instruction.next end def object_for reg id = get_register(reg) object = Register.machine.objects[id] object.nil? ? id : object end # Label is a noop. def execute_Label true end # Instruction interpretation starts here def execute_Branch label = @instruction.label set_instruction label false end def execute_IsZero @flags[:zero] ? execute_Branch : true end def execute_IsNotzero @flags[:zero] ? true : execute_Branch end def execute_IsPlus @flags[:plus] ? execute_Branch : true end def execute_IsMinus @flags[:minus] ? execute_Branch : true end def execute_LoadConstant to = @instruction.register value = @instruction.constant value = value.object_id unless value.is_a?(Fixnum) set_register( to , value ) true end def execute_GetSlot object = object_for( @instruction.array ) value = object.internal_object_get( @instruction.index ) value = value.object_id unless value.is_a? Fixnum set_register( @instruction.register , value ) true end def execute_SetSlot value = object_for( @instruction.register ) object = object_for( @instruction.array ) object.internal_object_set( @instruction.index , value ) trigger(:object_changed, @instruction.array , @instruction.index) true end def execute_RegisterTransfer value = get_register @instruction.from set_register @instruction.to , value true end def execute_FunctionCall @link = @instruction #puts "Call link #{@link}" set_instruction @instruction.method.source.instructions false end def execute_SaveReturn object = object_for @instruction.register raise "save return has nothing to save" unless @link #puts "Save Return link #{@link}" object.internal_object_set @instruction.index , @link trigger(:object_changed, @instruction.register , @instruction.index ) @link = nil true end def execute_FunctionReturn object = object_for( @instruction.register ) link = object.internal_object_get( @instruction.index ) #puts "FunctionReturn link #{@link}" @instruction = link # we jump back to the call instruction. so it is as if the call never happened and we continue true end def execute_Syscall name = @instruction.name ret_value = 0 case name when :putstring str = object_for( :r1 ) # should test length, ie r2 raise "NO string for putstring #{str.class}:#{str.object_id}" unless str.is_a? Symbol @stdout += str.to_s ret_value = str.to_s.length when :exit set_instruction(nil) return false else raise "un-implemented syscall #{name}" end set_register( :r0 , ret_value ) # syscalls return into r0 , usually some int true end def execute_OperatorInstruction left = get_register(@instruction.left) rr = @instruction.right right = get_register(rr) case @instruction.operator.to_s when "+" result = left + right when "-" result = left - right when "/" result = left / right when "*" #TODO set overflow, reduce result to int result = left * right when "==" result = (left == right) ? 1 : 0 else raise "unimplemented '#{@instruction.operator}' #{@instruction}" end #puts "#{@instruction} == #{result} (#{left}|#{right})" right = set_register(@instruction.left , result) true end end end