renames Typed to Vm
This commit is contained in:
207
lib/vm/method_compiler.rb
Normal file
207
lib/vm/method_compiler.rb
Normal file
@ -0,0 +1,207 @@
|
||||
require_relative "tree"
|
||||
require_relative "method_compiler/assignment"
|
||||
require_relative "method_compiler/basic_values"
|
||||
require_relative "method_compiler/call_site"
|
||||
require_relative "method_compiler/collections"
|
||||
require_relative "method_compiler/field_access"
|
||||
require_relative "method_compiler/if_statement"
|
||||
require_relative "method_compiler/name_expression"
|
||||
require_relative "method_compiler/operator_expression"
|
||||
require_relative "method_compiler/return_statement"
|
||||
require_relative "method_compiler/statement_list"
|
||||
require_relative "method_compiler/while_statement"
|
||||
|
||||
module Vm
|
||||
|
||||
CompilerModules = [ "assignment" , "basic_values" , "call_site",
|
||||
"collections" , "field_access",
|
||||
"if_statement" , "name_expression" ,
|
||||
"operator_expression" , "return_statement", "statement_list",
|
||||
"while_statement"]
|
||||
|
||||
CompilerModules.each do |mod|
|
||||
# require_relative "method_compiler/" + mod
|
||||
end
|
||||
|
||||
# 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 TypedMethod 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 = MethodCompiler.new
|
||||
code = Vm.ast_to_code statement
|
||||
compiler.process code
|
||||
end
|
||||
|
||||
class MethodCompiler
|
||||
CompilerModules.each do |mod|
|
||||
include Vm.const_get( mod.camelize )
|
||||
end
|
||||
|
||||
def initialize( method = nil )
|
||||
@regs = []
|
||||
if method
|
||||
@method = method
|
||||
@type = method.for_type
|
||||
else
|
||||
@type = Parfait.object_space.get_type()
|
||||
@method = @type.get_method( :main )
|
||||
@method = @type.create_method( :main ,{}) unless @method
|
||||
end
|
||||
@current = @method.instructions
|
||||
end
|
||||
attr_reader :type , :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 [Vm::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
|
||||
|
||||
# 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 = Parfait.object_space.get_class_by_name! class_name
|
||||
create_method_for( clazz.instance_type , method_name , args)
|
||||
end
|
||||
|
||||
# create a method for the given type ( Parfait type object)
|
||||
# method_name is a Symbol
|
||||
# args a hash that will be converted to a type
|
||||
# the created method is set as the current and the given type too
|
||||
# return the compiler (for chaining)
|
||||
def create_method_for( type , method_name , args )
|
||||
@type = type
|
||||
raise "create_method #{type.inspect} is not a Type" unless type.is_a? Parfait::Type
|
||||
raise "Args must be Hash #{args}" unless args.is_a?(Hash)
|
||||
raise "create_method #{method_name}.#{method_name.class}" unless method_name.is_a? Symbol
|
||||
@method = type.create_method( method_name , args)
|
||||
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_type.name}.#{method.name}"
|
||||
@current = @method.set_instructions( Register.label(source, name))
|
||||
|
||||
# add the type of the locals to the existing NamedList instance
|
||||
locals_reg = use_reg(:Type , method.locals )
|
||||
list_reg = use_reg(:NamedList )
|
||||
add_load_constant("#{name} load locals type", method.locals , locals_reg)
|
||||
add_slot_to_reg( "#{name} get locals from method" , :message , :locals , list_reg )
|
||||
add_reg_to_slot( "#{name} store locals type in locals" , locals_reg , list_reg , 1 )
|
||||
|
||||
enter = @current # this is where method body goes
|
||||
add_label( source, "return #{name}")
|
||||
#load the return address into pc, affecting return. (other cpus have commands for this, but not arm)
|
||||
add_function_return( source , Register.message_reg , Register.resolve_to_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
|
||||
raise instruction.to_s unless instruction.is_a?(Register::Instruction)
|
||||
raise instruction.to_s if( instruction.class.name.split("::").first == "Arm")
|
||||
@current.insert(instruction) #insert after current
|
||||
@current = instruction
|
||||
self
|
||||
end
|
||||
|
||||
[:label, :reg_to_slot , :slot_to_reg , :load_constant, :function_return ,
|
||||
:transfer , :reg_to_slot , :byte_to_reg , :reg_to_byte].each do |method|
|
||||
define_method("add_#{method}".to_sym) do |*args|
|
||||
add_code Register.send( method , *args )
|
||||
end
|
||||
end
|
||||
|
||||
# require a (temporary) register. code must give this back with release_reg
|
||||
def use_reg( type , value = nil )
|
||||
raise "Not type #{type.inspect}" unless type.is_a?(Symbol) or type.is_a?(Parfait::Type)
|
||||
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
|
||||
|
||||
end
|
||||
end
|
57
lib/vm/method_compiler/README.md
Normal file
57
lib/vm/method_compiler/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
### Compiling
|
||||
|
||||
The typed syntax tree is created by the ruby compiler.
|
||||
|
||||
The code in this directory compiles the typed tree to the register 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 class of typed tree, named along on_xxx with xxx as the type
|
||||
|
||||
#### Compiler holds scope
|
||||
|
||||
The Compiler instance can hold arbitrary scope needed during the compilation.
|
||||
A class statement sets the current @type 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 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 TypedMethod 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.
|
32
lib/vm/method_compiler/assignment.rb
Normal file
32
lib/vm/method_compiler/assignment.rb
Normal file
@ -0,0 +1,32 @@
|
||||
module Vm
|
||||
module Assignment
|
||||
|
||||
def on_Assignment( statement )
|
||||
reset_regs # statements reset registers, ie have all at their disposal
|
||||
value = process(statement.value)
|
||||
raise "Not register #{v}" unless value.is_a?(Register::RegisterValue)
|
||||
name = check_name(statement.name.name)
|
||||
named_list = use_reg(:NamedList)
|
||||
if( index = @method.has_arg(name))
|
||||
type = :arguments
|
||||
value_type = @method.argument_type( index )
|
||||
else
|
||||
index = @method.has_local( name )
|
||||
type = :locals
|
||||
raise "must define variable #{statement.name.name} before using it in #{@method.inspect}" unless index
|
||||
value_type = @method.locals_type( index )
|
||||
end
|
||||
raise "Type mismatch for #{type} access #{value.type}!=#{value_type}" unless value.type == value_type
|
||||
add_slot_to_reg(statement , :message , type , named_list )
|
||||
add_reg_to_slot(statement , value , named_list , index + 1 ) # one for type
|
||||
end
|
||||
|
||||
# ensure the name given is not space and raise exception otherwise
|
||||
# return the name
|
||||
def check_name( name )
|
||||
raise "space is a reserved name" if name == :space
|
||||
name
|
||||
end
|
||||
|
||||
end
|
||||
end
|
52
lib/vm/method_compiler/basic_values.rb
Normal file
52
lib/vm/method_compiler/basic_values.rb
Normal file
@ -0,0 +1,52 @@
|
||||
module Vm
|
||||
# collection of the simple ones, int and strings and such
|
||||
|
||||
module BasicValues
|
||||
|
||||
# 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_load_constant( expression, int , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_TrueExpression expression
|
||||
reg = use_reg :Boolean
|
||||
add_load_constant( expression, true , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_FalseExpression expression
|
||||
reg = use_reg :Boolean
|
||||
add_load_constant( expression, false , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_NilExpression expression
|
||||
reg = use_reg :NilClass
|
||||
add_load_constant( 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_load_constant( expression, value , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def on_ClassExpression expression
|
||||
name = expression.value
|
||||
raise "No meta class #{name}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
94
lib/vm/method_compiler/call_site.rb
Normal file
94
lib/vm/method_compiler/call_site.rb
Normal file
@ -0,0 +1,94 @@
|
||||
module Vm
|
||||
module CallSite
|
||||
|
||||
def on_CallSite( 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_slot_to_reg(statement, :message , :next_message , new_message )
|
||||
me = get_me( statement )
|
||||
type = get_my_type(me)
|
||||
|
||||
method = type.get_method(statement.name)
|
||||
raise "Method not implemented #{type.inspect}.#{statement.name}" unless method
|
||||
|
||||
# move our receiver there
|
||||
add_reg_to_slot( statement , me , :new_message , :receiver)
|
||||
|
||||
set_message_details(method , statement , statement.arguments)
|
||||
set_arguments(method , statement.arguments)
|
||||
ret = use_reg( :Integer ) #FIXME real return type
|
||||
|
||||
Register.issue_call( self , method )
|
||||
|
||||
# the effect of the method is that the NewMessage Return slot will be filled, return it
|
||||
# but move it into a register too
|
||||
add_slot_to_reg(statement, :new_message , :return_value , ret )
|
||||
ret
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_me( statement )
|
||||
if statement.receiver
|
||||
me = process( statement.receiver )
|
||||
else
|
||||
me = use_reg @method.for_type
|
||||
add_slot_to_reg(statement, :message , :receiver , me )
|
||||
end
|
||||
me
|
||||
end
|
||||
def get_my_type( me )
|
||||
# now we have to resolve the method name (+ receiver) into a callable method
|
||||
case me.type
|
||||
when Parfait::Type
|
||||
type = me.type
|
||||
when Symbol
|
||||
type = Parfait.object_space.get_class_by_name(me.type).instance_type
|
||||
else
|
||||
raise me.inspect
|
||||
end
|
||||
raise "Not type #{type}" unless type.is_a? Parfait::Type
|
||||
type
|
||||
end
|
||||
|
||||
# load method name and set to new message (for exceptions/debug)
|
||||
def set_message_details( method , name_s , arguments )
|
||||
name = name_s.name
|
||||
name_tmp = use_reg(:Word)
|
||||
add_load_constant("#{name} load method name", name , name_tmp)
|
||||
add_reg_to_slot( "#{name} store method name" , name_tmp , :new_message , :name)
|
||||
# next arg type
|
||||
args_reg = use_reg(:Type , method.arguments )
|
||||
list_reg = use_reg(:NamedList , arguments )
|
||||
add_load_constant("#{name} load arguments type", method.arguments , args_reg)
|
||||
add_slot_to_reg( "#{name} get args from method" , :new_message , :arguments , list_reg )
|
||||
add_reg_to_slot( "#{name} store args type in args" , args_reg , list_reg , 1 )
|
||||
end
|
||||
|
||||
def set_arguments( method , arguments )
|
||||
# reset tmp regs for each and load result into new_message
|
||||
arg_type = method.arguments
|
||||
message = "Arg number mismatch, method=#{arg_type.instance_length - 1} , call=#{arguments.length}"
|
||||
raise message if (arg_type.instance_length - 1 ) != arguments.length
|
||||
arguments.each_with_index do |arg , i |
|
||||
store_arg_no(arguments , arg_type , arg , i + 1) #+1 for ruby(0 based)
|
||||
end
|
||||
end
|
||||
|
||||
def store_arg_no(arguments , arg_type , arg , i )
|
||||
reset_regs
|
||||
i = i + 1 # disregarding type field
|
||||
val = process( arg) # processing should return the register with the value
|
||||
raise "Not register #{val}" unless val.is_a?(Register::RegisterValue)
|
||||
#FIXME definately needs some tests
|
||||
raise "TypeMismatch calling with #{val.type} , instead of #{arg_type.type_at(i)}" if val.type != arg_type.type_at(i)
|
||||
list_reg = use_reg(:NamedList , arguments )
|
||||
add_slot_to_reg( "Set arg #{i}:#{arg}" , :new_message , :arguments , list_reg )
|
||||
# which we load int the new_message at the argument's index
|
||||
add_reg_to_slot( arg , val , list_reg , i ) #one for type and one for ruby
|
||||
end
|
||||
end
|
||||
end
|
14
lib/vm/method_compiler/collections.rb
Normal file
14
lib/vm/method_compiler/collections.rb
Normal file
@ -0,0 +1,14 @@
|
||||
module Vm
|
||||
|
||||
module Collections
|
||||
|
||||
# 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
|
27
lib/vm/method_compiler/field_access.rb
Normal file
27
lib/vm/method_compiler/field_access.rb
Normal file
@ -0,0 +1,27 @@
|
||||
module Vm
|
||||
module FieldAccess
|
||||
|
||||
def on_FieldAccess statement
|
||||
# receiver_ast , field_ast = *statement
|
||||
receiver = process(statement.receiver)
|
||||
|
||||
type = receiver.type
|
||||
if(type.is_a?(Symbol))
|
||||
type = Parfait.object_space.get_class_by_name(type).instance_type
|
||||
end
|
||||
field_name = statement.field.name
|
||||
|
||||
index = type.variable_index(field_name)
|
||||
raise "no such field:#{field_name} for class #{type.inspect}" unless index
|
||||
value = use_reg(type.type_at(index))
|
||||
|
||||
add_slot_to_reg(statement , receiver , index, value)
|
||||
|
||||
value
|
||||
end
|
||||
|
||||
def on_receiver expression
|
||||
process expression.first
|
||||
end
|
||||
end
|
||||
end
|
41
lib/vm/method_compiler/if_statement.rb
Normal file
41
lib/vm/method_compiler/if_statement.rb
Normal file
@ -0,0 +1,41 @@
|
||||
module Vm
|
||||
module IfStatement
|
||||
|
||||
# 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
|
||||
|
||||
true_block = compile_if_condition( statement )
|
||||
merge = compile_if_false( statement )
|
||||
add_code true_block
|
||||
compile_if_true(statement)
|
||||
add_code merge
|
||||
nil # statements don't return anything
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compile_if_condition( statement )
|
||||
reset_regs
|
||||
process(statement.condition)
|
||||
branch_class = Object.const_get "Register::Is#{statement.branch_type.capitalize}"
|
||||
true_block = Register.label(statement, "if_true")
|
||||
add_code branch_class.new( statement.condition , true_block )
|
||||
return true_block
|
||||
end
|
||||
def compile_if_true( statement )
|
||||
reset_regs
|
||||
process(statement.if_true)
|
||||
end
|
||||
|
||||
def compile_if_false( statement )
|
||||
reset_regs
|
||||
process(statement.if_false) if statement.if_false.statements
|
||||
merge = Register.label(statement , "if_merge")
|
||||
add_code Register::Branch.new(statement.if_false, merge )
|
||||
merge
|
||||
end
|
||||
end
|
||||
end
|
60
lib/vm/method_compiler/name_expression.rb
Normal file
60
lib/vm/method_compiler/name_expression.rb
Normal file
@ -0,0 +1,60 @@
|
||||
module Vm
|
||||
module NameExpression
|
||||
|
||||
# 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
|
||||
[:self , :space , :message].each do |special|
|
||||
return send(:"load_special_#{special}" , statement ) if name == special
|
||||
end
|
||||
return load_argument(statement) if( @method.has_arg(name))
|
||||
load_local(statement)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_argument(statement)
|
||||
name = statement.name
|
||||
index = @method.has_arg(name)
|
||||
named_list = use_reg :NamedList
|
||||
ret = use_reg @method.argument_type(index)
|
||||
#puts "For #{name} at #{index} got #{@method.arguments.inspect}"
|
||||
add_slot_to_reg("#{statement} load args" , :message , :arguments, named_list )
|
||||
add_slot_to_reg("#{statement} load #{name}" , named_list , index + 1, ret )
|
||||
return ret
|
||||
end
|
||||
|
||||
def load_local( statement )
|
||||
name = statement.name
|
||||
index = @method.has_local( name )
|
||||
raise "must define variable '#{name}' before using it" unless index
|
||||
named_list = use_reg :NamedList
|
||||
add_slot_to_reg("#{name} load locals" , :message , :locals , named_list )
|
||||
ret = use_reg @method.locals_type( index )
|
||||
add_slot_to_reg("#{name} load from locals" , named_list , index + 1, ret )
|
||||
return ret
|
||||
end
|
||||
|
||||
def load_special_self(statement)
|
||||
ret = use_reg @type
|
||||
add_slot_to_reg("#{statement} load self" , :message , :receiver , ret )
|
||||
return ret
|
||||
end
|
||||
|
||||
def load_special_space(statement)
|
||||
space = Parfait.object_space
|
||||
reg = use_reg :Space , space
|
||||
add_load_constant( "#{statement} load space", space , reg )
|
||||
return reg
|
||||
end
|
||||
|
||||
def load_special_message(statement)
|
||||
reg = use_reg :Message
|
||||
add_transfer( "#{statement} load message", Register.message_reg , reg )
|
||||
return reg
|
||||
end
|
||||
end #module
|
||||
end
|
15
lib/vm/method_compiler/operator_expression.rb
Normal file
15
lib/vm/method_compiler/operator_expression.rb
Normal file
@ -0,0 +1,15 @@
|
||||
module Vm
|
||||
module OperatorExpression
|
||||
|
||||
def on_OperatorExpression statement
|
||||
# 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)
|
||||
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/vm/method_compiler/return_statement.rb
Normal file
10
lib/vm/method_compiler/return_statement.rb
Normal file
@ -0,0 +1,10 @@
|
||||
module Vm
|
||||
module ReturnStatement
|
||||
|
||||
def on_ReturnStatement statement
|
||||
reg = process(statement.return_value)
|
||||
add_reg_to_slot( statement, reg , :message , :return_value)
|
||||
nil # statements don't return
|
||||
end
|
||||
end
|
||||
end
|
7
lib/vm/method_compiler/statement_list.rb
Normal file
7
lib/vm/method_compiler/statement_list.rb
Normal file
@ -0,0 +1,7 @@
|
||||
module Vm
|
||||
module StatementList
|
||||
def on_Statements statement
|
||||
process_all( statement.statements )
|
||||
end
|
||||
end
|
||||
end
|
42
lib/vm/method_compiler/while_statement.rb
Normal file
42
lib/vm/method_compiler/while_statement.rb
Normal file
@ -0,0 +1,42 @@
|
||||
module Vm
|
||||
module WhileStatement
|
||||
|
||||
def on_WhileStatement statement
|
||||
#branch_type , condition , statements = *statement
|
||||
|
||||
condition_label = compile_while_preamble( statement ) #jump there
|
||||
|
||||
start = compile_while_body( statement )
|
||||
|
||||
# This is where the loop starts, though in subsequent iterations it's in the middle
|
||||
add_code condition_label
|
||||
|
||||
compile_while_condition( statement )
|
||||
|
||||
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
|
||||
private
|
||||
|
||||
def compile_while_preamble( statement )
|
||||
condition_label = Register.label(statement.condition , "condition_label")
|
||||
# unconditionally branch to the condition upon entering the loop
|
||||
add_code Register::Branch.new(statement.condition , condition_label)
|
||||
condition_label
|
||||
end
|
||||
def compile_while_body( statement )
|
||||
start = Register.label(statement , "while_start" )
|
||||
add_code start
|
||||
reset_regs
|
||||
process(statement.statements)
|
||||
start
|
||||
end
|
||||
def compile_while_condition( statement )
|
||||
reset_regs
|
||||
process(statement.condition)
|
||||
end
|
||||
end
|
||||
end
|
18
lib/vm/parfait.rb
Normal file
18
lib/vm/parfait.rb
Normal file
@ -0,0 +1,18 @@
|
||||
# Parfait is the ruby runtime
|
||||
module Parfait
|
||||
end
|
||||
|
||||
require_relative "parfait/integer"
|
||||
require_relative "parfait/object"
|
||||
require_relative "parfait/behaviour"
|
||||
require_relative "parfait/class"
|
||||
require_relative "parfait/list"
|
||||
require_relative "parfait/word"
|
||||
require_relative "parfait/binary_code"
|
||||
require_relative "parfait/typed_method"
|
||||
require_relative "parfait/dictionary"
|
||||
require_relative "parfait/type"
|
||||
require_relative "parfait/message"
|
||||
require_relative "parfait/named_list"
|
||||
require_relative "parfait/space"
|
||||
require_relative "parfait/symbol_adapter"
|
34
lib/vm/parfait/README.md
Normal file
34
lib/vm/parfait/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
### Parfait: a thin layer
|
||||
|
||||
Parfait is the run-time of the object system.
|
||||
To be more precise, it is that part of the run-time needed to boot.
|
||||
|
||||
The run-time needs to contain quite a lot of functionality for a dynamic system.
|
||||
And a large part of that functionality must actually be used at compile time too.
|
||||
|
||||
We reuse the Parfait code at compile-time, to create the data for the compiled vm.
|
||||
To do this the vm (re) defines the object memory (in parfait_adapter).
|
||||
|
||||
A work in progress that started from here : http://ruby-x.org/2014/06/10/more-clarity.html
|
||||
went on here http://ruby-x.org/2014/07/05/layers-vs-passes.html
|
||||
|
||||
A step back: the code (program) we compile runs at run - time.
|
||||
And so does parfait. So all we have to do is compile it with the program.
|
||||
|
||||
And thus parfait can be used at run-time.
|
||||
|
||||
It's too simple: just slips off the mind like a fish into water.
|
||||
|
||||
Parfait has a brother, the Builtin module. Builtin contains everything that can not be coded in
|
||||
ruby, but we still need (things like List access).
|
||||
|
||||
### Vm vs language- core
|
||||
|
||||
Parfait is not the language core library. Core library functionality differs between
|
||||
languages and so the language core lib must be on top of the vm parfait.
|
||||
|
||||
To make this point clear, i have started using different names for the core classes. Hopefully
|
||||
more sensible ones, ie List instead of Array, Dictionary instead of Hash.
|
||||
|
||||
Also Parfait is meant to be as thin as humanly possibly, so extra (nice to have) functionality
|
||||
will be in future modules.
|
55
lib/vm/parfait/behaviour.rb
Normal file
55
lib/vm/parfait/behaviour.rb
Normal file
@ -0,0 +1,55 @@
|
||||
# Behaviour is something that has methods, basically class and modules superclass
|
||||
|
||||
# instance_methods is the attribute in the including class that has the methods
|
||||
|
||||
module Parfait
|
||||
module Behaviour
|
||||
|
||||
def initialize
|
||||
super()
|
||||
@instance_methods = List.new
|
||||
end
|
||||
|
||||
def methods
|
||||
m = @instance_methods
|
||||
return m if m
|
||||
@instance_methods = List.new
|
||||
end
|
||||
|
||||
def method_names
|
||||
names = List.new
|
||||
self.methods.each do |method|
|
||||
names.push method.name
|
||||
end
|
||||
names
|
||||
end
|
||||
|
||||
def add_instance_method( method )
|
||||
raise "not implemented #{method.class} #{method.inspect}" unless method.is_a? RubyMethod
|
||||
method
|
||||
end
|
||||
|
||||
def remove_instance_method( method_name )
|
||||
found = get_instance_method( method_name )
|
||||
found ? self.methods.delete(found) : false
|
||||
end
|
||||
|
||||
def get_instance_method( fname )
|
||||
raise "get_instance_method #{fname}.#{fname.class}" unless fname.is_a?(Symbol)
|
||||
#if we had a hash this would be easier. Detect or find would help too
|
||||
self.methods.find {|m| m.name == fname }
|
||||
end
|
||||
|
||||
# get the method and if not found, try superclasses. raise error if not found
|
||||
def resolve_method m_name
|
||||
raise "resolve_method #{m_name}.#{m_name.class}" unless m_name.is_a?(Symbol)
|
||||
method = get_instance_method(m_name)
|
||||
return method if method
|
||||
if( @super_class_name != :Object )
|
||||
method = self.super_class.resolve_method(m_name)
|
||||
end
|
||||
method
|
||||
end
|
||||
|
||||
end
|
||||
end
|
17
lib/vm/parfait/binary_code.rb
Normal file
17
lib/vm/parfait/binary_code.rb
Normal file
@ -0,0 +1,17 @@
|
||||
# A typed method object is a description of the method, it's name etc
|
||||
#
|
||||
# But the code that the method represents, the binary, is held as an array
|
||||
# in one of these.
|
||||
#
|
||||
|
||||
module Parfait
|
||||
# obviously not a "Word" but a ByteArray , but no such class yet
|
||||
# As our String (Word) on the other hand has no encoding (yet) it is close enough
|
||||
class BinaryCode < Word
|
||||
|
||||
def to_s
|
||||
"BinaryCode #{self.char_length}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
74
lib/vm/parfait/class.rb
Normal file
74
lib/vm/parfait/class.rb
Normal file
@ -0,0 +1,74 @@
|
||||
# Class is mainly a list of methods with a name. The methods are untyped.
|
||||
|
||||
# The memory layout of an object is determined by the Type (see there).
|
||||
# The class carries the "current" type, ie the type an object would be if you created an instance
|
||||
# of the class. Note that this changes over time and so many types share the same class.
|
||||
|
||||
# For dynamic OO it is essential that the class (the object defining the class)
|
||||
# can carry methods. It does so as instance variables.
|
||||
# In fact this property is implemented in the Type, as methods
|
||||
# may be added to any object at run-time.
|
||||
|
||||
# An Object carries the data for the instance variables it has.
|
||||
# The Type lists the names of the instance variables
|
||||
# The Class keeps a list of instance methods, these have a name and code
|
||||
|
||||
module Parfait
|
||||
class Class < Object
|
||||
include Behaviour
|
||||
|
||||
attr_reader :instance_type , :name , :instance_methods , :super_class_name
|
||||
|
||||
def initialize( name , superclass , instance_type)
|
||||
super()
|
||||
@name = name
|
||||
@super_class_name = superclass
|
||||
@methods = {}
|
||||
set_instance_type( instance_type )
|
||||
end
|
||||
|
||||
def sof_reference_name
|
||||
name
|
||||
end
|
||||
|
||||
def inspect
|
||||
"Class(#{name})"
|
||||
end
|
||||
|
||||
def add_method(method)
|
||||
@methods[method.name] = method
|
||||
end
|
||||
|
||||
def get_method(name)
|
||||
@methods[name]
|
||||
end
|
||||
|
||||
# setting the type generates all methods for this type
|
||||
# (or will do, once we store the methods code to do that)
|
||||
def set_instance_type( type )
|
||||
raise "type must be type #{type}" unless type.is_a?(Type)
|
||||
@instance_type = type
|
||||
end
|
||||
|
||||
def super_class
|
||||
raise "No super_class for class #{@name}" unless @super_class_name
|
||||
s = Parfait.object_space.get_class_by_name(@super_class_name)
|
||||
raise "superclass not found for class #{@name} (#{@super_class_name})" unless s
|
||||
s
|
||||
end
|
||||
|
||||
|
||||
# ruby 2.1 list (just for reference, keep at bottom)
|
||||
#:allocate, :new, :superclass
|
||||
|
||||
# + modules
|
||||
# :<, :<=, :>, :>=, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods,
|
||||
# :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?,
|
||||
# :const_missing, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set,
|
||||
# :class_variable_defined?, :public_constant, :private_constant, :singleton_class?, :include, :prepend,
|
||||
# :module_exec, :class_exec, :module_eval, :class_eval, :method_defined?, :public_method_defined?,
|
||||
# :private_method_defined?, :protected_method_defined?, :public_class_method, :private_class_method, :autoload,
|
||||
# :autoload?, :instance_method, :public_instance_method
|
||||
|
||||
end
|
||||
end
|
96
lib/vm/parfait/dictionary.rb
Normal file
96
lib/vm/parfait/dictionary.rb
Normal file
@ -0,0 +1,96 @@
|
||||
# almost simplest hash imaginable. make good use of Lists
|
||||
|
||||
module Parfait
|
||||
class Dictionary < Object
|
||||
|
||||
# only empty initialization for now
|
||||
#
|
||||
# internally we store keys and values in lists, which means this does **not** scale well
|
||||
def initialize
|
||||
super()
|
||||
@keys = List.new()
|
||||
@values = List.new()
|
||||
end
|
||||
|
||||
def keys
|
||||
@keys.dup
|
||||
end
|
||||
|
||||
def values
|
||||
@values.dup
|
||||
end
|
||||
|
||||
# are there any key/value items in the list
|
||||
def empty?
|
||||
@keys.empty?
|
||||
end
|
||||
|
||||
# How many key/value pairs there are
|
||||
def length()
|
||||
return @keys.get_length()
|
||||
end
|
||||
|
||||
# get a value fot the given key
|
||||
# key identity is checked with == not === (ie equals not identity)
|
||||
# return nil if no such key
|
||||
def get(key)
|
||||
index = key_index(key)
|
||||
if( index )
|
||||
@values.get(index)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# same as get(key)
|
||||
def [](key)
|
||||
get(key)
|
||||
end
|
||||
|
||||
# private method
|
||||
def key_index(key)
|
||||
@keys.index_of(key)
|
||||
end
|
||||
|
||||
# set key with value, returns value
|
||||
def set(key , value)
|
||||
index = key_index(key)
|
||||
if( index )
|
||||
@values.set(index , value)
|
||||
else
|
||||
@keys.push(key)
|
||||
@values.push(value)
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
#same as set(k,v)
|
||||
def []=(key,val)
|
||||
set(key,val)
|
||||
end
|
||||
|
||||
# yield to each key value pair
|
||||
def each
|
||||
index = 1
|
||||
while index <= @keys.get_length
|
||||
key = @keys.get(index)
|
||||
value = @values.get(index)
|
||||
yield key , value
|
||||
index = index + 1
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def inspect
|
||||
string = "Dictionary{"
|
||||
each do |key , value|
|
||||
string += key.to_s + " => " + value.to_s + " ,"
|
||||
end
|
||||
string + "}"
|
||||
end
|
||||
|
||||
def to_sof_node(writer , level , ref)
|
||||
Sof.hash_to_sof_node( self , writer , level , ref)
|
||||
end
|
||||
end
|
||||
end
|
25
lib/vm/parfait/integer.rb
Normal file
25
lib/vm/parfait/integer.rb
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
# Integer class for representing maths on Integers
|
||||
# Integers are Values (not Objects),
|
||||
# - they have fixed value
|
||||
# - they are immutable
|
||||
# you can *not* assign instance variables or methods
|
||||
|
||||
# TODO how this idea works with Numeric ?
|
||||
|
||||
module Parfait
|
||||
class Integer
|
||||
|
||||
# :integer?, :odd?, :even?, :upto, :downto, :times, :succ, :next, :pred, :chr, :ord, :to_i, :to_int, :floor,
|
||||
# :ceil, :truncate, :round, :gcd, :lcm, :gcdlcm, :numerator, :denominator, :to_r, :rationalize,
|
||||
# :singleton_method_added, :coerce, :i, :+@, :-@, :fdiv, :div, :divmod, :%, :modulo, :remainder, :abs, :magnitude,
|
||||
# :real?, :zero?, :nonzero?, :step, :quo, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase,
|
||||
# :rectangular, :rect, :polar, :conjugate, :conj, :>, :>=, :<, :<=, :between?
|
||||
#
|
||||
# Numeric
|
||||
# :singleton_method_added, :coerce, :i, :+@, :-@, :fdiv, :div, :divmod, :%, :modulo, :remainder, :abs, :magnitude,
|
||||
# :to_int, :real?, :integer?, :zero?, :nonzero?, :floor, :ceil, :round, :truncate, :step, :numerator, :denominator,
|
||||
# :quo, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase, :rectangular, :rect, :polar, :conjugate, :conj,
|
||||
# :>, :>=, :<, :<=, :between?
|
||||
end
|
||||
end
|
278
lib/vm/parfait/list.rb
Normal file
278
lib/vm/parfait/list.rb
Normal file
@ -0,0 +1,278 @@
|
||||
# A List, or rather an ordered list, is just that, a list of items.
|
||||
|
||||
# For a programmer this may be a little strange as this new start goes with trying to break old
|
||||
# bad habits. A List would be an array in some languages, but list is a better name, closer to
|
||||
# common language.
|
||||
# Another bad habit is to start a list from 0. This is "just" programmers lazyness, as it goes
|
||||
# with the standard c implementation. But it bends the mind, and in oo we aim not to.
|
||||
# If you have a list of three items, they will be first, second and third, ie 1,2,3
|
||||
#
|
||||
# For the implementation we use Objects memory which is index addressable
|
||||
# But, objects are also lists where indexes start with 1, except 1 is taken for the Type
|
||||
# so all incoming/outgoing indexes have to be shifted one up/down
|
||||
|
||||
module Parfait
|
||||
class List < Object
|
||||
def self.get_length_index
|
||||
2
|
||||
end
|
||||
def self.get_indexed(index)
|
||||
index + 2
|
||||
end
|
||||
|
||||
def get_offset
|
||||
2
|
||||
end
|
||||
|
||||
def get_length
|
||||
r = get_internal_word( 2 ) #one for type
|
||||
r.nil? ? 0 : r
|
||||
end
|
||||
|
||||
# set the value at index.
|
||||
# Lists start from index 1
|
||||
def set( index , value)
|
||||
raise "Only positive indexes #{index}" if index <= 0
|
||||
if index > get_length
|
||||
grow_to(index)
|
||||
end
|
||||
# start one higher than offset, which is where the length is
|
||||
set_internal_word( index + 2, value)
|
||||
end
|
||||
|
||||
# set the value at index.
|
||||
# Lists start from index 1
|
||||
def get( index )
|
||||
raise "Only positive indexes, #{index}" if index <= 0
|
||||
ret = nil
|
||||
if(index <= get_length)
|
||||
# start one higher than offset, which is where the length is
|
||||
ret = get_internal_word(index + 2 )
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
def grow_to( len)
|
||||
raise "Only positive lenths, #{len}" if len < 0
|
||||
old_length = get_length
|
||||
return if old_length >= len
|
||||
# raise "bounds error at #{len}" if( len + offset > 16 )
|
||||
# be nice to use the indexed_length , but that relies on booted space
|
||||
set_internal_word( 2 , len) #one for type
|
||||
end
|
||||
|
||||
def shrink_to( len )
|
||||
raise "Only positive lenths, #{len}" if len < 0
|
||||
old_length = get_length
|
||||
return if old_length <= len
|
||||
set_internal_word( 2 , len)
|
||||
end
|
||||
|
||||
def indexed_length
|
||||
get_length()
|
||||
end
|
||||
|
||||
def initialize( )
|
||||
super()
|
||||
@memory = []
|
||||
end
|
||||
|
||||
# include? means non nil index
|
||||
def include? item
|
||||
return index_of(item) != nil
|
||||
end
|
||||
|
||||
# index of item, remeber first item has index 1
|
||||
# return nil if no such item
|
||||
def index_of( item )
|
||||
max = self.get_length
|
||||
#puts "length #{max} #{max.class}"
|
||||
counter = 1
|
||||
while( counter <= max )
|
||||
if( get(counter) == item)
|
||||
return counter
|
||||
end
|
||||
counter = counter + 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# push means add to the end
|
||||
# this automatically grows the List
|
||||
def push( value )
|
||||
to = self.get_length + 1
|
||||
set( to , value)
|
||||
to
|
||||
end
|
||||
|
||||
def delete( value )
|
||||
index = index_of value
|
||||
return false unless index
|
||||
delete_at index
|
||||
end
|
||||
|
||||
def delete_at( index )
|
||||
# TODO bounds check
|
||||
while(index < self.get_length)
|
||||
set( index , get(index + 1))
|
||||
index = index + 1
|
||||
end
|
||||
set_length( self.get_length - 1)
|
||||
true
|
||||
end
|
||||
|
||||
def first
|
||||
return nil if empty?
|
||||
get(1)
|
||||
end
|
||||
|
||||
def last
|
||||
return nil if empty?
|
||||
get(get_length())
|
||||
end
|
||||
|
||||
def empty?
|
||||
self.get_length == 0
|
||||
end
|
||||
|
||||
def equal? other
|
||||
# this should call parfait get_class, alas that is not implemented yet
|
||||
return false if other.class != self.class
|
||||
return false if other.get_length != self.get_length
|
||||
index = self.get_length
|
||||
while(index > 0)
|
||||
return false if other.get(index) != self.get(index)
|
||||
index = index - 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
# above, correct, implementation causes problems in the machine object space
|
||||
# because when a second empty (newly created) list is added, it is not actually
|
||||
# added as it exists already. TODO, but hack with below identity function
|
||||
def == other
|
||||
self.object_id == other.object_id
|
||||
end
|
||||
|
||||
# word length (padded) is the amount of space taken by the object
|
||||
# For your basic object this means the number of instance variables as determined by type
|
||||
# This is off course 0 for a list, unless someone squeezed an instance variable in
|
||||
# but additionally, the amount of data comes on top.
|
||||
# unfortuntely we can't just use super because of the Padding
|
||||
def padded_length
|
||||
Padding.padded_words( get_type().instance_length + get_length() )
|
||||
end
|
||||
|
||||
def each
|
||||
index = 1
|
||||
while index <= self.get_length
|
||||
item = get(index)
|
||||
yield item
|
||||
index = index + 1
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def each_with_index
|
||||
index = 1
|
||||
while index <= self.get_length
|
||||
item = get(index)
|
||||
yield item , index
|
||||
index = index + 1
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def each_pair
|
||||
index = 1
|
||||
while index <= self.get_length
|
||||
key = get( index )
|
||||
value = get(index + 1)
|
||||
yield key , value
|
||||
index = index + 2
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def find
|
||||
index = 1
|
||||
while index <= self.get_length
|
||||
item = get(index)
|
||||
return item if yield item
|
||||
index = index + 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def set_length len
|
||||
was = self.get_length
|
||||
return if was == len
|
||||
if(was < len)
|
||||
grow_to len
|
||||
else
|
||||
shrink_to len
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
index = 1
|
||||
ret = ""
|
||||
while index <= self.get_length
|
||||
item = get(index)
|
||||
ret += item.inspect
|
||||
ret += "," unless index == self.get_length
|
||||
index = index + 1
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def get_internal_word(index)
|
||||
@memory[index]
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def set_internal_word(index , value)
|
||||
raise "Word[#{index}] = " if((self.class == Parfait::Word) and value.nil? )
|
||||
@memory[index] = value
|
||||
value
|
||||
end
|
||||
|
||||
alias :[] :get
|
||||
|
||||
def to_sof_node(writer , level , ref )
|
||||
Sof.array_to_sof_node(self , writer , level , ref )
|
||||
end
|
||||
|
||||
def dup
|
||||
list = List.new
|
||||
each do |item|
|
||||
list.push(item)
|
||||
end
|
||||
list
|
||||
end
|
||||
|
||||
def to_a
|
||||
array = []
|
||||
index = 1
|
||||
while( index <= self.get_length)
|
||||
array[index - 1] = get(index)
|
||||
index = index + 1
|
||||
end
|
||||
array
|
||||
end
|
||||
end
|
||||
|
||||
# new list from ruby array to be precise
|
||||
def self.new_list array
|
||||
list = Parfait::List.new
|
||||
list.set_length array.length
|
||||
index = 1
|
||||
while index <= array.length do
|
||||
list.set(index , array[index - 1])
|
||||
index = index + 1
|
||||
end
|
||||
list
|
||||
end
|
||||
|
||||
end
|
36
lib/vm/parfait/message.rb
Normal file
36
lib/vm/parfait/message.rb
Normal file
@ -0,0 +1,36 @@
|
||||
|
||||
# A message is what is sent when you invoke a method. Args and stuff are packed up in to a Message
|
||||
# and the Message is sent to the receiver.
|
||||
|
||||
# Part of the housekeeping (see attributes) makes messages a double linked list (next_message and
|
||||
# caller) , and maybe surprisingly this means that we can create all messages at runtime
|
||||
# and link them up and never have to touch that list again.
|
||||
# All the args and receiver data changes, but the list of messages stays constant.
|
||||
|
||||
module Parfait
|
||||
class Message < Object
|
||||
|
||||
attr_reader :locals , :receiver , :return_value , :name
|
||||
attr_accessor :next_message
|
||||
|
||||
def initialize next_m
|
||||
@next_message = next_m
|
||||
@locals = NamedList.new()
|
||||
@arguments = NamedList.new()
|
||||
super()
|
||||
end
|
||||
|
||||
def set_receiver(rec)
|
||||
@receiver = rec
|
||||
end
|
||||
|
||||
def set_caller(caller)
|
||||
@caller = caller
|
||||
end
|
||||
|
||||
def get_type_for(name)
|
||||
index = @type.get_index(name)
|
||||
get_at(index)
|
||||
end
|
||||
end
|
||||
end
|
31
lib/vm/parfait/named_list.rb
Normal file
31
lib/vm/parfait/named_list.rb
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
# A NamedList is used to store local variables and arguments when calling methods.
|
||||
# Also temporary variables, which are local variables named by the system
|
||||
|
||||
# The items are named (and typed) by the objects type instance. In effect the
|
||||
# variables are like instance variables
|
||||
|
||||
# A Message with is arguments, and a NamedList make up the two sides of message passing:
|
||||
# A Message (see details there) is created by the caller and control is transferred
|
||||
# A NamedList is created by the receiver
|
||||
# PS: it turns out that both messages and named_lists are created at compile, not run-time, and
|
||||
# just constantly reused. Each message has two named_list object ready and is also linked
|
||||
# to the next message.
|
||||
# The better way to say above is that a message is *used* by the caller, and a named_list
|
||||
# by the callee.
|
||||
|
||||
# Also at runtime Messages and NamedLists remain completely "normal" objects.
|
||||
# Ie they have have type and instances and so on.*
|
||||
# Which resolves the dichotomy of objects on the stack or heap. Sama sama.
|
||||
#
|
||||
# *Alas the type for each call instance is unique.
|
||||
#
|
||||
module Parfait
|
||||
class NamedList < Object
|
||||
|
||||
def self.type_for( arguments )
|
||||
my_class = Parfait.object_space.classes[:NamedList]
|
||||
Type.for_hash( my_class , {type: my_class.instance_type}.merge(arguments))
|
||||
end
|
||||
end
|
||||
end
|
126
lib/vm/parfait/object.rb
Normal file
126
lib/vm/parfait/object.rb
Normal file
@ -0,0 +1,126 @@
|
||||
# From a programmers perspective an object has hash like data (with instance variables as keys)
|
||||
# and functions to work on that data.
|
||||
# Only the object may access it's data directly.
|
||||
|
||||
# From an implementation perspective it is a chunk of memory with a type as the first
|
||||
# word (instance of class Type).
|
||||
|
||||
# Objects are arranged or layed out (in memory) according to their Type
|
||||
# every object has a Type. Type objects are immutalbe and may be reused for a group/class
|
||||
# off objects.
|
||||
# The Type of an object may change, but then a new Type is created
|
||||
# The Type also defines the class of the object
|
||||
# The Type is **always** the first entry (index 1) in an object
|
||||
|
||||
module Parfait
|
||||
TYPE_INDEX = 1
|
||||
|
||||
class Object
|
||||
|
||||
def self.new *args
|
||||
object = self.allocate
|
||||
|
||||
# have to grab the class, because we are in the ruby class not the parfait one
|
||||
cl = Parfait.object_space.get_class_by_name( self.name.split("::").last.to_sym)
|
||||
|
||||
# and have to set the type before we let the object do anything. otherwise boom
|
||||
object.set_type cl.instance_type
|
||||
|
||||
object.send :initialize , *args
|
||||
object
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def get_internal_word(index)
|
||||
name = get_type().name_at(index)
|
||||
return nil unless name
|
||||
eval "@#{name}"
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def set_internal_word(index , value)
|
||||
return set_type(value) if( index == 1)
|
||||
raise "not type #{@type.class}" unless @type.is_a?(Type)
|
||||
name = @type.name_at(index)
|
||||
raise "object type has no name at index #{index} " unless name
|
||||
eval "@#{name} = value"
|
||||
value
|
||||
end
|
||||
|
||||
def == other
|
||||
self.object_id == other.object_id
|
||||
end
|
||||
|
||||
# This is the crux of the object system. The class of an object is stored in the objects
|
||||
# memory (as opposed to an integer that has no memory and so always has the same class)
|
||||
#
|
||||
# In RubyX we store the class in the Type, and so the Type is the only fixed
|
||||
# data that every object carries.
|
||||
def get_class()
|
||||
l = get_type()
|
||||
#puts "Type #{l.class} in #{self.class} , #{self}"
|
||||
l.object_class()
|
||||
end
|
||||
|
||||
# private
|
||||
def set_type(type)
|
||||
# puts "Type was set for #{self.class}"
|
||||
raise "not type #{type.class}" unless type.is_a?(Type)
|
||||
@type = type
|
||||
end
|
||||
|
||||
# so we can keep the raise in get_type
|
||||
def has_type?
|
||||
! @type.nil?
|
||||
end
|
||||
|
||||
def get_type()
|
||||
raise "No type #{self.object_id.to_s(16)}:#{self.class} " unless has_type?
|
||||
@type
|
||||
end
|
||||
|
||||
# return the metaclass
|
||||
def meta
|
||||
MetaClass.new self
|
||||
end
|
||||
|
||||
def get_instance_variables
|
||||
@type.names
|
||||
end
|
||||
|
||||
def get_instance_variable( name )
|
||||
index = instance_variable_defined(name)
|
||||
#puts "getting #{name} at #{index}"
|
||||
return nil if index == nil
|
||||
return get_internal_word(index)
|
||||
end
|
||||
|
||||
def set_instance_variable( name , value )
|
||||
index = instance_variable_defined(name)
|
||||
return nil if index == nil
|
||||
return set_internal_word(index , value)
|
||||
end
|
||||
|
||||
def instance_variable_defined( name )
|
||||
@type.variable_index(name)
|
||||
end
|
||||
|
||||
def padded_length
|
||||
Padding.padded_words( @type.instance_length )
|
||||
end
|
||||
|
||||
# parfait versions are deliberately called different, so we "relay"
|
||||
# have to put the "@" on the names for sof to take them off again
|
||||
def instance_variables
|
||||
get_instance_variables.to_a.collect{ |n| "@#{n}".to_sym }
|
||||
end
|
||||
|
||||
# name comes in as a ruby @var name
|
||||
def instance_variable_get name
|
||||
var = get_instance_variable name.to_s[1 .. -1].to_sym
|
||||
#puts "getting #{name} #{var}"
|
||||
var
|
||||
end
|
||||
|
||||
end
|
||||
end
|
15
lib/vm/parfait/page.rb
Normal file
15
lib/vm/parfait/page.rb
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
# A Page (from the traditional memory page) represents a collection of
|
||||
# objects in a physically form. Ie the page holds the memory or data, that
|
||||
# the objects are made up of.
|
||||
|
||||
# Pages have a total size, but more importantly an object size.
|
||||
# All objects of a Page are same sized, and multiples of the smallest
|
||||
# object. The smallest object is usually a cache line, 16 bytes or
|
||||
# an exponent of two larger.
|
||||
|
||||
module Parfait
|
||||
class Page < Object
|
||||
|
||||
end
|
||||
end
|
126
lib/vm/parfait/space.rb
Normal file
126
lib/vm/parfait/space.rb
Normal file
@ -0,0 +1,126 @@
|
||||
|
||||
# A Space is a collection of pages. It stores objects, the data for the objects,
|
||||
# not references. See Page for more detail.
|
||||
|
||||
# Pages are stored by the object size they represent in a hash.
|
||||
|
||||
# Space and Page work together in making *new* objects available.
|
||||
# "New" is slightly misleading in that normal operation only ever
|
||||
# recycles objects.
|
||||
|
||||
module Parfait
|
||||
# Make the object space globally available
|
||||
def self.object_space
|
||||
@@object_space
|
||||
end
|
||||
|
||||
# TODO Must get rid of the setter (move the boot process ?)
|
||||
def self.set_object_space space
|
||||
@@object_space = space
|
||||
end
|
||||
|
||||
# The Space contains all objects for a program. In functional terms it is a program, but in oo
|
||||
# it is a collection of objects, some of which are data, some classes, some functions
|
||||
|
||||
# The main entry is a function called (of all things) "main".
|
||||
# This _must be supplied by the compled code (similar to c)
|
||||
# There is a start and exit block that call main, which receives an List of strings
|
||||
|
||||
# While data ususally would live in a .data section, we may also "inline" it into the code
|
||||
# in an oo system all data is represented as objects
|
||||
|
||||
class Space < Object
|
||||
|
||||
def initialize(classes )
|
||||
@classes = classes
|
||||
@types = Dictionary.new
|
||||
message = Message.new(nil)
|
||||
50.times do
|
||||
@first_message = Message.new message
|
||||
#puts "INIT caller #{message.object_id} to #{@first_message.object_id}"
|
||||
message.set_caller @first_message
|
||||
message = @first_message
|
||||
end
|
||||
@classes.each do |name , cl|
|
||||
add_type(cl.instance_type)
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :classes , :first_message
|
||||
|
||||
def each_type
|
||||
@types.values.each do |type|
|
||||
yield(type)
|
||||
end
|
||||
end
|
||||
|
||||
def add_type(type)
|
||||
hash = type.hash
|
||||
raise "upps #{hash} #{hash.class}" unless hash.is_a?(Fixnum)
|
||||
was = @types[hash]
|
||||
return was if was
|
||||
@types[hash] = type
|
||||
end
|
||||
|
||||
def get_type_for( hash )
|
||||
@types[hash]
|
||||
end
|
||||
|
||||
# all methods form all types
|
||||
def collect_methods
|
||||
methods = []
|
||||
each_type do | type |
|
||||
type.methods.each do |meth|
|
||||
methods << meth
|
||||
end
|
||||
end
|
||||
methods
|
||||
end
|
||||
|
||||
def get_main
|
||||
kernel = get_class_by_name :Space
|
||||
kernel.instance_type.get_method :main
|
||||
end
|
||||
|
||||
def get_init
|
||||
kernel = get_class_by_name :Kernel
|
||||
kernel.instance_type.get_method :__init__
|
||||
end
|
||||
|
||||
# get a class by name (symbol)
|
||||
# return nili if no such class. Use bang version if create should be implicit
|
||||
def get_class_by_name( name )
|
||||
raise "get_class_by_name #{name}.#{name.class}" unless name.is_a?(Symbol)
|
||||
c = @classes[name]
|
||||
#puts "MISS, no class #{name} #{name.class}" unless c # " #{@classes}"
|
||||
#puts "CLAZZ, #{name} #{c.get_type.get_length}" if c
|
||||
c
|
||||
end
|
||||
|
||||
# get or create the class by the (symbol) name
|
||||
# notice that this method of creating classes implies Object superclass
|
||||
def get_class_by_name!(name , super_class = :Object)
|
||||
c = get_class_by_name(name)
|
||||
return c if c
|
||||
create_class( name ,super_class)
|
||||
end
|
||||
|
||||
# this is the way to instantiate classes (not Parfait::Class.new)
|
||||
# so we get and keep exactly one per name
|
||||
def create_class( name , superclass = nil )
|
||||
raise "create_class #{name.class}" unless name.is_a? Symbol
|
||||
superclass = :Object unless superclass
|
||||
raise "create_class #{superclass.class}" unless superclass.is_a? Symbol
|
||||
type = get_class_by_name(superclass).instance_type
|
||||
c = Class.new(name , superclass , type )
|
||||
@classes[name] = c
|
||||
end
|
||||
|
||||
def sof_reference_name
|
||||
"space"
|
||||
end
|
||||
|
||||
end
|
||||
# ObjectSpace
|
||||
# :each_object, :garbage_collect, :define_finalizer, :undefine_finalizer, :_id2ref, :count_objects
|
||||
end
|
16
lib/vm/parfait/symbol_adapter.rb
Normal file
16
lib/vm/parfait/symbol_adapter.rb
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
class Symbol
|
||||
|
||||
def has_type?
|
||||
true
|
||||
end
|
||||
def get_type
|
||||
l = Parfait.object_space.classes[:Word].instance_type
|
||||
#puts "LL #{l.class}"
|
||||
l
|
||||
end
|
||||
def padded_length
|
||||
Padding.padded( to_s.length + 4)
|
||||
end
|
||||
|
||||
end
|
218
lib/vm/parfait/type.rb
Normal file
218
lib/vm/parfait/type.rb
Normal file
@ -0,0 +1,218 @@
|
||||
# An Object is really a hash like structure. It is dynamic and
|
||||
# you want to store values by name (instance variable names).
|
||||
#
|
||||
# One could (like mri), store the names in each object, but that is wasteful in both time and space.
|
||||
# Instead we store only the values, and access them by index.
|
||||
# The Type allows the mapping of names to index.
|
||||
|
||||
# The Type of an object describes the memory layout of the object. In a c analogy, it is the
|
||||
# information defined in a struct.
|
||||
# The Type is a list of the names of instance variables, and their value types (int etc).
|
||||
#
|
||||
# Every object has a Type to describe it, so it's *first* instance variable is **always**
|
||||
# "type". This means the name "type" is the first name in the list
|
||||
# for every Type instance.
|
||||
|
||||
# But, as we want every Object to have a class, the Type carries that class.
|
||||
# So the type of type has an entry "object_class"
|
||||
|
||||
# But Objects must also be able to carry methods themselves (ruby calls singleton_methods)
|
||||
# and those too are stored in the Type (both type and class include behaviour)
|
||||
|
||||
# The object is an List of values of length n
|
||||
|
||||
# The Type is a list of n names and n types that describe the values stored in an actual object.
|
||||
|
||||
# Together they turn the object into a hash like structure
|
||||
|
||||
# For types to be a useful concept, they have to be unique and immutable. Any "change", like adding
|
||||
# a name/type pair, will result in a new instance.
|
||||
|
||||
# The Type class carries a hash of types of the systems, which is used to ensure that
|
||||
# there is only one instance of every type. Hash and equality are defined on type
|
||||
# for this to work.
|
||||
|
||||
module Parfait
|
||||
class Type < Object
|
||||
|
||||
attr_reader :object_class , :names , :types , :methods
|
||||
|
||||
def self.for_hash( object_class , hash)
|
||||
hash = {type: object_class.name }.merge(hash) unless hash[:type]
|
||||
new_type = Type.new( object_class , hash)
|
||||
Parfait.object_space.add_type(new_type)
|
||||
end
|
||||
|
||||
def initialize( object_class , hash )
|
||||
super()
|
||||
set_object_class( object_class)
|
||||
init_lists( hash )
|
||||
end
|
||||
|
||||
# this part of the init is seperate because at boot time we can not use normal new
|
||||
# new is overloaded to grab the type from space, and before boot, that is not set up
|
||||
def init_lists(hash)
|
||||
@methods = List.new
|
||||
@names = List.new
|
||||
@types = List.new
|
||||
raise "No type Type in #{hash}" unless hash[:type]
|
||||
private_add_instance_variable(:type , hash[:type]) #first
|
||||
hash.each do |name , type|
|
||||
private_add_instance_variable(name , type) unless name == :type
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{@object_class.name}-#{@names.inspect}"
|
||||
end
|
||||
|
||||
def method_names
|
||||
names = List.new
|
||||
@methods.each do |method|
|
||||
names.push method.name
|
||||
end
|
||||
names
|
||||
end
|
||||
|
||||
def create_method( method_name , arguments )
|
||||
raise "create_method #{method_name}.#{method_name.class}" unless method_name.is_a?(Symbol)
|
||||
#puts "Self: #{self.class} clazz: #{clazz.name}"
|
||||
arg_type = arguments
|
||||
arg_type = NamedList.type_for( arguments ) if arguments.is_a?(Hash)
|
||||
add_method TypedMethod.new( self , method_name , arg_type )
|
||||
end
|
||||
|
||||
def add_method( method )
|
||||
raise "not a method #{method.class} #{method.inspect}" unless method.is_a? TypedMethod
|
||||
raise "syserr #{method.name.class}" unless method.name.is_a? Symbol
|
||||
if self.is_a?(Class) and (method.for_type != self)
|
||||
raise "Adding to wrong class, should be #{method.for_class}"
|
||||
end
|
||||
found = get_method( method.name )
|
||||
if found
|
||||
@methods.delete(found)
|
||||
end
|
||||
@methods.push method
|
||||
#puts "#{self.name} add #{method.name}"
|
||||
method
|
||||
end
|
||||
|
||||
def remove_method( method_name )
|
||||
found = get_method( method_name )
|
||||
raise "No such method #{method_name} in #{self.name}" unless found
|
||||
@methods.delete(found)
|
||||
end
|
||||
|
||||
def get_method( fname )
|
||||
raise "get_method #{fname}.#{fname.class}" unless fname.is_a?(Symbol)
|
||||
#if we had a hash this would be easier. Detect or find would help too
|
||||
@methods.each do |m|
|
||||
return m if(m.name == fname )
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def == other
|
||||
self.object_id == other.object_id
|
||||
end
|
||||
|
||||
# add the name of an instance variable
|
||||
# Type objects are immutable, so a new object is returned
|
||||
# As types are also unique, two same adds will result in identical results
|
||||
def add_instance_variable( name , type )
|
||||
raise "No nil name" unless name
|
||||
raise "No nil type" unless type
|
||||
hash = to_hash
|
||||
hash[name] = type
|
||||
return Type.for_hash( @object_class , hash)
|
||||
end
|
||||
|
||||
def set_object_class(oc)
|
||||
raise "object class should be a class, not #{oc.class}" unless oc.is_a?(Class)
|
||||
@object_class = oc
|
||||
end
|
||||
|
||||
def instance_length
|
||||
@names.get_length()
|
||||
end
|
||||
|
||||
# index of the variable when using get_internal_word
|
||||
# (get_internal_word is 1 based and 1 is always the type)
|
||||
def variable_index( name )
|
||||
has = names.index_of(name)
|
||||
return nil unless has
|
||||
raise "internal error #{name}:#{has}" if has < 1
|
||||
has
|
||||
end
|
||||
|
||||
def get_length()
|
||||
@names.get_length()
|
||||
end
|
||||
|
||||
def name_at( index )
|
||||
@names.get(index)
|
||||
end
|
||||
|
||||
def type_at( index )
|
||||
@types.get(index)
|
||||
end
|
||||
|
||||
def inspect
|
||||
"Type[#{names.inspect}]"
|
||||
end
|
||||
|
||||
def sof_reference_name
|
||||
"#{@object_class.name}_Type"
|
||||
end
|
||||
alias :name :sof_reference_name
|
||||
|
||||
def each
|
||||
index = 1
|
||||
while( index <= get_length() )
|
||||
yield( name_at(index) , type_at(index) )
|
||||
index += 1
|
||||
end
|
||||
end
|
||||
|
||||
def to_hash
|
||||
hash = {}
|
||||
each do |name , type|
|
||||
hash[name] = type
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
def hash
|
||||
index = 1
|
||||
hash_code = Type.str_hash( @object_class.name )
|
||||
each do |name , type|
|
||||
item_hash = Type.str_hash(name) + Type.str_hash(type)
|
||||
hash_code += item_hash + (item_hash / 256 ) * index
|
||||
index += 1
|
||||
end
|
||||
hash_code % (2 ** 62)
|
||||
end
|
||||
|
||||
def self.str_hash(str)
|
||||
if RUBY_ENGINE == 'opal'
|
||||
hash = 5381
|
||||
str.to_s.each_char do |c|
|
||||
hash = ((hash << 5) + hash) + c.to_i; # hash * 33 + c without getting bignums
|
||||
end
|
||||
hash % (2 ** 51)
|
||||
else
|
||||
str.hash
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def private_add_instance_variable( name , type)
|
||||
raise "Name shouldn't be nil" unless name
|
||||
raise "Value Type shouldn't be nil" unless type
|
||||
@names.push(name)
|
||||
@types.push(type)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
102
lib/vm/parfait/typed_method.rb
Normal file
102
lib/vm/parfait/typed_method.rb
Normal file
@ -0,0 +1,102 @@
|
||||
# A TypedMethod is static object that primarily holds the executable code.
|
||||
# It is called typed, because all arguments and variables it uses are typed.
|
||||
# (Type means basic type, ie integer or reference)
|
||||
|
||||
# It's relation to the method a ruby programmer knows (called RubyMethod) is many to one,
|
||||
# meaning one RubyMethod (untyped) has many TypedMethod implementations.
|
||||
# The RubyMethod only holds ruby code, no binary.
|
||||
|
||||
# The Typed method has the following instance variables
|
||||
# - name : This is the same as the ruby method name it implements
|
||||
# - source: is currently the ast (or string) that represents the "code". This is historic
|
||||
# and will change to the RubyMethod that it implements
|
||||
# - instructions: The sequence of instructions the source (ast) was compiled to
|
||||
# Instructions derive from class Instruction and form a linked list
|
||||
# - binary: The binary (jumpable) code that the instructions get assembled into
|
||||
# - arguments: A type object describing the arguments (name+types) to be passed
|
||||
# - locals: A type object describing the local variables that the method has
|
||||
# - for_type: The Type the Method is for
|
||||
|
||||
|
||||
module Parfait
|
||||
|
||||
class TypedMethod < Object
|
||||
|
||||
attr_reader :name , :instructions , :for_type ,:arguments , :locals , :binary
|
||||
|
||||
# not part of the parfait model, hence ruby accessor
|
||||
attr_accessor :source
|
||||
|
||||
def initialize( type , name , arguments )
|
||||
super()
|
||||
raise "No class #{name}" unless type
|
||||
raise "For type, not class #{type}" unless type.is_a?(Type)
|
||||
raise "Wrong argument type, expect Type not #{arguments.class}" unless arguments.is_a? Type
|
||||
@for_type = type
|
||||
@name = name
|
||||
@binary = BinaryCode.new 0
|
||||
@arguments = arguments
|
||||
@locals = Parfait.object_space.get_class_by_name( :NamedList ).instance_type
|
||||
end
|
||||
|
||||
def set_instructions(inst)
|
||||
@instructions = inst
|
||||
end
|
||||
|
||||
# determine whether this method has an argument by the name
|
||||
def has_arg( name )
|
||||
raise "has_arg #{name}.#{name.class}" unless name.is_a? Symbol
|
||||
index = arguments.variable_index( name )
|
||||
index ? (index - 1) : index
|
||||
end
|
||||
|
||||
def add_argument(name , type)
|
||||
@arguments = @arguments.add_instance_variable(name,type)
|
||||
end
|
||||
|
||||
def arguments_length
|
||||
arguments.instance_length - 1
|
||||
end
|
||||
|
||||
def argument_name( index )
|
||||
arguments.names.get(index + 1)
|
||||
end
|
||||
def argument_type( index )
|
||||
arguments.types.get(index + 1)
|
||||
end
|
||||
|
||||
# determine if method has a local variable or tmp (anonymous local) by given name
|
||||
def has_local( name )
|
||||
raise "has_local #{name}.#{name.class}" unless name.is_a? Symbol
|
||||
index = locals.variable_index( name )
|
||||
index ? (index - 1) : index
|
||||
end
|
||||
|
||||
def add_local( name , type )
|
||||
index = has_local name
|
||||
return index if index
|
||||
@locals = @locals.add_instance_variable(name,type)
|
||||
end
|
||||
|
||||
def locals_length
|
||||
locals.instance_length - 1
|
||||
end
|
||||
|
||||
def locals_name( index )
|
||||
locals.names.get(index + 1)
|
||||
end
|
||||
|
||||
def locals_type( index )
|
||||
locals.types.get(index + 1)
|
||||
end
|
||||
|
||||
def sof_reference_name
|
||||
"Method: " + @name.to_s
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#{@for_type.object_class.name}:#{name}(#{arguments.inspect})"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
218
lib/vm/parfait/word.rb
Normal file
218
lib/vm/parfait/word.rb
Normal file
@ -0,0 +1,218 @@
|
||||
|
||||
|
||||
module Parfait
|
||||
# A word is a a short sequence of characters
|
||||
# Characters are not modeled as objects but as (small) integers
|
||||
# The small means two of them have to fit into a machine word, utf16 or similar
|
||||
#
|
||||
# Words are constant, maybe like js strings, ruby symbols
|
||||
# Words are short, but may have spaces
|
||||
|
||||
# Words are objects, that means they carry Type as index 0
|
||||
# So all indexes are offset by one in the implementation
|
||||
# Object length is measured in non-type cells though
|
||||
|
||||
class Word < Object
|
||||
attr_reader :char_length
|
||||
|
||||
#semi "indexed" methods for interpreter
|
||||
def self.get_length_index
|
||||
2 # 2 is the amount of attributes, type and char_length. the offset after which chars start
|
||||
end
|
||||
def self.get_indexed i
|
||||
i + get_length_index * 4
|
||||
end
|
||||
# initialize with length. For now we try to keep all non-parfait (including String) out
|
||||
# String will contain spaces for non-zero length
|
||||
# Register provides methods to create Parfait objects from ruby
|
||||
def initialize len
|
||||
super()
|
||||
@char_length = 0
|
||||
@memory = []
|
||||
raise "Must init with int, not #{len.class}" unless len.kind_of? Fixnum
|
||||
raise "Must init with positive, not #{len}" if len < 0
|
||||
set_length( len , 32 ) unless len == 0 #32 beeing ascii space
|
||||
#puts "type #{self.get_type} #{self.object_id.to_s(16)}"
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def get_internal_word(index)
|
||||
@memory[index]
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def set_internal_word(index , value)
|
||||
raise "Word[#{index}] = nil" if( value.nil? )
|
||||
@memory[index] = value
|
||||
value
|
||||
end
|
||||
|
||||
|
||||
# return a copy of self
|
||||
def copy
|
||||
cop = Word.new( self.length )
|
||||
index = 1
|
||||
while( index <= self.length )
|
||||
cop.set_char(index , self.get_char(index))
|
||||
index = index + 1
|
||||
end
|
||||
cop
|
||||
end
|
||||
|
||||
# return the number of characters
|
||||
def length()
|
||||
obj_len = @char_length
|
||||
return obj_len
|
||||
end
|
||||
|
||||
# make every char equal the given one
|
||||
def fill_with char
|
||||
fill_from_with(0 , char)
|
||||
end
|
||||
|
||||
def fill_from_with from , char
|
||||
len = self.length()
|
||||
return if from <= 0
|
||||
while( from <= len)
|
||||
set_char( from , char)
|
||||
from = from + 1
|
||||
end
|
||||
from
|
||||
end
|
||||
|
||||
# true if no characters
|
||||
def empty?
|
||||
return self.length == 0
|
||||
end
|
||||
|
||||
# pad the string with the given character to the given length
|
||||
#
|
||||
def set_length(len , fill_char)
|
||||
return if len <= 0
|
||||
old = @char_length
|
||||
return if old >= len
|
||||
@char_length = len
|
||||
check_length
|
||||
fill_from_with( old + 1 , fill_char )
|
||||
end
|
||||
|
||||
# set the character at the given index to the given character
|
||||
# character must be an integer, as is the index
|
||||
# the index starts at one, but may be negative to count from the end
|
||||
# indexes out of range will raise an error
|
||||
def set_char at , char
|
||||
raise "char not fixnum #{char.class}" unless char.kind_of? Fixnum
|
||||
index = range_correct_index(at)
|
||||
set_internal_byte( index , char)
|
||||
end
|
||||
|
||||
def set_internal_byte index , char
|
||||
word_index = (index) / 4
|
||||
rest = ((index) % 4)
|
||||
shifted = char << (rest * 8)
|
||||
was = get_internal_word( word_index )
|
||||
was = 0 unless was.is_a?(Numeric)
|
||||
mask = 0xFF << (rest * 8)
|
||||
mask = 0xFFFFFFFF - mask
|
||||
masked = was & mask
|
||||
put = masked + shifted
|
||||
set_internal_word( word_index , put )
|
||||
msg = "set index=#{index} word_index=#{word_index} rest=#{rest}= "
|
||||
msg += "char=#{char.to_s(16)} shifted=#{shifted.to_s(16)} "
|
||||
msg += "was=#{was.to_s(16)} masked=#{masked.to_s(16)} put=#{put.to_s(16)}"
|
||||
#puts msg
|
||||
char
|
||||
end
|
||||
|
||||
# get the character at the given index (lowest 1)
|
||||
# the index starts at one, but may be negative to count from the end
|
||||
# indexes out of range will raise an error
|
||||
#the return "character" is an integer
|
||||
def get_char at
|
||||
index = range_correct_index(at)
|
||||
get_internal_byte(index)
|
||||
end
|
||||
|
||||
|
||||
def get_internal_byte( index )
|
||||
word_index = (index ) / 4
|
||||
rest = ((index) % 4)
|
||||
char = get_internal_word(word_index)
|
||||
char = 0 unless char.is_a?(Numeric)
|
||||
shifted = char >> (8 * rest)
|
||||
ret = shifted & 0xFF
|
||||
msg = "get index=#{index} word_index=#{word_index} rest=#{rest}= "
|
||||
msg += " char=#{char.to_s(16)} shifted=#{shifted.to_s(16)} ret=#{ret.to_s(16)}"
|
||||
#puts msg
|
||||
return ret
|
||||
end
|
||||
|
||||
# private method to calculate negative indexes into positives
|
||||
def range_correct_index at
|
||||
index = at
|
||||
# index = self.length + at if at < 0
|
||||
raise "index must be positive , not #{at}" if (index <= 0)
|
||||
raise "index too large #{at} > #{self.length}" if (index > self.length )
|
||||
return index + 11
|
||||
end
|
||||
|
||||
# compare the word to another
|
||||
# currently checks for same class, though really identity of the characters
|
||||
# in right order would suffice
|
||||
def compare( other )
|
||||
return false if other.class != self.class
|
||||
return false if other.length != self.length
|
||||
len = self.length
|
||||
while(len > 0)
|
||||
return false if self.get_char(len) != other.get_char(len)
|
||||
len = len - 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def == other
|
||||
return false unless other.is_a?(String) or other.is_a?(Word)
|
||||
as_string = self.to_string
|
||||
unless other.is_a? String
|
||||
other = other.to_string
|
||||
end
|
||||
as_string == other
|
||||
end
|
||||
|
||||
def to_string
|
||||
string = ""
|
||||
index = 1
|
||||
while( index <= @char_length)
|
||||
char = get_char(index)
|
||||
string += char ? char.chr : "*"
|
||||
index = index + 1
|
||||
end
|
||||
string
|
||||
end
|
||||
|
||||
# as we answered is_value? with true, sof will create a basic node with this string
|
||||
def to_sof
|
||||
"'" + to_s + "'"
|
||||
end
|
||||
|
||||
def padded_length
|
||||
Padding.padded( 4 * get_type().instance_length + @char_length )
|
||||
end
|
||||
|
||||
private
|
||||
def check_length
|
||||
raise "Length out of bounds #{@char_length}" if @char_length > 1000
|
||||
end
|
||||
end
|
||||
|
||||
# Word from string
|
||||
def self.new_word( string )
|
||||
string = string.to_s if string.is_a? Symbol
|
||||
word = Word.new( string.length )
|
||||
string.codepoints.each_with_index do |code , index |
|
||||
word.set_char(index + 1 , code)
|
||||
end
|
||||
word
|
||||
end
|
||||
|
||||
end
|
48
lib/vm/tree.rb
Normal file
48
lib/vm/tree.rb
Normal file
@ -0,0 +1,48 @@
|
||||
# Base class for Expresssion and Statement
|
||||
module Vm
|
||||
|
||||
class Code ; end
|
||||
class Statement < Code ; end
|
||||
class Expression < Code ; end
|
||||
|
||||
module ValuePrinter
|
||||
def to_s
|
||||
@value.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require_relative "tree/while_statement"
|
||||
require_relative "tree/if_statement"
|
||||
require_relative "tree/return_statement"
|
||||
require_relative "tree/statements"
|
||||
require_relative "tree/operator_expression"
|
||||
require_relative "tree/field_access"
|
||||
require_relative "tree/call_site"
|
||||
require_relative "tree/basic_values"
|
||||
require_relative "tree/assignment"
|
||||
require_relative "tree/to_code"
|
||||
|
||||
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
|
13
lib/vm/tree/assignment.rb
Normal file
13
lib/vm/tree/assignment.rb
Normal file
@ -0,0 +1,13 @@
|
||||
module Vm
|
||||
module Tree
|
||||
class Assignment < Statement
|
||||
attr_accessor :name , :value
|
||||
def initialize(n = nil , v = nil )
|
||||
@name , @value = n , v
|
||||
end
|
||||
def to_s
|
||||
"#{name} = #{value}\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
55
lib/vm/tree/basic_values.rb
Normal file
55
lib/vm/tree/basic_values.rb
Normal file
@ -0,0 +1,55 @@
|
||||
module Vm
|
||||
module Tree
|
||||
class IntegerExpression < Expression
|
||||
include ValuePrinter
|
||||
attr_accessor :value
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
class FloatExpression < Expression
|
||||
include ValuePrinter
|
||||
attr_accessor :value
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
class TrueExpression < Expression
|
||||
def to_s
|
||||
"true"
|
||||
end
|
||||
end
|
||||
class FalseExpression < Expression
|
||||
def to_s
|
||||
"false"
|
||||
end
|
||||
end
|
||||
class NilExpression < Expression
|
||||
def to_s
|
||||
"nil"
|
||||
end
|
||||
end
|
||||
class StringExpression < Expression
|
||||
include ValuePrinter
|
||||
attr_accessor :value
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
class NameExpression < Expression
|
||||
include ValuePrinter
|
||||
attr_accessor :value
|
||||
alias :name :value
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
class ClassExpression < Expression
|
||||
include ValuePrinter
|
||||
attr_accessor :value
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
lib/vm/tree/call_site.rb
Normal file
12
lib/vm/tree/call_site.rb
Normal file
@ -0,0 +1,12 @@
|
||||
module Vm
|
||||
module Tree
|
||||
class CallSite < Expression
|
||||
attr_accessor :name , :receiver , :arguments
|
||||
|
||||
def to_s
|
||||
str = receiver ? "#{receiver}.#{name}" : name.to_s
|
||||
str + arguments.collect{|a| a.to_s }.join(",")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/vm/tree/field_access.rb
Normal file
10
lib/vm/tree/field_access.rb
Normal file
@ -0,0 +1,10 @@
|
||||
module Vm
|
||||
module Tree
|
||||
class FieldAccess < Expression
|
||||
attr_accessor :receiver , :field
|
||||
def to_s
|
||||
"#{receiver}.#{field}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
lib/vm/tree/if_statement.rb
Normal file
12
lib/vm/tree/if_statement.rb
Normal file
@ -0,0 +1,12 @@
|
||||
module Vm
|
||||
module Tree
|
||||
class IfStatement < Statement
|
||||
attr_accessor :branch_type , :condition , :if_true , :if_false
|
||||
def to_s
|
||||
str = "if_#{branch_type}(#{condition}) \n #{if_true}\n"
|
||||
str += "else\n #{if_false}\n" if if_false
|
||||
str + "end\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
10
lib/vm/tree/operator_expression.rb
Normal file
10
lib/vm/tree/operator_expression.rb
Normal file
@ -0,0 +1,10 @@
|
||||
module Vm
|
||||
module Tree
|
||||
class OperatorExpression < Expression
|
||||
attr_accessor :operator , :left_expression , :right_expression
|
||||
def to_s
|
||||
"#{left_expression} #{operator} #{right_expression}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
11
lib/vm/tree/return_statement.rb
Normal file
11
lib/vm/tree/return_statement.rb
Normal file
@ -0,0 +1,11 @@
|
||||
module Vm
|
||||
module Tree
|
||||
class ReturnStatement < Statement
|
||||
attr_accessor :return_value
|
||||
|
||||
def to_s
|
||||
"return #{return_value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
9
lib/vm/tree/statements.rb
Normal file
9
lib/vm/tree/statements.rb
Normal file
@ -0,0 +1,9 @@
|
||||
module Vm
|
||||
class Statements < Statement
|
||||
attr_accessor :statements
|
||||
def to_s
|
||||
return "" unless statements
|
||||
statements.collect() { |s| s.to_s }.join
|
||||
end
|
||||
end
|
||||
end
|
132
lib/vm/tree/to_code.rb
Normal file
132
lib/vm/tree/to_code.rb
Normal file
@ -0,0 +1,132 @@
|
||||
module Vm
|
||||
|
||||
def self.ast_to_code statement
|
||||
compiler = ToCode.new
|
||||
compiler.process statement
|
||||
end
|
||||
|
||||
class ToCode < AST::Processor
|
||||
|
||||
def handler_missing node
|
||||
raise "No handler on_#{node.type}(node)"
|
||||
end
|
||||
|
||||
def on_parameters statement
|
||||
params = {}
|
||||
statement.children.each do |param , type , name|
|
||||
type , name = *param
|
||||
params[name] = type
|
||||
end
|
||||
params
|
||||
end
|
||||
|
||||
def on_while_statement statement
|
||||
branch_type , condition , statements = *statement
|
||||
w = Tree::WhileStatement.new()
|
||||
w.branch_type = branch_type
|
||||
w.condition = process(condition)
|
||||
w.statements = process(statements)
|
||||
w
|
||||
end
|
||||
|
||||
def on_if_statement statement
|
||||
branch_type , condition , if_true , if_false = *statement
|
||||
w = Tree::IfStatement.new()
|
||||
w.branch_type = branch_type
|
||||
w.condition = process(condition)
|
||||
w.if_true = process(if_true)
|
||||
w.if_false = process(if_false)
|
||||
w
|
||||
end
|
||||
|
||||
def process_first code
|
||||
raise "Too many children #{code.inspect}" if code.children.length != 1
|
||||
process code.children.first
|
||||
end
|
||||
alias :on_conditional :process_first
|
||||
alias :on_condition :process_first
|
||||
alias :on_field :process_first
|
||||
|
||||
def on_statements statement
|
||||
w = Statements.new()
|
||||
return w unless statement.children
|
||||
return w unless statement.children.first
|
||||
w.statements = process_all(statement.children)
|
||||
w
|
||||
end
|
||||
alias :on_true_statements :on_statements
|
||||
alias :on_false_statements :on_statements
|
||||
|
||||
def on_return statement
|
||||
w = Tree::ReturnStatement.new()
|
||||
w.return_value = process(statement.children.first)
|
||||
w
|
||||
end
|
||||
|
||||
def on_operator_value statement
|
||||
operator , left_e , right_e = *statement
|
||||
w = Tree::OperatorExpression.new()
|
||||
w.operator = operator
|
||||
w.left_expression = process(left_e)
|
||||
w.right_expression = process(right_e)
|
||||
w
|
||||
end
|
||||
|
||||
def on_field_access statement
|
||||
receiver_ast , field_ast = *statement
|
||||
w = Tree::FieldAccess.new()
|
||||
w.receiver = process(receiver_ast)
|
||||
w.field = process(field_ast)
|
||||
w
|
||||
end
|
||||
|
||||
def on_receiver expression
|
||||
process expression.children.first
|
||||
end
|
||||
|
||||
def on_call statement
|
||||
name_s , arguments , receiver = *statement
|
||||
w = Tree::CallSite.new()
|
||||
w.name = name_s.children.first
|
||||
w.arguments = process_all(arguments)
|
||||
w.receiver = process(receiver)
|
||||
w
|
||||
end
|
||||
|
||||
def on_int expression
|
||||
Tree::IntegerExpression.new(expression.children.first)
|
||||
end
|
||||
|
||||
def on_true _expression
|
||||
Tree::TrueExpression.new
|
||||
end
|
||||
|
||||
def on_false _expression
|
||||
Tree::FalseExpression.new
|
||||
end
|
||||
|
||||
def on_nil _expression
|
||||
Tree::NilExpression.new
|
||||
end
|
||||
|
||||
def on_name statement
|
||||
Tree::NameExpression.new(statement.children.first)
|
||||
end
|
||||
def on_string expression
|
||||
Tree::StringExpression.new(expression.children.first)
|
||||
end
|
||||
|
||||
def on_class_name expression
|
||||
Tree::ClassExpression.new(expression.children.first)
|
||||
end
|
||||
|
||||
def on_assignment statement
|
||||
name , value = *statement
|
||||
w = Vm::Tree::Assignment.new()
|
||||
w.name = process name
|
||||
w.value = process(value)
|
||||
w
|
||||
end
|
||||
|
||||
end
|
||||
end
|
11
lib/vm/tree/while_statement.rb
Normal file
11
lib/vm/tree/while_statement.rb
Normal file
@ -0,0 +1,11 @@
|
||||
module Vm
|
||||
module Tree
|
||||
class WhileStatement < Statement
|
||||
attr_accessor :branch_type , :condition , :statements
|
||||
def to_s
|
||||
str = "while_#{branch_type}(#{condition}) do\n"
|
||||
str + statements.to_s + "\nend\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user