/** Constructs a new empty program with the given GUI. */
  public VMProgram(VMProgramGUI gui) {
    super(gui != null);
    this.gui = gui;
    listeners = new Vector();
    staticRange = new Hashtable();
    functions = new Hashtable();

    if (hasGUI) {
      gui.addProgramListener(this);
      gui.addErrorListener(this);
    }

    reset();
  }
 public short getAddress(String functionName) throws ProgramException {
   Short address = (Short) functions.get(functionName);
   if (address != null) {
     return address.shortValue();
   } else {
     String className = functionName.substring(0, functionName.indexOf("."));
     if (staticRange.get(className) == null) {
       // The class is not implemented by a VM file - search for a
       // built-in implementation later. Display a popup to confirm
       // this as this is not a feature from the book but a later
       // addition.
       if (builtInAccessStatus == BUILTIN_ACCESS_UNDECIDED) {
         if (hasGUI && gui.confirmBuiltInAccess()) {
           builtInAccessStatus = BUILTIN_ACCESS_AUTHORIZED;
         } else {
           builtInAccessStatus = BUILTIN_ACCESS_DENIED;
         }
       }
       if (builtInAccessStatus == BUILTIN_ACCESS_AUTHORIZED) {
         return BUILTIN_FUNCTION_ADDRESS;
       }
     }
     // Either:
     // 1.The class is implemented by a VM file and no implementation
     //     for the function is found - don't override with built-in
     // - or -
     // 2.The user did not authorize using built-in implementations.
     throw new ProgramException(
         className
             + ".vm not found "
             + "or function "
             + functionName
             + " not found in "
             + className
             + ".vm");
   }
 }
  /**
   * 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);
  }
 public void refreshGUI() {
   if (displayChanges) {
     gui.setContents(instructions, visibleInstructionsLength);
     gui.setCurrentInstruction(nextPC);
   }
 }
 // Sets the GUI's current instruction index
 private void setGUIPC() {
   if (displayChanges) gui.setCurrentInstruction(nextPC);
 }
 // Sets the gui's contents (if a gui exists)
 private void setGUIContents() {
   if (displayChanges) {
     gui.setContents(instructions, visibleInstructionsLength);
     gui.setCurrentInstruction(nextPC);
   }
 }
 /**
  * Sets the program counter to a specially created infinite loop in the end of the programs for
  * access by built-in functions, de-facto halting the program. important so that tests and other
  * scripts finish counting (since a built-in infinite loop doesn't count as steps). also needed
  * because there is no good way to use the stop button to stop an infinite loop in a built-in jack
  * class. A message containing information may be provided (can be null).
  */
 public void setPCToInfiniteLoopForBuiltIns(String message) {
   if (hasGUI) {
     gui.notify(message);
   }
   setPC(infiniteLoopForBuiltInsAddress);
 }