public void run() {
   clearErrorListeners();
   try {
     loadProgram(fileName);
   } catch (ProgramException pe) {
     notifyErrorListeners(pe.getMessage());
   }
 }
  /**
   * Creates a vm program. If the given file is a dir, creates a program composed of the vm files in
   * the dir. The vm files are scanned twice: in the first scan a symbol table (that maps function &
   * label names into addresses) is built. In the second scan, the instructions array is built.
   * Throws ProgramException if an error occurs while loading the program.
   */
  public void loadProgram(String fileName) throws ProgramException {
    File file = new File(fileName);
    if (!file.exists()) throw new ProgramException("cannot find " + fileName);

    File[] files;

    if (file.isDirectory()) {
      files = file.listFiles(new HackFileFilter(".vm"));
      if (files == null || files.length == 0)
        throw new ProgramException("No vm files found in " + fileName);
    } else files = new File[] {file};

    if (displayChanges) gui.showMessage("Loading...");

    // First scan
    staticRange.clear();
    functions.clear();
    builtInAccessStatus = BUILTIN_ACCESS_UNDECIDED;
    Hashtable symbols = new Hashtable();
    nextPC = 0;
    for (int i = 0; i < files.length; i++) {
      String name = files[i].getName();
      String className = name.substring(0, name.indexOf("."));
      // put some dummy into static range - just to tell the function
      // getAddress in the second pass which classes exist
      staticRange.put(className, new Boolean(true));
      try {
        updateSymbolTable(files[i], symbols, functions);
      } catch (ProgramException pe) {
        if (displayChanges) gui.hideMessage();
        throw new ProgramException(name + ": " + pe.getMessage());
      }
    }
    boolean addCallBuiltInSysInit = false;
    if ((file.isDirectory() || symbols.get("Main.main") != null)
        && symbols.get("Sys.init") == null) {
      // If the program is in multiple files or there's a Main.main
      // function it is assumed that it should be run by calling Sys.init.
      // If no Sys.init is found, add an invisible line with a call
      // to Sys.init to start on - the builtin version will be called.
      addCallBuiltInSysInit = true;
      getAddress("Sys.init"); // confirm calling the built-in Sys.init
      ++nextPC; // A "call Sys.init 0" line will be added
    }

    instructions = new VMEmulatorInstruction[nextPC + 4];

    // Second scan
    nextPC = 0;
    currentStaticIndex = Definitions.VAR_START_ADDRESS;
    for (int i = 0; i < files.length; i++) {
      String name = files[i].getName();
      String className = name.substring(0, name.indexOf("."));

      largestStaticIndex = -1;
      int[] range = new int[2];
      range[0] = currentStaticIndex;

      try {
        // functions is not passed as an argument since it is accessed
        // through getAddress()
        buildProgram(files[i], symbols);
      } catch (ProgramException pe) {
        if (displayChanges) gui.hideMessage();
        throw new ProgramException(name + ": " + pe.getMessage());
      }

      currentStaticIndex += largestStaticIndex + 1;
      range[1] = currentStaticIndex - 1;
      staticRange.put(className, range);
    }
    instructionsLength = visibleInstructionsLength = nextPC;
    if (builtInAccessStatus == BUILTIN_ACCESS_AUTHORIZED) {
      // Add some "invisible" code in the end to make everything work
      instructionsLength += 4;
      if (addCallBuiltInSysInit) {
        instructionsLength += 1;
      }
      short indexInInvisibleCode = 0;
      // Add a jump to the end (noone should get here since
      // both calls to built-in functions indicate that
      // that this is a function-based program and not a script
      // a-la proj7, but just to be on the safe side...).
      instructions[nextPC] =
          new VMEmulatorInstruction(
              HVMInstructionSet.GOTO_CODE, (short) instructionsLength, indexInInvisibleCode);
      instructions[nextPC].setStringArg("afterInvisibleCode");
      nextPC++;
      // Add a small infinite loop for built-in
      // methods to call (for example when Sys.halt is
      // called it must call a non-built-in infinite loop
      // because otherwise the current script would not
      // finish running - a problem for the OS tests.
      instructions[nextPC] = new VMEmulatorInstruction(HVMInstructionSet.LABEL_CODE, (short) -1);
      instructions[nextPC].setStringArg("infiniteLoopForBuiltIns");
      nextPC++;
      infiniteLoopForBuiltInsAddress = nextPC;
      instructions[nextPC] =
          new VMEmulatorInstruction(HVMInstructionSet.GOTO_CODE, nextPC, ++indexInInvisibleCode);
      instructions[nextPC].setStringArg("infiniteLoopForBuiltIns");
      nextPC++;
      if (addCallBuiltInSysInit) { // Add a call to the built-in Sys.init
        instructions[nextPC] =
            new VMEmulatorInstruction(
                HVMInstructionSet.CALL_CODE,
                getAddress("Sys.init"),
                (short) 0,
                ++indexInInvisibleCode);
        instructions[nextPC].setStringArg("Sys.init");
        startAddress = nextPC;
        nextPC++;
      }
      // Add the label that the first invisible code line jumps to
      instructions[nextPC] = new VMEmulatorInstruction(HVMInstructionSet.LABEL_CODE, (short) -1);
      instructions[nextPC].setStringArg("afterInvisibleCode");
      nextPC++;
    }

    if (!addCallBuiltInSysInit) {
      Short sysInitAddress = (Short) symbols.get("Sys.init");
      if (sysInitAddress == null) // Single file, no Sys.init - start at 0
      startAddress = 0;
      else // Implemented Sys.init - start there
      startAddress = sysInitAddress.shortValue();
    }

    if (displayChanges) gui.hideMessage();

    nextPC = startAddress;
    setGUIContents();

    notifyProgramListeners(ProgramEvent.LOAD, fileName);
  }
  // Scans the given file and creates symbols for its functions & label names.
  private void buildProgram(File file, Hashtable symbols) throws ProgramException {

    BufferedReader reader = null;

    try {
      reader = new BufferedReader(new FileReader(file.getAbsolutePath()));
    } catch (FileNotFoundException fnfe) {
      throw new ProgramException("file does not exist");
    }

    int lineNumber = 0;
    String line;
    String label;
    String instructionName;
    String currentFunction = null;
    short indexInFunction = 0;
    byte opCode;
    short arg0, arg1;
    short pc = nextPC;
    HVMInstructionSet instructionSet = HVMInstructionSet.getInstance();

    isSlashStar = false;
    try {
      while ((line = unCommentLine(reader.readLine())) != null) {
        lineNumber++;

        if (!line.trim().equals("")) {
          StringTokenizer tokenizer = new StringTokenizer(line);
          instructionName = tokenizer.nextToken();

          opCode = instructionSet.instructionStringToCode(instructionName);
          if (opCode == HVMInstructionSet.UNKNOWN_INSTRUCTION)
            throw new ProgramException(
                "in line " + lineNumber + ": unknown instruction - " + instructionName);

          switch (opCode) {
            case HVMInstructionSet.PUSH_CODE:
              String segment = tokenizer.nextToken();
              try {
                arg0 = translateSegment(segment, instructionSet, file.getName());
              } catch (ProgramException pe) {
                throw new ProgramException("in line " + lineNumber + pe.getMessage());
              }
              arg1 = Short.parseShort(tokenizer.nextToken());
              if (arg1 < 0)
                throw new ProgramException(
                    "in line " + lineNumber + ": Illegal argument - " + line);

              if (arg0 == HVMInstructionSet.STATIC_SEGMENT_CODE && arg1 > largestStaticIndex)
                largestStaticIndex = arg1;

              instructions[pc] = new VMEmulatorInstruction(opCode, arg0, arg1, indexInFunction);
              break;

            case HVMInstructionSet.POP_CODE:
              int n = tokenizer.countTokens();
              segment = tokenizer.nextToken();
              try {
                arg0 = translateSegment(segment, instructionSet, file.getName());
              } catch (ProgramException pe) {
                throw new ProgramException("in line " + lineNumber + pe.getMessage());
              }
              arg1 = Short.parseShort(tokenizer.nextToken());

              if (arg1 < 0)
                throw new ProgramException(
                    "in line " + lineNumber + ": Illegal argument - " + line);

              if (arg0 == HVMInstructionSet.STATIC_SEGMENT_CODE && arg1 > largestStaticIndex)
                largestStaticIndex = arg1;

              instructions[pc] = new VMEmulatorInstruction(opCode, arg0, arg1, indexInFunction);
              break;

            case HVMInstructionSet.FUNCTION_CODE:
              currentFunction = tokenizer.nextToken();
              indexInFunction = 0;
              arg0 = Short.parseShort(tokenizer.nextToken());

              if (arg0 < 0)
                throw new ProgramException(
                    "in line " + lineNumber + ": Illegal argument - " + line);

              instructions[pc] = new VMEmulatorInstruction(opCode, arg0, indexInFunction);
              instructions[pc].setStringArg(currentFunction);
              break;

            case HVMInstructionSet.CALL_CODE:
              String functionName = tokenizer.nextToken();
              try {
                arg0 = getAddress(functionName);
              } catch (ProgramException pe) {
                throw new ProgramException("in line " + lineNumber + ": " + pe.getMessage());
              }
              arg1 = Short.parseShort(tokenizer.nextToken());

              if (arg1 < 0
                  || ((arg0 < 0 || arg0 > Definitions.ROM_SIZE)
                      && arg0 != BUILTIN_FUNCTION_ADDRESS))
                throw new ProgramException(
                    "in line " + lineNumber + ": Illegal argument - " + line);

              instructions[pc] = new VMEmulatorInstruction(opCode, arg0, arg1, indexInFunction);
              instructions[pc].setStringArg(functionName);
              break;

            case HVMInstructionSet.LABEL_CODE:
              label = currentFunction + "$" + tokenizer.nextToken();
              instructions[pc] = new VMEmulatorInstruction(opCode, (short) (-1));
              instructions[pc].setStringArg(label);
              indexInFunction--; // since Label is not a "physical" instruction
              break;

            case HVMInstructionSet.GOTO_CODE:
              label = currentFunction + "$" + tokenizer.nextToken();
              Short labelAddress = (Short) symbols.get(label);
              if (labelAddress == null)
                throw new ProgramException("in line " + lineNumber + ": Unknown label - " + label);
              arg0 = labelAddress.shortValue();

              if (arg0 < 0 || arg0 > Definitions.ROM_SIZE)
                throw new ProgramException(
                    "in line " + lineNumber + ": Illegal argument - " + line);

              instructions[pc] = new VMEmulatorInstruction(opCode, arg0, indexInFunction);
              instructions[pc].setStringArg(label);
              break;

            case HVMInstructionSet.IF_GOTO_CODE:
              label = currentFunction + "$" + tokenizer.nextToken();
              labelAddress = (Short) symbols.get(label);
              if (labelAddress == null)
                throw new ProgramException("in line " + lineNumber + ": Unknown label - " + label);

              arg0 = labelAddress.shortValue();

              if (arg0 < 0 || arg0 > Definitions.ROM_SIZE)
                throw new ProgramException(
                    "in line " + lineNumber + ": Illegal argument - " + line);

              instructions[pc] = new VMEmulatorInstruction(opCode, arg0, indexInFunction);
              instructions[pc].setStringArg(label);
              break;

              // All other instructions have either 1 or 0 arguments and require no
              // special treatment
            default:
              if (tokenizer.countTokens() == 0) {
                instructions[pc] = new VMEmulatorInstruction(opCode, indexInFunction);
              } else {
                arg0 = Short.parseShort(tokenizer.nextToken());

                if (arg0 < 0)
                  throw new ProgramException(
                      "in line " + lineNumber + ": Illegal argument - " + line);

                instructions[pc] = new VMEmulatorInstruction(opCode, arg0, indexInFunction);
              }
              break;
          }

          // check end of command
          if (tokenizer.hasMoreTokens())
            throw new ProgramException("in line " + lineNumber + ": Too many arguments - " + line);

          pc++;
          indexInFunction++;
        }

        nextPC = pc;
      }
      reader.close();
    } catch (IOException ioe) {
      throw new ProgramException("Error while reading from file");
    } catch (NumberFormatException nfe) {
      throw new ProgramException("Illegal 16-bit value");
    } catch (NoSuchElementException nsee) {
      throw new ProgramException("In line " + lineNumber + ": unexpected end of command");
    }
    if (isSlashStar) {
      throw new ProgramException("Unterminated /* comment at end of file");
    }
  }