PRÁCTICA 7

MÁQUINAS VIRTUALES

El lenguaje de VM consta de 4 categorías de comandos, de los cuales, en el proyecto 7 nos enfocaremos en los 2 primeros. Se tiene como principal objetivo el desarrollar un traductor de VM básico que sea capaz de traducir programas de VM que incluyen comandos aritméticos, lógicos y de acceso a la memoria en lenguaje de máquina. Seguidamente, en el proyecto 8, se abordarán las otras categorías de lenguaje VM que lo harán estar completo. Finalmente, para cada proyecto, se designaron unos programas en lenguaje VM para probar la traducción de los mismos.

Video explicativo

Código Basic VMTranslator (Ruby)

class Parser

attr_reader :current_command

def initialize(path_to_vm_file)

@vm_file = File.open(path_to_vm_file, "r")

end


def has_more_commands?

!@vm_file.eof?

end


def advance

@current_command = @vm_file.gets.gsub(/\/\.+|\n|\r/, "")

end


def [](index)

split_command[index]

end


def line_number

@vm_file.lineno

end


def file_name

File.basename(@vm_file.path, ".vm")

end


private

def split_command

@current_command.split

end

end


class CodeWriter

def initialize(path_to_asm_file, single_file)

@asm_file = File.open(path_to_asm_file, "w")

@function_count = 1

write_init if !single_file

end


def set_file_name(path_to_vm_file)

@parser = Parser.new(path_to_vm_file)

end


def write

while @parser.has_more_commands?

if !@parser.advance.empty?

translate

end

end

end


def translate

case @parser[0]

when "add","sub","eq","gt","lt","and","or","neg","not"

write_arithmetic

when "push"

write_push

when "pop"

write_pop

when "label"

write_label

when "goto","if-goto"

write_goto

when "call"

write_call

when "function"

write_function

when "return"

write_return

end

end


def write_arithmetic

case @parser[0]

when "add"

arithmetic(calc: "+")

when "sub"

arithmetic(calc: "-")

when "eq"

arithmetic(calc: "-", jump_type: "JEQ")

when "gt"

arithmetic(calc: "-", jump_type: "JGT")

when "lt"

arithmetic(calc: "-", jump_type: "JLT")

when "and"

arithmetic(calc: "&")

when "or"

arithmetic(calc: "|")

when "neg"

arithmetic(calc: "-", unary: true)

when "not"

arithmetic(calc: "!", unary: true)

end

end


def write_push

case @parser[1]

when "constant"

push_stack(constant:@parser[2])

when "static"

load_static

push_stack

else

load_memory

push_stack

end

end


def write_pop

pop_stack

if @parser[1] == "static"

load_static(pop: true)

else

write_file(string: "@13\nM=D")

load_memory(save_from_r13: true)

end

end


def write_label

write_file(string: "(#{@parser[1]})")

end


def write_goto

if @parser[0] == "if-goto"

pop_stack

jump = true

end

write_file(string: "@#{@parser[1]}")

write_file(string: "#{jump ? "D;JNE" : "0;JMP"}")

end


def write_function

write_file(string: "(#{@parser[1]})")

@parser[2].to_i.times do

write_file(string: "@0\nD=A")

push_stack

end

@function_name = @parser[1]

end


def write_call(init: false)

@argument_count = init ? 0 : @parser[2]

function_init

write_file(string: "@#{init ? "Sys.init" : @parser[1]}\n0;JMP")

write_file(string: "(RETURN#{@function_count - 1})", comment: "return address of #{init ? "Sys.init" : @parser[1]}")

end


def write_return

write_file(string: "@5\nD=A\n@LCL\nA=M-D\nD=M\n@15\nM=D")

pop_stack

write_file(string: "@ARG\nA=M\nM=D\nD=A+1\n@SP\nM=D")

["THAT", "THIS", "ARG", "LCL"].each do |register|

write_file(string: "@LCL\nAM=M-1\nD=M\n@#{register}\nM=D")

end

write_file(string: "@15\nA=M", comment: "going back to the return address of #{@parser[1]}")

write_file(string: "0;JMP")

end


def function_init

write_file(string: "@RETURN#{@function_count}\nD=A")

push_stack

["LCL", "ARG", "THIS", "THAT"].each do |register|

write_file(string: "@#{register}\nD=M")

push_stack

end

write_file(string: "@#{@argument_count.to_i + 5}\nD=A\n@SP\nD=M-D\n@ARG\nM=D\n@SP\nD=M\n@LCL\nM=D")

@function_count += 1

end


def load_static(pop: false)

write_file(string: "@#{@parser.file_name.upcase}.#{@parser[2]}")

write_file(string: "#{pop ? "M=D" : "D=M"}")

end


def load_memory(pop: false, save_from_r13: false)

symbol_hash = Hash["local", "LCL", "argument", "ARG", "this", "THIS", "that", "THAT",

"pointer", "THIS", "temp", "5"]

write_file(string: "@#{@parser[2]}")

write_file(string: "D=A")

write_file(string: "@#{symbol_hash[@parser[1]]}")

write_file(string: "#{(@parser[1] == "temp" || @parser[1] == "pointer") ? "AD=A+D" : "AD=M+D"}")

write_file(string: "#{save_from_r13 ? "@14\nM=D\n@13\nD=M\n@14\nA=M\nM=D" : "D=M"}")

end



def push_stack(constant: nil)

write_file(string: "@#{constant}\nD=A") if constant

write_file(string: "@SP\nA=M\nM=D\n@SP\nM=M+1")

end


def pop_stack(save_to_d: true)

write_file(string: "@SP\nM=M-1\nA=M#{save_to_d ? "\nD=M" : ""}")

end


def jump(jump_type)

write_file(string: "@TRUE_JUMP", set_file_name: true, label: "@")

write_file(string: "D; #{jump_type}\nD=0")

write_file(string: "@FALSE_NO_JUMP", set_file_name: true, label: "@")

write_file(string: "0;JMP")

write_file(string: "(TRUE_JUMP", set_file_name: true, label: "(")

write_file(string: "D=-1")

write_file(string: "(FALSE_NO_JUMP", set_file_name: true, label: "(")

end


def arithmetic(calc:, jump_type: nil, unary: false)

pop_stack

pop_stack(save_to_d: false) if !unary

write_file(string: "D=#{unary ? "" : "M"}#{calc}D")

jump(jump_type) if jump_type

push_stack

end


def write_init

write_file(string: "@256\nD=A\n@SP\nM=D")

write_call(init: true)

end


def close

@asm_file.close

end


private

def write_file(string:"", set_line_number: false, comment: "", set_file_name: false, label: "")

line_number = set_line_number ? @parser.line_number : ""

if !set_file_name

@asm_file.write("#{string}#{line_number}#{comment == "" ? "\n" : "//#{comment}\n"}")

elsif label == "@"

@asm_file.write("#{string}.#{@parser.file_name.upcase}.#{@parser.line_number}#{comment == "" ? "\n" : "//#{comment}\n"}")

else

@asm_file.write("#{string}.#{@parser.file_name.upcase}.#{@parser.line_number}#{comment == "" ? ")\n" : ")//#{comment}\n"}")

end

end

end


class VMTranslator

def initialize(path)

path = path[0...-1] if path[-1] == "/"

@vm_path = File.expand_path(path)

if path[-3..-1] == ".vm"

file_name = path.split("/")[-1][0..-4]

@asm_path = "#{@vm_path[0..-4]}.asm"

@single_file = true

else

@asm_path = "#{@vm_path}/#{@vm_path.split("/")[-1]}.asm"

@single_file = false

end

@writer = CodeWriter.new(@asm_path, @single_file)

end


def compile

@single_file ? translate(@vm_path) : translate_all

@writer.close

end


private

def translate(vm_path)

@writer.set_file_name(vm_path)

@writer.write

end


def translate_all

Dir["#{@vm_path}/*.vm"].each {|file| translate(file)}

end

end


if __FILE__ == $0

VMTranslator.new(ARGV[0]).compile

end

SimpleAdd

Salida

Salida

StackTest

Salida

Salida

BasicTest

Salida

Salida

PointerTest

Salida

Salida

StaticTest

Salida

Salida

Código full-scale VMTranslator (Ruby)

class Parser

attr_reader :current_command

def initialize(path_to_vm_file)

@vm_file = File.open(path_to_vm_file, "r")

end


def has_more_commands?

!@vm_file.eof?

end


def advance

@current_command = @vm_file.gets.gsub(/\/\.+|\n|\r/, "")

end


def [](index)

split_command[index]

end


def line_number

@vm_file.lineno

end


def file_name

File.basename(@vm_file.path, ".vm")

end


private

def split_command

@current_command.split

end

end


class CodeWriter

def initialize(path_to_asm_file, single_file)

@asm_file = File.open(path_to_asm_file, "w")

@function_count = 1

write_init if !single_file

end


def set_file_name(path_to_vm_file)

@parser = Parser.new(path_to_vm_file)

end


def write

while @parser.has_more_commands?

if !@parser.advance.empty?

translate

end

end

end


def translate

case @parser[0]

when "add","sub","eq","gt","lt","and","or","neg","not"

write_arithmetic

when "push"

write_push

when "pop"

write_pop

when "label"

write_label

when "goto","if-goto"

write_goto

when "call"

write_call

when "function"

write_function

when "return"

write_return

end

end


def write_arithmetic

case @parser[0]

when "add"

arithmetic(calc: "+")

when "sub"

arithmetic(calc: "-")

when "eq"

arithmetic(calc: "-", jump_type: "JEQ")

when "gt"

arithmetic(calc: "-", jump_type: "JGT")

when "lt"

arithmetic(calc: "-", jump_type: "JLT")

when "and"

arithmetic(calc: "&")

when "or"

arithmetic(calc: "|")

when "neg"

arithmetic(calc: "-", unary: true)

when "not"

arithmetic(calc: "!", unary: true)

end

end


def write_push

case @parser[1]

when "constant"

push_stack(constant:@parser[2])

when "static"

load_static

push_stack

else

load_memory

push_stack

end

end


def write_pop

pop_stack

if @parser[1] == "static"

load_static(pop: true)

else

write_file(string: "@13\nM=D")

load_memory(save_from_r13: true)

end

end


def write_label

write_file(string: "(#{@parser[1]})")

end


def write_goto

if @parser[0] == "if-goto"

pop_stack

jump = true

end

write_file(string: "@#{@parser[1]}")

write_file(string: "#{jump ? "D;JNE" : "0;JMP"}")

end


def write_function

write_file(string: "(#{@parser[1]})")

@parser[2].to_i.times do

write_file(string: "@0\nD=A")

push_stack

end

@function_name = @parser[1]

end


def write_call(init: false)

@argument_count = init ? 0 : @parser[2]

function_init

write_file(string: "@#{init ? "Sys.init" : @parser[1]}\n0;JMP")

write_file(string: "(RETURN#{@function_count - 1})", comment: "return address of #{init ? "Sys.init" : @parser[1]}")

end


def write_return

write_file(string: "@5\nD=A\n@LCL\nA=M-D\nD=M\n@15\nM=D")

pop_stack

write_file(string: "@ARG\nA=M\nM=D\nD=A+1\n@SP\nM=D")

["THAT", "THIS", "ARG", "LCL"].each do |register|

write_file(string: "@LCL\nAM=M-1\nD=M\n@#{register}\nM=D")

end

write_file(string: "@15\nA=M", comment: "going back to the return address of #{@parser[1]}")

write_file(string: "0;JMP")

end


def function_init

write_file(string: "@RETURN#{@function_count}\nD=A")

push_stack

["LCL", "ARG", "THIS", "THAT"].each do |register|

write_file(string: "@#{register}\nD=M")

push_stack

end

write_file(string: "@#{@argument_count.to_i + 5}\nD=A\n@SP\nD=M-D\n@ARG\nM=D\n@SP\nD=M\n@LCL\nM=D")

@function_count += 1

end


def load_static(pop: false)

write_file(string: "@#{@parser.file_name.upcase}.#{@parser[2]}")

write_file(string: "#{pop ? "M=D" : "D=M"}")

end


def load_memory(pop: false, save_from_r13: false)

symbol_hash = Hash["local", "LCL", "argument", "ARG", "this", "THIS", "that", "THAT",

"pointer", "THIS", "temp", "5"]

write_file(string: "@#{@parser[2]}")

write_file(string: "D=A")

write_file(string: "@#{symbol_hash[@parser[1]]}")

write_file(string: "#{(@parser[1] == "temp" || @parser[1] == "pointer") ? "AD=A+D" : "AD=M+D"}")

write_file(string: "#{save_from_r13 ? "@14\nM=D\n@13\nD=M\n@14\nA=M\nM=D" : "D=M"}")

end



def push_stack(constant: nil)

write_file(string: "@#{constant}\nD=A") if constant

write_file(string: "@SP\nA=M\nM=D\n@SP\nM=M+1")

end


def pop_stack(save_to_d: true)

write_file(string: "@SP\nM=M-1\nA=M#{save_to_d ? "\nD=M" : ""}")

end


def jump(jump_type)

write_file(string: "@TRUE_JUMP", set_file_name: true, label: "@")

write_file(string: "D; #{jump_type}\nD=0")

write_file(string: "@FALSE_NO_JUMP", set_file_name: true, label: "@")

write_file(string: "0;JMP")

write_file(string: "(TRUE_JUMP", set_file_name: true, label: "(")

write_file(string: "D=-1")

write_file(string: "(FALSE_NO_JUMP", set_file_name: true, label: "(")

end


def arithmetic(calc:, jump_type: nil, unary: false)

pop_stack

pop_stack(save_to_d: false) if !unary

write_file(string: "D=#{unary ? "" : "M"}#{calc}D")

jump(jump_type) if jump_type

push_stack

end


def write_init

write_file(string: "@256\nD=A\n@SP\nM=D")

write_call(init: true)

end


def close

@asm_file.close

end


private

def write_file(string:"", set_line_number: false, comment: "", set_file_name: false, label: "")

line_number = set_line_number ? @parser.line_number : ""

if !set_file_name

@asm_file.write("#{string}#{line_number}#{comment == "" ? "\n" : "//#{comment}\n"}")

elsif label == "@"

@asm_file.write("#{string}.#{@parser.file_name.upcase}.#{@parser.line_number}#{comment == "" ? "\n" : "//#{comment}\n"}")

else

@asm_file.write("#{string}.#{@parser.file_name.upcase}.#{@parser.line_number}#{comment == "" ? ")\n" : ")//#{comment}\n"}")

end

end

end


class VMTranslator

def initialize(path)

path = path[0...-1] if path[-1] == "/"

@vm_path = File.expand_path(path)

if path[-3..-1] == ".vm"

file_name = path.split("/")[-1][0..-4]

@asm_path = "#{@vm_path[0..-4]}.asm"

@single_file = true

else

@asm_path = "#{@vm_path}/#{@vm_path.split("/")[-1]}.asm"

@single_file = false

end

@writer = CodeWriter.new(@asm_path, @single_file)

end


def compile

@single_file ? translate(@vm_path) : translate_all

@writer.close

end


private

def translate(vm_path)

@writer.set_file_name(vm_path)

@writer.write

end


def translate_all

Dir["#{@vm_path}/*.vm"].each {|file| translate(file)}

end

end


if __FILE__ == $0

VMTranslator.new(ARGV[0]).compile

end

FibonacciElement

Salida

Salida

NestedCall

Salida

Salida

SimpleFunction

Salida

Salida

StaticsTest

Salida

Salida