ruby 错误:nil:NilClass的未定义方法“[]”(NoMethodError)

5n0oy7gb  于 2023-04-05  发布在  Ruby
关注(0)|答案(1)|浏览(175)

我正在用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

我知道它发生在对象没有定义的时候,但是,我甚至在运行程序并到达那一点之前就得到了错误。
你对此有什么帮助吗?非常感谢!

bvjxkvbb

bvjxkvbb1#

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

首先,String#[]运算符可以 return nil。这可能是你的问题,也可能不是,但绝对值得记住。考虑一下:

nil.to_s[-1]
#=> nil

尝试#slice nil(这就是#[]正在做的)* 必须 * 引发NoMethodError异常,因为它既不是String也不是Array,并且NilClass没有定义#[]方法。与其尝试调试代码的大墙,我建议三件事:
1.首先,尝试确保 path 不是nil。注意这不会帮助你找到 * 为什么 * 它是nil,但会防止异常,所以你可以用其他方式处理nil路径。

path = path[...-1] if !!path && path[-1].eql?("/")
  1. VMTranslator#new需要一个参数来填充 path,但是没有什么可以阻止它被传递一个文字nil值。您应该通过在#initialize方法的顶部引发自己的异常来防止这种情况,然后检查回溯以查看是否可以找到罪魁祸首。
raise ArgumentError, "caller passed nil to VMTranslator#new"

1.去掉所有不必要的东西,创建一个最小的、可重复的代码子集,这样你就可以更容易地调试它。
你可以做很多其他的事情来防止nils或处理异常。选项可能包括将值强制转换为String(例如String(path)path.to_s),或者拯救NoMethodError异常。但是,除非path.empty?path.nil?是代码逻辑的有效选项,否则你需要修复调用者或在收到不可接受的输入时退出。

相关问题