diff --git a/lib/asm/block.rb b/lib/asm/block.rb index f4fc790a..f6376d5f 100644 --- a/lib/asm/block.rb +++ b/lib/asm/block.rb @@ -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 \ No newline at end of file diff --git a/lib/asm/program.rb b/lib/asm/program.rb index 76c1d0b6..83983521 100644 --- a/lib/asm/program.rb +++ b/lib/asm/program.rb @@ -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) diff --git a/test/test_small_program.rb b/test/test_small_program.rb index 97e0a4bb..cd6ee822 100644 --- a/test/test_small_program.rb +++ b/test/test_small_program.rb @@ -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