我正在用Ruby做nand 2 tetris项目,我正在编写一个从VM到Jack的翻译器。我一直得到错误:in initialize': undefined method
[]' for nil:NilClass(NoMethodError)
path = path[0...-1] if path[-1] == "/"\r
^^^^
不管我想在程序中改变什么。
#parse a vm file
class Parser
attr_reader :current_command #returns the current VM command that was parsed
#constructor takes a path to a vm file and opens it in read-only mode
def initialize(path_to_vm_file)
@vm_file = File.open(path_to_vm_file, "r")
end
#checks if there are more command to parse
def has_more_commands?
!@vm_file.eof?
end
#reads the next command and sets current_command to the clean version of that command
# (gsub removes comments, newlines...)
def advance
@current_command = @vm_file.gets.gsub(/\/\.+|\n|\r/, "")
end
#allows the user to access a specific part of the current command by index
def [](index)
split_command[index]
end
#returns the current line number
def line_number
@vm_file.lineno
end
#returns the name of the vm file without extension
def file_name
File.basename(@vm_file.path, ".vm")
end
#helper method that splits the current command into an array of strings based on whitespaces
# (used by the [] method)
private
def split_command
@current_command.split
end
end
#translate VM code into Hack
class CodeWriter
#constructor: takes path to the output and opens it in write mode
def initialize(path_to_asm_file, single_file)
@asm_file = File.open(path_to_asm_file, "w")
end
#sets the name of the current vm file being translated
def set_file_name(path_to_vm_file)
@parser = Parser.new(path_to_vm_file)
end
# end
#reads each command form the parser and translates it into Hack using translate
def write
while @parser.has_more_commands?
if !@parser.advance.empty?
translate
end
end
end
#translates a VM command into Hack
# first determines the type of command and then calls the appropriate method
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
end
end
#translates vm arithmetic commands into Hack
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
#pushes a value onto the stack based on the segment specified in the vm command
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
#pops a value from the stack and stores it in the specific segment
def write_pop
pop_stack
#if static, loads the address of the static variable and stores popped value at that address
if @parser[1] == "static"
load_static(pop: true)
else
#else, stores in D register
write_file(string: "@13\nM=D")
load_memory(save_from_r13: true)
end
end
#loads the value of a static variable
# (if pop=true, stores the value at the top of the stack into the static variable)
def load_static(pop: false)
write_file(string: "@#{@parser.file_name.upcase}.#{@parser[2]}")
write_file(string: "#{pop ? "M=D" : "D=M"}")
end
#loads value from memory onto the top of the stack
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
#pushes a value onto the stack
def push_stack(constant: nil)
write_file(string: "@#{constant}\nD=A") if constant
#if constant, then load that value in D and push it onto the stack
# otherwise just pushes the value in the D register
write_file(string: "@SP\nA=M\nM=D\n@SP\nM=M+1")
end
#pops a value and optionally stores it in the D register
# decrements SP and accesses the value and the new top
def pop_stack(save_to_d: true)
write_file(string: "@SP\nM=M-1\nA=M#{save_to_d ? "\nD=M" : ""}")
end
#performs a jump instruction according to jump_type parameter
# sets D register to -1 if the jump condition is met or 0 if not, by jumping to either the true or false label that marks the jump location.
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
#pops top 2 values from the stack and performs the calculation
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
# #initializes by putting the stack pointer at memory location 256
# def write_init
# write_file(string: "@256\nD=A\n@SP\nM=D")
# write_call(init: true) #init: initializes vm
# end
#closes the asm file when done
def close
@asm_file.close
end
#writes in the output asm file the new command
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)
print(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 #if more than 1 file, all vm files in the directory will be translated
@asm_path = "#{@vm_path}/#{@vm_path.split("/")[-1]}.asm"
@single_file = false
end
@writer = CodeWriter.new(@asm_path, @single_file)
end
def compile
puts "Input the path to a file: "
@vm_path = gets.chomp
@single_file ? translate(@vm_path) : translate_all
@writer.close
end
#sets file name in codeWriter
private
def translate(vm_path)
@writer.set_file_name(vm_path)
@writer.write
end
#if more than 1 vm file, iterate over them and translate them all
def translate_all
Dir["#{@vm_path}/*.vm"].each {|file| translate(file)}
end
end
#pass the file path to the constructor of VM translator to start the translation
if __FILE__ == $0
VMTranslator.new(ARGV[0]).compile
end
我知道它发生在对象没有定义的时候,但是,我甚至在运行程序并到达那一点之前就得到了错误。
你对此有什么帮助吗?非常感谢!
1条答案
按热度按时间bvjxkvbb1#
首先,String#[]运算符可以 return nil。这可能是你的问题,也可能不是,但绝对值得记住。考虑一下:
尝试#slice nil(这就是#[]正在做的)* 必须 * 引发NoMethodError异常,因为它既不是String也不是Array,并且NilClass没有定义#[]方法。与其尝试调试代码的大墙,我建议三件事:
1.首先,尝试确保 path 不是nil。注意这不会帮助你找到 * 为什么 * 它是nil,但会防止异常,所以你可以用其他方式处理nil路径。
nil
值。您应该通过在#initialize方法的顶部引发自己的异常来防止这种情况,然后检查回溯以查看是否可以找到罪魁祸首。1.去掉所有不必要的东西,创建一个最小的、可重复的代码子集,这样你就可以更容易地调试它。
你可以做很多其他的事情来防止nils或处理异常。选项可能包括将值强制转换为String(例如
String(path)
或path.to_s
),或者拯救NoMethodError异常。但是,除非path.empty?
或path.nil?
是代码逻辑的有效选项,否则你需要修复调用者或在收到不可接受的输入时退出。