diff --git a/lib/bosl/compiler.rb b/lib/bosl/compiler.rb index 8fa7d862..738d0940 100644 --- a/lib/bosl/compiler.rb +++ b/lib/bosl/compiler.rb @@ -1,9 +1,7 @@ module Bosl class Compiler < AST::Processor - attr_reader :method - def initialize(method) - @method = method + def initialize() end def handler_missing node raise "No handler on_#{node.type}(node)" @@ -24,8 +22,8 @@ module Bosl # This makes the dispatch extensible, ie Expressions may be added by external code, # as long as matching compile methods are supplied too. # - def self.compile expression , method - compiler = Compiler.new method + def self.compile expression + compiler = Compiler.new compiler.process expression end diff --git a/lib/bosl/compiler/README.md b/lib/bosl/compiler/README.md index b325500e..1228a14d 100644 --- a/lib/bosl/compiler/README.md +++ b/lib/bosl/compiler/README.md @@ -19,11 +19,16 @@ Similarly, the result of compiling is two-fold: a static and a dynamic part. 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. -Each ast class gets a compile method that does the compilation. +The compiler has a method for each type for ast, named along on_xxx with xxx as the type -#### MethodSource and Instructions +#### Compiler holds scope + +The Compiler instance can hold arbitrary scope needed during the compilation. Since we compile bosl +(a static language) things have become more simple. + +A class statement sets the current @clazz scope , a method definition the @method. +If either are not set when needed compile errors will follow. So easy, so nice. -The first argument to the compile method is the MethodSource. 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. @@ -57,29 +62,17 @@ The objects the machine works on are: and working on means, these are the only objects which the machine accesses. Ie all others would have to be moved first. -When a Method needs to make a call, or send a Message, it creates a NewMessage object. -Messages contain return addresses and arguments. - -Then the machine must find the method to call. -This is a function of the virtual machine and is implemented in ruby. - -Then a new Method receives the Message, creates a Frame for local and temporary variables -and continues execution. +When a Method 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. -And interestingly we can partly use ruby to find the method, so in a way it is not just a top -down transformation. Instead the sending goes back up and then down again. +### Distinclty future proof -The Message object is the second parameter to the compile method, the run-time part as it were. -Why? Since it only exists at runtime: to make compile time analysis possible -(it is after all the Virtual version, not Parfait. ie compile-time, not run-time). -Especially for those times when we can resolve the method at compile time. +Bosl is designed to be used as an implementation language for a higher oo language. Some, or +even many, features may not make sense on their own. But these features, like several return +addresses are important to implement the higher language. - -* -As ruby is a dynamic language, it also compiles at run-time. This line of thought does not help -though as it sort of mixes the seperate times up, even they are not. -Even in a running ruby programm the stages of compile and run are seperate. -Similarly it does not help to argue that the code is static too, not dynamic, -as that leaves us with a worse working model. +In fact, Bosl main purpose is not even to be written. The main purpose is to have a language to +compile ruby to. In the same way that the assembler layer in salama is not designed to be written, +we just need it to create our layers. diff --git a/lib/bosl/compiler/basic_expressions.rb b/lib/bosl/compiler/basic_expressions.rb index ce4b4426..cf9fd2f4 100644 --- a/lib/bosl/compiler/basic_expressions.rb +++ b/lib/bosl/compiler/basic_expressions.rb @@ -14,25 +14,25 @@ module Bosl def on_int expression int = expression.first to = Virtual::Return.new(Virtual::Integer , int) - method.source.add_code Virtual::Set.new( int , to ) + @method.source.add_code Virtual::Set.new( int , to ) to end def on_true expression to = Virtual::Return.new(Virtual::Reference , true ) - method.source.add_code Virtual::Set.new( true , to ) + @method.source.add_code Virtual::Set.new( true , to ) to end def on_false expression to = Virtual::Return.new(Virtual::Reference , false) - method.source.add_code Virtual::Set.new( false , to ) + @method.source.add_code Virtual::Set.new( false , to ) to end def on_nil expression to = Virtual::Return.new(Virtual::Reference , nil) - method.source.add_code Virtual::Set.new( nil , to ) + @method.source.add_code Virtual::Set.new( nil , to ) to end @@ -40,8 +40,8 @@ module Bosl # Clearly a TODO here to implement strings rather than reusing symbols value = expression.first.to_sym to = Virtual::Return.new(Virtual::Reference , value) - method.source.constants << value - method.source.add_code Virtual::Set.new( value , to ) + @method.source.constants << value + @method.source.add_code Virtual::Set.new( value , to ) to end end diff --git a/lib/bosl/compiler/callsite_expression.rb b/lib/bosl/compiler/callsite_expression.rb index a5537d4b..65559173 100644 --- a/lib/bosl/compiler/callsite_expression.rb +++ b/lib/bosl/compiler/callsite_expression.rb @@ -4,7 +4,7 @@ module Bosl def on_call expression name , arguments , receiver = *expression name = name.to_a.first - + raise "not inside method " unless @method if receiver me = process( receiver.to_a.first ) else @@ -12,9 +12,9 @@ module Bosl end ## need two step process, compile and save to frame # then move from frame to new message - method.source.add_code Virtual::NewMessage.new - method.source.add_code Virtual::Set.new( me , Virtual::NewSelf.new(me.type)) - method.source.add_code Virtual::Set.new( name.to_sym , Virtual::NewMessageName.new(:int)) + @method.source.add_code Virtual::NewMessage.new + @method.source.add_code Virtual::Set.new( me , Virtual::NewSelf.new(me.type)) + @method.source.add_code Virtual::Set.new( name.to_sym , Virtual::NewMessageName.new(:int)) compiled_args = [] arguments.to_a.each_with_index do |arg , i| #compile in the running method, ie before passing control @@ -24,7 +24,7 @@ module Bosl # so the next free is +1 to = Virtual::NewArgSlot.new(i + 1 ,val.type , val) # (doing this immediately, not after the loop, so if it's a return it won't get overwritten) - method.source.add_code Virtual::Set.new( val , to ) + @method.source.add_code Virtual::Set.new( val , to ) compiled_args << to end #method.source.add_code Virtual::MessageSend.new(name , me , compiled_args) #and pass control @@ -41,7 +41,7 @@ module Bosl elsif( me.is_a? Fixnum ) name = :plus if name == :+ method = Virtual.machine.space.get_class_by_name(:Integer).get_instance_method(name) - #puts Virtual.machine.space.get_class_by_name(:Integer).method_names.to_a + puts Virtual.machine.space.get_class_by_name(:Integer).method_names.to_a raise "Method not implemented Integer.#{name}" unless method @method.source.add_code Virtual::MethodCall.new( method ) else diff --git a/lib/bosl/compiler/class_field.rb b/lib/bosl/compiler/class_field.rb index f7bbf96a..5a8906ce 100644 --- a/lib/bosl/compiler/class_field.rb +++ b/lib/bosl/compiler/class_field.rb @@ -5,7 +5,8 @@ module Bosl #puts expression.inspect type , name , value = *expression - for_class = self.method.for_class + for_class = @clazz + raise "no class" unless for_class index = for_class.object_layout.variable_index(name) #raise "class field already defined:#{name} for class #{for_class.name}" if index puts "Define field #{name} on class #{for_class.name}" diff --git a/lib/bosl/compiler/field_access.rb b/lib/bosl/compiler/field_access.rb index 03ceb45b..ab2e56cb 100644 --- a/lib/bosl/compiler/field_access.rb +++ b/lib/bosl/compiler/field_access.rb @@ -9,11 +9,10 @@ module Bosl case receiver when :self - for_class = self.method.for_class - index = for_class.object_layout.variable_index(field_name) - raise "field access, but no such field:#{field_name} for class #{for_class.name}" unless index + index = @clazz.object_layout.variable_index(field_name) + raise "field access, but no such field:#{field_name} for class #{@clazz.name}" unless index value = Virtual::Return.new(:int) - method.source.add_code Virtual::Set.new( Virtual::SelfSlot.new(index, :int ) , value ) + @method.source.add_code Virtual::Set.new( Virtual::SelfSlot.new(index, :int ) , value ) when :message #message Slot raise "message not yet" diff --git a/lib/bosl/compiler/field_def.rb b/lib/bosl/compiler/field_def.rb index 34b24e69..1083808b 100644 --- a/lib/bosl/compiler/field_def.rb +++ b/lib/bosl/compiler/field_def.rb @@ -5,7 +5,7 @@ module Bosl #puts expression.inspect type , name , value = *expression - index = method.ensure_local( name , type ) + index = @method.ensure_local( name , type ) if value value = process( value ) diff --git a/lib/bosl/compiler/function_expression.rb b/lib/bosl/compiler/function_expression.rb index 472b82ad..1c8dcb14 100644 --- a/lib/bosl/compiler/function_expression.rb +++ b/lib/bosl/compiler/function_expression.rb @@ -1,6 +1,6 @@ module Bosl Compiler.class_eval do -# function attr_reader :name, :params, :body , :receiver + def on_function expression #puts expression.inspect return_type , name , parameters, kids , receiver = *expression @@ -24,23 +24,30 @@ module Bosl end end else - r = Virtual::Self.new(:int) - class_name = method.for_class.name + r = @clazz + class_name = @clazz.name end - new_method = Virtual::MethodSource.create_method(class_name, return_type, name , args ) - new_method.source.receiver = r - new_method.for_class.add_instance_method new_method + raise "Already in method #{@method}" if @method + @method = @clazz.get_instance_method( name ) + if(@method) + puts "Warning, redefining method #{name}" unless name == :main + #TODO check args / type compatibility + @method.source.init @method + else + @method = Virtual::MethodSource.create_method(class_name, return_type, name , args ) + @method.for_class.add_instance_method @method + end + @method.source.receiver = r + puts "compile method #{@method.name}" - old_method = @method - @method = new_method #frame = frame.new_frame kids.to_a.each do |ex| return_type = process(ex) raise return_type.inspect if return_type.is_a? Virtual::Instruction end - @method = old_method - new_method.source.return_type = return_type + @method.source.return_type = return_type + @method = nil Virtual::Return.new(return_type) end end diff --git a/lib/bosl/compiler/if_expression.rb b/lib/bosl/compiler/if_expression.rb index 97ef4cd5..98da7875 100644 --- a/lib/bosl/compiler/if_expression.rb +++ b/lib/bosl/compiler/if_expression.rb @@ -8,28 +8,28 @@ module Bosl # to execute the logic as the if states it, the blocks are the other way around # so we can the jump over the else if true , # and the else joins unconditionally after the true_block - merge_block = method.source.new_block "if_merge" # last one, created first - true_block = method.source.new_block "if_true" # second, linked in after current, before merge - false_block = method.source.new_block "if_false" # directly next in order, ie if we don't jump we land here + merge_block = @method.source.new_block "if_merge" # last one, created first + true_block = @method.source.new_block "if_true" # second, linked in after current, before merge + false_block = @method.source.new_block "if_false" # directly next in order, ie if we don't jump we land here is = process(condition ) # TODO should/will use different branches for different conditions. # just a scetch : cond_val = cond_val.is_true?(method) unless cond_val.is_a? BranchCondition - method.source.add_code Virtual::IsTrueBranch.new( true_block ) + @method.source.add_code Virtual::IsTrueBranch.new( true_block ) # compile the true block (as we think of it first, even it is second in sequential order) - method.source.current true_block + @method.source.current true_block last = process_all(if_true).last # compile the false block - method.source.current false_block + @method.source.current false_block last = process_all(if_false).last if if_false - method.source.add_code Virtual::UnconditionalBranch.new( merge_block ) + @method.source.add_code Virtual::UnconditionalBranch.new( merge_block ) #puts "compiled if: end" - method.source.current merge_block + @method.source.current merge_block #TODO should return the union of the true and false types last diff --git a/lib/bosl/compiler/module_expression.rb b/lib/bosl/compiler/module_expression.rb index 53f05b1a..01d22d82 100644 --- a/lib/bosl/compiler/module_expression.rb +++ b/lib/bosl/compiler/module_expression.rb @@ -9,10 +9,11 @@ module Bosl def on_class expression #puts expression.inspect name , derives , expressions = *expression - clazz = Parfait::Space.object_space.get_class_by_name! name - #puts "Compiling class #{clazz.name.inspect}" + raise "classes dont yet play babushka, get coding #{name}" if @clazz + @clazz = Parfait::Space.object_space.get_class_by_name! name + puts "Compiling class #{@clazz.name.inspect}" expression_value = process_all(expressions).last - + @clazz = nil return expression_value end end diff --git a/lib/bosl/compiler/name_expression.rb b/lib/bosl/compiler/name_expression.rb index fd0ee9a6..a7c43cb8 100644 --- a/lib/bosl/compiler/name_expression.rb +++ b/lib/bosl/compiler/name_expression.rb @@ -7,15 +7,15 @@ module Bosl # whichever way this goes the result is stored in the return slot (as all compiles) def on_name expression name = expression.to_a.first - return Virtual::Self.new( Virtual::Reference.new(method.for_class)) if name == :self + return Virtual::Self.new( Virtual::Reference.new(@clazz)) if name == :self # either an argument, so it's stored in message ret = Virtual::Return.new :int - if( index = method.has_arg(name)) - method.source.add_code Virtual::Set.new( Virtual::ArgSlot.new(index,:int ) , ret) + if( index = @method.has_arg(name)) + @method.source.add_code Virtual::Set.new( Virtual::ArgSlot.new(index,:int ) , ret) else # or a local so it is in the frame - index = method.has_local( name ) + index = @method.has_local( name ) if(index) - method.source.add_code Virtual::Set.new(Virtual::FrameSlot.new(index,:int ) , ret ) + @method.source.add_code Virtual::Set.new(Virtual::FrameSlot.new(index,:int ) , ret ) else raise "must define variable #{name} before using it" end diff --git a/lib/bosl/compiler/operator_expressions.rb b/lib/bosl/compiler/operator_expressions.rb index 2f5ed142..806e98ae 100644 --- a/lib/bosl/compiler/operator_expressions.rb +++ b/lib/bosl/compiler/operator_expressions.rb @@ -3,6 +3,7 @@ module Bosl # operator attr_reader :operator, :left, :right def on_operator expression operator , left , right = *expression + #raise "not quite there" Virtual::Return.new(:int) end @@ -10,13 +11,13 @@ module Bosl name , value = *expression name = name.to_a.first v = process(value) - index = method.has_local( name ) + index = @method.has_local( name ) if(index) - method.source.add_code Virtual::Set.new(Virtual::FrameSlot.new(:int,index ) , v ) + @method.source.add_code Virtual::Set.new(Virtual::FrameSlot.new(:int,index ) , v ) else - index = method.has_arg( name ) + index = @method.has_arg( name ) if(index) - method.source.add_code Virtual::Set.new(Virtual::ArgSlot.new(:int,index ) , v ) + @method.source.add_code Virtual::Set.new(Virtual::ArgSlot.new(:int,index ) , v ) else raise "must define variable #{name} before using it in #{@method.inspect}" end diff --git a/lib/bosl/compiler/while_expression.rb b/lib/bosl/compiler/while_expression.rb index 55201638..b56deaa0 100644 --- a/lib/bosl/compiler/while_expression.rb +++ b/lib/bosl/compiler/while_expression.rb @@ -7,22 +7,22 @@ module Bosl condition = condition.first # this is where the while ends and both branches meet - merge = method.source.new_block("while merge") + merge = @method.source.new_block("while merge") # this comes after the current and beofre the merge - start = method.source.new_block("while_start" ) - method.source.current start + start = @method.source.new_block("while_start" ) + @method.source.current start cond = process(condition) - method.source.add_code Virtual::IsTrueBranch.new(merge) + @method.source.add_code Virtual::IsTrueBranch.new(merge) last = process_all(expressions).last # unconditionally branch to the start - method.source.add_code Virtual::UnconditionalBranch.new(start) + @method.source.add_code Virtual::UnconditionalBranch.new(start) # continue execution / compiling at the merge block - method.source.current merge + @method.source.current merge last end end diff --git a/lib/parfait/list.rb b/lib/parfait/list.rb index 78fb8b4a..73b13d2d 100644 --- a/lib/parfait/list.rb +++ b/lib/parfait/list.rb @@ -90,6 +90,7 @@ module Parfait return internal_object_get(index + 1) end end + alias :[] :get def empty? self.get_length == 0 diff --git a/lib/parfait/space.rb b/lib/parfait/space.rb index bcc8bc85..827b4325 100644 --- a/lib/parfait/space.rb +++ b/lib/parfait/space.rb @@ -32,6 +32,7 @@ module Parfait message = Message.new(nil) 5.times do self.first_message = Message.new message + #puts "INIT caller #{message.object_id} to #{self.first_message.object_id}" message.set_caller self.first_message message = self.first_message end diff --git a/lib/register/assembler.rb b/lib/register/assembler.rb index 7842576b..00d9fe80 100644 --- a/lib/register/assembler.rb +++ b/lib/register/assembler.rb @@ -111,7 +111,7 @@ module Register begin code.assemble( stream ) rescue => e - puts "Method error #{method.name}\n#{Sof.write(method.source.blocks).to_s[0...2000]}" + puts "Assembly error #{method.name}\n#{Sof.write(method.source.blocks).to_s[0...2000]}" puts Sof.write(code) raise e end diff --git a/lib/virtual/machine.rb b/lib/virtual/machine.rb index 2d9959c2..7e33a4b3 100644 --- a/lib/virtual/machine.rb +++ b/lib/virtual/machine.rb @@ -132,11 +132,11 @@ module Virtual self end - def compile_main bytes + def parse_and_compile bytes syntax = @parser.parse_with_debug(bytes) parts = Parser::Transform.new.apply(syntax) #puts parts.inspect - Bosl::Compiler.compile( parts , @space.get_main ) + Bosl::Compiler.compile( parts ) end private diff --git a/lib/virtual/method_source.rb b/lib/virtual/method_source.rb index 9e6976c4..a7d03a25 100644 --- a/lib/virtual/method_source.rb +++ b/lib/virtual/method_source.rb @@ -38,23 +38,27 @@ module Virtual clazz = Virtual.machine.space.get_class_by_name class_name raise "No such class #{class_name}" unless clazz return_type = Virtual::Type.from_sym return_type - arguemnts = [] + arguments = [] args.each_with_index do | arg , index | unless arg.is_a? Parfait::Variable raise "not type #{arg}:#{arg.class}" unless arg == :int || arg == :ref arg = Parfait::Variable.new arg , "arg#{index}".to_sym end - arguemnts << arg + arguments << arg end - method = clazz.create_instance_method( method_name , Virtual.new_list(arguemnts)) + method = clazz.create_instance_method( method_name , Virtual.new_list(arguments)) method.source = MethodSource.new(method , return_type) method end # just passing the method object in for Instructions to make decisions (later) def initialize method , return_type + init( method , return_type) + end + + def init method , return_type = nil # first block we have to create with .new , as new_block assumes a current enter = Block.new( "enter" , method ).add_code(MethodEnter.new( method )) - @return_type = return_type + @return_type = return_type if return_type @blocks = [enter] @current = enter new_block("return").add_code(MethodReturn.new(method)) diff --git a/test/compiler/code_checker.rb b/test/compiler/code_checker.rb index 297b669f..cce9073a 100644 --- a/test/compiler/code_checker.rb +++ b/test/compiler/code_checker.rb @@ -1,6 +1,6 @@ module CodeChecker def check - Virtual.machine.boot.compile_main @string_input + Virtual.machine.boot.parse_and_compile @string_input produced = Virtual.machine.space.get_main.source assert @output , "No output given" assert_equal @output.length , produced.blocks.length , "Block length" diff --git a/test/compiler/compiler_helper.rb b/test/compiler/compiler_helper.rb index 358f3021..666db943 100644 --- a/test/compiler/compiler_helper.rb +++ b/test/compiler/compiler_helper.rb @@ -5,7 +5,7 @@ require 'parslet/convenience' Bosl::Compiler.class_eval do def check - Virtual.machine.boot.compile_main @string_input + Virtual.machine.boot.parse_and_compile @string_input produced = Virtual.machine.space.get_main.source assert_equal @output , produced Virtual.machine.run_passes diff --git a/test/compiler/test_all.rb b/test/compiler/test_all.rb index cd863bb4..ea76ec60 100644 --- a/test/compiler/test_all.rb +++ b/test/compiler/test_all.rb @@ -1,4 +1,3 @@ -require_relative "test_basic" require_relative "test_methods" require_relative "test_hello" require_relative "test_compiler" diff --git a/test/compiler/test_basic.rb b/test/compiler/test_basic.rb index efbb57e4..9379f8ef 100644 --- a/test/compiler/test_basic.rb +++ b/test/compiler/test_basic.rb @@ -3,7 +3,15 @@ require_relative "compiler_helper" class TestBasic < MiniTest::Test def check - expressions = Virtual.machine.boot.compile_main @string_input + input = < 1 ) - int tmp = a - a = b - b = tmp + b - n = n - 1 - end - b.putint() - return b -end +class Object + int fibonaccit(int n) + int a = 0 + int b = 1 + while( n > 1 ) + int tmp = a + a = b + b = tmp + b + n = n - 1 + end + b.putint() + return b + end -fibonaccit( 10 ) + int main() + fibonaccit( 10 ) + end +end HERE @expect = [Virtual::Return ] check diff --git a/test/interpreter/test_add.rb b/test/interpreter/test_add.rb index 27bafdb7..1aa64587 100644 --- a/test/interpreter/test_add.rb +++ b/test/interpreter/test_add.rb @@ -3,14 +3,21 @@ require_relative "helper" class AddTest < MiniTest::Test include AST::Sexp include Ticker - + def setup Virtual.machine.boot - code = s(:call, - s(:name, :plus), - s(:arguments , s(:int , 5)), - s(:receiver, s(:int, 2))) - Bosl::Compiler.compile( code , Virtual.machine.space.get_main ) + code = s(:class, :Object, + s(:derives, nil), + s(:expressions, + s(:function, :int, + s(:name, :main), + s(:parameters), + s(:expressions, + s(:call, + s(:name, :plus), + s(:arguments , s(:int , 5)), + s(:receiver, s(:int, 2))))))) + Bosl::Compiler.compile( code ) Virtual.machine.run_before "Register::CallImplementation" @interpreter = Interpreter::Interpreter.new @interpreter.start Virtual.machine.init diff --git a/test/interpreter/test_puti.rb b/test/interpreter/test_puti.rb index 528f95c3..8854f21b 100644 --- a/test/interpreter/test_puti.rb +++ b/test/interpreter/test_puti.rb @@ -2,6 +2,7 @@ require_relative "helper" class AddTest < MiniTest::Test include Ticker + include AST::Sexp def test_puti @string_input = <