Цани обнови решението на 13.01.2014 18:01 (преди почти 11 години)
+module Asm
+ class X86CPU
+ class Register
+ include Comparable
+ attr_reader :val
+ def initialize
+ @val = 0
+ end
+
+ def set (value)
+ @val = value
+ end
+
+ #Let's make our register work with operations on Fixnum
+ def + (value)
+ @val + value
+ end
+
+ def - (value)
+ @val - value
+ end
+
+ def <=> (value)
+ @val <=> value
+ end
+
+ def coerce (something) #Coerce into Fixnum
+ [something,@val]
+ end
+ end
+ @@registers =
+ {
+ :ax => Register.new,
+ :bx => Register.new,
+ :cx => Register.new,
+ :dx => Register.new,
+ :cmpx => Register.new,
+ :ip => Register.new, #This should be taken from each executable
+ #and we should pass it to the CPU so it can
+ #manipulate it. But let's do it here for ezness
+ }
+ @@instruction_set = #A simple hash holding lambdas for our instructions
+ {
+ :mov => ->(reg,value) {@@registers[reg].set(parse(value))},
+ :inc => ->(reg,value=1) {@@registers[reg].set(@@registers[reg] + parse(value))},
+ :dec => ->(reg,value=1) {@@registers[reg].set(@@registers[reg] - parse(value))},
+ :cmp => ->(reg,value) {@@registers[:cmpx].set(@@registers[reg]<=>parse(value))},
+ :jmp => ->(pos) {@@registers[:ip].set(pos-1)},#pos-1 is a shitty fix, but ez
+ :je => ->(pos) {@@registers[:ip].set(pos-1) if @@registers[:cmpx] == 0},
+ :jne => ->(pos) {@@registers[:ip].set(pos-1) if @@registers[:cmpx] != 0},
+ :jl => ->(pos) {@@registers[:ip].set(pos-1) if @@registers[:cmpx] < 0},
+ :jle => ->(pos) {@@registers[:ip].set(pos-1) if @@registers[:cmpx] <= 0},
+ :jg => ->(pos) {@@registers[:ip].set(pos-1) if @@registers[:cmpx] > 0},
+ :jge => ->(pos) {@@registers[:ip].set(pos-1) if @@registers[:cmpx] >= 0}
+ }
+
+ def self.parse (val) #since we don't know if we are getting a symbol
+ case val #or a Fixnum, parse it beforehand
+ when Symbol
+ @@registers[val] #Here we return the register itself, because we can coerce it
+ else
+ val
+ end
+ end
+
+ def self.ip #We need access to our instruction pointer for our executable
+ @@registers[:ip].val
+ end
+
+ def self.instruction_set #We need access to our instruction set for our builder
+ @@instruction_set
+ end
+
+ def self.registers
+ [@@registers[:ax].val,@@registers[:bx].val,
+ @@registers[:cx].val,@@registers[:dx].val]
+ end
+
+ def self.reset_registers
+ @@registers.each_value {|register| register.set(0)}
+ end
+
+ def self.execute_instruction (instruction,*args) #This is where the magic happens
+ @@instruction_set[instruction][*args]
+ @@instruction_set[:inc][:ip]
+ end
+ end
+
+ class X86Executable
+ def initialize(instr) #our executable is just an array of instructions
+ @instructions = instr
+ end
+
+ def run #here we just execute the instruction at :ip until we reach the end
+ p "Running executable"
+ X86CPU.reset_registers
+ while X86CPU.ip < @instructions.length do
+ p "Executing (IP:#{X86CPU.ip}) #{@instructions[X86CPU.ip]}"
+ X86CPU.execute_instruction(*@instructions[X86CPU.ip])
+ end
+ X86CPU.registers
+ end
+ end
+
+ class X86Builder
+ attr_accessor :instructions, :labels
+ def initialize
+ @instructions = []
+ @labels = {}
+ end
+
+ def method_missing(m,*args)
+ if X86CPU.instruction_set.has_key? m #if this is an instruction, add it
+ @instructions << [m,*args]
+ else
+ m #otherwise it's an argument, return it as a symbol
+ end
+ end
+
+ def label (lbl)
+ labels[lbl] = @instructions.length
+ end
+
+ def parse_labels #we replace the labels with their respective instructions
+ @instructions.each do |instruction|
+ instruction.collect! do |val|
+ if @labels.has_key? val then
+ @labels[val]
+ else
+ val
+ end
+ end
+ end
+ end
+
+ def build (&block)
+ instance_eval &block
+ parse_labels #compile the labels, no need to keep them
+ X86Executable.new @instructions
+ end
+ end
+
+ def self.asm(&block)
+ exe = X86Builder.new.build &block
+ exe.run
+ end
+end