move compiler to typed
starting to get rid of soml, bit by bit
This commit is contained in:
23
lib/typed/ast_helper.rb
Normal file
23
lib/typed/ast_helper.rb
Normal file
@ -0,0 +1,23 @@
|
||||
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
|
207
lib/typed/compiler.rb
Normal file
207
lib/typed/compiler.rb
Normal file
@ -0,0 +1,207 @@
|
||||
module Typed
|
||||
# Compiling is the conversion of the AST into 2 things:
|
||||
# - code (ie sequences of Instructions inside Methods)
|
||||
# - an object graph containing all the Methods, their classes and Constants
|
||||
#
|
||||
# Some compile methods just add code, some may add Instructions while
|
||||
# others instantiate Class and Method objects
|
||||
#
|
||||
# Everything in ruby is an statement, ie returns a value. So the effect of every compile
|
||||
# is that a value is put into the ReturnSlot of the current Message.
|
||||
# The compile method (so every compile method) returns the value that it deposits.
|
||||
#
|
||||
# The process uses a visitor pattern (from AST::Processor) to dispatch according to the
|
||||
# type the statement. So a s(:if xx) will become an on_if(node) call.
|
||||
# This makes the dispatch extensible, ie Expressions may be added by external code,
|
||||
# as long as matching compile methods are supplied too.
|
||||
#
|
||||
# A compiler can also be used to generate code for a method without AST nodes. In the same way
|
||||
# compile methods do, ie adding Instructions etc. In this way code may be generated that
|
||||
# has no code equivalent.
|
||||
#
|
||||
# The Compiler also keeps a list of used registers, from which one may take to use and return to
|
||||
# when done. The list may be reset.
|
||||
#
|
||||
# The Compiler also carries method and class instance variables. The method is where code is
|
||||
# added to (with add_code). To be more precise, the @current instruction is where code is added
|
||||
# to, and that may be changed with set_current
|
||||
|
||||
# All Statements reset the registers and return nil.
|
||||
# Expressions use registers and return the register where their value is stored.
|
||||
|
||||
# Helper function to create a new compiler and compie the statement(s)
|
||||
def self.compile statement
|
||||
compiler = Compiler.new
|
||||
code = Soml.ast_to_code statement
|
||||
compiler.process code
|
||||
end
|
||||
|
||||
class Compiler
|
||||
|
||||
def initialize( method = nil )
|
||||
@regs = []
|
||||
return unless method
|
||||
@method = method
|
||||
@clazz = method.for_class
|
||||
@current = method.instructions
|
||||
end
|
||||
attr_reader :clazz , :method
|
||||
|
||||
|
||||
# Dispatches `code` according to it's class name, for class NameExpression
|
||||
# a method named `on_NameExpression` is invoked with one argument, the `code`
|
||||
#
|
||||
# @param [Soml::Code, nil] code
|
||||
def process(code)
|
||||
name = code.class.name.split("::").last
|
||||
# Invoke a specific handler
|
||||
on_handler = :"on_#{name}"
|
||||
if respond_to? on_handler
|
||||
return send on_handler, code
|
||||
else
|
||||
raise "No handler on_#{name}(code) #{code.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
# {#process}es each code from `codes` and returns an array of
|
||||
# results.
|
||||
#
|
||||
def process_all(codes)
|
||||
codes.to_a.map do |code|
|
||||
process code
|
||||
end
|
||||
end
|
||||
|
||||
def on_Statements(codes)
|
||||
process_all codes.statements
|
||||
end
|
||||
|
||||
# create the method, do some checks and set it as the current method to be added to
|
||||
# class_name and method_name are pretty clear, args are given as a ruby array
|
||||
def create_method( class_name , method_name , args = {})
|
||||
raise "create_method #{class_name}.#{class_name.class}" unless class_name.is_a? Symbol
|
||||
clazz = Register.machine.space.get_class_by_name class_name
|
||||
raise "No such class #{class_name}" unless clazz
|
||||
create_method_for( clazz , method_name , args)
|
||||
end
|
||||
|
||||
# create a method for the given class ( Parfait class object)
|
||||
# method_name is a Symbol
|
||||
# args a ruby array
|
||||
# the created method is set as the current and the given class too
|
||||
# return the compiler (for chaining)
|
||||
def create_method_for clazz , method_name , args
|
||||
@clazz = clazz
|
||||
raise "create_method #{method_name}.#{method_name.class}" unless method_name.is_a? Symbol
|
||||
arguments = []
|
||||
if( args.is_a? Array)
|
||||
arguments = args
|
||||
else
|
||||
args.each do | name , type |
|
||||
arguments << Parfait::Variable.new( type , name.to_sym)
|
||||
end
|
||||
end
|
||||
@method = clazz.create_instance_method( method_name , Parfait.new_list(arguments))
|
||||
self
|
||||
end
|
||||
|
||||
# add method entry and exit code. Mainly save_return for the enter and
|
||||
# message shuffle and FunctionReturn for the return
|
||||
# return self for chaining
|
||||
def init_method
|
||||
source = "_init_method"
|
||||
name = "#{method.for_class.name}.#{method.name}"
|
||||
@method.instructions = Register::Label.new(source, name)
|
||||
@current = enter = method.instructions
|
||||
add_code Register::Label.new( source, "return #{name}")
|
||||
#load the return address into pc, affecting return. (other cpus have commands for this, but not arm)
|
||||
add_code Register::FunctionReturn.new( source , Register.message_reg , Register.resolve_index(:message , :return_address) )
|
||||
@current = enter
|
||||
self
|
||||
end
|
||||
|
||||
# set the insertion point (where code is added with add_code)
|
||||
def set_current c
|
||||
@current = c
|
||||
end
|
||||
|
||||
# add an instruction after the current (insertion point)
|
||||
# the added instruction will become the new insertion point
|
||||
def add_code instruction
|
||||
unless instruction.is_a?(Register::Instruction)
|
||||
raise instruction.to_s
|
||||
end
|
||||
@current.insert(instruction) #insert after current
|
||||
@current = instruction
|
||||
self
|
||||
end
|
||||
|
||||
# require a (temporary) register. code must give this back with release_reg
|
||||
def use_reg type , value = nil
|
||||
if @regs.empty?
|
||||
reg = Register.tmp_reg(type , value)
|
||||
else
|
||||
reg = @regs.last.next_reg_use(type , value)
|
||||
end
|
||||
@regs << reg
|
||||
return reg
|
||||
end
|
||||
|
||||
def copy reg , source
|
||||
copied = use_reg reg.type
|
||||
add_code Reister.transfer source , reg , copied
|
||||
copied
|
||||
end
|
||||
|
||||
# releasing a register (accuired by use_reg) makes it available for use again
|
||||
# thus avoiding possibly using too many registers
|
||||
def release_reg reg
|
||||
last = @regs.pop
|
||||
raise "released register in wrong order, expect #{last} but was #{reg}" if reg != last
|
||||
end
|
||||
|
||||
# reset the registers to be used. Start at r4 for next usage.
|
||||
# Every statement starts with this, meaning each statement may use all registers, but none
|
||||
# get saved. Statements have affect on objects.
|
||||
def reset_regs
|
||||
@regs.clear
|
||||
end
|
||||
|
||||
# ensure the name given is not space and raise exception otherwise
|
||||
# return the name for chaining
|
||||
def no_space name
|
||||
raise "space is a reserved name" if name == :space
|
||||
name
|
||||
end
|
||||
|
||||
# load the soml files from parfait directory
|
||||
# Need to remove this and put it back into ruby code
|
||||
def self.load_parfait
|
||||
["word","class","type","message" ,"integer", "object"].each do |o|
|
||||
str = File.open(File.expand_path("parfait/#{o}.soml", File.dirname(__FILE__))).read
|
||||
syntax = Parser::Salama.new.parse_with_debug(str, reporter: Parslet::ErrorReporter::Deepest.new)
|
||||
parts = Parser::Transform.new.apply(syntax)
|
||||
code = Soml.ast_to_code parts
|
||||
self.new.process( code )
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require_relative "ast_helper"
|
||||
require_relative "compiler/assignment"
|
||||
require_relative "compiler/basic_values"
|
||||
require_relative "compiler/call_site"
|
||||
require_relative "compiler/class_field"
|
||||
require_relative "compiler/class_statement"
|
||||
require_relative "compiler/collections"
|
||||
require_relative "compiler/field_def"
|
||||
require_relative "compiler/field_access"
|
||||
require_relative "compiler/function_definition"
|
||||
require_relative "compiler/if_statement"
|
||||
require_relative "compiler/name_expression"
|
||||
require_relative "compiler/operator_value"
|
||||
require_relative "compiler/return_statement"
|
||||
require_relative "compiler/statement_list"
|
||||
require_relative "compiler/while_statement"
|
69
lib/typed/compiler/README.md
Normal file
69
lib/typed/compiler/README.md
Normal file
@ -0,0 +1,69 @@
|
||||
### Compiling
|
||||
|
||||
The Ast (abstract syntax tree) is created by [salama-reader](https://github.com/salama/salama-reader)
|
||||
gem and the classes defined there
|
||||
|
||||
The code in this directory compiles the AST to the virtual 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 type for ast, named along on_xxx with xxx as the type
|
||||
|
||||
#### Compiler holds scope
|
||||
|
||||
The Compiler instance can hold arbitrary scope needed during the compilation. Since we compile Soml
|
||||
(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.
|
||||
|
||||
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 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 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 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.
|
||||
|
||||
### Distinctly future proof
|
||||
|
||||
Soml 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.
|
||||
|
||||
In fact, Soml's 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.
|
33
lib/typed/compiler/assignment.rb
Normal file
33
lib/typed/compiler/assignment.rb
Normal file
@ -0,0 +1,33 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_Assignment statement
|
||||
reset_regs # statements reset registers, ie have all at their disposal
|
||||
#puts statement.inspect
|
||||
# name , value = *statement
|
||||
name_s = no_space statement.name
|
||||
v = process(statement.value)
|
||||
raise "Not register #{v}" unless v.is_a?(Register::RegisterValue)
|
||||
code = nil
|
||||
if( index = @method.has_arg(name_s.name))
|
||||
# TODO, check type @method.arguments[index].type
|
||||
code = Register.set_slot(statement , v , :message , Parfait::Message.get_indexed(index) )
|
||||
else # or a local so it is in the frame
|
||||
index = @method.has_local( name_s.name )
|
||||
if(index)
|
||||
# TODO, check type @method.locals[index].type
|
||||
frame = use_reg(:Frame)
|
||||
add_code Register.get_slot(statement , :message , :frame , frame )
|
||||
code = Register.set_slot(statement , v , frame , Parfait::Frame.get_indexed(index) )
|
||||
end
|
||||
end
|
||||
if( code )
|
||||
#puts "addin code #{code}"
|
||||
add_code code
|
||||
else
|
||||
raise "must define variable #{name} before using it in #{@method.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
56
lib/typed/compiler/basic_values.rb
Normal file
56
lib/typed/compiler/basic_values.rb
Normal file
@ -0,0 +1,56 @@
|
||||
module Typed
|
||||
# collection of the simple ones, int and strings and such
|
||||
|
||||
Compiler.class_eval do
|
||||
|
||||
# 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 Register.
|
||||
# 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_code Register::LoadConstant.new( expression, int , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_TrueExpression expression
|
||||
reg = use_reg :Boolean
|
||||
add_code Register::LoadConstant.new( expression, true , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_FalseExpression expression
|
||||
reg = use_reg :Boolean
|
||||
add_code Register::LoadConstant.new( expression, false , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_NilExpression expression
|
||||
reg = use_reg :NilClass
|
||||
add_code Register::LoadConstant.new( expression, nil , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_StringExpression expression
|
||||
value = Parfait.new_word expression.value.to_sym
|
||||
reg = use_reg :Word
|
||||
Register.machine.constants << value
|
||||
add_code Register::LoadConstant.new( expression, value , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_ClassExpression expression
|
||||
name = expression.value
|
||||
clazz = Parfait::Space.object_space.get_class_by_name! name
|
||||
raise "No such class #{name}" unless clazz
|
||||
reg = use_reg :MetaClass , clazz
|
||||
add_code Register::LoadConstant.new( expression, clazz , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
end
|
||||
end
|
71
lib/typed/compiler/call_site.rb
Normal file
71
lib/typed/compiler/call_site.rb
Normal file
@ -0,0 +1,71 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_CallSite statement
|
||||
#puts statement
|
||||
# name_s , arguments , receiver = *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
|
||||
new_message = Register.resolve_to_register(:new_message)
|
||||
add_code Register.get_slot(statement, :message , :next_message , new_message )
|
||||
if statement.receiver
|
||||
me = process( statement.receiver )
|
||||
else
|
||||
me = use_reg @method.for_class.name
|
||||
add_code Register.get_slot(statement, :message , :receiver , me )
|
||||
end
|
||||
if(me.type == :MetaClass)
|
||||
clazz = me.value.meta
|
||||
else
|
||||
# now we have to resolve the method name (+ receiver) into a callable method
|
||||
clazz = Register.machine.space.get_class_by_name(me.type)
|
||||
end
|
||||
# move our receiver there
|
||||
add_code Register.set_slot( statement , me , :new_message , :receiver)
|
||||
|
||||
set_message_details(statement , statement.arguments)
|
||||
set_arguments(statement.arguments)
|
||||
ret = use_reg( :Integer ) #TODO real return type
|
||||
do_call(clazz , statement)
|
||||
# the effect of the method is that the NewMessage Return slot will be filled, return it
|
||||
# but move it into a register too
|
||||
add_code Register.get_slot(statement, :new_message , :return_value , ret )
|
||||
ret
|
||||
end
|
||||
|
||||
private
|
||||
def do_call clazz , statement
|
||||
name = statement.name
|
||||
#puts "clazz #{clazz.name}"
|
||||
raise "No such class" unless clazz
|
||||
method = clazz.resolve_method(name)
|
||||
#puts Register.machine.space.get_class_by_name(:Integer).method_names.to_a
|
||||
raise "Method not implemented #{clazz.name}.#{name}" unless method
|
||||
Register.issue_call( self , method )
|
||||
end
|
||||
def set_message_details name_s , arguments
|
||||
name = name_s.name
|
||||
# load method name and set to new message (for exceptions/debug)
|
||||
name_tmp = use_reg(:Word)
|
||||
add_code Register::LoadConstant.new(name_s, name , name_tmp)
|
||||
add_code Register.set_slot( name_s , name_tmp , :new_message , :name)
|
||||
# next arguments. first length then args
|
||||
len_tmp = use_reg(:Integer , arguments.to_a.length )
|
||||
add_code Register::LoadConstant.new(name_s, arguments.to_a.length , len_tmp)
|
||||
add_code Register.set_slot( name_s , len_tmp , :new_message , :indexed_length)
|
||||
end
|
||||
def set_arguments arguments
|
||||
# reset tmp regs for each and load result into new_message
|
||||
arguments.to_a.each_with_index do |arg , i|
|
||||
reset_regs
|
||||
# processing should return the register with the value
|
||||
val = process( arg)
|
||||
raise "Not register #{val}" unless val.is_a?(Register::RegisterValue)
|
||||
# which we load int the new_message at the argument's index (the one comes from c index)
|
||||
set = Register.set_slot( arg , val , :new_message , Parfait::Message.get_indexed(i+1))
|
||||
add_code set
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
lib/typed/compiler/class_field.rb
Normal file
18
lib/typed/compiler/class_field.rb
Normal file
@ -0,0 +1,18 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_ClassField statement
|
||||
#puts statement.inspect
|
||||
#type , name , value = *statement
|
||||
|
||||
for_class = @clazz
|
||||
raise "no class" unless for_class
|
||||
index = for_class.instance_type.variable_index(statement.name)
|
||||
#raise "class field already defined:#{name} for class #{for_class.name}" if index
|
||||
#puts "Define field #{name} on class #{for_class.name}"
|
||||
for_class.instance_type.add_instance_variable( statement.name , statement.type )
|
||||
|
||||
return nil # statements don't reurn values, only expressions
|
||||
end
|
||||
end
|
||||
end
|
14
lib/typed/compiler/class_statement.rb
Normal file
14
lib/typed/compiler/class_statement.rb
Normal file
@ -0,0 +1,14 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_ClassStatement statement
|
||||
#puts statement.inspect
|
||||
raise "classes dont yet play babushka, get coding #{name}" if @clazz
|
||||
@clazz = Parfait::Space.object_space.get_class_by_name! statement.name
|
||||
#puts "Compiling class #{@clazz.name.inspect}"
|
||||
statement_value = process(statement.statements).last
|
||||
@clazz = nil
|
||||
return statement_value
|
||||
end
|
||||
end
|
||||
end
|
13
lib/typed/compiler/collections.rb
Normal file
13
lib/typed/compiler/collections.rb
Normal file
@ -0,0 +1,13 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
# 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
|
26
lib/typed/compiler/field_access.rb
Normal file
26
lib/typed/compiler/field_access.rb
Normal file
@ -0,0 +1,26 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_FieldAccess statement
|
||||
# receiver_ast , field_ast = *statement
|
||||
receiver = process(statement.receiver)
|
||||
|
||||
clazz = Register.machine.space.get_class_by_name receiver.type
|
||||
|
||||
field_name = statement.field.name
|
||||
|
||||
|
||||
index = clazz.instance_type.variable_index(field_name)
|
||||
raise "field access, but no such field:#{field_name} for class #{clazz.name}" unless index
|
||||
value = use_reg(clazz.instance_type.type_at(index))
|
||||
|
||||
add_code Register.get_slot(statement , receiver , index, value)
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
def on_receiver expression
|
||||
process expression.first
|
||||
end
|
||||
end
|
||||
end
|
16
lib/typed/compiler/field_def.rb
Normal file
16
lib/typed/compiler/field_def.rb
Normal file
@ -0,0 +1,16 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
include AST::Sexp
|
||||
|
||||
def on_FieldDef statement
|
||||
reset_regs # field_def is a statement, no return and all regs
|
||||
#puts statement.inspect
|
||||
# type , name , value = *statement
|
||||
name_s = no_space( statement.name.value )
|
||||
@method.ensure_local( name_s, statement.type ) unless( @method.has_arg(name_s))
|
||||
# if there is a value assigned, process it as am assignemnt statement (kind of call on_assign)
|
||||
process( Soml::Assignment.new(statement.name , statement.value ) ) if statement.value
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
43
lib/typed/compiler/function_definition.rb
Normal file
43
lib/typed/compiler/function_definition.rb
Normal file
@ -0,0 +1,43 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_FunctionStatement statement
|
||||
#puts statement.inspect
|
||||
# return_type , name , parameters, kids , receiver = *statement
|
||||
name = statement.name
|
||||
raise "Already in method #{@method}" if @method
|
||||
|
||||
args = statement.parameters.collect do |p|
|
||||
Parfait::Variable.new( *p )
|
||||
end
|
||||
|
||||
class_method = nil
|
||||
if(statement.receiver )
|
||||
if( statement.receiver.first == :self) #class method
|
||||
class_method = @clazz
|
||||
@clazz = @clazz.meta
|
||||
else
|
||||
raise "Not covered #{statement.receiver}"
|
||||
end
|
||||
end
|
||||
|
||||
@method = @clazz.get_instance_method( name )
|
||||
if(@method)
|
||||
#puts "Warning, redefining method #{name}" unless name == :main
|
||||
#TODO check args / type compatibility
|
||||
init_method
|
||||
else
|
||||
create_method_for(@clazz, name , args ).init_method
|
||||
end
|
||||
@method.source = statement
|
||||
#puts "compile method #{@method.name}"
|
||||
|
||||
process(statement.statements)
|
||||
|
||||
@clazz = class_method if class_method
|
||||
@method = nil
|
||||
# function definition is a statement, does not return any value
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
35
lib/typed/compiler/if_statement.rb
Normal file
35
lib/typed/compiler/if_statement.rb
Normal file
@ -0,0 +1,35 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
# 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
|
||||
# condition = condition.first
|
||||
|
||||
reset_regs
|
||||
process(statement.condition)
|
||||
|
||||
branch_class = Object.const_get "Register::Is#{statement.branch_type.capitalize}"
|
||||
true_block = Register::Label.new(statement, "if_true")
|
||||
add_code branch_class.new( statement.condition , true_block )
|
||||
|
||||
# compile the false block
|
||||
reset_regs
|
||||
process(statement.if_false) if statement.if_false.statements
|
||||
merge = Register::Label.new(statement , "if_merge")
|
||||
add_code Register::Branch.new(statement.if_false, merge )
|
||||
|
||||
# compile the true block
|
||||
add_code true_block
|
||||
reset_regs
|
||||
process(statement.if_true)
|
||||
|
||||
#puts "compiled if: end"
|
||||
add_code merge
|
||||
|
||||
nil # statements don't return anything
|
||||
end
|
||||
end
|
||||
end
|
45
lib/typed/compiler/name_expression.rb
Normal file
45
lib/typed/compiler/name_expression.rb
Normal file
@ -0,0 +1,45 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
# 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_NameExpression statement
|
||||
name = statement.name
|
||||
if( name == :self)
|
||||
ret = use_reg @clazz.name
|
||||
add_code Register.get_slot(statement , :message , :receiver , ret )
|
||||
return ret
|
||||
end
|
||||
if(name == :space)
|
||||
space = Parfait::Space.object_space
|
||||
reg = use_reg :Space , space
|
||||
add_code Register::LoadConstant.new( statement, space , reg )
|
||||
return reg
|
||||
end
|
||||
if(name == :message)
|
||||
reg = use_reg :Message
|
||||
add_code Register::RegisterTransfer.new( statement, Register.message_reg , reg )
|
||||
return reg
|
||||
end
|
||||
# either an argument, so it's stored in message
|
||||
if( index = @method.has_arg(name))
|
||||
ret = use_reg @method.arguments[index].value_type
|
||||
add_code Register.get_slot(statement , :message , Parfait::Message.get_indexed(index), ret )
|
||||
return ret
|
||||
else # or a local so it is in the frame
|
||||
index = @method.has_local( name )
|
||||
if(index)
|
||||
frame = use_reg :Frame
|
||||
add_code Register.get_slot(statement , :message , :frame , frame )
|
||||
ret = use_reg @method.locals[index].value_type
|
||||
add_code Register.get_slot(statement , frame , Parfait::Frame.get_indexed(index), ret )
|
||||
return ret
|
||||
end
|
||||
end
|
||||
raise "must define variable '#{name}' before using it"
|
||||
end
|
||||
|
||||
end #module
|
||||
end
|
18
lib/typed/compiler/operator_value.rb
Normal file
18
lib/typed/compiler/operator_value.rb
Normal file
@ -0,0 +1,18 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_OperatorExpression statement
|
||||
#puts "operator #{statement.inspect}"
|
||||
# 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?(Register::RegisterValue)
|
||||
raise "Not register #{right_reg}" unless right_reg.is_a?(Register::RegisterValue)
|
||||
#puts "left #{left_reg}"
|
||||
#puts "right #{right_reg}"
|
||||
add_code Register::OperatorInstruction.new(statement,statement.operator,left_reg,right_reg)
|
||||
return left_reg # though this has wrong value attached
|
||||
end
|
||||
end
|
||||
end
|
10
lib/typed/compiler/return_statement.rb
Normal file
10
lib/typed/compiler/return_statement.rb
Normal file
@ -0,0 +1,10 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_ReturnStatement statement
|
||||
reg = process(statement.return_value)
|
||||
add_code Register.set_slot( statement, reg , :message , :return_value)
|
||||
nil # statements don't return
|
||||
end
|
||||
end
|
||||
end
|
9
lib/typed/compiler/statement_list.rb
Normal file
9
lib/typed/compiler/statement_list.rb
Normal file
@ -0,0 +1,9 @@
|
||||
module Typed
|
||||
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_statements statement
|
||||
process_all( statement.children )
|
||||
end
|
||||
end
|
||||
end
|
28
lib/typed/compiler/while_statement.rb
Normal file
28
lib/typed/compiler/while_statement.rb
Normal file
@ -0,0 +1,28 @@
|
||||
module Typed
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_WhileStatement statement
|
||||
#puts statement.inspect
|
||||
#branch_type , condition , statements = *statement
|
||||
|
||||
condition_label = Register::Label.new(statement.condition , "condition_label")
|
||||
# unconditionally branch to the condition upon entering the loop
|
||||
add_code Register::Branch.new(statement.condition,condition_label)
|
||||
|
||||
add_code start = Register::Label.new(statement , "while_start" )
|
||||
reset_regs
|
||||
process(statement.statements)
|
||||
|
||||
# This is where the loop starts, though in subsequent iterations it's in the middle
|
||||
add_code condition_label
|
||||
reset_regs
|
||||
process(statement.condition)
|
||||
|
||||
branch_class = Object.const_get "Register::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
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user