block dsl facelift

This commit is contained in:
Torsten Ruger 2014-04-27 15:19:48 +03:00
parent 88ed97ac3b
commit 6fafeda66d
3 changed files with 88 additions and 49 deletions

View File

@ -9,20 +9,44 @@ module Asm
# A Block is the smalles unit of code, a list of instructions as it were
# It is also a point to jump/branch to. An address in the final stream.
# To allow for forward branches creation does not fix the position. Either set or assembling does that.
# To allow for forward branches creation does not fix the position.
# Thee position is fixed in one of three ways
# - create the block with ruby block, signalling that the instantiation poin is the position
# - call block.code with the code or if you wish program.add_block (and add you code with calls)
# - the assmebly process will pin it if it wasn't set
# creating blocks is done by calling the blocks name/label on either a program or a block
# (method missing will cathc the call and create the block)
# and the easiest way is to go into a ruby block and start writing instructions
# Example (backward jump):
# program.loop do create a new block with label loop
# sub r1 , r1 , 1 count the r1 register down
# bne :loop jump back to loop when the counter is not zero
# end (initialization and actual code missing off course)
# Example (forward jump)
# else_block = program.else
# program.if do
# test r1 , 0 test some condition
# beq :else_block
# mov . . .. .. do whatever the if block does
# end
# else_block.code do
# ldr .... do whatever else does
# end
# Blocks are also used to create instructions, and so Block has functions for every cpu instruction
# and to make using the apu function easier, there are functions that create registers as well
class Block < Code
extend Forwardable # forward block call back to program
def_delegator :@program, :block
def initialize(asm)
def initialize(name , prog)
super()
@name = name.to_sym
@codes = []
@position = 0
@program = asm
@program = prog
end
attr_reader :name
ArmMachine::REGISTERS.each do |reg , number|
define_method(reg) { Asm::Register.new(reg , number) }
@ -39,6 +63,9 @@ module Asm
arg_nodes << @program.add_string(arg)
elsif (arg.is_a?(Asm::Block))
arg_nodes << arg
elsif (arg.is_a?(Symbol))
block = @program.get_block arg
arg_nodes << block
else
raise "Invalid argument #{arg.inspect} for instruction"
end
@ -84,12 +111,11 @@ module Asm
define_instruction(inst , CallInstruction)
end
# setting a block fixes it's position in the stream.
# For backwards jumps, positions of blocks are known at creation, but for forward off course not.
# So then one can create a block, branch to it and set it later.
def set!
# codeing a block fixes it's position in the stream.
# You must call with a block, which is instance_eval'd and provides the actual code for the block
def code &block
@program.add_block self
self
self.instance_eval block
end
# length of the codes. In arm it would be the length * 4
@ -111,6 +137,17 @@ module Asm
end
end
# this is used to create blocks.
# All functions that have no args are interpreted as block names
# In fact the block calls are delegated to the program which then instantiates the blocks
def method_missing(meth, *args, &block)
if args.length == 0
@program.send(meth , *args , &block)
else
super
end
end
end
end

View File

@ -53,24 +53,6 @@ module Asm
@blocks.inject(0) {| sum , item | sum + item.length}
end
# call block to create a new (code) block. The simple way is to do this with a block and
# use the yielded block to add code, ie something like:
# prog.block do |loop|
# loop.instance_eval do #this part you can acheive with calls too
# mov r0 , 10
# subs r0 , 1
# bne block
# end
# end
# Easy, because it's a backward jump. For forward branches that doesn't work and so you have to
# create the block without a ruby block. You can then jumpt to it immediately
# But the block is not part of the program (since we don't know where) and so you have to add it later
def block
block = Block.new(self)
yield block.set! if block_given? #yield the block (which set returns)
block
end
# This is how you add a forward declared block. This is called automatically when you
# call block with ruby block, but has to be done manually if not
def add_block block
@ -78,6 +60,32 @@ module Asm
@blocks << block
end
# return the block of the given name
# or raise an exception, as this is meant to be called when the block is available
def get_block name
block = @blocks.find {|b| b.name == name}
raise "No block found for #{name} (in #{blocks.collect{|b|b.name}.join(':')})" unless block
block
end
# this is used to create blocks.
# All functions that have no args are interpreted as block names
# and if a block is provided, it is evaluated in the (ruby)blocks scope and the block added to the
# program immediately.
# If no block is provided (forward declaration), you must call code on it later
def method_missing(meth, *args, &block)
if args.length == 0
code = Block.new(meth.to_s , self )
if block_given?
add_block code
code.instance_eval(&block)
end
return code
else
super
end
end
private
def assemble(io)

View File

@ -13,17 +13,13 @@ class TestSmallProg < MiniTest::Test
end
def test_loop
@program.block do |main|
main.instance_eval do
mov r0, 5 #1
main.block do |start|
start.instance_eval do
subs r0, r0, 1 #2
bne start #3
end
mov r7, 1 #4
swi 0 #5 5 instructions
end
@program.main do
mov r0, 5 #1
start do
subs r0, r0, 1 #2
bne :start #3
mov r7, 1 #4
swi 0 #5 5 instructions
end
end
write( 5 , "loop" )
@ -31,16 +27,14 @@ class TestSmallProg < MiniTest::Test
def test_hello
hello = "Hello Raisa\n"
@program.block do |main|
main.instance_eval do
mov r7, 4 # 4 == write
mov r0 , 1 # stdout
add r1 , pc , hello # address of "hello Raisa"
mov r2 , hello.length
swi 0 #software interupt, ie kernel syscall
mov r7, 1 # 1 == exit
swi 0
end
@program.main do
mov r7, 4 # 4 == write
mov r0 , 1 # stdout
add r1 , pc , hello # address of "hello Raisa"
mov r2 , hello.length
swi 0 #software interupt, ie kernel syscall
mov r7, 1 # 1 == exit
swi 0
end
write(7 + hello.length/4 + 1 , 'hello')
end