move parfait up one, as per its module structure
This commit is contained in:
34
lib/parfait/README.md
Normal file
34
lib/parfait/README.md
Normal file
@ -0,0 +1,34 @@
|
||||
### Parfait: a thin layer
|
||||
|
||||
Parfait is the run-time of the object system.
|
||||
To be more precise, it is that part of the run-time needed to boot.
|
||||
|
||||
The run-time needs to contain quite a lot of functionality for a dynamic system.
|
||||
And a large part of that functionality must actually be used at compile time too.
|
||||
|
||||
We reuse the Parfait code at compile-time, to create the data for the compiled vm.
|
||||
To do this the vm (re) defines the object memory (in parfait_adapter).
|
||||
|
||||
A work in progress that started from here : http://ruby-x.org/2014/06/10/more-clarity.html
|
||||
went on here http://ruby-x.org/2014/07/05/layers-vs-passes.html
|
||||
|
||||
A step back: the code (program) we compile runs at run - time.
|
||||
And so does parfait. So all we have to do is compile it with the program.
|
||||
|
||||
And thus parfait can be used at run-time.
|
||||
|
||||
It's too simple: just slips off the mind like a fish into water.
|
||||
|
||||
Parfait has a brother, the Builtin module. Builtin contains everything that can not be coded in
|
||||
ruby, but we still need (things like List access).
|
||||
|
||||
### Vm vs language- core
|
||||
|
||||
Parfait is not the language core library. Core library functionality differs between
|
||||
languages and so the language core lib must be on top of the vm parfait.
|
||||
|
||||
To make this point clear, i have started using different names for the core classes. Hopefully
|
||||
more sensible ones, ie List instead of Array, Dictionary instead of Hash.
|
||||
|
||||
Also Parfait is meant to be as thin as humanly possibly, so extra (nice to have) functionality
|
||||
will be in future modules.
|
55
lib/parfait/behaviour.rb
Normal file
55
lib/parfait/behaviour.rb
Normal file
@ -0,0 +1,55 @@
|
||||
# Behaviour is something that has methods, basically class and modules superclass
|
||||
|
||||
# instance_methods is the attribute in the including class that has the methods
|
||||
|
||||
module Parfait
|
||||
module Behaviour
|
||||
|
||||
def initialize
|
||||
super()
|
||||
@instance_methods = List.new
|
||||
end
|
||||
|
||||
def methods
|
||||
m = @instance_methods
|
||||
return m if m
|
||||
@instance_methods = List.new
|
||||
end
|
||||
|
||||
def method_names
|
||||
names = List.new
|
||||
self.methods.each do |method|
|
||||
names.push method.name
|
||||
end
|
||||
names
|
||||
end
|
||||
|
||||
def add_instance_method( method )
|
||||
raise "not implemented #{method.class} #{method.inspect}" unless method.is_a? RubyMethod
|
||||
method
|
||||
end
|
||||
|
||||
def remove_instance_method( method_name )
|
||||
found = get_instance_method( method_name )
|
||||
found ? self.methods.delete(found) : false
|
||||
end
|
||||
|
||||
def get_instance_method( fname )
|
||||
raise "get_instance_method #{fname}.#{fname.class}" unless fname.is_a?(Symbol)
|
||||
#if we had a hash this would be easier. Detect or find would help too
|
||||
self.methods.find {|m| m.name == fname }
|
||||
end
|
||||
|
||||
# get the method and if not found, try superclasses. raise error if not found
|
||||
def resolve_method m_name
|
||||
raise "resolve_method #{m_name}.#{m_name.class}" unless m_name.is_a?(Symbol)
|
||||
method = get_instance_method(m_name)
|
||||
return method if method
|
||||
if( @super_class_name != :Object )
|
||||
method = self.super_class.resolve_method(m_name)
|
||||
end
|
||||
method
|
||||
end
|
||||
|
||||
end
|
||||
end
|
17
lib/parfait/binary_code.rb
Normal file
17
lib/parfait/binary_code.rb
Normal file
@ -0,0 +1,17 @@
|
||||
# A typed method object is a description of the method, it's name etc
|
||||
#
|
||||
# But the code that the method represents, the binary, is held as an array
|
||||
# in one of these.
|
||||
#
|
||||
|
||||
module Parfait
|
||||
# obviously not a "Word" but a ByteArray , but no such class yet
|
||||
# As our String (Word) on the other hand has no encoding (yet) it is close enough
|
||||
class BinaryCode < Word
|
||||
|
||||
def to_s
|
||||
"BinaryCode #{self.char_length}"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
79
lib/parfait/class.rb
Normal file
79
lib/parfait/class.rb
Normal 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
96
lib/parfait/dictionary.rb
Normal file
@ -0,0 +1,96 @@
|
||||
# almost simplest hash imaginable. make good use of Lists
|
||||
|
||||
module Parfait
|
||||
class Dictionary < Object
|
||||
|
||||
# only empty initialization for now
|
||||
#
|
||||
# internally we store keys and values in lists, which means this does **not** scale well
|
||||
def initialize
|
||||
super()
|
||||
@keys = List.new()
|
||||
@values = List.new()
|
||||
end
|
||||
|
||||
def keys
|
||||
@keys.dup
|
||||
end
|
||||
|
||||
def values
|
||||
@values.dup
|
||||
end
|
||||
|
||||
# are there any key/value items in the list
|
||||
def empty?
|
||||
@keys.empty?
|
||||
end
|
||||
|
||||
# How many key/value pairs there are
|
||||
def length()
|
||||
return @keys.get_length()
|
||||
end
|
||||
|
||||
# get a value fot the given key
|
||||
# key identity is checked with == not === (ie equals not identity)
|
||||
# return nil if no such key
|
||||
def get(key)
|
||||
index = key_index(key)
|
||||
if( index )
|
||||
@values.get(index)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# same as get(key)
|
||||
def [](key)
|
||||
get(key)
|
||||
end
|
||||
|
||||
# private method
|
||||
def key_index(key)
|
||||
@keys.index_of(key)
|
||||
end
|
||||
|
||||
# set key with value, returns value
|
||||
def set(key , value)
|
||||
index = key_index(key)
|
||||
if( index )
|
||||
@values.set(index , value)
|
||||
else
|
||||
@keys.push(key)
|
||||
@values.push(value)
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
#same as set(k,v)
|
||||
def []=(key,val)
|
||||
set(key,val)
|
||||
end
|
||||
|
||||
# yield to each key value pair
|
||||
def each
|
||||
index = 1
|
||||
while index <= @keys.get_length
|
||||
key = @keys.get(index)
|
||||
value = @values.get(index)
|
||||
yield key , value
|
||||
index = index + 1
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def inspect
|
||||
string = "Dictionary{"
|
||||
each do |key , value|
|
||||
string += key.to_s + " => " + value.to_s + " ,"
|
||||
end
|
||||
string + "}"
|
||||
end
|
||||
|
||||
def to_sof_node(writer , level , ref)
|
||||
Sof.hash_to_sof_node( self , writer , level , ref)
|
||||
end
|
||||
end
|
||||
end
|
25
lib/parfait/integer.rb
Normal file
25
lib/parfait/integer.rb
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
# Integer class for representing maths on Integers
|
||||
# Integers are Values (not Objects),
|
||||
# - they have fixed value
|
||||
# - they are immutable
|
||||
# you can *not* assign instance variables or methods
|
||||
|
||||
# TODO how this idea works with Numeric ?
|
||||
|
||||
module Parfait
|
||||
class Integer
|
||||
|
||||
# :integer?, :odd?, :even?, :upto, :downto, :times, :succ, :next, :pred, :chr, :ord, :to_i, :to_int, :floor,
|
||||
# :ceil, :truncate, :round, :gcd, :lcm, :gcdlcm, :numerator, :denominator, :to_r, :rationalize,
|
||||
# :singleton_method_added, :coerce, :i, :+@, :-@, :fdiv, :div, :divmod, :%, :modulo, :remainder, :abs, :magnitude,
|
||||
# :real?, :zero?, :nonzero?, :step, :quo, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase,
|
||||
# :rectangular, :rect, :polar, :conjugate, :conj, :>, :>=, :<, :<=, :between?
|
||||
#
|
||||
# Numeric
|
||||
# :singleton_method_added, :coerce, :i, :+@, :-@, :fdiv, :div, :divmod, :%, :modulo, :remainder, :abs, :magnitude,
|
||||
# :to_int, :real?, :integer?, :zero?, :nonzero?, :floor, :ceil, :round, :truncate, :step, :numerator, :denominator,
|
||||
# :quo, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase, :rectangular, :rect, :polar, :conjugate, :conj,
|
||||
# :>, :>=, :<, :<=, :between?
|
||||
end
|
||||
end
|
278
lib/parfait/list.rb
Normal file
278
lib/parfait/list.rb
Normal file
@ -0,0 +1,278 @@
|
||||
# A List, or rather an ordered list, is just that, a list of items.
|
||||
|
||||
# For a programmer this may be a little strange as this new start goes with trying to break old
|
||||
# bad habits. A List would be an array in some languages, but list is a better name, closer to
|
||||
# common language.
|
||||
# Another bad habit is to start a list from 0. This is "just" programmers lazyness, as it goes
|
||||
# with the standard c implementation. But it bends the mind, and in oo we aim not to.
|
||||
# If you have a list of three items, they will be first, second and third, ie 1,2,3
|
||||
#
|
||||
# For the implementation we use Objects memory which is index addressable
|
||||
# But, objects are also lists where indexes start with 1, except 1 is taken for the Type
|
||||
# so all incoming/outgoing indexes have to be shifted one up/down
|
||||
|
||||
module Parfait
|
||||
class List < Object
|
||||
def self.get_length_index
|
||||
2
|
||||
end
|
||||
def self.get_indexed(index)
|
||||
index + 2
|
||||
end
|
||||
|
||||
def get_offset
|
||||
2
|
||||
end
|
||||
|
||||
def get_length
|
||||
r = get_internal_word( 2 ) #one for type
|
||||
r.nil? ? 0 : r
|
||||
end
|
||||
|
||||
# set the value at index.
|
||||
# Lists start from index 1
|
||||
def set( index , value)
|
||||
raise "Only positive indexes #{index}" if index <= 0
|
||||
if index > get_length
|
||||
grow_to(index)
|
||||
end
|
||||
# start one higher than offset, which is where the length is
|
||||
set_internal_word( index + 2, value)
|
||||
end
|
||||
|
||||
# set the value at index.
|
||||
# Lists start from index 1
|
||||
def get( index )
|
||||
raise "Only positive indexes, #{index}" if index <= 0
|
||||
ret = nil
|
||||
if(index <= get_length)
|
||||
# start one higher than offset, which is where the length is
|
||||
ret = get_internal_word(index + 2 )
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
def grow_to( len)
|
||||
raise "Only positive lenths, #{len}" if len < 0
|
||||
old_length = get_length
|
||||
return if old_length >= len
|
||||
# raise "bounds error at #{len}" if( len + offset > 16 )
|
||||
# be nice to use the indexed_length , but that relies on booted space
|
||||
set_internal_word( 2 , len) #one for type
|
||||
end
|
||||
|
||||
def shrink_to( len )
|
||||
raise "Only positive lenths, #{len}" if len < 0
|
||||
old_length = get_length
|
||||
return if old_length <= len
|
||||
set_internal_word( 2 , len)
|
||||
end
|
||||
|
||||
def indexed_length
|
||||
get_length()
|
||||
end
|
||||
|
||||
def initialize( )
|
||||
super()
|
||||
@memory = []
|
||||
end
|
||||
|
||||
# include? means non nil index
|
||||
def include? item
|
||||
return index_of(item) != nil
|
||||
end
|
||||
|
||||
# index of item, remeber first item has index 1
|
||||
# return nil if no such item
|
||||
def index_of( item )
|
||||
max = self.get_length
|
||||
#puts "length #{max} #{max.class}"
|
||||
counter = 1
|
||||
while( counter <= max )
|
||||
if( get(counter) == item)
|
||||
return counter
|
||||
end
|
||||
counter = counter + 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# push means add to the end
|
||||
# this automatically grows the List
|
||||
def push( value )
|
||||
to = self.get_length + 1
|
||||
set( to , value)
|
||||
to
|
||||
end
|
||||
|
||||
def delete( value )
|
||||
index = index_of value
|
||||
return false unless index
|
||||
delete_at index
|
||||
end
|
||||
|
||||
def delete_at( index )
|
||||
# TODO bounds check
|
||||
while(index < self.get_length)
|
||||
set( index , get(index + 1))
|
||||
index = index + 1
|
||||
end
|
||||
set_length( self.get_length - 1)
|
||||
true
|
||||
end
|
||||
|
||||
def first
|
||||
return nil if empty?
|
||||
get(1)
|
||||
end
|
||||
|
||||
def last
|
||||
return nil if empty?
|
||||
get(get_length())
|
||||
end
|
||||
|
||||
def empty?
|
||||
self.get_length == 0
|
||||
end
|
||||
|
||||
def equal? other
|
||||
# this should call parfait get_class, alas that is not implemented yet
|
||||
return false if other.class != self.class
|
||||
return false if other.get_length != self.get_length
|
||||
index = self.get_length
|
||||
while(index > 0)
|
||||
return false if other.get(index) != self.get(index)
|
||||
index = index - 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
# above, correct, implementation causes problems in the machine object space
|
||||
# because when a second empty (newly created) list is added, it is not actually
|
||||
# added as it exists already. TODO, but hack with below identity function
|
||||
def == other
|
||||
self.object_id == other.object_id
|
||||
end
|
||||
|
||||
# word length (padded) is the amount of space taken by the object
|
||||
# For your basic object this means the number of instance variables as determined by type
|
||||
# This is off course 0 for a list, unless someone squeezed an instance variable in
|
||||
# but additionally, the amount of data comes on top.
|
||||
# unfortuntely we can't just use super because of the Padding
|
||||
def padded_length
|
||||
Padding.padded_words( get_type().instance_length + get_length() )
|
||||
end
|
||||
|
||||
def each
|
||||
index = 1
|
||||
while index <= self.get_length
|
||||
item = get(index)
|
||||
yield item
|
||||
index = index + 1
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def each_with_index
|
||||
index = 1
|
||||
while index <= self.get_length
|
||||
item = get(index)
|
||||
yield item , index
|
||||
index = index + 1
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def each_pair
|
||||
index = 1
|
||||
while index <= self.get_length
|
||||
key = get( index )
|
||||
value = get(index + 1)
|
||||
yield key , value
|
||||
index = index + 2
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def find
|
||||
index = 1
|
||||
while index <= self.get_length
|
||||
item = get(index)
|
||||
return item if yield item
|
||||
index = index + 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def set_length len
|
||||
was = self.get_length
|
||||
return if was == len
|
||||
if(was < len)
|
||||
grow_to len
|
||||
else
|
||||
shrink_to len
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
index = 1
|
||||
ret = ""
|
||||
while index <= self.get_length
|
||||
item = get(index)
|
||||
ret += item.inspect
|
||||
ret += "," unless index == self.get_length
|
||||
index = index + 1
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def get_internal_word(index)
|
||||
@memory[index]
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def set_internal_word(index , value)
|
||||
raise "Word[#{index}] = " if((self.class == Parfait::Word) and value.nil? )
|
||||
@memory[index] = value
|
||||
value
|
||||
end
|
||||
|
||||
alias :[] :get
|
||||
|
||||
def to_sof_node(writer , level , ref )
|
||||
Sof.array_to_sof_node(self , writer , level , ref )
|
||||
end
|
||||
|
||||
def dup
|
||||
list = List.new
|
||||
each do |item|
|
||||
list.push(item)
|
||||
end
|
||||
list
|
||||
end
|
||||
|
||||
def to_a
|
||||
array = []
|
||||
index = 1
|
||||
while( index <= self.get_length)
|
||||
array[index - 1] = get(index)
|
||||
index = index + 1
|
||||
end
|
||||
array
|
||||
end
|
||||
end
|
||||
|
||||
# new list from ruby array to be precise
|
||||
def self.new_list array
|
||||
list = Parfait::List.new
|
||||
list.set_length array.length
|
||||
index = 1
|
||||
while index <= array.length do
|
||||
list.set(index , array[index - 1])
|
||||
index = index + 1
|
||||
end
|
||||
list
|
||||
end
|
||||
|
||||
end
|
36
lib/parfait/message.rb
Normal file
36
lib/parfait/message.rb
Normal file
@ -0,0 +1,36 @@
|
||||
|
||||
# A message is what is sent when you invoke a method. Args and stuff are packed up in to a Message
|
||||
# and the Message is sent to the receiver.
|
||||
|
||||
# Part of the housekeeping (see attributes) makes messages a double linked list (next_message and
|
||||
# caller) , and maybe surprisingly this means that we can create all messages at runtime
|
||||
# and link them up and never have to touch that list again.
|
||||
# All the args and receiver data changes, but the list of messages stays constant.
|
||||
|
||||
module Parfait
|
||||
class Message < Object
|
||||
|
||||
attr_reader :locals , :receiver , :return_value , :name
|
||||
attr_accessor :next_message
|
||||
|
||||
def initialize next_m
|
||||
@next_message = next_m
|
||||
@locals = NamedList.new()
|
||||
@arguments = NamedList.new()
|
||||
super()
|
||||
end
|
||||
|
||||
def set_receiver(rec)
|
||||
@receiver = rec
|
||||
end
|
||||
|
||||
def set_caller(caller)
|
||||
@caller = caller
|
||||
end
|
||||
|
||||
def get_type_for(name)
|
||||
index = @type.get_index(name)
|
||||
get_at(index)
|
||||
end
|
||||
end
|
||||
end
|
31
lib/parfait/named_list.rb
Normal file
31
lib/parfait/named_list.rb
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
# A NamedList is used to store local variables and arguments when calling methods.
|
||||
# Also temporary variables, which are local variables named by the system
|
||||
|
||||
# The items are named (and typed) by the objects type instance. In effect the
|
||||
# variables are like instance variables
|
||||
|
||||
# A Message with is arguments, and a NamedList make up the two sides of message passing:
|
||||
# A Message (see details there) is created by the caller and control is transferred
|
||||
# A NamedList is created by the receiver
|
||||
# PS: it turns out that both messages and named_lists are created at compile, not run-time, and
|
||||
# just constantly reused. Each message has two named_list object ready and is also linked
|
||||
# to the next message.
|
||||
# The better way to say above is that a message is *used* by the caller, and a named_list
|
||||
# by the callee.
|
||||
|
||||
# Also at runtime Messages and NamedLists remain completely "normal" objects.
|
||||
# Ie they have have type and instances and so on.*
|
||||
# Which resolves the dichotomy of objects on the stack or heap. Sama sama.
|
||||
#
|
||||
# *Alas the type for each call instance is unique.
|
||||
#
|
||||
module Parfait
|
||||
class NamedList < Object
|
||||
|
||||
def self.type_for( arguments )
|
||||
my_class = Parfait.object_space.classes[:NamedList]
|
||||
Type.for_hash( my_class , {type: my_class.instance_type}.merge(arguments))
|
||||
end
|
||||
end
|
||||
end
|
126
lib/parfait/object.rb
Normal file
126
lib/parfait/object.rb
Normal file
@ -0,0 +1,126 @@
|
||||
# From a programmers perspective an object has hash like data (with instance variables as keys)
|
||||
# and functions to work on that data.
|
||||
# Only the object may access it's data directly.
|
||||
|
||||
# From an implementation perspective it is a chunk of memory with a type as the first
|
||||
# word (instance of class Type).
|
||||
|
||||
# Objects are arranged or layed out (in memory) according to their Type
|
||||
# every object has a Type. Type objects are immutalbe and may be reused for a group/class
|
||||
# off objects.
|
||||
# The Type of an object may change, but then a new Type is created
|
||||
# The Type also defines the class of the object
|
||||
# The Type is **always** the first entry (index 1) in an object
|
||||
|
||||
module Parfait
|
||||
TYPE_INDEX = 1
|
||||
|
||||
class Object
|
||||
|
||||
def self.new *args
|
||||
object = self.allocate
|
||||
|
||||
# have to grab the class, because we are in the ruby class not the parfait one
|
||||
cl = Parfait.object_space.get_class_by_name( self.name.split("::").last.to_sym)
|
||||
|
||||
# and have to set the type before we let the object do anything. otherwise boom
|
||||
object.set_type cl.instance_type
|
||||
|
||||
object.send :initialize , *args
|
||||
object
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def get_internal_word(index)
|
||||
name = get_type().name_at(index)
|
||||
return nil unless name
|
||||
eval "@#{name}"
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def set_internal_word(index , value)
|
||||
return set_type(value) if( index == 1)
|
||||
raise "not type #{@type.class}" unless @type.is_a?(Type)
|
||||
name = @type.name_at(index)
|
||||
raise "object type has no name at index #{index} " unless name
|
||||
eval "@#{name} = value"
|
||||
value
|
||||
end
|
||||
|
||||
def == other
|
||||
self.object_id == other.object_id
|
||||
end
|
||||
|
||||
# This is the crux of the object system. The class of an object is stored in the objects
|
||||
# memory (as opposed to an integer that has no memory and so always has the same class)
|
||||
#
|
||||
# In RubyX we store the class in the Type, and so the Type is the only fixed
|
||||
# data that every object carries.
|
||||
def get_class()
|
||||
l = get_type()
|
||||
#puts "Type #{l.class} in #{self.class} , #{self}"
|
||||
l.object_class()
|
||||
end
|
||||
|
||||
# private
|
||||
def set_type(type)
|
||||
# puts "Type was set for #{self.class}"
|
||||
raise "not type #{type.class}" unless type.is_a?(Type)
|
||||
@type = type
|
||||
end
|
||||
|
||||
# so we can keep the raise in get_type
|
||||
def has_type?
|
||||
! @type.nil?
|
||||
end
|
||||
|
||||
def get_type()
|
||||
raise "No type #{self.object_id.to_s(16)}:#{self.class} " unless has_type?
|
||||
@type
|
||||
end
|
||||
|
||||
# return the metaclass
|
||||
def meta
|
||||
MetaClass.new self
|
||||
end
|
||||
|
||||
def get_instance_variables
|
||||
@type.names
|
||||
end
|
||||
|
||||
def get_instance_variable( name )
|
||||
index = instance_variable_defined(name)
|
||||
#puts "getting #{name} at #{index}"
|
||||
return nil if index == nil
|
||||
return get_internal_word(index)
|
||||
end
|
||||
|
||||
def set_instance_variable( name , value )
|
||||
index = instance_variable_defined(name)
|
||||
return nil if index == nil
|
||||
return set_internal_word(index , value)
|
||||
end
|
||||
|
||||
def instance_variable_defined( name )
|
||||
@type.variable_index(name)
|
||||
end
|
||||
|
||||
def padded_length
|
||||
Padding.padded_words( @type.instance_length )
|
||||
end
|
||||
|
||||
# parfait versions are deliberately called different, so we "relay"
|
||||
# have to put the "@" on the names for sof to take them off again
|
||||
def instance_variables
|
||||
get_instance_variables.to_a.collect{ |n| "@#{n}".to_sym }
|
||||
end
|
||||
|
||||
# name comes in as a ruby @var name
|
||||
def instance_variable_get name
|
||||
var = get_instance_variable name.to_s[1 .. -1].to_sym
|
||||
#puts "getting #{name} #{var}"
|
||||
var
|
||||
end
|
||||
|
||||
end
|
||||
end
|
15
lib/parfait/page.rb
Normal file
15
lib/parfait/page.rb
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
# A Page (from the traditional memory page) represents a collection of
|
||||
# objects in a physically form. Ie the page holds the memory or data, that
|
||||
# the objects are made up of.
|
||||
|
||||
# Pages have a total size, but more importantly an object size.
|
||||
# All objects of a Page are same sized, and multiples of the smallest
|
||||
# object. The smallest object is usually a cache line, 16 bytes or
|
||||
# an exponent of two larger.
|
||||
|
||||
module Parfait
|
||||
class Page < Object
|
||||
|
||||
end
|
||||
end
|
126
lib/parfait/space.rb
Normal file
126
lib/parfait/space.rb
Normal file
@ -0,0 +1,126 @@
|
||||
|
||||
# A Space is a collection of pages. It stores objects, the data for the objects,
|
||||
# not references. See Page for more detail.
|
||||
|
||||
# Pages are stored by the object size they represent in a hash.
|
||||
|
||||
# Space and Page work together in making *new* objects available.
|
||||
# "New" is slightly misleading in that normal operation only ever
|
||||
# recycles objects.
|
||||
|
||||
module Parfait
|
||||
# Make the object space globally available
|
||||
def self.object_space
|
||||
@@object_space
|
||||
end
|
||||
|
||||
# TODO Must get rid of the setter (move the boot process ?)
|
||||
def self.set_object_space space
|
||||
@@object_space = space
|
||||
end
|
||||
|
||||
# The Space contains all objects for a program. In functional terms it is a program, but in oo
|
||||
# it is a collection of objects, some of which are data, some classes, some functions
|
||||
|
||||
# The main entry is a function called (of all things) "main".
|
||||
# This _must be supplied by the compled code (similar to c)
|
||||
# There is a start and exit block that call main, which receives an List of strings
|
||||
|
||||
# While data ususally would live in a .data section, we may also "inline" it into the code
|
||||
# in an oo system all data is represented as objects
|
||||
|
||||
class Space < Object
|
||||
|
||||
def initialize(classes )
|
||||
@classes = classes
|
||||
@types = Dictionary.new
|
||||
message = Message.new(nil)
|
||||
50.times do
|
||||
@first_message = Message.new message
|
||||
#puts "INIT caller #{message.object_id} to #{@first_message.object_id}"
|
||||
message.set_caller @first_message
|
||||
message = @first_message
|
||||
end
|
||||
@classes.each do |name , cl|
|
||||
add_type(cl.instance_type)
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :classes , :first_message
|
||||
|
||||
def each_type
|
||||
@types.values.each do |type|
|
||||
yield(type)
|
||||
end
|
||||
end
|
||||
|
||||
def add_type(type)
|
||||
hash = type.hash
|
||||
raise "upps #{hash} #{hash.class}" unless hash.is_a?(Fixnum)
|
||||
was = @types[hash]
|
||||
return was if was
|
||||
@types[hash] = type
|
||||
end
|
||||
|
||||
def get_type_for( hash )
|
||||
@types[hash]
|
||||
end
|
||||
|
||||
# all methods form all types
|
||||
def collect_methods
|
||||
methods = []
|
||||
each_type do | type |
|
||||
type.methods.each do |meth|
|
||||
methods << meth
|
||||
end
|
||||
end
|
||||
methods
|
||||
end
|
||||
|
||||
def get_main
|
||||
kernel = get_class_by_name :Space
|
||||
kernel.instance_type.get_method :main
|
||||
end
|
||||
|
||||
def get_init
|
||||
kernel = get_class_by_name :Kernel
|
||||
kernel.instance_type.get_method :__init__
|
||||
end
|
||||
|
||||
# get a class by name (symbol)
|
||||
# return nili if no such class. Use bang version if create should be implicit
|
||||
def get_class_by_name( name )
|
||||
raise "get_class_by_name #{name}.#{name.class}" unless name.is_a?(Symbol)
|
||||
c = @classes[name]
|
||||
#puts "MISS, no class #{name} #{name.class}" unless c # " #{@classes}"
|
||||
#puts "CLAZZ, #{name} #{c.get_type.get_length}" if c
|
||||
c
|
||||
end
|
||||
|
||||
# get or create the class by the (symbol) name
|
||||
# notice that this method of creating classes implies Object superclass
|
||||
def get_class_by_name!(name , super_class = :Object)
|
||||
c = get_class_by_name(name)
|
||||
return c if c
|
||||
create_class( name ,super_class)
|
||||
end
|
||||
|
||||
# this is the way to instantiate classes (not Parfait::Class.new)
|
||||
# so we get and keep exactly one per name
|
||||
def create_class( name , superclass = nil )
|
||||
raise "create_class #{name.class}" unless name.is_a? Symbol
|
||||
superclass = :Object unless superclass
|
||||
raise "create_class #{superclass.class}" unless superclass.is_a? Symbol
|
||||
type = get_class_by_name(superclass).instance_type
|
||||
c = Class.new(name , superclass , type )
|
||||
@classes[name] = c
|
||||
end
|
||||
|
||||
def sof_reference_name
|
||||
"space"
|
||||
end
|
||||
|
||||
end
|
||||
# ObjectSpace
|
||||
# :each_object, :garbage_collect, :define_finalizer, :undefine_finalizer, :_id2ref, :count_objects
|
||||
end
|
16
lib/parfait/symbol_adapter.rb
Normal file
16
lib/parfait/symbol_adapter.rb
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
class Symbol
|
||||
|
||||
def has_type?
|
||||
true
|
||||
end
|
||||
def get_type
|
||||
l = Parfait.object_space.classes[:Word].instance_type
|
||||
#puts "LL #{l.class}"
|
||||
l
|
||||
end
|
||||
def padded_length
|
||||
Padding.padded( to_s.length + 4)
|
||||
end
|
||||
|
||||
end
|
220
lib/parfait/type.rb
Normal file
220
lib/parfait/type.rb
Normal 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
102
lib/parfait/typed_method.rb
Normal file
@ -0,0 +1,102 @@
|
||||
# A TypedMethod is static object that primarily holds the executable code.
|
||||
# It is called typed, because all arguments and variables it uses are typed.
|
||||
# (Type means basic type, ie integer or reference)
|
||||
|
||||
# It's relation to the method a ruby programmer knows (called RubyMethod) is many to one,
|
||||
# meaning one RubyMethod (untyped) has many TypedMethod implementations.
|
||||
# The RubyMethod only holds ruby code, no binary.
|
||||
|
||||
# The Typed method has the following instance variables
|
||||
# - name : This is the same as the ruby method name it implements
|
||||
# - source: is currently the ast (or string) that represents the "code". This is historic
|
||||
# and will change to the RubyMethod that it implements
|
||||
# - instructions: The sequence of instructions the source (ast) was compiled to
|
||||
# Instructions derive from class Instruction and form a linked list
|
||||
# - binary: The binary (jumpable) code that the instructions get assembled into
|
||||
# - arguments: A type object describing the arguments (name+types) to be passed
|
||||
# - locals: A type object describing the local variables that the method has
|
||||
# - for_type: The Type the Method is for
|
||||
|
||||
|
||||
module Parfait
|
||||
|
||||
class TypedMethod < Object
|
||||
|
||||
attr_reader :name , :instructions , :for_type ,:arguments , :locals , :binary
|
||||
|
||||
# not part of the parfait model, hence ruby accessor
|
||||
attr_accessor :source
|
||||
|
||||
def initialize( type , name , arguments )
|
||||
super()
|
||||
raise "No class #{name}" unless type
|
||||
raise "For type, not class #{type}" unless type.is_a?(Type)
|
||||
raise "Wrong argument type, expect Type not #{arguments.class}" unless arguments.is_a? Type
|
||||
@for_type = type
|
||||
@name = name
|
||||
@binary = BinaryCode.new 0
|
||||
@arguments = arguments
|
||||
@locals = Parfait.object_space.get_class_by_name( :NamedList ).instance_type
|
||||
end
|
||||
|
||||
def set_instructions(inst)
|
||||
@instructions = inst
|
||||
end
|
||||
|
||||
# determine whether this method has an argument by the name
|
||||
def has_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
218
lib/parfait/word.rb
Normal file
@ -0,0 +1,218 @@
|
||||
|
||||
|
||||
module Parfait
|
||||
# A word is a a short sequence of characters
|
||||
# Characters are not modeled as objects but as (small) integers
|
||||
# The small means two of them have to fit into a machine word, utf16 or similar
|
||||
#
|
||||
# Words are constant, maybe like js strings, ruby symbols
|
||||
# Words are short, but may have spaces
|
||||
|
||||
# Words are objects, that means they carry Type as index 0
|
||||
# So all indexes are offset by one in the implementation
|
||||
# Object length is measured in non-type cells though
|
||||
|
||||
class Word < Object
|
||||
attr_reader :char_length
|
||||
|
||||
#semi "indexed" methods for interpreter
|
||||
def self.get_length_index
|
||||
2 # 2 is the amount of attributes, type and char_length. the offset after which chars start
|
||||
end
|
||||
def self.get_indexed i
|
||||
i + get_length_index * 4
|
||||
end
|
||||
# initialize with length. For now we try to keep all non-parfait (including String) out
|
||||
# String will contain spaces for non-zero length
|
||||
# Register provides methods to create Parfait objects from ruby
|
||||
def initialize len
|
||||
super()
|
||||
@char_length = 0
|
||||
@memory = []
|
||||
raise "Must init with int, not #{len.class}" unless len.kind_of? Fixnum
|
||||
raise "Must init with positive, not #{len}" if len < 0
|
||||
set_length( len , 32 ) unless len == 0 #32 beeing ascii space
|
||||
#puts "type #{self.get_type} #{self.object_id.to_s(16)}"
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def get_internal_word(index)
|
||||
@memory[index]
|
||||
end
|
||||
|
||||
# 1 -based index
|
||||
def set_internal_word(index , value)
|
||||
raise "Word[#{index}] = nil" if( value.nil? )
|
||||
@memory[index] = value
|
||||
value
|
||||
end
|
||||
|
||||
|
||||
# return a copy of self
|
||||
def copy
|
||||
cop = Word.new( self.length )
|
||||
index = 1
|
||||
while( index <= self.length )
|
||||
cop.set_char(index , self.get_char(index))
|
||||
index = index + 1
|
||||
end
|
||||
cop
|
||||
end
|
||||
|
||||
# return the number of characters
|
||||
def length()
|
||||
obj_len = @char_length
|
||||
return obj_len
|
||||
end
|
||||
|
||||
# make every char equal the given one
|
||||
def fill_with char
|
||||
fill_from_with(0 , char)
|
||||
end
|
||||
|
||||
def fill_from_with from , char
|
||||
len = self.length()
|
||||
return if from <= 0
|
||||
while( from <= len)
|
||||
set_char( from , char)
|
||||
from = from + 1
|
||||
end
|
||||
from
|
||||
end
|
||||
|
||||
# true if no characters
|
||||
def empty?
|
||||
return self.length == 0
|
||||
end
|
||||
|
||||
# pad the string with the given character to the given length
|
||||
#
|
||||
def set_length(len , fill_char)
|
||||
return if len <= 0
|
||||
old = @char_length
|
||||
return if old >= len
|
||||
@char_length = len
|
||||
check_length
|
||||
fill_from_with( old + 1 , fill_char )
|
||||
end
|
||||
|
||||
# set the character at the given index to the given character
|
||||
# character must be an integer, as is the index
|
||||
# the index starts at one, but may be negative to count from the end
|
||||
# indexes out of range will raise an error
|
||||
def set_char at , char
|
||||
raise "char not fixnum #{char.class}" unless char.kind_of? Fixnum
|
||||
index = range_correct_index(at)
|
||||
set_internal_byte( index , char)
|
||||
end
|
||||
|
||||
def set_internal_byte index , char
|
||||
word_index = (index) / 4
|
||||
rest = ((index) % 4)
|
||||
shifted = char << (rest * 8)
|
||||
was = get_internal_word( word_index )
|
||||
was = 0 unless was.is_a?(Numeric)
|
||||
mask = 0xFF << (rest * 8)
|
||||
mask = 0xFFFFFFFF - mask
|
||||
masked = was & mask
|
||||
put = masked + shifted
|
||||
set_internal_word( word_index , put )
|
||||
msg = "set index=#{index} word_index=#{word_index} rest=#{rest}= "
|
||||
msg += "char=#{char.to_s(16)} shifted=#{shifted.to_s(16)} "
|
||||
msg += "was=#{was.to_s(16)} masked=#{masked.to_s(16)} put=#{put.to_s(16)}"
|
||||
#puts msg
|
||||
char
|
||||
end
|
||||
|
||||
# get the character at the given index (lowest 1)
|
||||
# the index starts at one, but may be negative to count from the end
|
||||
# indexes out of range will raise an error
|
||||
#the return "character" is an integer
|
||||
def get_char at
|
||||
index = range_correct_index(at)
|
||||
get_internal_byte(index)
|
||||
end
|
||||
|
||||
|
||||
def get_internal_byte( index )
|
||||
word_index = (index ) / 4
|
||||
rest = ((index) % 4)
|
||||
char = get_internal_word(word_index)
|
||||
char = 0 unless char.is_a?(Numeric)
|
||||
shifted = char >> (8 * rest)
|
||||
ret = shifted & 0xFF
|
||||
msg = "get index=#{index} word_index=#{word_index} rest=#{rest}= "
|
||||
msg += " char=#{char.to_s(16)} shifted=#{shifted.to_s(16)} ret=#{ret.to_s(16)}"
|
||||
#puts msg
|
||||
return ret
|
||||
end
|
||||
|
||||
# private method to calculate negative indexes into positives
|
||||
def range_correct_index at
|
||||
index = at
|
||||
# index = self.length + at if at < 0
|
||||
raise "index must be positive , not #{at}" if (index <= 0)
|
||||
raise "index too large #{at} > #{self.length}" if (index > self.length )
|
||||
return index + 11
|
||||
end
|
||||
|
||||
# compare the word to another
|
||||
# currently checks for same class, though really identity of the characters
|
||||
# in right order would suffice
|
||||
def compare( other )
|
||||
return false if other.class != self.class
|
||||
return false if other.length != self.length
|
||||
len = self.length
|
||||
while(len > 0)
|
||||
return false if self.get_char(len) != other.get_char(len)
|
||||
len = len - 1
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def == other
|
||||
return false unless other.is_a?(String) or other.is_a?(Word)
|
||||
as_string = self.to_string
|
||||
unless other.is_a? String
|
||||
other = other.to_string
|
||||
end
|
||||
as_string == other
|
||||
end
|
||||
|
||||
def to_string
|
||||
string = ""
|
||||
index = 1
|
||||
while( index <= @char_length)
|
||||
char = get_char(index)
|
||||
string += char ? char.chr : "*"
|
||||
index = index + 1
|
||||
end
|
||||
string
|
||||
end
|
||||
|
||||
# as we answered is_value? with true, sof will create a basic node with this string
|
||||
def to_sof
|
||||
"'" + to_s + "'"
|
||||
end
|
||||
|
||||
def padded_length
|
||||
Padding.padded( 4 * get_type().instance_length + @char_length )
|
||||
end
|
||||
|
||||
private
|
||||
def check_length
|
||||
raise "Length out of bounds #{@char_length}" if @char_length > 1000
|
||||
end
|
||||
end
|
||||
|
||||
# Word from string
|
||||
def self.new_word( string )
|
||||
string = string.to_s if string.is_a? Symbol
|
||||
word = Word.new( string.length )
|
||||
string.codepoints.each_with_index do |code , index |
|
||||
word.set_char(index + 1 , code)
|
||||
end
|
||||
word
|
||||
end
|
||||
|
||||
end
|
Reference in New Issue
Block a user