diff --git a/lib/ast/all.rb b/lib/ast/all.rb deleted file mode 100644 index ae40a610..00000000 --- a/lib/ast/all.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "ast/expression" -require_relative "basic_expressions" -require_relative "call_site_expression" -require_relative "compound_expressions" -require_relative "if_expression" -require_relative "function_expression" -require_relative "module_expression" -require_relative "operator_expressions" -require_relative "return_expression" -require_relative "while_expression" -require_relative "expression_list" diff --git a/lib/ast/compound_expressions.rb b/lib/ast/compound_expressions.rb deleted file mode 100644 index 8067e170..00000000 --- a/lib/ast/compound_expressions.rb +++ /dev/null @@ -1,20 +0,0 @@ -module Ast - - class ArrayExpression < Expression -# attr_reader :values - def compile context - to.do - end - end - class AssociationExpression < Expression -# attr_reader :key , :value - def compile context - to.do - end - end - class HashExpression < ArrayExpression - def compile context - to.do - end - end -end \ No newline at end of file diff --git a/lib/ast/expression_list.rb b/lib/ast/expression_list.rb deleted file mode 100644 index dc7e8a48..00000000 --- a/lib/ast/expression_list.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Ast - class ExpressionList < Expression -# attr_reader :expressions - def compile method , message - expressions.collect { |part| part.compile( method, message ) } - end - end -end \ No newline at end of file diff --git a/lib/ast/operator_expressions.rb b/lib/ast/operator_expressions.rb deleted file mode 100644 index 4bffb891..00000000 --- a/lib/ast/operator_expressions.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Ast - class OperatorExpression < Expression -# attr_reader :operator, :left, :right - def compile method , message - call = CallSiteExpression.new( operator , [right] , left ) - call.compile(method,message) - end - end -end \ No newline at end of file diff --git a/lib/compiler.rb b/lib/compiler.rb new file mode 100644 index 00000000..109ed501 --- /dev/null +++ b/lib/compiler.rb @@ -0,0 +1,20 @@ +module Compiler + + def self.compile expression , method , message + exp_name = expression.class.split("::").last.sub("Expression","").downcase + puts "Expression #{exp_name}" + self.send exp_name.to_sym , method , message + end + +end + +require_relative "compiler/basic_expressions" +require_relative "compiler/call_site_expression" +require_relative "compiler/compound_expressions" +require_relative "compiler/if_expression" +require_relative "compiler/function_expression" +require_relative "compiler/module_expression" +require_relative "compiler/operator_expressions" +require_relative "compiler/return_expression" +require_relative "compiler/while_expression" +require_relative "compiler/expression_list" diff --git a/lib/ast/README.md b/lib/compiler/README.md similarity index 75% rename from lib/ast/README.md rename to lib/compiler/README.md index b40e84fd..d18ce217 100644 --- a/lib/ast/README.md +++ b/lib/compiler/README.md @@ -1,12 +1,10 @@ -### Ast +### Compiling The Ast (abstract syntax tree) is created by salama-reader gem and the classes defined there -### Compiling +The code in this directory compiles the AST to the virtual machine code. -The code in this directory compiles the AST to the virtual machine code. - -If this were an intrepreter, we would just walk the tree and do what it says. +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. @@ -17,11 +15,11 @@ 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 CompiledMethod -Too make things a little simpler, we create a very high level instruction stream at first and then run -transformation and optimisation passes on the stream to improve it. +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. - + #### Compiled Method and Instructions The first argument to the compile method is the CompiledMethod. @@ -30,23 +28,23 @@ Instructions are stored as a list of Blocks, and Blocks are the smallest unit of 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 genearal structure of the instructions is a graph (what with if's and whiles and breaks and what), -but we build it to have one start and *one* end (return). +This is so each compiling method can just do it's bit and be unaware of the larger structure +that is being created. +The genearal 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 -The virtual machine instructions obviously operate on the virtual machine. Since the machine is virtual, we have to define it, and since it 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 ot a register machine, but an object machine: it operates directly on objects and -also has no seperate stack, only objects. There are a number of objects which are accessible, + +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 are a number of objects which are accessible, and one can think of these (their addresses) as register contents. -And one wouldn't be far off as that is the implementation +(And one wouldn't be far off as that is the implementation.) The objects the machine works on are: @@ -55,21 +53,22 @@ The objects the machine works on are: - Self - NewMessage -and working on means, these are the only objects which the machine accesses. +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. + +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. +Then a new Method receives the Message, creates a Frame for local and temporary variables +and continues execution. 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. +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. 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 @@ -77,9 +76,9 @@ Why? Since it only exists at runtime: to make compile time analysis possible Especially for those times when we can resolve the method at compile time. -* +* 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. \ No newline at end of file +as that leaves us with a worse working model. diff --git a/lib/ast/basic_expressions.rb b/lib/compiler/basic_expressions.rb similarity index 60% rename from lib/ast/basic_expressions.rb rename to lib/compiler/basic_expressions.rb index cf514d02..303bdc45 100644 --- a/lib/ast/basic_expressions.rb +++ b/lib/compiler/basic_expressions.rb @@ -1,6 +1,6 @@ # collection of the simple ones, int and strings and such -module Ast +module Compiler # 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_ @@ -9,97 +9,78 @@ module Ast # The current approach moves the constant into a variable before using it # But in the future (in the one that holds great things) we optimize those unneccesay moves away - - class IntegerExpression < Expression + # attr_reader :value - def compile method , message + def compile_integer expession , method , message int = Virtual::IntegerConstant.new(value) to = Virtual::NewReturn.new(Virtual::Integer , int) method.add_code Virtual::Set.new( to , int) to end - end - class TrueExpression < Expression - def compile method , message + def compile_true expession , method , message value = Virtual::TrueConstant.new to = Virtual::Return.new(Virtual::Reference , value) method.add_code Virtual::Set.new( to , value ) to end - end - - class FalseExpression < Expression - def compile method , message + + def compile_false expession , method , message value = Virtual::FalseConstant.new to = Virtual::Return.new(Virtual::Reference , value) method.add_code Virtual::Set.new( to , value ) to end - end - - class NilExpression < Expression - def compile method , message + + def compile_nil expession , method , message value = Virtual::NilConstant.new to = Virtual::Return.new(Virtual::Reference , value) method.add_code Virtual::Set.new( to , value ) to end - end - class NameExpression < Expression # attr_reader :name - # compiling name needs to check if it's a variable and if so resolve it # otherwise it's a method without args and a send is ussued. # this makes the namespace static, ie when eval and co are implemented method needs recompilation - def compile method , message - return Virtual::Self.new( Virtual::Mystery ) if name == :self - if method.has_var(name) - message.compile_get(method , name ) + def compile_name expession , method , message + return Virtual::Self.new( Virtual::Mystery ) if expession.name == :self + if method.has_var(expession.name) + message.compile_get(method , expession.name ) else - raise "Unimplemented #{self}" - message.compile_send( method , name , Virtual::Self.new( Virtual::Mystery ) ) + raise "Unimplemented #{self}" + message.compile_send( method , expession.name , Virtual::Self.new( Virtual::Mystery ) ) end end - end - class ModuleName < NameExpression - def compile method , message + def compile_module expession , method , message clazz = Virtual::BootSpace.space.get_or_create_class name raise "uups #{clazz}.#{name}" unless clazz to = Virtual::Return.new(Virtual::Reference , clazz ) method.add_code Virtual::Set.new( to , clazz ) to - end - end + end - class StringExpression < Expression # attr_reader :string - def compile method , message - value = Virtual::StringConstant.new(string) + def compile_string expession , method , message + value = Virtual::StringConstant.new(expession.string) to = Virtual::Return.new(Virtual::Reference , value) - Virtual::BootSpace.space.add_object value + Virtual::BootSpace.space.add_object value method.add_code Virtual::Set.new( to , value ) to end - end - class AssignmentExpression < Expression + #attr_reader :left, :right - - def compile method , message - raise "must assign to NameExpression , not #{left}" unless left.instance_of? NameExpression + def compile_assignment expession , method , message + raise "must assign to NameExpression , not #{expession.left}" unless expession.left.instance_of? NameExpression r = right.compile(method,message) - raise "oh noo, nil from where #{right.inspect}" unless r - message.compile_set( method , left.name , r ) + raise "oh noo, nil from where #{expession.right.inspect}" unless r + message.compile_set( method , expession.left.name , r ) end - end - class VariableExpression < NameExpression - def compile method , message - method.add_code Virtual::InstanceGet.new(name) + def compile_variable expession, method , message + method.add_code Virtual::InstanceGet.new(expession.name) Virtual::NewReturn.new( Virtual::Mystery ) end - end -end \ No newline at end of file +end diff --git a/lib/ast/call_site_expression.rb b/lib/compiler/call_site_expression.rb similarity index 67% rename from lib/ast/call_site_expression.rb rename to lib/compiler/call_site_expression.rb index 2b9b3dff..a10c3abc 100644 --- a/lib/ast/call_site_expression.rb +++ b/lib/compiler/call_site_expression.rb @@ -1,28 +1,26 @@ -module Ast +module Compiler # operators are really function calls - - class CallSiteExpression < Expression -# attr_reader :name, :args , :receiver - def compile method , message - me = receiver.compile( method, message ) +# call_site - attr_reader :name, :args , :receiver + + def compile_call_site expession , method , message + me = expession.receiver.compile( method, message ) method.add_code Virtual::NewMessage.new method.add_code Virtual::Set.new(Virtual::NewSelf.new(me.type), me) - method.add_code Virtual::Set.new(Virtual::NewName.new(), Virtual::StringConstant.new(name)) + method.add_code Virtual::Set.new(Virtual::NewName.new(), Virtual::StringConstant.new(expession.name)) compiled_args = [] - args.each_with_index do |arg , i| + expession.args.each_with_index do |arg , i| #compile in the running method, ie before passing control - val = arg.compile( method, message) + val = arg.compile( method, message) # move the compiled value to it's slot in the new message to = Virtual::NewMessageSlot.new(i ,val.type , val) # (doing this immediately, not after the loop, so if it's a return it won't get overwritten) method.add_code Virtual::Set.new(to , val ) compiled_args << to end - method.add_code Virtual::MessageSend.new(name , me , compiled_args) #and pass control + method.add_code Virtual::MessageSend.new(expession.name , me , compiled_args) #and pass control # the effect of the method is that the NewMessage Return slot will be filled, return it # (this is what is moved _inside_ above loop for such expressions that are calls (or constants)) Virtual::NewReturn.new( method.return_type ) end - end -end \ No newline at end of file +end diff --git a/lib/compiler/compound_expressions.rb b/lib/compiler/compound_expressions.rb new file mode 100644 index 00000000..145ff5b5 --- /dev/null +++ b/lib/compiler/compound_expressions.rb @@ -0,0 +1,14 @@ +module Compiler + +# attr_reader :values + def compile_array expession, context + to.do + end +# attr_reader :key , :value + def compile_association context + to.do + end + def compile_hash context + to.do + end +end diff --git a/lib/compiler/expression_list.rb b/lib/compiler/expression_list.rb new file mode 100644 index 00000000..e4c5f613 --- /dev/null +++ b/lib/compiler/expression_list.rb @@ -0,0 +1,6 @@ +module Compiler +# list - attr_reader :expressions + def compile_list expession , method , message + expession.expressions.collect { |part| part.compile( method, message ) } + end +end diff --git a/lib/ast/function_expression.rb b/lib/compiler/function_expression.rb similarity index 76% rename from lib/ast/function_expression.rb rename to lib/compiler/function_expression.rb index 880d4262..d7487c45 100644 --- a/lib/ast/function_expression.rb +++ b/lib/compiler/function_expression.rb @@ -1,20 +1,19 @@ -module Ast - class FunctionExpression < Expression -# attr_reader :name, :params, :body , :receiver - def compile method , message - args = params.collect do |p| +module Compiler +# function attr_reader :name, :params, :body , :receiver + def compile_function expression, method , message + args = expession.params.collect do |p| raise "error, argument must be a identifier, not #{p}" unless p.is_a? NameExpression p.name end - r = receiver ? receiver.compile(method,message) : Virtual::Self.new() - new_method = Virtual::CompiledMethod.new(name , args , r ) + r = expession.receiver ? expession.receiver.compile(method,message) : Virtual::Self.new() + new_method = Virtual::CompiledMethod.new(expession.name , args , r ) new_method.class_name = r.is_a?(Virtual::BootClass) ? r.name : method.class_name clazz = Virtual::BootSpace.space.get_or_create_class(new_method.class_name) clazz.add_instance_method new_method #frame = frame.new_frame return_type = nil - body.each do |ex| + expession.body.each do |ex| return_type = ex.compile(new_method,message ) raise return_type.inspect if return_type.is_a? Virtual::Instruction end @@ -24,7 +23,7 @@ module Ast def scratch args = [] locals = {} - params.each_with_index do |param , index| + expession.params.each_with_index do |param , index| arg = param.name register = Virtual::RegisterReference.new(Virtual::RegisterMachine.instance.receiver_register).next_reg_use(index + 1) arg_value = Virtual::Integer.new(register) @@ -33,15 +32,15 @@ module Ast end # class depends on receiver me = Virtual::Integer.new( Virtual::RegisterMachine.instance.receiver_register ) - if receiver.nil? + if expession.receiver.nil? clazz = context.current_class else - c = context.object_space.get_or_create_class receiver.name.to_sym + c = context.object_space.get_or_create_class expession.receiver.name.to_sym clazz = c.meta_class end function = Virtual::Function.new(name , me , args ) - clazz.add_code_function function + clazz.add_code_function function parent_locals = context.locals parent_function = context.function @@ -49,12 +48,12 @@ module Ast context.function = function last_compiled = nil - body.each do |b| + expession.body.each do |b| puts "compiling in function #{b}" last_compiled = b.compile(context) raise "alarm #{last_compiled} \n #{b}" unless last_compiled.is_a? Virtual::Word end - + return_reg = Virtual::Integer.new(Virtual::RegisterMachine.instance.return_register) if last_compiled.is_a?(Virtual::IntegerConstant) or last_compiled.is_a?(Virtual::ObjectConstant) return_reg.load function , last_compiled if last_compiled.register_symbol != return_reg.register_symbol @@ -62,10 +61,9 @@ module Ast return_reg.move( function, last_compiled ) if last_compiled.register_symbol != return_reg.register_symbol end function.set_return return_reg - + context.locals = parent_locals context.function = parent_function function end - end -end \ No newline at end of file +end diff --git a/lib/ast/if_expression.rb b/lib/compiler/if_expression.rb similarity index 90% rename from lib/ast/if_expression.rb rename to lib/compiler/if_expression.rb index 358e0cda..57bef3a8 100644 --- a/lib/ast/if_expression.rb +++ b/lib/compiler/if_expression.rb @@ -1,17 +1,16 @@ -module Ast - class IfExpression < Expression -# attr_reader :cond, :if_true, :if_false +module Compiler +# if - attr_reader :cond, :if_true, :if_false - def compile method , message + def compile_if expression , method , message # 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.new_block "if_merge" # last one, created first true_block = method.new_block "if_true" # second, linked in after current, before merge false_block = method.new_block "if_false" # directly next in order, ie if we don't jump we land here - + is = cond.compile(method,message) - # TODO should/will use different branches for different conditions. + # TODO should/will use different branches for different conditions. # just a scetch : cond_val = cond_val.is_true?(method) unless cond_val.is_a? Virtual::BranchCondition method.add_code Virtual::IsTrueBranch.new( true_block ) @@ -38,5 +37,4 @@ module Ast #TODO should return the union of the true and false types last end - end -end \ No newline at end of file +end diff --git a/lib/ast/module_expression.rb b/lib/compiler/module_expression.rb similarity index 55% rename from lib/ast/module_expression.rb rename to lib/compiler/module_expression.rb index d26d3d9e..ff4a40d6 100644 --- a/lib/ast/module_expression.rb +++ b/lib/compiler/module_expression.rb @@ -1,26 +1,22 @@ -module Ast - class ModuleExpression < Expression -# attr_reader :name ,:expressions - def compile context +module Compiler +# module attr_reader :name ,:expressions + def compile_module expression , context return clazz end - end - class ClassExpression < ModuleExpression - def compile method , message + def compile_class expression , method , message clazz = ::Virtual::BootSpace.space.get_or_create_class name puts "Created class #{clazz.name.inspect}" - expressions.each do |expression| + expression.expressions.each do |expr| # check if it's a function definition and add # if not, execute it, but that does means we should be in salama (executable), not ruby. ie throw an error for now - raise "only functions for now #{expression.inspect}" unless expression.is_a? Ast::FunctionExpression + raise "only functions for now #{expr.inspect}" unless expr.is_a? Ast::FunctionExpression #puts "compiling expression #{expression}" - expression_value = expression.compile(method,message ) + expression_value = expr.compile(method,message ) clazz.add_instance_method(expression_value) #puts "compiled expression #{expression_value.inspect}" end return clazz end - end -end \ No newline at end of file +end diff --git a/lib/compiler/operator_expressions.rb b/lib/compiler/operator_expressions.rb new file mode 100644 index 00000000..825e5c05 --- /dev/null +++ b/lib/compiler/operator_expressions.rb @@ -0,0 +1,7 @@ +module Compiler +# operator attr_reader :operator, :left, :right + def compile_operator expression, method , message + call = CallSiteExpression.new( operator , [right] , left ) + call.compile(method,message) + end +end diff --git a/lib/ast/return_expression.rb b/lib/compiler/return_expression.rb similarity index 78% rename from lib/ast/return_expression.rb rename to lib/compiler/return_expression.rb index fcf820d5..3fe9370b 100644 --- a/lib/ast/return_expression.rb +++ b/lib/compiler/return_expression.rb @@ -1,10 +1,10 @@ -module Ast - class ReturnExpression < Expression -# attr_reader :expression - def compile scope ,method +module Compiler + +# return attr_reader :expression + def compile_return expression, scope ,method Virtual::Reference.new end - def sc + def old into = context.function puts "compiling return expression #{expression}, now return in return_regsiter" expression_value = expression.compile(context) @@ -12,14 +12,11 @@ module Ast return_reg = Virtual::Integer.new(Virtual::RegisterMachine.instance.return_register) if expression_value.is_a?(Virtual::IntegerConstant) or expression_value.is_a?(Virtual::ObjectConstant) - return_reg.load into , expression_value + return_reg.load into , expression_value else return_reg.move( into, expression_value ) if expression_value.register_symbol != return_reg.register_symbol end #function.set_return return_reg return return_reg end - end - - -end \ No newline at end of file +end diff --git a/lib/ast/while_expression.rb b/lib/compiler/while_expression.rb similarity index 75% rename from lib/ast/while_expression.rb rename to lib/compiler/while_expression.rb index 8f4dd427..383dc558 100644 --- a/lib/ast/while_expression.rb +++ b/lib/compiler/while_expression.rb @@ -1,16 +1,16 @@ -module Ast - class WhileExpression < Expression -# attr_reader :condition, :body - def compile method , message +module Compiler + +# while- attr_reader :condition, :body + def compile_while expression, method , message start = Virtual::Label.new("while_start") method.add_code start - is = condition.compile(method,message) + is = expression.condition.compile(method,message) branch = Virtual::IsTrueBranch.new "while" merge = Virtual::Label.new(branch.name) branch.other = merge #false jumps to end of while method.add_code branch last = is - body.each do |part| + expression.body.each do |part| last = part.compile(method,message ) raise part.inspect if last.nil? end @@ -22,5 +22,4 @@ module Ast method.current = merge last end - end -end \ No newline at end of file +end diff --git a/lib/salama.rb b/lib/salama.rb index 82027a1f..291fcefa 100644 --- a/lib/salama.rb +++ b/lib/salama.rb @@ -4,7 +4,7 @@ module Parfait eval(File.open("./lib/parfait/hash.rb").read) end -require "ast/all" +require "compiler" require "stream_reader" require "elf/object_writer" require 'salama-reader'