How to implement goto and label methods in Ruby?

As a homework on DSL, I need to write inline assembler in Ruby. I know The joke is on us: how Ruby 1.9 supports the Goto statement , but I should not use it. This is a very simple implementation when the assembler has four registers - ax , bx , cx , dx , containing integer values ​​on which I can perform some operations, such as setting their values ​​( mov ), comparing two registers ( cmp ), increasing the register ( inc ), jumping to a specific location ( jmp ) and several other types. 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 the label name or the serial 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 can I track the current function number. My implementation looks something 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 jmp aka goto method. One of my ideas was to use a hash containing all the called methods and their arguments, or a loop loop containing instructions, and to execute or not to execute methods based on conditions stored in global variables, but I could not handle it. So, for example, is there a way to split a block and store each instruction in an array / hash, and then execute it based on its index or something similar. Any help is appreciated. Thank you so much in advance.

+6
source share
2 answers

Here is the idea: first β€œparse” your assembler code, and then execute it. This consists in changing a few things. Here is 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 

We proceed to this step by step. Instead of having a method for each instruction, I use a hash from lambda. Then I use method_missing to do three things:

  • If the method name is just a random character (and not the command name), I simply return the character. So now cx equivalent to :cx .
  • If this is the name of the command, add it and arguments to the program array
  • If it is a label , add the index of the following statement to the hashes of the labels under this name.

The return value (if not zero) of the lambda command is used as the new value of the program counter (so there are more jumps, such as a jump, if zero can be added).

+7
source

For me, every function call would click on an object that represents an instruction on an array, and then at the end of the block you evaluate the entire program, knowing where all the instructions are (as you assume).

For shortcuts, the team should probably add a label to the Hash display from label => instruction index so that you can quickly find the transition target if you get a label.

+4
source

All Articles