homing in on line length 100
This commit is contained in:
@ -15,8 +15,8 @@ module Virtual
|
||||
|
||||
# They also have local variables.
|
||||
|
||||
# Code-wise Methods are made up from a list of Blocks, in a similar way blocks are made up of Instructions
|
||||
# The function starts with one block, and that has a start and end (return)
|
||||
# Code-wise Methods are made up from a list of Blocks, in a similar way blocks are made up of
|
||||
# Instructions. The function starts with one block, and that has a start and end (return)
|
||||
|
||||
# Blocks can be linked in two ways:
|
||||
# -linear: flow continues from one to the next as they are sequential both logically and
|
||||
@ -59,7 +59,9 @@ module Virtual
|
||||
# add an instruction after the current (insertion point)
|
||||
# the added instruction will become the new insertion point
|
||||
def add_code instruction
|
||||
raise instruction.inspect unless (instruction.is_a?(Instruction) or instruction.is_a?(Register::Instruction))
|
||||
unless (instruction.is_a?(Instruction) or instruction.is_a?(Register::Instruction))
|
||||
raise instruction.inspect
|
||||
end
|
||||
@current.add_code(instruction) #insert after current
|
||||
self
|
||||
end
|
||||
@ -79,21 +81,24 @@ module Virtual
|
||||
used.uniq
|
||||
end
|
||||
|
||||
# control structures need to see blocks as a graph, but they are stored as a list with implict branches
|
||||
# control structures need to see blocks as a graph, but they are stored as a list with implict
|
||||
# branches
|
||||
# So when creating a new block (with new_block), it is only added to the list, but instructions
|
||||
# still go to the current one
|
||||
# With this function one can change the current block, to actually code it.
|
||||
# This juggling is (unfortunately) neccessary, as all compile functions just keep puring their code into the
|
||||
# method and don't care what other compiles (like if's) do.
|
||||
# This juggling is (unfortunately) neccessary, as all compile functions just keep puring their
|
||||
# code into the method and don't care what other compiles (like if's) do.
|
||||
|
||||
# Example: while, needs 2 extra blocks
|
||||
# 1 condition code, must be its own blockas we jump back to it
|
||||
# - the body, can actually be after the condition as we don't need to jump there
|
||||
# 2 after while block. Condition jumps here
|
||||
# After block 2, the function is linear again and the calling code does not need to know what happened
|
||||
# After block 2, the function is linear again and the calling code does not need to know what
|
||||
# happened
|
||||
|
||||
# But subsequent statements are still using the original block (self) to add code to
|
||||
# So the while expression creates the extra blocks, adds them and the code and then "moves" the insertion point along
|
||||
# So the while expression creates the extra blocks, adds them and the code and then "moves"
|
||||
# the insertion point along
|
||||
def current block
|
||||
@current = block
|
||||
self
|
||||
|
@ -79,7 +79,9 @@ module Virtual
|
||||
|
||||
#attr_reader :left, :right
|
||||
def self.compile_assignment expression , method
|
||||
raise "must assign to NameExpression , not #{expression.left}" unless expression.left.instance_of? Ast::NameExpression
|
||||
unless expression.left.instance_of? Ast::NameExpression
|
||||
raise "must assign to NameExpression , not #{expression.left}"
|
||||
end
|
||||
r = Compiler.compile(expression.right , method )
|
||||
raise "oh noo, nil from where #{expression.right.inspect}" unless r
|
||||
index = method.has_arg(Virtual.new_word name)
|
||||
|
@ -4,7 +4,8 @@ module Virtual
|
||||
|
||||
def self.compile_if expression , method
|
||||
# to execute the logic as the if states it, the blocks are the other way around
|
||||
# so we can the jump over the else if true ,and the else joins unconditionally after the true_block
|
||||
# so we can the jump over the else if true ,
|
||||
# and the else joins unconditionally after the true_block
|
||||
merge_block = method.info.new_block "if_merge" # last one, created first
|
||||
true_block = method.info.new_block "if_true" # second, linked in after current, before merge
|
||||
false_block = method.info.new_block "if_false" # directly next in order, ie if we don't jump we land here
|
||||
|
@ -10,7 +10,8 @@ module Virtual
|
||||
puts "Created class #{clazz.name.inspect}"
|
||||
expression.expressions.each do |expr|
|
||||
# check if it's a function definition and add
|
||||
# if not, execute it, but that does means we should be in salama (executable), not ruby. ie throw an error for now
|
||||
# if not, execute it, but that does means we should be in salama (executable), not ruby.
|
||||
# ie throw an error for now
|
||||
raise "only functions for now #{expr.inspect}" unless expr.is_a? Ast::FunctionExpression
|
||||
#puts "compiling expression #{expression}"
|
||||
expression_value = Compiler.compile(expr,method )
|
||||
|
@ -15,7 +15,9 @@ module Virtual
|
||||
if expression_value.is_a?(IntegerConstant) or expression_value.is_a?(ObjectConstant)
|
||||
return_reg.load into , expression_value
|
||||
else
|
||||
return_reg.move( into, expression_value ) if expression_value.register_symbol != return_reg.register_symbol
|
||||
if expression_value.register_symbol != return_reg.register_symbol
|
||||
return_reg.move( into, expression_value )
|
||||
end
|
||||
end
|
||||
#function.set_return return_reg
|
||||
return return_reg
|
||||
|
@ -1,10 +1,11 @@
|
||||
module Virtual
|
||||
|
||||
|
||||
# the first instruction we need is to stop. Off course in a real machine this would be a syscall, but that is just
|
||||
# an implementation (in a programm it would be a function).
|
||||
# the first instruction we need is to stop. Off course in a real machine this would be a syscall,
|
||||
# but that is just an implementation (in a programm it would be a function).
|
||||
# But in a virtual machine, not only do we need this instruction,
|
||||
# it is indeed the first instruction as just this instruction is the smallest possible programm for the machine.
|
||||
# it is indeed the first instruction as just this instruction is the smallest possible programm
|
||||
# for the machine.
|
||||
# As such it is the next instruction for any first instruction that we generate.
|
||||
class Halt < Instruction
|
||||
end
|
||||
|
@ -1,7 +1,9 @@
|
||||
module Virtual
|
||||
|
||||
# Get a instance variable by _name_ . So we have to resolve the name to an index to trnsform into a Slot
|
||||
# The slot may the be used in a set on left or right hand. The transformation is done by GetImplementation
|
||||
# Get a instance variable by _name_ . So we have to resolve the name to an index to
|
||||
# transform into a Slot
|
||||
# The slot may the be used in a set on left or right hand.
|
||||
# The transformation is done by GetImplementation
|
||||
class InstanceGet < Instruction
|
||||
def initialize name
|
||||
@name = name.to_sym
|
||||
|
@ -1,8 +1,8 @@
|
||||
module Virtual
|
||||
|
||||
# class for Set instructions, A set is basically a mem move.
|
||||
# to and from are indexes into the known objects(frame,message,self and new_message), these are represented as slots
|
||||
# (see there)
|
||||
# to and from are indexes into the known objects(frame,message,self and new_message),
|
||||
# these are represented as slots (see there)
|
||||
# from may be a Constant (Object,Integer,String,Class)
|
||||
class Set < Instruction
|
||||
def initialize to , from
|
||||
|
@ -1,34 +1,37 @@
|
||||
module Virtual
|
||||
# The Virtual Machine is a value based virtual machine in which ruby is implemented. While it is value based,
|
||||
# it resembles oo in basic ways of object encapsulation and method invokation, it is a "closed" / static sytem
|
||||
# in that all types are know and there is no dynamic dispatch (so we don't bite our tail here).
|
||||
# The Virtual Machine is a value based virtual machine in which ruby is implemented.
|
||||
# While it is value based, it resembles oo in basic ways of object encapsulation and method
|
||||
# invocation, it is a "closed" / static sytem in that all types are know and there is no
|
||||
# dynamic dispatch (so we don't bite our tail here).
|
||||
#
|
||||
# It is minimal and realistic and low level
|
||||
# - minimal means that if one thing can be implemented by another, it is left out. This is quite the opposite from
|
||||
# ruby, which has several loops, many redundant if forms and the like.
|
||||
# - realistic means it is easy to implement on a 32 bit machine (arm) and possibly 64 bit. Memory access, a stack,
|
||||
# some registers of same size are the underlying hardware. (not ie byte machine)
|
||||
# - low level means it's basic instructions are realively easily implemented in a register machine. ie send is not
|
||||
# a an instruction but a function.
|
||||
# - minimal means that if one thing can be implemented by another, it is left out. This is quite
|
||||
# the opposite from ruby, which has several loops, many redundant if forms and the like.
|
||||
# - realistic means it is easy to implement on a 32 bit machine (arm) and possibly 64 bit.
|
||||
# Memory access,some registers of same size are the underlying hardware. (not ie byte machine)
|
||||
# - low level means it's basic instructions are realively easily implemented in a register machine.
|
||||
# ie send is not a an instruction but a function.
|
||||
#
|
||||
# So the memory model of the machine allows for indexed access into an "object" . A fixed number of objects exist
|
||||
# (ie garbage collection is reclaming, not destroying and recreating) although there may be a way to increase that number.
|
||||
# So the memory model of the machine allows for indexed access into an "object" .
|
||||
# A fixed number of objects exist (ie garbage collection is reclaming, not destroying and
|
||||
# recreating) although there may be a way to increase that number.
|
||||
#
|
||||
# The ast is transformed to virtaul-machine objects, some of which represent code, some data.
|
||||
#
|
||||
# The next step transforms to the register machine layer, which is what actually executes.
|
||||
#
|
||||
|
||||
# More concretely, a virtual machine is a sort of oo turing machine, it has a current instruction, executes the
|
||||
# instructions, fetches the next one and so on.
|
||||
# More concretely, a virtual machine is a sort of oo turing machine, it has a current instruction,
|
||||
# executes the instructions, fetches the next one and so on.
|
||||
# Off course the instructions are not soo simple, but in oo terms quite so.
|
||||
#
|
||||
# The machine is virtual in the sense that it is completely modeled in software,
|
||||
# it's complete state explicitly available (not implicitly by walking stacks or something)
|
||||
|
||||
# The machine has a no register, but local variables, a scope at each point in time.
|
||||
# Scope changes with calls and blocks, but is saved at each level. In terms of lower level implementation this means
|
||||
# that the the model is such that what is a variable in ruby, never ends up being just on the pysical stack.
|
||||
# Scope changes with calls and blocks, but is saved at each level. In terms of lower level
|
||||
# implementation this means that the the model is such that what is a variable in ruby,
|
||||
# never ends up being just on the pysical stack.
|
||||
#
|
||||
class Machine
|
||||
|
||||
|
@ -1,21 +1,23 @@
|
||||
module Virtual
|
||||
# So when an object calls a method, or sends a message, this is what it sends: a Message
|
||||
|
||||
# A message contains the sender, return and exceptional return addresses,the arguments, and a slot for the frame.
|
||||
# A message contains the sender, return and exceptional return addresses,the arguments,
|
||||
# and a slot for the frame.
|
||||
|
||||
# As such it is a very run-time object, deep in the machinery as it were, and does not have meaningful
|
||||
# methods you could call at compile time.
|
||||
# As such it is a very run-time object, deep in the machinery as it were, and does not have
|
||||
# meaningful methods you could call at compile time.
|
||||
|
||||
# The methods that are there, are nevertheless meant to be called at compile time and generate code, rather than
|
||||
# executing it.
|
||||
# The methods that are there, are nevertheless meant to be called at compile time and generate
|
||||
# code, rather than executing it.
|
||||
|
||||
# The caller creates the Message and passes control to the receiver's method
|
||||
|
||||
# The receiver create a new Frame to hold local and temporary variables and (later) creates default values for
|
||||
# arguments that were not passed
|
||||
# The receiver create a new Frame to hold local and temporary variables and (later) creates
|
||||
# default values for arguments that were not passed
|
||||
|
||||
# How the actual finding of the method takes place (acording to the ruby rules) is not simple, but as there is a
|
||||
# guaranteed result (be it method_missing) it does not matter to the passing mechanism described
|
||||
# How the actual finding of the method takes place (acording to the ruby rules) is not simple,
|
||||
# but as there is a guaranteed result (be it method_missing) it does not matter to the passing
|
||||
# mechanism described
|
||||
|
||||
# During compilation Message and frame objects are created to do type analysis
|
||||
|
||||
|
@ -25,7 +25,9 @@ module FakeMem
|
||||
end
|
||||
#TODO, this is copied from module Positioned, maybe avoid duplication ?
|
||||
def position
|
||||
raise "position accessed but not set at #{mem_length} for #{self.inspect[0...1000]}" if @position == nil
|
||||
if @position == nil
|
||||
raise "position accessed but not set at #{mem_length} for #{self.inspect[0...1000]}"
|
||||
end
|
||||
@position
|
||||
end
|
||||
def set_position pos
|
||||
|
@ -3,14 +3,18 @@ module Virtual
|
||||
|
||||
# Frames and Message are very similar apart from the class name
|
||||
# - All existing instances are stored in the space for both
|
||||
# - Size is currently 2, ie 16 words (TODO a little flexibility here would not hurt, but the mountain is big)
|
||||
# - Unused instances for a linked list with their first instance variable. This is HARD coded to avoid any lookup
|
||||
# - Size is currently 2, ie 16 words
|
||||
# (TODO a little flexibility here would not hurt, but the mountain is big)
|
||||
# - Unused instances for a linked list with their first instance variable.
|
||||
# This is HARD coded to avoid any lookup
|
||||
|
||||
# Just as a reminder: a message object is created before a send and holds return address/message and arguemnts + self
|
||||
# frames are created upon entering a method and hold local and temporary variables
|
||||
# as a result one of each is created for every single method call. A LOT, so make it fast luke
|
||||
# Note: this is off course the reason for stack based implementations that just increment a known pointer/register or
|
||||
# something. But i think most programs are memory bound and a few extra instructions don't hurt.
|
||||
# Just as a reminder: a message object is created before a send and holds return address/message
|
||||
# and arguemnts + self frames are created upon entering a method and hold local and temporary
|
||||
# variables as a result one of each is created for every single method call.
|
||||
# A LOT, so make it fast luke
|
||||
# Note: this is off course the reason for stack based implementations that just increment
|
||||
# a known pointer/register or something. But i think most programs are memory bound
|
||||
# and a few extra instructions don't hurt.
|
||||
# After all, we are buying a big prize:oo, otherwise known as sanity.
|
||||
|
||||
class FrameImplementation
|
||||
|
@ -1,7 +1,9 @@
|
||||
module Virtual
|
||||
# This implements instance variable get (not the opposite of Set, such a thing does not exists, their slots)
|
||||
# This implements instance variable get (not the opposite of Set,
|
||||
# such a thing does not exists, their slots)
|
||||
|
||||
# Ivar get needs to acces the layout, find the index of the name, and shuffle the data to return register
|
||||
# Ivar get needs to acces the layout, find the index of the name,
|
||||
# and shuffle the data to return register
|
||||
# In short it's so complicated we implement it in ruby and stick the implementation here
|
||||
class GetImplementation
|
||||
def run block
|
||||
|
@ -4,9 +4,11 @@ module Virtual
|
||||
# That off course opens up an endless loop possibility that we stop by
|
||||
# implementing Class and Module methods
|
||||
|
||||
# Note: I find it slightly unsemetrical that the NewMessage object needs to be created before this instruction
|
||||
# This is because all expressions create a (return) value and that return value is overwritten by the next
|
||||
# expression unless saved. And since the message is the place to save it it needs to exist. qed
|
||||
# Note: I find it slightly unsymmetrical that the NewMessage object needs to be created
|
||||
# before this instruction.
|
||||
# This is because all expressions create a (return) value and that return value is
|
||||
# overwritten by the next expression unless saved.
|
||||
# And since the message is the place to save it it needs to exist. qed
|
||||
class SendImplementation
|
||||
def run block
|
||||
block.codes.dup.each do |code|
|
||||
|
@ -4,18 +4,22 @@ module Virtual
|
||||
# Data in a Block is usefull in the same way data in objects is. Plocks being otherwise just code.
|
||||
#
|
||||
# But the concept is not quite straigtforwrd: If one thinks of a Plock embedded in a normal method,
|
||||
# the a data in the Plock would be static data. In OO terms this comes quite close to a Proc, if the data is the local
|
||||
# variables. Quite possibly they shall be used to implement procs, but that is not the direction now.
|
||||
# the a data in the Plock would be static data. In OO terms this comes quite close to a Proc,
|
||||
# if the data is the local variables.
|
||||
# Quite possibly they shall be used to implement procs, but that is not the direction now.
|
||||
#
|
||||
# For now we use Plocks behaind the scenes as it were. In the code that you never see, method invocation mainly.
|
||||
#
|
||||
# In terms of implementation the Plock is a Block with data (Not too much data, mainly a couple of references).
|
||||
# The block writes it's instructions as normal, but a jump is inserted as the last instruction. The jump is to the
|
||||
# next block, over the data that is inserted after the block code (and so before the next)
|
||||
# For now we use Plocks behaind the scenes as it were. In the code that you never see,
|
||||
# method invocation mainly.
|
||||
#
|
||||
# In terms of implementation the Plock is a Block with data
|
||||
# (Not too much data, mainly a couple of references).
|
||||
# The block writes it's instructions as normal, but a jump is inserted as the last instruction.
|
||||
# The jump is to the next block, over the data that is inserted after the block code
|
||||
# (and so before the next)
|
||||
#
|
||||
# It follows that Plocks should be linear blocks.
|
||||
class Plock < Block
|
||||
|
||||
|
||||
def initialize(name , method , next_block )
|
||||
super
|
||||
@data = []
|
||||
|
@ -2,7 +2,9 @@ require_relative "type"
|
||||
|
||||
module Positioned
|
||||
def position
|
||||
raise "position accessed but not set at #{mem_length} for #{self.inspect[0...500]}" if @position == nil
|
||||
if @position == nil
|
||||
raise "position accessed but not set at #{mem_length} for #{self.inspect[0...500]}"
|
||||
end
|
||||
@position
|
||||
end
|
||||
def set_position pos
|
||||
|
Reference in New Issue
Block a user