From 7216300452de09c683673bad2e3d894bcebfbd2b Mon Sep 17 00:00:00 2001 From: Torsten Ruger Date: Thu, 30 Jul 2015 19:18:12 +0300 Subject: [PATCH] add interpreter here (moved from debugger) --- Gemfile.lock | 2 +- lib/interpreter/eventable.rb | 37 +++++++ lib/interpreter/interpreter.rb | 174 +++++++++++++++++++++++++++++++++ test/graph_helper.rb | 22 ----- test/interpreter/helper.rb | 2 + test/interpreter/test_all.rb | 1 + test/interpreter/test_puts.rb | 83 ++++++++++++++++ test/test_all.rb | 5 +- 8 files changed, 301 insertions(+), 25 deletions(-) create mode 100644 lib/interpreter/eventable.rb create mode 100644 lib/interpreter/interpreter.rb delete mode 100644 test/graph_helper.rb create mode 100644 test/interpreter/helper.rb create mode 100644 test/interpreter/test_all.rb create mode 100644 test/interpreter/test_puts.rb diff --git a/Gemfile.lock b/Gemfile.lock index affd8107..3fa90e00 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: git://github.com/salama/salama-arm.git - revision: 9a1c18dfe974909e02e33f7b086ac516e3384f4b + revision: 0bd5091e3f284ecf040e0086a41d2449cd5afb7a specs: salama-arm (0.0.1) diff --git a/lib/interpreter/eventable.rb b/lib/interpreter/eventable.rb new file mode 100644 index 00000000..a7274304 --- /dev/null +++ b/lib/interpreter/eventable.rb @@ -0,0 +1,37 @@ +# A simple event registering/triggering module to mix into classes. +# Events are stored in the `@events` ivar. +module Eventable + + # Register a handler for the given event name. + # The event name is the method name called on the handler object + # + # obj.on(:foo , some_object_that_implements foo( whateverargs) + # + # @param [String, Symbol] name event name + # @param [Object] object handling the event, ie implement the function name + # @return handler + def register_event(name, handler) + event_table[name] << handler + handler + end + + def unregister_event(name, handler) + event_table[name].delete handler + end + + def event_table + return @event_table if @event_table + @event_table = Hash.new { |hash, key| hash[key] = [] } + end + + # Trigger the given event name and passes all args to each handler + # for this event. + # + # obj.trigger(:foo) + # obj.trigger(:foo, 1, 2, 3) + # + # @param [String, Symbol] name event name to trigger + def trigger(name, *args) + event_table[name].each { |handler| handler.send( name.to_sym , *args) } + end +end diff --git a/lib/interpreter/interpreter.rb b/lib/interpreter/interpreter.rb new file mode 100644 index 00000000..62945599 --- /dev/null +++ b/lib/interpreter/interpreter.rb @@ -0,0 +1,174 @@ + +require_relative "eventable" + +module Interpreter + class Interpreter + # fire events for changed pc and register contents + include Eventable + + # current instruction or pc + attr_reader :instruction + + # current instruction or pc + attr_reader :clock + + # an (arm style) link register. store the return address to return to + attr_reader :link + + # current executing block. since this is not a hardware simulator this is luxury + attr_reader :block + + # the registers, 16 + attr_reader :registers + + # collect the output + attr_reader :stdout + + attr_reader :state + + def initialize + @state = "runnnig" + @stdout = "" + @registers = {} + @clock = 0 + (0...16).each do |r| + set_register "r#{r}".to_sym , "r#{r}:unknown" + end + end + + def start bl + set_block bl + end + + def set_block bl + return if @block == bl + raise "Error, nil block" unless bl + old = @block + @block = bl + trigger(:block_changed , old , bl) + set_instruction bl.codes.first + end + + def set_instruction i + @state = "exited" unless i + return if @instruction == i + old = @instruction + @instruction = i + trigger(:instruction_changed, old , i) + end + + def get_register( reg ) + reg = reg.symbol if reg.is_a? Register::RegisterReference + raise "Not a register #{reg}" unless Register::RegisterReference.look_like_reg(reg) + @registers[reg] + end + + def set_register reg , val + old = get_register( reg ) # also ensures format + return if old === val + reg = reg.symbol if reg.is_a? Register::RegisterReference + @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 + if(@instruction != @block.codes.last) + set_instruction @block.codes[ @block.codes.index(@instruction) + 1] + else + next_b = @block.method.source.blocks.index(@block) + 1 + set_block @block.method.source.blocks[next_b] + end + end + + def object_for reg + id = get_register(reg) + Virtual.machine.objects[id] + end + + # Instruction interpretation starts here + def execute_Branch + target = @instruction.block + set_block target + false + end + + def execute_LoadConstant + to = @instruction.register + value = @instruction.constant.object_id + 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? Integer + 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 ) + true + end + + def execute_RegisterTransfer + value = get_register @instruction.from + set_register @instruction.to , value + true + end + + def execute_FunctionCall + @link = [@block , @instruction] + next_block = @instruction.method.source.blocks.first + set_block next_block + false + end + + def execute_SaveReturn + object = object_for @instruction.register + raise "save return has nothing to save" unless @link + trigger(:object_changed, @instruction.register ) + object.internal_object_set @instruction.index , @link + @link = nil + true + end + + def execute_Syscall + name = @instruction.name + 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 + when :exit + set_instruction(nil) + return false + else + raise "un-implemented syscall #{name}" + end + true + end + + def execute_FunctionReturn + object = object_for( @instruction.register ) + #wouldn't need to assign to link, but makes testing easier + link = object.internal_object_get( @instruction.index ) + @block , @instruction = link + # we jump back to the call instruction. so it is as if the call never happened and we continue + true + end + end +end diff --git a/test/graph_helper.rb b/test/graph_helper.rb deleted file mode 100644 index ce98bbfc..00000000 --- a/test/graph_helper.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'gritty' - -def dump(obj, name) - g = digraph do - graph_attribs << 'bgcolor=black' - - node_attribs << 'color=white' - node_attribs << 'penwidth=2' - node_attribs << 'fontcolor=white' - node_attribs << 'labelloc=c' - node_attribs << 'fontname="Courier New"' - node_attribs << 'fontsize=36' - - edge_attribs << 'color=white' - edge_attribs << 'penwidth=2' - - builder = Gritty::NodeBuilder.new self - builder.build obj, 'root' - - save name, 'pdf' - end -end diff --git a/test/interpreter/helper.rb b/test/interpreter/helper.rb new file mode 100644 index 00000000..a6946d19 --- /dev/null +++ b/test/interpreter/helper.rb @@ -0,0 +1,2 @@ +require_relative "../helper" +require "interpreter/interpreter" diff --git a/test/interpreter/test_all.rb b/test/interpreter/test_all.rb new file mode 100644 index 00000000..1b24e62a --- /dev/null +++ b/test/interpreter/test_all.rb @@ -0,0 +1 @@ +require_relative "test_puts" diff --git a/test/interpreter/test_puts.rb b/test/interpreter/test_puts.rb new file mode 100644 index 00000000..72f96ef1 --- /dev/null +++ b/test/interpreter/test_puts.rb @@ -0,0 +1,83 @@ +require_relative "helper" + +class InterpreterTest < MiniTest::Test + + def setup + Virtual.machine.boot + code = Ast::ExpressionList.new( [Ast::CallSiteExpression.new(:putstring, [] ,Ast::StringExpression.new("Hello again"))]) + Virtual::Compiler.compile( code , Virtual.machine.space.get_main ) + Virtual.machine.run_before "Register::CallImplementation" + @interpreter = Interpreter::Interpreter.new + @interpreter.start Virtual.machine.init + end + + def ticks num + last = nil + num.times do + last = @interpreter.instruction + @interpreter.tick + end + return last + end + + def test_branch + was = @interpreter.block + assert_equal Register::Branch , ticks(1).class + assert was != @interpreter.block + end + def test_load + assert_equal Register::LoadConstant , ticks(2).class + assert_equal Parfait::Space , Virtual.machine.objects[ @interpreter.get_register(:r1)].class + assert_equal :r1, @interpreter.instruction.array.symbol + end + def test_get + assert_equal Register::GetSlot , ticks(3).class + assert @interpreter.get_register( :r3 ) + assert @interpreter.get_register( :r3 ).is_a? Integer + end + def test_transfer + transfer = ticks 5 + assert_equal Register::RegisterTransfer , transfer.class + assert_equal @interpreter.get_register(transfer.to) , @interpreter.get_register(transfer.from) + end + def test_call + assert_equal Register::FunctionCall , ticks(7).class + assert @interpreter.link + end + def test_save + done = ticks(8) + assert_equal Register::SaveReturn , done.class + assert @interpreter.get_register done.register.symbol + end + + def test_chain + ["Branch" , "LoadConstant" , "GetSlot" , "SetSlot" , "RegisterTransfer" , + "GetSlot" , "FunctionCall" , "SaveReturn" , "LoadConstant" , "SetSlot" , + "GetSlot" , "GetSlot" , "SetSlot" , "LoadConstant" , "SetSlot" , + "RegisterTransfer" , "GetSlot" , "FunctionCall" , "SaveReturn" , "RegisterTransfer" , + "Syscall" , "RegisterTransfer" , "RegisterTransfer" , "SetSlot" , "GetSlot" , + "GetSlot" , "RegisterTransfer" ,"GetSlot" , "GetSlot","GetSlot", + "FunctionReturn" , "RegisterTransfer" , "Syscall" , "NilClass"].each_with_index do |name , index| + got = ticks(1) + assert got.class.name.index(name) , "Wrong class for #{index+1}, expect #{name} , got #{got}" + end + end + + def test_putstring + done = ticks(21) + assert_equal Register::Syscall , done.class + assert_equal "Hello again" , @interpreter.stdout + end + + def test_return + done = ticks(31) + assert_equal Register::FunctionReturn , done.class + assert @interpreter.block.is_a?(Virtual::Block) + assert @interpreter.instruction.is_a?(Register::Instruction) , "not instruction #{@interpreter.instruction}" + end + + def test_exit + done = ticks(34) + assert_equal NilClass , done.class + end +end diff --git a/test/test_all.rb b/test/test_all.rb index 10d89efa..e6bc93fe 100644 --- a/test/test_all.rb +++ b/test/test_all.rb @@ -1,8 +1,9 @@ -# All working tests (ahm), still working on the others, so no use to be constantly reminded +require_relative "compiler/test_all" + require_relative "parfait/test_all" require_relative "fragments/test_all" require_relative "virtual/test_all" -require_relative "compiler/test_all" +require_relative "interpreter/test_all"