stash old vm

moving on to getting mom to work and can’t have both
interpreter and elf broke, about 100 tests  went
This commit is contained in:
Torsten Ruger
2018-03-11 17:02:42 +05:30
parent f7aac1d1a4
commit 5fe0ba06ab
56 changed files with 80 additions and 143 deletions

3
stash/test_vm/helper.rb Normal file
View File

@ -0,0 +1,3 @@
require_relative "../helper"
Risc.machine.boot unless Risc.machine.booted

View File

@ -0,0 +1,82 @@
require_relative '../helper'
module Risc
module SpaceHack
# test hack to in place change object type
def add_space_field(name,type)
class_type = Parfait.object_space.get_class_by_name(:Space).instance_type
class_type.send(:private_add_instance_variable, name , type)
end
end
module ExpressionHelper
include SpaceHack
def check
Risc.machine.boot unless Risc.machine.booted
compiler = Vm::MethodCompiler.new Parfait.object_space.get_main
code = Vm.ast_to_code @input
assert code.to_s , @input
produced = compiler.process( code )
assert @output , "No output given"
assert_equal produced.class , @output , "Wrong class"
produced
end
end
module Statements
include AST::Sexp
include CleanCompile
include SpaceHack
def setup
Risc.machine.boot # force boot to reset main
end
def preamble
[Label, SlotToReg , LoadConstant, RegToSlot, LoadConstant,RegToSlot, LoadConstant, SlotToReg, SlotToReg ]
end
def postamble
[ Label, FunctionReturn]
end
def check_nil
assert @expect , "No output given"
compiler = Vm::MethodCompiler.new(:main)
code = Vm.ast_to_code( @input )
assert code.to_s , @input
produced = compiler.process( code )
produced = Parfait.object_space.get_main.instructions
compare_instructions produced , @expect
end
def check_return
was = check_nil
raise was if was
Parfait.object_space.get_main.instructions
end
def compare_instructions( instruction , expect )
index = 0
all = instruction.to_arr
full_expect = preamble + expect + postamble
full_expect = expect
begin
should = full_expect[index]
return "No instruction at #{index}" unless should
return "Expected at #{index+1}\n#{should(all)}" unless instruction.class == should
index += 1
instruction = instruction.next
end while( instruction )
nil
end
def should( all )
#preamble.each {all.shift}
#postamble.each {all.pop}
str = all.to_s.gsub("Risc::","")
ret = ""
str.split(",").each_slice(6).each do |line|
ret += " " + line.join(",") + " ,\n"
end
ret
end
end
end

View File

@ -0,0 +1,112 @@
require_relative 'helper'
module Risc
class TestAssignStatement < MiniTest::Test
include Statements
def test_assign_op
Parfait.object_space.get_main.add_local(:r , :Integer)
@input = s(:statements, s(:l_assignment, s(:local, :r), s(:operator_value, :+, s(:int, 10), s(:int, 1))))
@expect = [Label, LoadConstant, LoadConstant, OperatorInstruction, SlotToReg, RegToSlot ,
LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_assign_ivar_notpresent
@input =s(:statements, s(:i_assignment, s(:ivar, :r), s(:int, 5)))
@expect = []
assert_raises{ check_nil }
end
def test_assign_ivar
add_space_field(:r , :Integer)
@input =s(:statements, s(:i_assignment, s(:ivar, :r), s(:int, 5)))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_assign_local_assign
Parfait.object_space.get_main.add_local(:r , :Integer)
@input = s(:statements, s(:l_assignment, s(:local, :r), s(:int, 5)))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_assign_call
Parfait.object_space.get_main.add_local(:r , :Object)
@input = s(:statements, s(:l_assignment, s(:local, :r), s(:call, :main, s(:arguments))))
@expect = [Label, SlotToReg, SlotToReg, RegToSlot, LoadConstant, RegToSlot ,
LoadConstant, SlotToReg, RegToSlot, LoadConstant, RegToSlot, RiscTransfer ,
FunctionCall, Label, RiscTransfer, SlotToReg, SlotToReg, SlotToReg ,
RegToSlot, LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_named_list_get
Parfait.object_space.get_main.add_local(:r , :Integer)
@input = s(:statements, s(:l_assignment, s(:local, :r), s(:int, 5)), s(:return, s(:local, :r)))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, SlotToReg, SlotToReg ,
RegToSlot, LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
was = check_return
get = was.next(5)
assert_equal SlotToReg , get.class
assert_equal 1 + 1, get.index , "Get to named_list index must be offset, not #{get.index}"
end
def test_assign_local_int
Parfait.object_space.get_main.add_local(:r , :Integer)
@input = s(:statements, s(:l_assignment, s(:local, :r), s(:int, 5)) )
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
was = check_return
set = was.next(3)
assert_equal RegToSlot , set.class
assert_equal 1 + 1, set.index , "Set to named_list index must be offset, not #{set.index}"
end
def test_misassign_local
Parfait.object_space.get_main.add_local(:r , :Integer)
@input = s(:statements, s(:l_assignment, s(:local, :r), s(:string, "5")) )
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_raises {check }
end
def test_assign_arg
Parfait.object_space.get_main.add_argument(:blar , :Integer)
@input = s(:statements, s(:a_assignment, s(:arg, :blar), s(:int, 5)))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
was = check_return
set = was.next(3)
assert_equal RegToSlot , set.class
assert_equal 1 + 1, set.index , "Set to args index must be offset, not #{set.index}"
end
def test_misassign_arg
Parfait.object_space.get_main.add_argument(:blar , :Integer)
@input = s(:statements, s(:a_assignment, s(:arg, :blar), s(:string, "5")))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_raises {check }
end
def test_arg_get
# have to define bar externally, just because redefining main. Otherwise that would be automatic
Parfait.object_space.get_main.add_argument(:balr , :Integer)
@input = s(:statements, s(:return, s(:arg, :balr)))
@expect = [Label, SlotToReg, SlotToReg, RegToSlot, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
was = check_return
get = was.next(2)
assert_equal SlotToReg , get.class
assert_equal 1 + 1, get.index , "Get to args index must be offset, not #{get.index}"
end
end
end

View File

@ -0,0 +1,41 @@
require_relative "helper"
module Risc
class TestBasic < MiniTest::Test
include ExpressionHelper
include AST::Sexp
def setup
Risc.machine.boot
@output = Risc::RiscValue
end
def test_number
@input = s(:int , 42)
assert_equal 42 , check.value
end
def test_true
@input = s(:true)
check
end
def test_false
@input = s(:false)
check
end
def test_nil
@input = s(:nil)
check
end
def test_self
@input = s(:known, :self)
check
end
def test_string
@input = s(:string , "hello")
check
end
end
end

View File

@ -0,0 +1,43 @@
require_relative "helper"
module Risc
class TestCall < MiniTest::Test
include ExpressionHelper
include AST::Sexp
def setup
Risc.machine.boot
@output = Risc::RiscValue
end
def test_call_main_plain
@input = s(:call , :main ,s(:arguments))
check
end
def test_call_main_int
Parfait.object_space.get_main.add_argument(:blar , :Integer)
@input =s(:call, :main ,s(:arguments , s(:int, 1)))
check
end
def test_call_main_string
Parfait.object_space.get_main.add_argument(:blar , :Word)
@input =s(:call, :main ,s(:arguments , s(:string, "1") ))
check
end
def test_call_main_op
Parfait.object_space.get_main.add_local(:bar , :Integer)
Parfait.object_space.get_main.add_argument(:blar , :Integer)
@input =s(:call, :main ,s(:arguments , s(:local, :bar) ))
check
end
def test_call_string_put
@input = s(:call, :putstring,s(:arguments),s(:receiver,s(:string, "Hello Raisa, I am rubyx")))
check
end
end
end

View File

@ -0,0 +1,68 @@
require_relative 'helper'
require_relative "test_call_expression"
module Risc
class TestCallStatement < MiniTest::Test
include Statements
def test_call_constant_int
clean_compile :Integer, :puti, {}, s(:statements, s(:return, s(:int, 1)))
@input = s(:call, :puti , s(:arguments), s(:receiver, s(:int, 42)))
@expect = [Label, SlotToReg, LoadConstant, RegToSlot, LoadConstant, RegToSlot, LoadConstant ,
SlotToReg, RegToSlot, LoadConstant, RegToSlot, RiscTransfer, FunctionCall, Label ,
RiscTransfer, SlotToReg, SlotToReg, LoadConstant, SlotToReg, RegToSlot, Label ,
FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_call_constant_string
clean_compile :Word, :putstr,{}, s(:statements, s(:return, s(:int, 1)))
@input =s(:call, :putstr, s(:arguments), s(:receiver, s(:string, "Hello")))
@expect = [Label, SlotToReg, LoadConstant, RegToSlot, LoadConstant, RegToSlot, LoadConstant ,
SlotToReg, RegToSlot, LoadConstant, RegToSlot, RiscTransfer, FunctionCall, Label ,
RiscTransfer, SlotToReg, SlotToReg, LoadConstant, SlotToReg, RegToSlot, Label ,
FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_call_local_int
Parfait.object_space.get_main.add_local(:testi , :Integer)
clean_compile :Integer, :putint, {}, s(:statements, s(:return, s(:int, 1)))
@input = s(:statements, s(:l_assignment, s(:local, :testi), s(:int, 20)), s(:call, :putint, s(:arguments), s(:receiver, s(:local, :testi))))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, SlotToReg, SlotToReg, SlotToReg ,
RegToSlot, LoadConstant, RegToSlot, LoadConstant, SlotToReg, RegToSlot, LoadConstant ,
RegToSlot, RiscTransfer, FunctionCall, Label, RiscTransfer, SlotToReg, SlotToReg ,
LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_call_local_class
Parfait.object_space.get_main.add_local(:test_l , :List)
clean_compile :List, :add, {}, s(:statements, s(:return, s(:int, 1)))
@input =s(:statements, s(:call, :add, s(:arguments), s(:receiver, s(:local, :test_l))))
@expect = [Label, SlotToReg, SlotToReg, SlotToReg, RegToSlot, LoadConstant, RegToSlot ,
LoadConstant, SlotToReg, RegToSlot, LoadConstant, RegToSlot, RiscTransfer, FunctionCall ,
Label, RiscTransfer, SlotToReg, SlotToReg, LoadConstant, SlotToReg, RegToSlot ,
Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_call_puts
clean_compile :Space, :putstr, {str: :Word}, s(:statements, s(:return, s(:arg, :str)))
@input =s(:call, :putstr , s(:arguments, s(:string, "Hello") ) )
@expect = [Label, SlotToReg, SlotToReg, RegToSlot, LoadConstant, RegToSlot ,
LoadConstant, SlotToReg, RegToSlot, LoadConstant, SlotToReg, RegToSlot ,
LoadConstant, RegToSlot, RiscTransfer, FunctionCall, Label, RiscTransfer ,
SlotToReg, SlotToReg, LoadConstant, SlotToReg, RegToSlot, Label ,
FunctionReturn]
was = check_return
set = was.next(8)
assert_equal RegToSlot , set.class
assert_equal 1, set.index , "Set to message must be offset, not #{set.index}"
end
end
end

View File

@ -0,0 +1,34 @@
require_relative "helper"
module Risc
class TestFields < MiniTest::Test
include ExpressionHelper
include AST::Sexp
def setup
Risc.machine.boot
end
def test_field_not_defined
@root = :field_access
@input = s(:field_access, s(:receiver, s(:known, :self)), s(:field, s(:ivar, :a)))
assert_raises(RuntimeError) { check }
end
def test_field_not_space
@root = :field_access
@input = s(:field_access, s(:receiver, s(:known, :self)), s(:field, s(:ivar, :space)))
assert_raises(RuntimeError) { check }
end
def test_field
add_space_field(:bro,:Object)
@root = :field_access
@input = s(:field_access,s(:receiver, s(:known, :self)),s(:field,s(:ivar, :bro)))
@output = Risc::RiscValue
check
end
end
end

View File

@ -0,0 +1,42 @@
require_relative 'helper'
module Risc
class TestFieldStatement < MiniTest::Test
include Statements
def test_field_named_list
Parfait.object_space.get_main.add_local( :m , :Message)
@input = s(:statements, s(:return, s(:field_access,
s(:receiver, s(:local, :m)), s(:field, s(:ivar, :name)))))
@expect = [Label, SlotToReg, SlotToReg, SlotToReg, RegToSlot, LoadConstant ,
SlotToReg, RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_field_arg
Parfait.object_space.get_main.add_local( :m , :Message)
clean_compile :Space, :get_name, { :main => :Message},
s(:statements, s(:return, s(:field_access,
s(:receiver, s(:arg, :main)), s(:field, s(:ivar, :name)))))
@input =s(:statements, s(:return, s(:call, :get_name, s(:arguments, s(:local, :m)))))
@expect = [Label, SlotToReg, SlotToReg, RegToSlot, LoadConstant, RegToSlot ,
LoadConstant, SlotToReg, RegToSlot, SlotToReg, SlotToReg, SlotToReg ,
RegToSlot, LoadConstant, RegToSlot, RiscTransfer, FunctionCall, Label ,
RiscTransfer, SlotToReg, SlotToReg, RegToSlot, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_message_field
Parfait.object_space.get_main.add_local(:name , :Word)
@input = s(:statements, s(:l_assignment, s(:local, :name), s(:field_access, s(:receiver, s(:known, :message)), s(:field, s(:ivar, :name)))), s(:return, s(:local, :name)))
@expect = [Label, RiscTransfer, SlotToReg, SlotToReg, RegToSlot, SlotToReg ,
SlotToReg, RegToSlot, LoadConstant, SlotToReg, RegToSlot, Label ,
FunctionReturn]
assert_nil msg = check_nil , msg
end
end
end

View File

@ -0,0 +1,36 @@
require_relative 'helper'
module Risc
class TestIfStatement < MiniTest::Test
include Statements
def test_if_basicr
@input = s(:statements, s(:if_statement, :plus, s(:condition, s(:operator_value, :-, s(:int, 10), s(:int, 12))), s(:true_statements, s(:return, s(:int, 3))), s(:false_statements, s(:return, s(:int, 4)))))
@expect = [Label, LoadConstant, LoadConstant, OperatorInstruction, IsPlus, LoadConstant ,
RegToSlot, Branch, Label, LoadConstant, RegToSlot, Label ,
LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_if_small_minus
@input = s(:statements, s(:if_statement, :minus, s(:condition, s(:operator_value, :-, s(:int, 10), s(:int, 12))), s(:true_statements, s(:return, s(:int, 3))), s(:false_statements, nil)))
@expect = [Label, LoadConstant, LoadConstant, OperatorInstruction, IsMinus, Branch ,
Label, LoadConstant, RegToSlot, Label, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_if_small_zero
@input = s(:statements, s(:if_statement, :zero, s(:condition, s(:operator_value, :-, s(:int, 10), s(:int, 12))), s(:true_statements, s(:return, s(:int, 3))), s(:false_statements, nil)))
@expect = [Label, LoadConstant, LoadConstant, OperatorInstruction, IsZero, Branch ,
Label, LoadConstant, RegToSlot, Label, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
end
end

View File

@ -0,0 +1,34 @@
require_relative "helper"
module Risc
class TestFields < MiniTest::Test
include ExpressionHelper
include AST::Sexp
def setup
Risc.machine.boot
end
def test_local
Parfait.object_space.get_main.add_local(:bar , :Integer)
@input = s(:local, :bar)
@output = Risc::RiscValue
check
end
def test_space
@root = :name
@input = s(:known, :space)
@output = Risc::RiscValue
check
end
def test_args
Parfait.object_space.get_main.add_argument(:bar , :Integer)
@input = s(:arg, :bar)
@output = Risc::RiscValue
check
end
end
end

View File

@ -0,0 +1,46 @@
require_relative "helper"
module Risc
class TestOps < MiniTest::Test
include ExpressionHelper
include AST::Sexp
def setup
Risc.machine.boot
@root = :operator_value
@output = Risc::RiscValue
end
def operators
[:+ , :- , :* , :/ , :== ]
end
def test_ints
operators.each do |op|
@input = s(:operator_value, op , s(:int, 2), s(:int, 3))
check
end
end
def test_local_int
Parfait.object_space.get_main.add_local(:bar , :Integer)
@input = s(:operator_value, :+, s(:local, :bar), s(:int, 3))
check
end
def test_int_local
Parfait.object_space.get_main.add_local(:bar , :Integer)
@input = s(:operator_value, :+, s(:int, 3), s(:local, :bar))
check
end
def test_field_int
add_space_field(:bro,:Integer)
@input = s(:operator_value, :+, s(:field_access,s(:receiver, s(:known, :self)), s(:field, s(:ivar, :bro))), s(:int, 3))
check
end
def test_int_field
add_space_field(:bro,:Integer)
@input = s(:operator_value, :+, s(:int, 3), s(:field_access, s(:receiver, s(:known, :self)), s(:field,s(:ivar, :bro))))
check
end
end
end

View File

@ -0,0 +1,47 @@
require_relative 'helper'
module Risc
class TestReturnStatement < MiniTest::Test
include Statements
def test_return_int
@input = s(:statements, s(:return, s(:int, 5)))
@expect = [Label, LoadConstant, RegToSlot, LoadConstant, SlotToReg, RegToSlot ,
Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_return_local
Parfait.object_space.get_main.add_local(:runner , :Integer)
@input = s(:statements, s(:return, s(:local , :runner)))
@expect = [Label, SlotToReg, SlotToReg, RegToSlot, LoadConstant, SlotToReg ,
RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_return_local_assign
Parfait.object_space.get_main.add_local(:runner , :Integer)
@input = s(:statements, s(:l_assignment, s(:local, :runner), s(:int, 5)), s(:return, s(:local, :runner)))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, SlotToReg, SlotToReg ,
RegToSlot, LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_return_call
@input =s(:statements, s(:return, s(:call, :main, s(:arguments))))
@expect = [Label, SlotToReg, SlotToReg, RegToSlot, LoadConstant, RegToSlot ,
LoadConstant, SlotToReg, RegToSlot, LoadConstant, RegToSlot, RiscTransfer ,
FunctionCall, Label, RiscTransfer, SlotToReg, SlotToReg, RegToSlot ,
LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def pest_return_space_length # need to add runtime first
Parfait.object_space.get_main.add_local(:l , :Type)
@input = s(:statements, s(:l_assignment, s(:local, :l), s(:call, :get_type, s(:arguments), s(:receiver, s(:known, :space)))), s(:return, s(:field_access, s(:receiver, s(:known, :self)), s(:field, s(:ivar, :runner)))))
@expect = [Label, SlotToReg,SlotToReg ,RegToSlot,Label,FunctionReturn]
assert_nil msg = check_nil , msg
end
end
end

View File

@ -0,0 +1,49 @@
require_relative 'helper'
module Risc
class TestWhile < MiniTest::Test
include Statements
def test_while_mini
@input = s(:statements, s(:while_statement, :plus, s(:conditional, s(:int, 1)), s(:statements, s(:return, s(:int, 3)))))
@expect = [Label, Branch, Label, LoadConstant, RegToSlot, Label ,
LoadConstant, IsPlus, LoadConstant, SlotToReg, RegToSlot, Label ,
FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_while_assign
Parfait.object_space.get_main.add_local(:n , :Integer)
@input = s(:statements, s(:l_assignment, s(:local, :n), s(:int, 5)),
s(:while_statement, :plus, s(:conditional, s(:local, :n)),
s(:statements, s(:l_assignment, s(:local, :n),
s(:operator_value, :-, s(:local, :n), s(:int, 1))))),
s(:return, s(:local, :n)))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, Branch, Label ,
SlotToReg, SlotToReg, LoadConstant, OperatorInstruction, SlotToReg, RegToSlot ,
Label, SlotToReg, SlotToReg, IsPlus, SlotToReg, SlotToReg ,
RegToSlot, LoadConstant, SlotToReg, RegToSlot, Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
def test_while_return
Parfait.object_space.get_main.add_local(:n , :Integer)
@input = s(:statements, s(:l_assignment, s(:local, :n), s(:int, 10)), s(:while_statement, :plus, s(:conditional, s(:operator_value, :-, s(:local, :n), s(:int, 5))),
s(:statements, s(:l_assignment, s(:local, :n), s(:operator_value, :+, s(:local, :n), s(:int, 1))),
s(:return, s(:local, :n)))))
@expect = [Label, LoadConstant, SlotToReg, RegToSlot, Branch, Label ,
SlotToReg, SlotToReg, LoadConstant, OperatorInstruction, SlotToReg, RegToSlot ,
SlotToReg, SlotToReg, RegToSlot, Label, SlotToReg, SlotToReg ,
LoadConstant, OperatorInstruction, IsPlus, LoadConstant, SlotToReg, RegToSlot ,
Label, FunctionReturn]
assert_nil msg = check_nil , msg
end
end
end

View File

@ -0,0 +1,55 @@
require_relative "helper"
class ToCodeTest < MiniTest::Test
include AST::Sexp
def check clazz
tree = Vm.ast_to_code @statement
assert_equal tree.class , Vm::Tree.const_get( clazz )
end
def test_field_access
@statement = s(:field_access, s(:receiver, s(:ivar, :m)), s(:field, s(:ivar, :index)))
check "FieldAccess"
end
def test_simple_while
@statement = s(:while_statement, :false, s(:conditional,s(:int, 1)), s(:statements))
check "WhileStatement"
end
def test_l_assignment
@statement = s(:l_assignment, s(:local, :i), s(:int, 0))
check "LocalAssignment"
end
def test_a_assignment
@statement = s(:a_assignment, s(:arg, :i), s(:int, 0))
check "ArgAssignment"
end
def test_i_assignment
@statement = s(:i_assignment, s(:ivar, :i), s(:int, 0))
check "IvarAssignment"
end
def test_nil
@statement = s(:nil)
check "NilExpression"
end
def test_true
@statement = s(:true)
check "TrueExpression"
end
def test_false
@statement = s(:false)
check "FalseExpression"
end
def test_known
@statement = s(:known, :self)
check "KnownName"
end
def test_ivar
@statement = s(:ivar, :you)
check "InstanceName"
end
def test_class_name
@statement =s(:class_name, :FooBar)
check "ClassExpression"
end
end

View File

@ -0,0 +1,57 @@
### Compiling
The typed syntax tree is created by the ruby compiler.
The code in this directory compiles the typed tree to the register machine code, and
Parfait object structure.
If this were an interpreter, we would just walk the tree and do what it says.
Since it's not things are a little more difficult, especially in time.
When compiling we deal with two times, compile-time and run-time.
All the headache comes from mixing those two up.*
Similarly, the result of compiling is two-fold: a static and a dynamic part.
- the static part are objects like the constants, but also defined classes and their methods
- the dynamic part is the code, which is stored as streams of instructions in the MethodSource
Too make things a little simpler, we create a very high level instruction stream at first and then
run transformation and optimization passes on the stream to improve it.
The compiler has a method for each class of typed tree, named along on_xxx with xxx as the type
#### Compiler holds scope
The Compiler instance can hold arbitrary scope needed during the compilation.
A class statement sets the current @type scope , a method definition the @method.
If either are not set when needed compile errors will follow. So easy, so nice.
All code is encoded as a stream of Instructions in the MethodSource.
Instructions are stored as a list of Blocks, and Blocks are the smallest unit of code,
which is always linear.
Code is added to the method (using add_code), rather than working with the actual instructions.
This is so each compiling method can just do it's bit and be unaware of the larger structure
that is being created.
The general structure of the instructions is a graph
(with if's and whiles and breaks and what), but we build it to have one start and *one* end (return).
#### Messages and frames
Since the machine is oo we define it in objects.
Also it is important to define how instructions operate, which is is in a physical machine would
be by changing the contents of registers or some stack.
Our machine is not a register machine, but an object machine: it operates directly on objects and
also has no separate stack, only objects. There is only one object which is accessible,
basically meaning pinned to a register, the Message.
One can think of the Message as an oo replacement of the stack.
When a TypedMethod needs to make a call, it creates a NewMessage object.
Messages contain return addresses (yes, plural) and arguments.
The important thing here is that Messages and Frames are normal objects.

View File

@ -0,0 +1,56 @@
module Vm
module Assignment
def on_IvarAssignment( statement )
value = assignment_value(statement)
name = check_name(statement.name.name)
index = @method.for_type.variable_index( name)
raise "No such ivar #{name} #{@method.for_type}" unless index
value_type = @method.for_type.type_at( index )
raise "Argument Type mismatch #{value.type}!=#{value_type}" unless value.type == value_type
value_reg = use_reg(:value_type)
add_slot_to_reg(statement , :message , :receiver , value_reg )
add_reg_to_slot(statement , value , value_reg , index + 1 ) # one for type
end
def on_LocalAssignment( statement )
do_assignment_for( statement , :local )
end
def on_ArgAssignment( statement )
do_assignment_for( statement , :argument )
end
private
def do_assignment_for( statement , type )
value = assignment_value(statement)
name = check_name(statement.name.name)
index = @method.send( "has_#{type}" , name)
raise "No such #{type} #{name} #{@method.inspect}" unless index
value_type = @method.send("#{type}s_type" , index )
raise "Argument Type mismatch #{value.type}!=#{value_type}" unless value.type == value_type
move_reg(statement , "#{type}s".to_sym , value , index)
end
def move_reg(statement , type , value , index)
named_list = use_reg(:NamedList)
add_slot_to_reg(statement , :message , type , named_list )
add_reg_to_slot(statement , value , named_list , index + 1 ) # one for type
end
def assignment_value(statement)
reset_regs # statements reset registers, ie have all at their disposal
value = process(statement.value)
raise "Not register #{v}" unless value.is_a?(Risc::RiscValue)
value
end
# ensure the name given is not space and raise exception otherwise
# return the name
def check_name( name )
raise "space is a reserved name" if name == :space
name
end
end
end

View File

@ -0,0 +1,52 @@
module Vm
# collection of the simple ones, int and strings and such
module BasicValues
# Constant expressions can by definition be evaluated at compile time.
# But that does not solve their storage, ie they need to be accessible at runtime from _somewhere_
# So expressions move the data into a Risc.
# All expressions return registers
# But in the future (in the one that holds great things) we optimize those unneccesay moves away
def on_IntegerExpression expression
int = expression.value
reg = use_reg :Integer , int
add_load_constant( expression, int , reg )
return reg
end
def on_TrueExpression expression
reg = use_reg :Boolean
add_load_constant( expression, true , reg )
return reg
end
def on_FalseExpression expression
reg = use_reg :Boolean
add_load_constant( expression, false , reg )
return reg
end
def on_NilExpression expression
reg = use_reg :NilClass
add_load_constant( expression, nil , reg )
return reg
end
def on_StringExpression expression
value = Parfait.new_word expression.value.to_sym
reg = use_reg :Word
Risc.machine.constants << value
add_load_constant( expression, value , reg )
return reg
end
def on_ClassExpression expression
name = expression.value
raise "No meta class #{name}"
end
end
end

View File

@ -0,0 +1,99 @@
module Vm
module CallSite
def on_CallSite( statement )
raise "not inside method " unless @method
reset_regs
#move the new message (that we need to populate to make a call) to std register
load_new_message(statement)
me = get_me( statement )
type = get_my_type(me)
method = type.get_method(statement.name)
raise "Method not implemented for me:#{me} #{type.inspect}.#{statement.name}" unless method
# move our receiver there
add_reg_to_slot( statement , me , :new_message , :receiver)
set_message_details(method , statement , statement.arguments)
set_arguments(method , statement.arguments)
ret = use_reg( :Object ) #FIXME real return type
Risc.issue_call( self , method )
# the effect of the method is that the NewMessage Return slot will be filled, return it
# but move it into a register too
add_slot_to_reg(statement, :new_message , :return_value , ret )
ret
end
private
def load_new_message(statement)
new_message = Risc.resolve_to_register(:new_message)
add_slot_to_reg(statement, :message , :next_message , new_message )
new_message
end
def get_me( statement )
if statement.receiver
me = process( statement.receiver )
else
me = use_reg @method.for_type
add_slot_to_reg(statement, :message , :receiver , me )
end
me
end
def get_my_type( me )
# now we have to resolve the method name (+ receiver) into a callable method
case me.type
when Parfait::Type
type = me.type
when Symbol
type = Parfait.object_space.get_class_by_name(me.type).instance_type
else
raise me.inspect
end
raise "Not type #{type}" unless type.is_a? Parfait::Type
type
end
# load method name and set to new message (for exceptions/debug)
def set_message_details( method , name_s , arguments )
name = name_s.name
name_tmp = use_reg(:Word)
add_load_constant("#{name} load method name", name , name_tmp)
add_reg_to_slot( "#{name} store method name" , name_tmp , :new_message , :name)
# next arg type
args_reg = use_reg(:Type , method.arguments )
list_reg = use_reg(:NamedList , arguments )
add_load_constant("#{name} load arguments type", method.arguments , args_reg)
add_slot_to_reg( "#{name} get args from method" , :new_message , :arguments , list_reg )
add_reg_to_slot( "#{name} store args type in args" , args_reg , list_reg , 1 )
end
def set_arguments( method , arguments )
# reset tmp regs for each and load result into new_message
arg_type = method.arguments
message = "Arg number mismatch, method=#{arg_type.instance_length - 1} , call=#{arguments.length}"
raise message if (arg_type.instance_length - 1 ) != arguments.length
arguments.each_with_index do |arg , i |
store_arg_no(arguments , arg_type , arg , i + 1) #+1 for ruby(0 based)
end
end
def store_arg_no(arguments , arg_type , arg , i )
reset_regs
i = i + 1 # disregarding type field
val = process( arg) # processing should return the register with the value
raise "Not register #{val}" unless val.is_a?(Risc::RiscValue)
#FIXME definately needs some tests
raise "TypeMismatch calling with #{val.type} , instead of #{arg_type.type_at(i)}" if val.type != arg_type.type_at(i)
list_reg = use_reg(:NamedList , arguments )
add_slot_to_reg( "Set arg #{i}:#{arg}" , :new_message , :arguments , list_reg )
# which we load int the new_message at the argument's index
add_reg_to_slot( arg , val , list_reg , i ) #one for type and one for ruby
end
end
end

View File

@ -0,0 +1,14 @@
module Vm
module Collections
# attr_reader :values
def on_array statement, context
end
# attr_reader :key , :value
def on_association context
end
def on_hash context
end
end
end

View File

@ -0,0 +1,27 @@
module Vm
module FieldAccess
def on_FieldAccess statement
# receiver_ast , field_ast = *statement
receiver = process(statement.receiver)
type = receiver.type
if(type.is_a?(Symbol))
type = Parfait.object_space.get_class_by_name(type).instance_type
end
field_name = statement.field.name
index = type.variable_index(field_name)
raise "no such field:#{field_name} for class #{type.inspect}" unless index
value = use_reg(type.type_at(index))
add_slot_to_reg(statement , receiver , index, value)
value
end
def on_receiver expression
process expression.first
end
end
end

View File

@ -0,0 +1,41 @@
module Vm
module IfStatement
# an if evaluates the condition and jumps to the true block if true
# so the else block is automatically after that.
# But then the else needs to jump over the true block unconditionally.
def on_IfStatement( statement )
# branch_type , condition , if_true , if_false = *statement
true_block = compile_if_condition( statement )
merge = compile_if_false( statement )
add_code true_block
compile_if_true(statement)
add_code merge
nil # statements don't return anything
end
private
def compile_if_condition( statement )
reset_regs
process(statement.condition)
branch_class = Object.const_get "Risc::Is#{statement.branch_type.capitalize}"
true_block = Risc.label(statement, "if_true")
add_code branch_class.new( statement.condition , true_block )
return true_block
end
def compile_if_true( statement )
reset_regs
process(statement.if_true)
end
def compile_if_false( statement )
reset_regs
process(statement.if_false) if statement.if_false.statements
merge = Risc.label(statement , "if_merge")
add_code Risc::Branch.new(statement.if_false, merge )
merge
end
end
end

View File

@ -0,0 +1,58 @@
module Vm
module NameExpression
# attr_reader :name
# compiling name needs to check if it's a local variable
# or an argument
# whichever way this goes the result is stored in the return slot (as all compiles)
def on_KnownName statement
name = statement.name
[:self , :space , :message].each do |special|
return send(:"load_special_#{special}" , statement ) if name == special
end
raise "Unknow 'known' expression #{name}"
end
def on_ArgumentName(statement)
name = statement.name
index = @method.has_argument(name)
raise "no arg #{name}" unless index
named_list = use_reg :NamedList
ret = use_reg @method.arguments_type(index)
#puts "For #{name} at #{index} got #{@method.arguments.inspect}"
add_slot_to_reg("#{statement} load args" , :message , :arguments, named_list )
add_slot_to_reg("#{statement} load #{name}" , named_list , index + 1, ret )
return ret
end
def on_LocalName( statement )
name = statement.name
index = @method.has_local( name )
raise "must define local '#{name}' before using it" unless index
named_list = use_reg :NamedList
add_slot_to_reg("#{name} load locals" , :message , :locals , named_list )
ret = use_reg @method.locals_type( index )
add_slot_to_reg("#{name} load from locals" , named_list , index + 1, ret )
return ret
end
def load_special_self(statement)
ret = use_reg @type
add_slot_to_reg("#{statement} load self" , :message , :receiver , ret )
return ret
end
def load_special_space(statement)
space = Parfait.object_space
reg = use_reg :Space , space
add_load_constant( "#{statement} load space", space , reg )
return reg
end
def load_special_message(statement)
reg = use_reg :Message
add_transfer( "#{statement} load message", Risc.message_reg , reg )
return reg
end
end #module
end

View File

@ -0,0 +1,15 @@
module Vm
module OperatorExpression
def on_OperatorExpression statement
# operator , left_e , right_e = *statement
# left and right must be expressions. Expressions return a register when compiled
left_reg = process(statement.left_expression)
right_reg = process(statement.right_expression)
raise "Not register #{left_reg}" unless left_reg.is_a?(Risc::RiscValue)
raise "Not register #{right_reg}" unless right_reg.is_a?(Risc::RiscValue)
add_code Risc::OperatorInstruction.new(statement,statement.operator,left_reg,right_reg)
return left_reg # though this has wrong value attached
end
end
end

View File

@ -0,0 +1,10 @@
module Vm
module ReturnStatement
def on_ReturnStatement statement
reg = process(statement.return_value)
add_reg_to_slot( statement, reg , :message , :return_value)
nil # statements don't return
end
end
end

View File

@ -0,0 +1,7 @@
module Vm
module StatementList
def on_Statements statement
process_all( statement.statements )
end
end
end

View File

@ -0,0 +1,42 @@
module Vm
module WhileStatement
def on_WhileStatement statement
#branch_type , condition , statements = *statement
condition_label = compile_while_preamble( statement ) #jump there
start = compile_while_body( statement )
# This is where the loop starts, though in subsequent iterations it's in the middle
add_code condition_label
compile_while_condition( statement )
branch_class = Object.const_get "Risc::Is#{statement.branch_type.capitalize}"
# this is where the while ends and both branches meet
add_code branch_class.new( statement.condition , start )
nil # statements don't return anything
end
private
def compile_while_preamble( statement )
condition_label = Risc.label(statement.condition , "condition_label")
# unconditionally branch to the condition upon entering the loop
add_code Risc::Branch.new(statement.condition , condition_label)
condition_label
end
def compile_while_body( statement )
start = Risc.label(statement , "while_start" )
add_code start
reset_regs
process(statement.statements)
start
end
def compile_while_condition( statement )
reset_regs
process(statement.condition)
end
end
end

48
stash/vm/tree.rb Normal file
View File

@ -0,0 +1,48 @@
# Base class for Expresssion and Statement
module Vm
class Code ; end
class Statement < Code ; end
class Expression < Code ; end
module ValuePrinter
def to_s
@value.to_s
end
end
end
require_relative "tree/while_statement"
require_relative "tree/if_statement"
require_relative "tree/return_statement"
require_relative "tree/statements"
require_relative "tree/operator_expression"
require_relative "tree/field_access"
require_relative "tree/call_site"
require_relative "tree/basic_values"
require_relative "tree/assignment"
require_relative "tree/to_code"
AST::Node.class_eval do
# def [](name)
# #puts self.inspect
# children.each do |child|
# if child.is_a?(AST::Node)
# #puts child.type
# if (child.type == name)
# return child.children
# end
# else
# #puts child.class
# end
# end
# nil
# end
#
# def first_from( node_name )
# from = self[node_name]
# return nil unless from
# from.first
# end
end

View File

@ -0,0 +1,30 @@
module Vm
module Tree
class Assignment < Statement
attr_accessor :name , :value
def initialize(name , value = nil )
@name , @value = name , value
end
def to_s
"#{name} = #{value}\n"
end
end
class IvarAssignment < Assignment
def to_s
"@#{name} = #{value}\n"
end
end
class ArgAssignment < Assignment
end
class LocalAssignment < Assignment
end
end
end

View File

@ -0,0 +1,66 @@
module Vm
module Tree
class IntegerExpression < Expression
include ValuePrinter
attr_accessor :value
def initialize(value)
@value = value
end
end
class FloatExpression < Expression
include ValuePrinter
attr_accessor :value
def initialize(value)
@value = value
end
end
class TrueExpression < Expression
def to_s
"true"
end
end
class FalseExpression < Expression
def to_s
"false"
end
end
class NilExpression < Expression
def to_s
"nil"
end
end
class StringExpression < Expression
include ValuePrinter
attr_accessor :value
def initialize(value)
@value = value
end
end
class NameExpression < Expression
include ValuePrinter
attr_accessor :value
alias :name :value
def initialize(value)
@value = value
end
end
class LocalName < NameExpression
end
class ArgumentName < NameExpression
end
class InstanceName < NameExpression
end
class KnownName < NameExpression
end
class ClassExpression < Expression
include ValuePrinter
attr_accessor :value
def initialize(value)
@value = value
end
end
end
end

View File

@ -0,0 +1,12 @@
module Vm
module Tree
class CallSite < Expression
attr_accessor :name , :receiver , :arguments
def to_s
str = receiver ? "#{receiver}.#{name}" : name.to_s
str + arguments.collect{|a| a.to_s }.join(",")
end
end
end
end

View File

@ -0,0 +1,10 @@
module Vm
module Tree
class FieldAccess < Expression
attr_accessor :receiver , :field
def to_s
"#{receiver}.#{field}"
end
end
end
end

View File

@ -0,0 +1,12 @@
module Vm
module Tree
class IfStatement < Statement
attr_accessor :branch_type , :condition , :if_true , :if_false
def to_s
str = "if_#{branch_type}(#{condition}) \n #{if_true}\n"
str += "else\n #{if_false}\n" if if_false
str + "end\n"
end
end
end
end

View File

@ -0,0 +1,10 @@
module Vm
module Tree
class OperatorExpression < Expression
attr_accessor :operator , :left_expression , :right_expression
def to_s
"#{left_expression} #{operator} #{right_expression}"
end
end
end
end

View File

@ -0,0 +1,11 @@
module Vm
module Tree
class ReturnStatement < Statement
attr_accessor :return_value
def to_s
"return #{return_value}"
end
end
end
end

View File

@ -0,0 +1,9 @@
module Vm
class Statements < Statement
attr_accessor :statements
def to_s
return "" unless statements
statements.collect() { |s| s.to_s }.join
end
end
end

162
stash/vm/tree/to_code.rb Normal file
View File

@ -0,0 +1,162 @@
module Vm
def self.ast_to_code statement
compiler = ToCode.new
compiler.process statement
end
# ToCode converts an ast (from the ast gem) into the vm code expressions
# Code is the base class of the tree that is transformed to and
# Expression and Statement the next two subclasses.
# While it is an ast, it is NOT a ruby parser generated ast. Instead the ast is generated
# with s-expressions (also from the ast gem), mostly in tests, but also a little in
# the generation of functions (Builtin)
#
class ToCode < AST::Processor
def handler_missing node
raise "No handler on_#{node.type}(node)"
end
def on_parameters statement
params = {}
statement.children.each do |param , type , name|
type , name = *param
params[name] = type
end
params
end
def on_while_statement statement
branch_type , condition , statements = *statement
whil = Tree::WhileStatement.new()
whil.branch_type = branch_type
whil.condition = process(condition)
whil.statements = process(statements)
whil
end
def on_if_statement statement
branch_type , condition , if_true , if_false = *statement
iff = Tree::IfStatement.new()
iff.branch_type = branch_type
iff.condition = process(condition)
iff.if_true = process(if_true)
iff.if_false = process(if_false)
iff
end
def process_first code
raise "Too many children #{code.inspect}" if code.children.length != 1
process code.children.first
end
alias :on_conditional :process_first
alias :on_condition :process_first
alias :on_field :process_first
def on_statements( statement )
list = Statements.new()
kids = statement.children
return list unless kids
return list unless kids.first
list.statements = process_all(kids)
list
end
alias :on_true_statements :on_statements
alias :on_false_statements :on_statements
def on_return statement
ret = Tree::ReturnStatement.new()
ret.return_value = process(statement.children.first)
ret
end
def on_operator_value statement
operator , left_e , right_e = *statement
op = Tree::OperatorExpression.new()
op.operator = operator
op.left_expression = process(left_e)
op.right_expression = process(right_e)
op
end
def on_field_access statement
receiver_ast , field_ast = *statement
field = Tree::FieldAccess.new()
field.receiver = process(receiver_ast)
field.field = process(field_ast)
field
end
def on_receiver expression
process expression.children.first
end
def on_call statement
name , arguments , receiver = *statement
call = Tree::CallSite.new()
call.name = name
call.arguments = process_all(arguments)
call.receiver = process(receiver)
call
end
def on_int expression
Tree::IntegerExpression.new(expression.children.first)
end
def on_true _expression
Tree::TrueExpression.new
end
def on_false _expression
Tree::FalseExpression.new
end
def on_nil _expression
Tree::NilExpression.new
end
def on_arg statement
Tree::ArgumentName.new(statement.children.first)
end
def on_local statement
Tree::LocalName.new(statement.children.first)
end
def on_ivar statement
Tree::InstanceName.new(statement.children.first)
end
def on_known statement
Tree::KnownName.new(statement.children.first)
end
def on_string expressions
Tree::StringExpression.new(expressions.children.first)
end
def on_class_name expression
Tree::ClassExpression.new(expression.children.first)
end
def on_i_assignment statement
assignment_for( statement, Vm::Tree::IvarAssignment)
end
def on_a_assignment statement
assignment_for( statement, Vm::Tree::ArgAssignment)
end
def on_l_assignment( statement )
assignment_for( statement, Vm::Tree::LocalAssignment)
end
def assignment_for( statement , clazz)
name , value = *statement
p_name = process name
p_value = process(value)
clazz.new(p_name , p_value)
end
end
end

View File

@ -0,0 +1,11 @@
module Vm
module Tree
class WhileStatement < Statement
attr_accessor :branch_type , :condition , :statements
def to_s
str = "while_#{branch_type}(#{condition}) do\n"
str + statements.to_s + "\nend\n"
end
end
end
end