As a homework on the topic DSL, I need to write an inline assembler in Ruby. I am aware of The Joke Is On Us: How Ruby 1.9 Supports the Goto Statement, but I must not use it. It is a very simple implementation with the assembler having four registers - ax
, bx
, cx
, dx
, holding integer values, on which I can do some operations such as setting their value (mov
), comparing two registers (cmp
), incrementing a register (inc
), jumping to a particular place (jmp
) and a few others of the sorts. The interface will be something like this:
Asm.asm do
mov cx, 1
jmp l1
mov ax, 1
label l1
mov dx, 1
end
The jmp
method will accept either a label name or the sequential number of one of the other functions. So my question is: in the block:
{
mov cx, 1
jmp l1
mov ax, 1
label l1
mov dx, 1
}
how do I keep track of the current number of a function. My implementation looks roughly like this:
module Asm
def self.asm(&block)
memory = Memory.new
memory.instance_eval(&block)
memory.table.values
end
class Memory
attr_reader :table
def initialize
@table = { ax: 0, bx: 0, cx: 0, dx: 0 }
...
end
...
def mov(destination_register, source)
...
end
def inc(destination_register, value = 1)
...
end
def dec(destination_register, value = 1)
...
end
...
end
end
I am stuck in implementing the jmp
aka goto
method. One idea I had was to use a hash holding all called methods and their arguments or to loop the block, containing the instructions and execute or not execute methods based on conditions saved in global variables, but I couldn't do much with that. So, for example, is there a way to break the block and save each instruction in an array/hash and then execute it based on its index or something similar. Any help is appreciated. Thank you very much in advance.
Here's an idea: pre "parse" your assembly code and then execute it. This consists of changing a few things. Here would be my implementation of the Memory class:
class Memory
attr_reader :table
def initialize
@table = { ax: 0, bx: 0, cx: 0, dx: 0 }
@program = []
@labels = {}
end
OPS = {
"mov" => lambda {|dest, src| @table[dest] = (src.is_a?(Symbol) ? @table[src] : src); nil},
"inc" => lambda {|dest, incval = 1| @table[dest] += incval; nil},
"jmp" => lambda {|lblname| @labels[lblname]}
#and so on
}
def method_missing(name, *args)
if(OPS.keys.include?(name.to_s))
@program << [name.to_s, args]
elsif(name.to_s == "label")
@labels[args.first] = @program.length
else
return name
end
end
def load_program(&block)
self.instance_exec(&block)
end
def run_program
pc = 0
until(pc == @program.length)
instruction = @program[pc]
retval = self.instance_exec(*instruction[1], &OPS[instruction[0]])
if(retval)
pc = retval
else
pc += 1
end
end
end
def asm(&block)
load_program(&block)
run_program
@table
end
end
Let's go over this step by step. Instead of having a method for each instruction, I use a hash of lambdas. Then, I use method_missing to do three things:
cx
is equivilent to :cx
.label
, add the index of the next instruction to the labels hash under that name.The return value (if not nil) of an instruction lambda is used as the new value of the program counter (so that more jumps like jump if zero can be added).
I would have each function call push an object representing the instruction on to an array then at the end of the block you evaluate the entire program knowing where all the instructions are (as you suggest).
For labels the instruction should probably add the label to a Hash mapping from label => instruction index
so you can quickly look up the jump target if you get a label.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With