move parfait up one, as per its module structure

This commit is contained in:
Torsten Ruger
2017-01-18 20:09:43 +02:00
parent f0c0128b38
commit da5823a1a0
32 changed files with 7 additions and 3 deletions

34
lib/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.

55
lib/parfait/behaviour.rb Normal file
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

79
lib/parfait/class.rb Normal file
View File

@ -0,0 +1,79 @@
# 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
# adding an instance changes the instance_type to include that variable
def add_instance_variable( name , type)
@instance_type = @instance_type.add_instance_variable( name , type )
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/parfait/dictionary.rb Normal file
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/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/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/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

31
lib/parfait/named_list.rb Normal file
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/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/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/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

220
lib/parfait/type.rb Normal file
View File

@ -0,0 +1,220 @@
# 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}"
found = get_method( method_name )
return found if found
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/parfait/typed_method.rb Normal file
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_argument( name )
raise "has_argument #{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 arguments_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/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