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 # 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. # 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 # 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 # and to make using the apu function easier, there are functions that create registers as well
class Block < Code class Block < Code
extend Forwardable # forward block call back to program
def_delegator :@program, :block
def initialize(asm) def initialize(name , prog)
super() super()
@name = name.to_sym
@codes = [] @codes = []
@position = 0 @position = 0
@program = asm @program = prog
end end
attr_reader :name
ArmMachine::REGISTERS.each do |reg , number| ArmMachine::REGISTERS.each do |reg , number|
define_method(reg) { Asm::Register.new(reg , number) } define_method(reg) { Asm::Register.new(reg , number) }
@ -39,6 +63,9 @@ module Asm
arg_nodes << @program.add_string(arg) arg_nodes << @program.add_string(arg)
elsif (arg.is_a?(Asm::Block)) elsif (arg.is_a?(Asm::Block))
arg_nodes << arg arg_nodes << arg
elsif (arg.is_a?(Symbol))
block = @program.get_block arg
arg_nodes << block
else else
raise "Invalid argument #{arg.inspect} for instruction" raise "Invalid argument #{arg.inspect} for instruction"
end end
@ -84,12 +111,11 @@ module Asm
define_instruction(inst , CallInstruction) define_instruction(inst , CallInstruction)
end end
# setting a block fixes it's position in the stream. # codeing 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. # You must call with a block, which is instance_eval'd and provides the actual code for the block
# So then one can create a block, branch to it and set it later. def code &block
def set!
@program.add_block self @program.add_block self
self self.instance_eval block
end end
# length of the codes. In arm it would be the length * 4 # length of the codes. In arm it would be the length * 4
@ -111,6 +137,17 @@ module Asm
end end
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
end end

View File

@ -53,24 +53,6 @@ module Asm
@blocks.inject(0) {| sum , item | sum + item.length} @blocks.inject(0) {| sum , item | sum + item.length}
end 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 # 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 # call block with ruby block, but has to be done manually if not
def add_block block def add_block block
@ -78,6 +60,32 @@ module Asm
@blocks << block @blocks << block
end 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 private
def assemble(io) def assemble(io)

View File

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