renames Typed to Vm

This commit is contained in:
Torsten Ruger
2017-01-14 19:28:44 +02:00
parent 75c7ca950e
commit bd78a2d555
95 changed files with 61 additions and 61 deletions

207
lib/vm/method_compiler.rb Normal file
View 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

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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,7 @@
module Vm
module StatementList
def on_Statements statement
process_all( statement.statements )
end
end
end

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

View 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

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

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

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

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

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

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

View 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

View 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

View 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

View 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

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

View 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