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:
- If the method name is just a random symbol (not an instruction name), I just return the symbol. So now,
cx
is equivilent to:cx
. - If it's an instruction name, add it and the arguments to the program array
- If it's a
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).