rename phisol to soml
This commit is contained in:
23
lib/soml/ast_helper.rb
Normal file
23
lib/soml/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
|
77
lib/soml/compiler.rb
Normal file
77
lib/soml/compiler.rb
Normal file
@ -0,0 +1,77 @@
|
||||
module Soml
|
||||
class Compiler < AST::Processor
|
||||
|
||||
def initialize()
|
||||
@regs = []
|
||||
end
|
||||
def handler_missing node
|
||||
raise "No handler on_#{node.type}(node)"
|
||||
end
|
||||
# Compiling is the conversion of the AST into 2 things:
|
||||
# - code (ie sequences of Instructions inside Blocks) carried by MethodSource
|
||||
# - an object graph containing all the Methods, their classes and Constants
|
||||
#
|
||||
# Some compile methods just add code, some may add structure (ie Blocks) 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.
|
||||
#
|
||||
def self.compile statement
|
||||
compiler = Compiler.new
|
||||
compiler.process statement
|
||||
end
|
||||
|
||||
# simple helper to add the given code to the current method (instance variable)
|
||||
def add_code code
|
||||
@method.source.add_code code
|
||||
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
|
||||
|
||||
# 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
|
||||
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/collections"
|
||||
require_relative "compiler/statement_list"
|
||||
require_relative "compiler/field_def"
|
||||
require_relative "compiler/field_access"
|
||||
require_relative "compiler/function_definition"
|
||||
require_relative "compiler/if_statement"
|
||||
require_relative "compiler/class_statement"
|
||||
require_relative "compiler/name_expression"
|
||||
require_relative "compiler/operator_value"
|
||||
require_relative "compiler/return_statement"
|
||||
require_relative "compiler/while_statement"
|
69
lib/soml/compiler/README.md
Normal file
69
lib/soml/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/soml/compiler/assignment.rb
Normal file
33
lib/soml/compiler/assignment.rb
Normal file
@ -0,0 +1,33 @@
|
||||
module Soml
|
||||
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 = name.to_a.first
|
||||
v = process(value)
|
||||
raise "Not register #{v}" unless v.is_a?(Register::RegisterValue)
|
||||
code = nil
|
||||
if( index = @method.has_arg(name))
|
||||
# TODO, check type @method.arguments[index].type
|
||||
code = Register.set_slot(statement , v , :message , index + Parfait::Message.offset )
|
||||
else # or a local so it is in the frame
|
||||
index = @method.has_local( 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 , index + Parfait::Frame.offset )
|
||||
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
|
47
lib/soml/compiler/basic_values.rb
Normal file
47
lib/soml/compiler/basic_values.rb
Normal file
@ -0,0 +1,47 @@
|
||||
module Soml
|
||||
# collection of the simple ones, int and strings and such
|
||||
|
||||
Compiler.class_eval do
|
||||
|
||||
# Constant statements 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 statement
|
||||
int = statement.first
|
||||
reg = use_reg :Integer , int
|
||||
add_code Register::LoadConstant.new( statement, int , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_true statement
|
||||
reg = use_reg :Boolean
|
||||
add_code Register::LoadConstant.new( statement, true , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_false statement
|
||||
reg = use_reg :Boolean
|
||||
add_code Register::LoadConstant.new( statement, false , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_nil statement
|
||||
reg = use_reg :NilClass
|
||||
add_code Register::LoadConstant.new( statement, nil , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_string statement
|
||||
value = statement.first.to_sym
|
||||
reg = use_reg :Word
|
||||
@method.source.constants << value
|
||||
add_code Register::LoadConstant.new( statement, value , reg )
|
||||
return reg
|
||||
end
|
||||
end
|
||||
end
|
50
lib/soml/compiler/call_site.rb
Normal file
50
lib/soml/compiler/call_site.rb
Normal file
@ -0,0 +1,50 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_call statement
|
||||
#puts statement
|
||||
name , arguments , receiver = *statement
|
||||
name = name.to_a.first
|
||||
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(@method, :message , :next_message , new_message )
|
||||
if receiver
|
||||
me = process( receiver.to_a.first )
|
||||
else
|
||||
me = use_reg @method.for_class.name
|
||||
add_code Register.get_slot(@method, :message , :receiver , me )
|
||||
end
|
||||
# move our receiver there
|
||||
add_code Register.set_slot( statement , me , :new_message , :receiver)
|
||||
# load method name and set to new message (for exceptions/debug)
|
||||
name_tmp = use_reg(:Word)
|
||||
add_code Register::LoadConstant.new(statement, name , name_tmp)
|
||||
add_code Register.set_slot( statement , name_tmp , :new_message , :name)
|
||||
# next 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( statement , val , :new_message , i + 1 + Parfait::Message.offset)
|
||||
add_code set
|
||||
end
|
||||
|
||||
# now we have to resolve the method name (+ receiver) into a callable method
|
||||
clazz = Register.machine.space.get_class_by_name(me.type)
|
||||
raise "No such class #{me.type}" unless clazz
|
||||
method = clazz.get_instance_method(name)
|
||||
#puts Register.machine.space.get_class_by_name(:Integer).method_names.to_a
|
||||
raise "Method not implemented #{me.type}.#{name}" unless method
|
||||
Register.issue_call( @method , method )
|
||||
ret = use_reg( method.source.return_type )
|
||||
# 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(@method, :message , :return_value , ret )
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
23
lib/soml/compiler/class_field.rb
Normal file
23
lib/soml/compiler/class_field.rb
Normal file
@ -0,0 +1,23 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_class_field statement
|
||||
#puts statement.inspect
|
||||
type , name , value = *statement
|
||||
|
||||
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
|
||||
|
||||
return nil # statements don't reurn values, only expressions
|
||||
end
|
||||
end
|
||||
end
|
15
lib/soml/compiler/class_statement.rb
Normal file
15
lib/soml/compiler/class_statement.rb
Normal file
@ -0,0 +1,15 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_class statement
|
||||
#puts statement.inspect
|
||||
name , derives , statements = *statement
|
||||
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}"
|
||||
statement_value = process_all(statements).last
|
||||
@clazz = nil
|
||||
return statement_value
|
||||
end
|
||||
end
|
||||
end
|
13
lib/soml/compiler/collections.rb
Normal file
13
lib/soml/compiler/collections.rb
Normal file
@ -0,0 +1,13 @@
|
||||
module Soml
|
||||
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
|
31
lib/soml/compiler/field_access.rb
Normal file
31
lib/soml/compiler/field_access.rb
Normal file
@ -0,0 +1,31 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_field_access statement
|
||||
#puts statement.inspect
|
||||
receiver_ast , field_ast = *statement
|
||||
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 = use_reg(@clazz.name) #TODO incorrect, this is the self, but should be the type of variable at index
|
||||
add_code Register.get_slot(statement , :message , :receiver , value )
|
||||
# reuse the register for next move
|
||||
move = Register.get_slot(statement, value , index , value )
|
||||
add_code move
|
||||
return value
|
||||
when :message
|
||||
#message Slot
|
||||
raise "message not yet"
|
||||
else
|
||||
#arg / frame Slot
|
||||
raise "frame not implemented"
|
||||
end
|
||||
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
15
lib/soml/compiler/field_def.rb
Normal file
15
lib/soml/compiler/field_def.rb
Normal file
@ -0,0 +1,15 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
include AST::Sexp
|
||||
|
||||
def on_field_def statement
|
||||
reset_regs # field_def is a statement, no return and all regs
|
||||
#puts statement.inspect
|
||||
type , name , value = *statement
|
||||
@method.ensure_local( name, type ) unless( @method.has_arg(name))
|
||||
# if there is a value assigned, process it as am assignemnt statement (kind of call on_assign)
|
||||
process( s(:assignment , s(:name , name) , value ) ) if value
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
51
lib/soml/compiler/function_definition.rb
Normal file
51
lib/soml/compiler/function_definition.rb
Normal file
@ -0,0 +1,51 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_function statement
|
||||
#puts statement.inspect
|
||||
return_type , name , parameters, kids , receiver = *statement
|
||||
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 = Register::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 = Register::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}"
|
||||
|
||||
kids.to_a.each do |ex|
|
||||
ret = process(ex)
|
||||
end
|
||||
@method = nil
|
||||
# function definition is a statement, does not return any value
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
39
lib/soml/compiler/if_statement.rb
Normal file
39
lib/soml/compiler/if_statement.rb
Normal file
@ -0,0 +1,39 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
# if - attr_reader :cond, :if_true, :if_false
|
||||
|
||||
def on_if_statement statement
|
||||
branch_type , condition , if_true , if_false = *statement
|
||||
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
|
||||
|
||||
reset_regs
|
||||
is = process(condition)
|
||||
branch_class = Object.const_get "Register::Is#{branch_type.capitalize}"
|
||||
add_code branch_class.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
|
||||
|
||||
reset_regs
|
||||
last = process_all(if_true).last
|
||||
|
||||
# compile the false block
|
||||
@method.source.current false_block
|
||||
reset_regs
|
||||
last = process_all(if_false).last if if_false
|
||||
add_code Register::Branch.new(statement, 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
|
34
lib/soml/compiler/name_expression.rb
Normal file
34
lib/soml/compiler/name_expression.rb
Normal file
@ -0,0 +1,34 @@
|
||||
module Soml
|
||||
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 statement
|
||||
name = statement.to_a.first
|
||||
if( name == :self)
|
||||
ret = use_reg @clazz.name
|
||||
add_code Register.get_slot(statement , :message , :receiver , ret )
|
||||
return ret
|
||||
end
|
||||
# either an argument, so it's stored in message
|
||||
if( index = @method.has_arg(name))
|
||||
ret = use_reg @method.arguments[index].type
|
||||
add_code Register.get_slot(statement , :message , index + Parfait::Message.offset , 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].type
|
||||
add_code Register.get_slot(statement , frame , index + Parfait::Frame.offset , ret )
|
||||
return ret
|
||||
end
|
||||
end
|
||||
raise "must define variable #{name} before using it"
|
||||
end
|
||||
|
||||
end #module
|
||||
end
|
18
lib/soml/compiler/operator_value.rb
Normal file
18
lib/soml/compiler/operator_value.rb
Normal file
@ -0,0 +1,18 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_operator_value 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(left_e)
|
||||
right_reg = process(right_e)
|
||||
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,operator,left_reg,right_reg)
|
||||
return left_reg # though this has wrong value attached
|
||||
end
|
||||
end
|
||||
end
|
9
lib/soml/compiler/return_statement.rb
Normal file
9
lib/soml/compiler/return_statement.rb
Normal file
@ -0,0 +1,9 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
# return attr_reader :statement
|
||||
def on_return statement
|
||||
return process(statement.to_a.first )
|
||||
end
|
||||
end
|
||||
end
|
8
lib/soml/compiler/statement_list.rb
Normal file
8
lib/soml/compiler/statement_list.rb
Normal file
@ -0,0 +1,8 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_statements statement
|
||||
process_all( statement.children )
|
||||
end
|
||||
end
|
||||
end
|
30
lib/soml/compiler/while_statement.rb
Normal file
30
lib/soml/compiler/while_statement.rb
Normal file
@ -0,0 +1,30 @@
|
||||
module Soml
|
||||
Compiler.class_eval do
|
||||
|
||||
def on_while_statement statement
|
||||
#puts statement.inspect
|
||||
branch_type , condition , statements = *statement
|
||||
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)
|
||||
|
||||
branch_class = Object.const_get "Register::Is#{branch_type.capitalize}"
|
||||
add_code branch_class.new( condition , merge )
|
||||
|
||||
last = process_all(statements).last
|
||||
|
||||
# unconditionally branch to the start
|
||||
add_code Register::Branch.new(statement,start)
|
||||
|
||||
# continue execution / compiling at the merge block
|
||||
@method.source.current merge
|
||||
last
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user