rename bosl to phisol
This commit is contained in:
78
lib/phisol/compiler/README.md
Normal file
78
lib/phisol/compiler/README.md
Normal file
@ -0,0 +1,78 @@
|
||||
### 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 Phisol
|
||||
(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 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.)
|
||||
|
||||
The objects the machine works on are:
|
||||
|
||||
- Message
|
||||
- Frame
|
||||
- Self
|
||||
- NewMessage
|
||||
|
||||
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, 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
|
||||
|
||||
Phisol 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, Phisol'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.
|
48
lib/phisol/compiler/basic_expressions.rb
Normal file
48
lib/phisol/compiler/basic_expressions.rb
Normal file
@ -0,0 +1,48 @@
|
||||
module Phisol
|
||||
# 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 we view ConstantExpressions like functions that return the value of the constant.
|
||||
# In other words, their storage is the return slot as it would be for a method
|
||||
|
||||
# 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
|
||||
|
||||
def on_int expression
|
||||
int = expression.first
|
||||
to = Virtual::Return.new(Virtual::Integer , int)
|
||||
@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 )
|
||||
to
|
||||
end
|
||||
|
||||
def on_false expression
|
||||
to = Virtual::Return.new(Virtual::Reference , false)
|
||||
@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 )
|
||||
to
|
||||
end
|
||||
|
||||
def on_string expression
|
||||
# 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 )
|
||||
to
|
||||
end
|
||||
end
|
||||
end
|
72
lib/phisol/compiler/callsite_expression.rb
Normal file
72
lib/phisol/compiler/callsite_expression.rb
Normal file
@ -0,0 +1,72 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
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
|
||||
if @method.class.name == :Integer
|
||||
me = Virtual::Self.new :int
|
||||
else
|
||||
me = Virtual::Self.new :ref
|
||||
end
|
||||
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))
|
||||
compiled_args = []
|
||||
arguments.to_a.each_with_index do |arg , i|
|
||||
#compile in the running method, ie before passing control
|
||||
val = process( arg)
|
||||
# move the compiled value to it's slot in the new message
|
||||
# + 1 as this is a ruby 0-start , but 0 is the last message ivar.
|
||||
# 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 )
|
||||
compiled_args << to
|
||||
end
|
||||
#method.source.add_code Virtual::MessageSend.new(name , me , compiled_args) #and pass control
|
||||
method = nil
|
||||
if(me.value)
|
||||
me = me.value
|
||||
if( me.is_a? Parfait::Class )
|
||||
raise "unimplemented #{code} me is #{me}"
|
||||
elsif( me.is_a? Symbol )
|
||||
# get the function from my class. easy peasy
|
||||
method = Virtual.machine.space.get_class_by_name(:Word).get_instance_method(name)
|
||||
raise "Method not implemented #{me.class}.#{code.name}" unless method
|
||||
@method.source.add_code Virtual::MethodCall.new( method )
|
||||
elsif( me.is_a? Fixnum )
|
||||
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
|
||||
raise "Method not implemented Integer.#{name}" unless method
|
||||
@method.source.add_code Virtual::MethodCall.new( method )
|
||||
else
|
||||
raise "unimplemented: \n#{code} \nfor #{ref.inspect}"
|
||||
end
|
||||
else
|
||||
if( me.type == :int)
|
||||
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
|
||||
raise "Method not implemented Integer.#{name}" unless method
|
||||
@method.source.add_code Virtual::MethodCall.new( method )
|
||||
else
|
||||
method = @clazz.get_instance_method(name)
|
||||
raise "Method not implemented #{@clazz.name}.#{name}" unless method
|
||||
@method.source.add_code Virtual::MethodCall.new( method )
|
||||
end
|
||||
end
|
||||
raise "Method not implemented #{me.value}.#{name}" unless method
|
||||
# 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::Return.new( method.source.return_type )
|
||||
end
|
||||
end
|
||||
end
|
23
lib/phisol/compiler/class_field.rb
Normal file
23
lib/phisol/compiler/class_field.rb
Normal file
@ -0,0 +1,23 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_class_field expression
|
||||
#puts expression.inspect
|
||||
type , name , value = *expression
|
||||
|
||||
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}"
|
||||
index = for_class.object_layout.add_instance_variable( name ) #TODO need typing
|
||||
|
||||
if value
|
||||
value = process( value )
|
||||
raise "value #{value}" #tbc
|
||||
end
|
||||
|
||||
Virtual::Return.new( index )
|
||||
end
|
||||
end
|
||||
end
|
13
lib/phisol/compiler/compound_expressions.rb
Normal file
13
lib/phisol/compiler/compound_expressions.rb
Normal file
@ -0,0 +1,13 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
# attr_reader :values
|
||||
def on_array expession, context
|
||||
end
|
||||
# attr_reader :key , :value
|
||||
def on_association context
|
||||
end
|
||||
def on_hash context
|
||||
end
|
||||
end
|
||||
end
|
8
lib/phisol/compiler/expression_list.rb
Normal file
8
lib/phisol/compiler/expression_list.rb
Normal file
@ -0,0 +1,8 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
# list - attr_reader :expressions
|
||||
def on_expressions expession
|
||||
process_all( expession.children )
|
||||
end
|
||||
end
|
||||
end
|
27
lib/phisol/compiler/field_access.rb
Normal file
27
lib/phisol/compiler/field_access.rb
Normal file
@ -0,0 +1,27 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_field_access expression
|
||||
#puts expression.inspect
|
||||
receiver_ast , field_ast = *expression
|
||||
receiver = receiver_ast.first_from(:name)
|
||||
field_name = field_ast.first_from(:name)
|
||||
|
||||
case receiver
|
||||
when :self
|
||||
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::SelfsSlot.new(index, :int ) , value )
|
||||
when :message
|
||||
#message Slot
|
||||
raise "message not yet"
|
||||
else
|
||||
#arg / frame Slot
|
||||
raise "frame not implemented"
|
||||
end
|
||||
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
17
lib/phisol/compiler/field_def.rb
Normal file
17
lib/phisol/compiler/field_def.rb
Normal file
@ -0,0 +1,17 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_field_def expression
|
||||
#puts expression.inspect
|
||||
type , name , value = *expression
|
||||
|
||||
index = @method.ensure_local( name , type )
|
||||
|
||||
if value
|
||||
value = process( value )
|
||||
end
|
||||
|
||||
Virtual::Return.new( type , value )
|
||||
end
|
||||
end
|
||||
end
|
54
lib/phisol/compiler/function_expression.rb
Normal file
54
lib/phisol/compiler/function_expression.rb
Normal file
@ -0,0 +1,54 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_function expression
|
||||
#puts expression.inspect
|
||||
return_type , name , parameters, kids , receiver = *expression
|
||||
name = name.to_a.first
|
||||
args = parameters.to_a.collect do |p|
|
||||
raise "error, argument must be a identifier, not #{p}" unless p.type == :parameter
|
||||
Parfait::Variable.new( *p)
|
||||
end
|
||||
|
||||
if receiver
|
||||
# compiler will always return slot. with known value or not
|
||||
r = receiver.first
|
||||
if( r.is_a? Parfait::Class )
|
||||
class_name = r.value.name
|
||||
else
|
||||
if( r != :self)
|
||||
raise "unimplemented case in function #{r}"
|
||||
else
|
||||
r = Virtual::Self.new()
|
||||
class_name = method.for_class.name
|
||||
end
|
||||
end
|
||||
else
|
||||
r = @clazz
|
||||
class_name = @clazz.name
|
||||
end
|
||||
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}"
|
||||
|
||||
|
||||
#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.source.return_type = return_type
|
||||
@method = nil
|
||||
Virtual::Return.new(return_type)
|
||||
end
|
||||
end
|
||||
end
|
37
lib/phisol/compiler/if_expression.rb
Normal file
37
lib/phisol/compiler/if_expression.rb
Normal file
@ -0,0 +1,37 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
# if - attr_reader :cond, :if_true, :if_false
|
||||
|
||||
def on_if expression
|
||||
condition , if_true , if_false = *expression
|
||||
condition = condition.first
|
||||
# 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
|
||||
|
||||
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 Register::IsZeroBranch.new( condition , true_block )
|
||||
|
||||
# compile the true block (as we think of it first, even it is second in sequential order)
|
||||
@method.source.current true_block
|
||||
|
||||
last = process_all(if_true).last
|
||||
|
||||
# compile the false block
|
||||
@method.source.current false_block
|
||||
last = process_all(if_false).last if if_false
|
||||
@method.source.add_code Register::AlwaysBranch.new(expression, merge_block )
|
||||
|
||||
#puts "compiled if: end"
|
||||
@method.source.current merge_block
|
||||
|
||||
#TODO should return the union of the true and false types
|
||||
last
|
||||
end
|
||||
end
|
||||
end
|
20
lib/phisol/compiler/module_expression.rb
Normal file
20
lib/phisol/compiler/module_expression.rb
Normal file
@ -0,0 +1,20 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
# module attr_reader :name ,:expressions
|
||||
def on_module expression
|
||||
name , rest = *expression
|
||||
return process_all(rest).last
|
||||
end
|
||||
|
||||
def on_class expression
|
||||
#puts expression.inspect
|
||||
name , derives , expressions = *expression
|
||||
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
|
||||
end
|
26
lib/phisol/compiler/name_expression.rb
Normal file
26
lib/phisol/compiler/name_expression.rb
Normal file
@ -0,0 +1,26 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
# 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 issued.
|
||||
# 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(@clazz)) if name == :self
|
||||
# either an argument, so it's stored in message
|
||||
if( index = @method.has_arg(name))
|
||||
type = @method.arguments[index].type
|
||||
return Virtual::ArgSlot.new(index , type )
|
||||
else # or a local so it is in the frame
|
||||
index = @method.has_local( name )
|
||||
if(index)
|
||||
type = @method.locals[index].type
|
||||
return Virtual::FrameSlot.new(index, type )
|
||||
end
|
||||
end
|
||||
raise "must define variable #{name} before using it"
|
||||
end
|
||||
|
||||
end #module
|
||||
end
|
44
lib/phisol/compiler/operator_expressions.rb
Normal file
44
lib/phisol/compiler/operator_expressions.rb
Normal file
@ -0,0 +1,44 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_operator expression
|
||||
puts "operator #{expression.inspect}"
|
||||
operator , left_e , right_e = *expression
|
||||
left_slot = process(left_e)
|
||||
right_slot = process(right_e)
|
||||
puts "left #{left_slot}"
|
||||
puts "right #{right_slot}"
|
||||
tmp1 = Register.tmp_reg
|
||||
tmp2 = tmp1.next_reg_use
|
||||
get = Register.get_slot_to(expression , left_slot , tmp1 )
|
||||
get2 = Register.get_slot_to(expression , right_slot , tmp2 )
|
||||
puts "GET #{get}"
|
||||
puts "GET2 #{get2}"
|
||||
@method.source.add_code get
|
||||
@method.source.add_code get2
|
||||
|
||||
@method.source.add_code Register::OperatorInstruction.new(expression,operator, tmp1,tmp2)
|
||||
|
||||
Virtual::Return.new(:int )
|
||||
end
|
||||
|
||||
def on_assign expression
|
||||
puts expression.inspect
|
||||
name , value = *expression
|
||||
name = name.to_a.first
|
||||
v = process(value)
|
||||
index = @method.has_local( name )
|
||||
if(index)
|
||||
@method.source.add_code Virtual::Set.new(Virtual::FrameSlot.new(index, :int ) , v )
|
||||
else
|
||||
index = @method.has_arg( name )
|
||||
if(index)
|
||||
@method.source.add_code Virtual::Set.new(Virtual::ArgSlot.new(index , :int ) , v )
|
||||
else
|
||||
raise "must define variable #{name} before using it in #{@method.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
9
lib/phisol/compiler/return_expression.rb
Normal file
9
lib/phisol/compiler/return_expression.rb
Normal file
@ -0,0 +1,9 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
# return attr_reader :expression
|
||||
def on_return expression
|
||||
return process(expression.to_a.first )
|
||||
end
|
||||
end
|
||||
end
|
29
lib/phisol/compiler/while_expression.rb
Normal file
29
lib/phisol/compiler/while_expression.rb
Normal file
@ -0,0 +1,29 @@
|
||||
module Phisol
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_while expression
|
||||
#puts expression.inspect
|
||||
condition , expressions = *expression
|
||||
condition = condition.first
|
||||
|
||||
# this is where the while ends and both branches meet
|
||||
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
|
||||
|
||||
cond = process(condition)
|
||||
|
||||
@method.source.add_code Register::IsZeroBranch.new(condition,merge)
|
||||
|
||||
last = process_all(expressions).last
|
||||
|
||||
# unconditionally branch to the start
|
||||
@method.source.add_code Register::AlwaysBranch.new(expression,start)
|
||||
|
||||
# continue execution / compiling at the merge block
|
||||
@method.source.current merge
|
||||
last
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user