rename bosl to phisol

This commit is contained in:
Torsten Ruger
2015-10-07 15:22:47 +03:00
parent f88fc8bba1
commit 99098951ca
26 changed files with 38 additions and 38 deletions

View 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.

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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