diff --git a/Guardfile b/Guardfile index 6529f42d..24f36e35 100644 --- a/Guardfile +++ b/Guardfile @@ -26,6 +26,9 @@ guard :minitest , all_on_start: false do # with Minitest::Unit # ruby compiler tests have a whole directory watch(%r{^lib/ruby/ruby_compiler.rb}) { Dir["test/ruby/test_*.rb"] } + # slot compiler tests have a whole directory + watch(%r{^lib/slot_machine/slot_compiler.rb}) { Dir["test/slot_machine/compiler/test_*.rb"] } + watch(%r{^lib/sol/statements/send_statement.rb}) { [ Dir["test/sol/send/test_*.rb"] ] } diff --git a/lib/slot_machine/macro_maker.rb b/lib/slot_machine/macro_maker.rb new file mode 100644 index 00000000..84988660 --- /dev/null +++ b/lib/slot_machine/macro_maker.rb @@ -0,0 +1,30 @@ +module SlotMachine + + class MacroMaker + # a list of instructions + + attr_reader :instructions + + # load slot code from a file, in a subdir code/ + filename + # use load_string to compile the content + def self.load_file(relative_name) + path = File.expand_path( "../code/#{relative_name}" , __FILE__) + load_string( File.read(path)) + end + + # compile the given SlotLanguage source + # the compiler returns an array of Makers which a new MacroMaker + # instance stores + # return the MacroMaker that represents the source + def self.load_string(source_code) + MacroMaker.new( SlotCompiler.compile(source_code) ) + end + + # must initialize with an array of Makers, which is stored + def initialize( instructions ) + @instructions = instructions + raise "undefined source #{instructions}" unless instructions.is_a?(Instruction) + end + + end +end diff --git a/lib/slot_machine/slot_compiler.rb b/lib/slot_machine/slot_compiler.rb new file mode 100644 index 00000000..5f5ed69a --- /dev/null +++ b/lib/slot_machine/slot_compiler.rb @@ -0,0 +1,134 @@ +require "parser/current" +require "ast" + +module SlotMachine + class SlotCompiler < AST::Processor + DEBUG = false + + # conditionals supported, currently only equal + def self.checks + [:==] + end + + def self.compile(input) + ast = Parser::CurrentRuby.parse( input ) + self.new.process(ast) + end + attr_reader :labels + def initialize + @labels = {} + end + + def not_implemented(node) + raise "Not implemented #{node.type}" + end + # default to error, so non implemented stuff shows early + def handler_missing(node) + not_implemented(node) + end + def process_all(exp) + arr = super(exp) + head = arr.shift + until( arr.empty?) + head << arr.shift + end + head + end + def on_send(statement) + kids = statement.children.dup + receiver = kids.shift + name = kids.shift + return label(name) if(name.to_s.end_with?("_label")) + return goto(name,kids) if(name == :goto) + return check(name,receiver, kids) if SlotCompiler.checks.include?(name) + return assign(receiver, name , kids) if(name.to_s.end_with?("=")) + puts "Send: #{statement} " if DEBUG + var = SlottedMessage.new( [name] ) + if(receiver) + puts "receiver at #{name} #{receiver}" if DEBUG + prev = process(receiver) + prev.set_next(var.slots) + prev + else + var + end + end + def on_lvar(lvar) + puts "lvar #{lvar}" if DEBUG + SlottedMessage.new( [lvar.children.first] ) + end + def on_lvasgn( expression) + puts "i/lvasgn #{expression}" if DEBUG + var = var_for(expression.children[0]) + value = process(expression.children[1]) + SlotLoad.new(expression.to_s,var , value) + end + alias :on_ivasgn :on_lvasgn + + def on_if(expression) + puts "if #{expression}" if DEBUG + condition = process(expression.children[0]) + jump = process(expression.children[1]) + condition.set_label( jump.label ) + condition + end + def on_begin(exp) + if( exp.children.length == 1) + process(exp.first) + else + process_all(exp) + end + end + def on_ivar(expression) + puts "ivar #{expression}" if DEBUG + var_for(expression.children.first) + end + + private + + def var_for( name ) + name = name.to_s + if(name[0] == "@") + var = name.to_s[1 .. -1].to_sym + SlottedMessage.new([:receiver , var]) + else + SlottedMessage.new([:receiver , name]) + end + end + def label(name) + raise "no label #{name}" unless(name.to_s.end_with?("_label")) + if @labels.has_key?(name) + return @labels[name] + else + @labels[name] = SlotMachine::Label.new(name.to_s , name) + end + end + def goto(name , args) + # error handling would not hurt + puts "goto #{name} , #{args}" if DEBUG + label = process(args.first) + Jump.new( label ) + end + def check(name , receiver , kids) + raise "Familiy too large #{kids}" if kids.length > 1 + puts "Kids " + kids.to_s if DEBUG + right = process(kids.first) + case name + when :== + return SameCheck.new(process(receiver) , right , Label.new("none", :dummy)) + else + raise "Only ==, not #{name}" unless name == :== + end + end + + def assign(receiver , name , kids) + receiver = process(receiver) + puts "Assign #{name} , #{receiver}" if DEBUG + raise "Only one arg #{kids}" unless kids.length == 1 + right = process kids.shift + name = name.to_s[0...-1].to_sym + receiver.set_next(Slot.new(name)) + SlotLoad.new("#{receiver} = #{name}",receiver,right) + end + end +end diff --git a/lib/slot_machine/slot_machine.rb b/lib/slot_machine/slot_machine.rb index c906b7b0..46a23a8b 100644 --- a/lib/slot_machine/slot_machine.rb +++ b/lib/slot_machine/slot_machine.rb @@ -23,3 +23,5 @@ require_relative "callable_compiler" require_relative "method_compiler" require_relative "block_compiler" require_relative "macro/macro" +require_relative "slot_compiler" +require_relative "macro_maker" diff --git a/lib/slot_machine/slotted.rb b/lib/slot_machine/slotted.rb index b17cfce6..e4e30987 100644 --- a/lib/slot_machine/slotted.rb +++ b/lib/slot_machine/slotted.rb @@ -21,7 +21,7 @@ module SlotMachine def initialize( slots = nil ) return unless slots - raise "stopped" unless slots.is_a?(Array) + raise "stopped #{slots.class}" unless slots.is_a?(Array) first = slots.shift raise "ended" unless first @slots = Slot.new(first) diff --git a/test/slot_machine/codes/mini.slot b/test/slot_machine/codes/mini.slot new file mode 100644 index 00000000..e1e25e32 --- /dev/null +++ b/test/slot_machine/codes/mini.slot @@ -0,0 +1,2 @@ +start_label +a = b diff --git a/test/slot_machine/compiler/test_assignment.rb b/test/slot_machine/compiler/test_assignment.rb new file mode 100644 index 00000000..b66c2c40 --- /dev/null +++ b/test/slot_machine/compiler/test_assignment.rb @@ -0,0 +1,66 @@ +require_relative "../helper" + +module SlotMachine + class TestAssignment < MiniTest::Test + include SlotHelper + def compile_assign(str) + assign = compile(str) + assert_equal SlotLoad , assign.class + assign + end + def test_slot_load_rinst + assign = compile_assign("a = @b") + assert_equal "receiver.a" , assign.left.slots.to_s + assert_equal "receiver.b" , assign.right.slots.to_s + end + def test_slot_load_linst + assign = compile_assign("@a = b") + assert_equal "receiver.a" , assign.left.slots.to_s + assert_equal "b" , assign.right.slots.to_s + end + def test_slot_load_lrinst + assign = compile_assign("@a = @b") + assert_equal "receiver.a" , assign.left.slots.to_s + assert_equal "receiver.b" , assign.right.slots.to_s + end + def test_assign + assign = compile_assign("a = b") + assert_equal "receiver.a" , assign.left.slots.to_s + assert_equal "b" , assign.right.slots.to_s + end + end + class TestAssignment2 < MiniTest::Test + include SlotHelper + + def test_slot_load_linst_trav + assert_equal SlotLoad , compile_class("@a = b.c") + end + def test_assign1 + assign = compile("c = c.next") + assert_equal SlotLoad , assign.class + end + def test_shift + load = compile("a = b.c") + assert_equal SlotLoad , load.class + assert_equal "receiver.a" , load.left.slots.to_s + assert_equal "b.c" , load.right.slots.to_s + end + end + class TestAssignment3 < MiniTest::Test + include SlotHelper + + def test_inst_ass + assign = compile("@a.b = c") + assert_equal SlotLoad , assign.class + assert_equal SlottedMessage , assign.left.class + assert_equal "receiver.a.b" , assign.left.slots.to_s + end + def test_local_ass + assign = compile("a.b = c") + assert_equal SlotLoad , assign.class + assert_equal SlottedMessage , assign.left.class + assert_equal "a.b" , assign.left.slots.to_s + assert_equal "c" , assign.right.slots.to_s + end + end +end diff --git a/test/slot_machine/compiler/test_equal_goto.rb b/test/slot_machine/compiler/test_equal_goto.rb new file mode 100644 index 00000000..952580a9 --- /dev/null +++ b/test/slot_machine/compiler/test_equal_goto.rb @@ -0,0 +1,83 @@ +require_relative "../helper" + +module SlotMachine + class TestEqualGoto < MiniTest::Test + include SlotHelper + + def do_check(code) + check = compile(code) + assert_equal SameCheck , check.class + assert_equal Label , check.false_label.class + assert check.left.is_a?(Slotted) + assert check.right.is_a?(Slotted) + check + end + def test_equal_local + check = do_check("goto(exit_label) if(a == b)") + assert_equal "message.a" , check.left.to_s + assert_equal "message.b" , check.right.to_s + end + def test_equal_inst_left + check = do_check("goto(exit_label) if(@a == b)") + assert_equal "message.receiver.a" , check.left.to_s + assert_equal "message.b" , check.right.to_s + end + def test_equal_inst_right + check = do_check("goto(exit_label) if(a == @b)") + assert_equal "message.a" , check.left.to_s + assert_equal "message.receiver.b" , check.right.to_s + end + end + + class TestEqualGotoFull < MiniTest::Test + include SlotHelper + def setup + @expr = compile("start_label;goto(start_label) if( b == c)") + end + def test_label + assert_equal SlotMachine::Label , @expr.class + assert_equal :start_label , @expr.name + end + def test_conditional + assert_equal SameCheck , @expr.last.class + assert_equal :start_label , @expr.last.false_label.name + end + def test_same_label + assert_equal @expr.object_id , @expr.next.false_label.object_id + end + def test_expression_left + assert_equal SlottedMessage , @expr.last.left.class + assert_equal "message.b" , @expr.last.left.to_s + end + def test_expression_right + assert_equal SlottedMessage , @expr.last.right.class + assert_equal "message.c" , @expr.last.right.to_s + end + end + class TestEqualGotoChain < MiniTest::Test + include SlotHelper + def setup + @expr = compile("goto(start_label) if( a.b == c)") + end + def test_eq + assert_equal SameCheck , @expr.class + end + def test_left + assert_equal SlottedMessage , @expr.left.class + assert_equal "message.a.b" , @expr.left.to_s + end + end + class TestEqualGotoChain2 < MiniTest::Test + include SlotHelper + def setup + @expr = compile("goto(start_label) if( a == @b.c)") + end + def test_eq + assert_equal SameCheck , @expr.class + end + def test_right + assert_equal SlottedMessage , @expr.right.class + assert_equal "message.receiver.b.c" , @expr.right.to_s + end + end +end diff --git a/test/slot_machine/compiler/test_goto.rb b/test/slot_machine/compiler/test_goto.rb new file mode 100644 index 00000000..5a857b33 --- /dev/null +++ b/test/slot_machine/compiler/test_goto.rb @@ -0,0 +1,36 @@ +require_relative "../helper" + +module SlotMachine + class TestGoto < MiniTest::Test + include SlotHelper + + def test_goto_class + assert_equal Jump , compile_class("goto(exit_label)") + end + def test_goto_label + goto = compile("goto(exit_label)") + assert_equal Jump , goto.class + assert_equal :exit_label , goto.label.name + end + + def test_label + label = compile("while_label") + assert_equal SlotMachine::Label , label.class + assert_equal :while_label , label.name + end + + def test_2_label + labels = compile("exit_label;exit_label") + assert_equal :exit_label , labels.name + assert_equal :exit_label , labels.next.name + assert_equal labels.object_id , labels.next.object_id + end + + def test_goto_with_label + gotos = compile("exit_label;goto(exit_label)") + assert_equal :exit_label , gotos.name + assert_equal :exit_label , gotos.next.label.name + assert_equal gotos.object_id , gotos.next.label.object_id + end + end +end diff --git a/test/slot_machine/compiler/test_slot_compiler.rb b/test/slot_machine/compiler/test_slot_compiler.rb new file mode 100644 index 00000000..c5a9947b --- /dev/null +++ b/test/slot_machine/compiler/test_slot_compiler.rb @@ -0,0 +1,17 @@ +require_relative "../helper" + +module SlotMachine + class TestSlotCompiler < MiniTest::Test + include SlotHelper + + def test_init + assert SlotCompiler.new + end + def test_labels + assert SlotCompiler.new.labels.empty? + end + def test_basic_compile + assert_equal SlottedMessage , compile("a").class + end + end +end diff --git a/test/slot_machine/compiler/test_variable.rb b/test/slot_machine/compiler/test_variable.rb new file mode 100644 index 00000000..965d3fe0 --- /dev/null +++ b/test/slot_machine/compiler/test_variable.rb @@ -0,0 +1,38 @@ +require_relative "../helper" + +module SlotMachine + class TestVariable < MiniTest::Test + include SlotHelper + def compile_var(str) + var = compile(str) + assert var.is_a?(Slotted) , "Was #{var.class}" + var + end + def test_local + assert_equal SlottedMessage , compile_var("a").class + end + def test_inst + var = compile_var("@a") + assert_equal SlottedMessage , var.class + end + def test_local_chain + chain = compile_var("a.b") + assert_equal Slot , chain.slots.class + assert_equal :b , chain.slots.next_slot.name + end + def test_local_chain2 + chain = compile_var("a.b.c") + assert_equal Slot , chain.slots.next_slot.next_slot.class + assert_equal :c , chain.slots.next_slot.next_slot.name + end + def test_inst_chain + chain = compile_var("@a.b") + assert_equal SlottedMessage , chain.class + assert_equal Slot , chain.slots.class + assert_equal :receiver , chain.slots.name + assert_equal Slot , chain.slots.class + assert_equal :a , chain.slots.next_slot.name + assert_equal :b , chain.slots.next_slot.next_slot.name + end + end +end diff --git a/test/slot_machine/helper.rb b/test/slot_machine/helper.rb index 6b075c4a..986d47ef 100644 --- a/test/slot_machine/helper.rb +++ b/test/slot_machine/helper.rb @@ -6,4 +6,18 @@ module SlotMachine super("mocking") end end + module SlotHelper + def compile(input) + SlotCompiler.compile(input) + end + def compile_class(input) + compile(input).class + end + end + module SlotToHelper + def setup + Parfait.boot!({}) + @compiler = SlotMachine::SlotCollection.compiler_for( :Space , :main,{},{}) + end + end end diff --git a/test/slot_machine/test_macro_maker.rb b/test/slot_machine/test_macro_maker.rb new file mode 100644 index 00000000..a5c94597 --- /dev/null +++ b/test/slot_machine/test_macro_maker.rb @@ -0,0 +1,47 @@ +require_relative "helper" + +module SlotMachine + + class TestMacroMakerLoad < MiniTest::Test + include SlotHelper + + def check_mini(maker) + assert_equal MacroMaker , maker.class + assert_equal SlotMachine::Label , maker.instructions.class + end + def mini_file + File.read(File.expand_path( "../codes/mini.slot" , __FILE__)) + end + def test_mini_string + check_mini MacroMaker.load_string( mini_file ) + end + def test_mini_source + check_mini MacroMaker.new( SlotCompiler.compile(mini_file)) + end + end + + class TestMacroMakerLoad2 < MiniTest::Test + + def setup + @macro = MacroMaker.load_string( mini_file ) + @instructions = @macro.instructions + end + def test_label + assert_equal SlotMachine::Label , @macro.instructions.class + end + def test_assign + assert_equal SlotMachine::SlotLoad , @instructions.next.class + assert_equal "message.receiver.a" , @instructions.next.left.to_s + assert_equal "message.b" , @instructions.next.right.to_s + end + def test_length + assert @instructions.next + assert_nil @instructions.next.next + end + def mini_file + File.read(File.expand_path( "../codes/mini.slot" , __FILE__)) + end + + end + +end diff --git a/test/slot_machine/test_slot_compiler.rb b/test/slot_machine/test_slot_compiler.rb new file mode 100644 index 00000000..90a85d49 --- /dev/null +++ b/test/slot_machine/test_slot_compiler.rb @@ -0,0 +1,17 @@ +require_relative "helper" + +module SlotMachine + class TestSlotCompiler < MiniTest::Test + include SlotHelper + + def test_init + assert SlotCompiler.new + end + def test_labels + assert SlotCompiler.new.labels.empty? + end + def test_basic_compile + assert_equal SlottedMessage , compile("a").class + end + end +end