rubyx/lib/vm/block.rb

112 lines
3.6 KiB
Ruby
Raw Normal View History

2014-05-03 14:13:44 +02:00
require_relative "values"
module Vm
# Think flowcharts: blocks are the boxes. The smallest unit of linear code
# Blocks must end in control instructions (jump/call/return).
# And the only valid argument for a jump is a Block
2014-05-05 08:35:40 +02:00
# Blocks form a linked list
2014-05-03 14:13:44 +02:00
# There are four ways for a block to get data (to work on)
# - hard coded constants (embedded in code)
# - memory move
# - values passed in (from previous blocks. ie local variables)
# See Value description on how to create code/instructions
# Codes then get assembled into bytes (after linking)
2014-05-05 08:35:40 +02:00
2014-05-03 21:18:04 +02:00
class Block < Code
2014-05-03 14:13:44 +02:00
def initialize(name)
super()
@name = name.to_sym
2014-05-03 17:51:47 +02:00
@next = nil
@codes = []
2014-05-03 14:13:44 +02:00
end
attr_reader :name , :next , :codes
2014-05-03 14:13:44 +02:00
2014-05-05 08:35:40 +02:00
def length
@codes.inject(0) {| sum , item | sum + item.length}
end
2014-05-03 21:18:04 +02:00
def add_code(kode)
2014-05-19 14:44:12 +02:00
if kode.is_a? Hash
raise "Hack only for 1 element #{inspect} #{kode.inspect}" unless kode.length == 1
instruction , result = kode.first
instruction.result = result
kode = instruction
end
raise "alarm #{kode}" if kode.is_a? Word
raise "alarm #{kode}" unless kode.is_a? Code
@codes << kode
2014-05-05 08:35:40 +02:00
self
2014-05-03 21:18:04 +02:00
end
alias :<< :add_code
2014-05-19 14:44:12 +02:00
alias :a :add_code
2014-05-03 21:18:04 +02:00
def link_at pos , context
@position = pos
@codes.each do |code|
code.link_at(pos , context)
pos += code.length
2014-05-05 08:35:40 +02:00
end
pos
2014-05-05 08:35:40 +02:00
end
2014-05-03 21:18:04 +02:00
def assemble(io)
@codes.each do |obj|
obj.assemble io
end
end
2014-05-06 20:36:28 +02:00
2014-05-20 10:03:18 +02:00
# to use the assignment syntax (see method_missing) the scope must be set, so variables can be resolved
# The scope you set should be a binding (literally, the kernel.binding)
# The function return the block, so it can be chained into an assignment
# Example (coding a function ) and having variable int defined
# b = function.body.scope(binding)
# b.int = 5 will create a mov instruction to set the register that int points to
def scope where
@scope = where
self
end
2014-05-03 17:51:47 +02:00
# set the next executed block after self.
# why is this useful? if it's unconditional, why not merge them:
# So the second block can be used as a jump target. You standard loop needs a block to setup
# and at least one to do the calculation
2014-05-06 20:36:28 +02:00
def set_next block
2014-05-05 08:35:40 +02:00
@next = block
2014-05-03 17:51:47 +02:00
end
2014-05-05 08:35:40 +02:00
2014-05-20 10:03:18 +02:00
# sugar to create instructions easily. Actually just got double sweet with two versions:
# 1 for any method that ends in = we evaluate the method name in the current scope (see scope())
# for the result we call assign with the right value. The resulting instruction is added to
# the block.
# Thus we emulate assignment,
# Example: block b
# b.variable = value looks like what it does, but actually generates
# an instruction for the block (mov or add)
#
# 2- any other method will be passed on to the CMachine and the result added to the block
# With this trick we can write what looks like assembler,
# Example b.instance_eval
# mov( r1 , r2 )
# add( r1 , r2 , 4)
# end
# mov and add will be called on Machine and generate Inststuction that are then added
# to the block
def method_missing(meth, *args, &block)
2014-05-20 10:03:18 +02:00
if( meth.to_s[-1] == "=" && args.length == 1)
l_val = @scope.eval meth.to_s[0 ... -1]
add_code l_val.assign(args[0])
else
add_code CMachine.instance.send(meth , *args)
end
end
2014-05-03 14:13:44 +02:00
end
end