/**
   * Calls a function according to the given function number stating that the given number of
   * arguments have been pushed onto the stack
   *
   * <p>If callerIsBuiltIn then the caller is a builtIn function that called this function through
   * callFunctionFromBuiltIn. If address is -1 then a native function should be looked up and
   * called.
   */
  public void callFunction(
      short address, short numberOfArguments, String functionName, boolean callerIsBuiltIn)
      throws ProgramException {
    stackFrames.addElement(new Integer(workingStackSegment.getStartAddress()));
    workingStackSegment.setStartAddress(getSP() + 5);

    if (callerIsBuiltIn) {
      pushValue(MAIN_STACK, VMProgram.BUILTIN_FUNCTION_ADDRESS);
    } else {
      pushValue(MAIN_STACK, program.getPC());
    }
    pushFromRAM(MAIN_STACK, Definitions.LOCAL_POINTER_ADDRESS);
    pushFromRAM(MAIN_STACK, Definitions.ARG_POINTER_ADDRESS);
    pushFromRAM(MAIN_STACK, Definitions.THIS_POINTER_ADDRESS);
    pushFromRAM(MAIN_STACK, Definitions.THAT_POINTER_ADDRESS);
    ram.setValueAt(
        Definitions.ARG_POINTER_ADDRESS, (short) (getSP() - numberOfArguments - 5), false);
    ram.setValueAt(Definitions.LOCAL_POINTER_ADDRESS, getSP(), false);

    // enable in the arg segment only the number of args that were sent to the called function.
    argSegment.setEnabledRange(
        argSegment.getStartAddress(), argSegment.getStartAddress() + numberOfArguments - 1, true);

    if (address == VMProgram.BUILTIN_FUNCTION_ADDRESS) {
      // Perform some actions normally done in the function() method
      localSegment.setEnabledRange(
          localSegment.getStartAddress(),
          localSegment.getStartAddress() - 1,
          true); // no local variables
      callStack.pushFunction(functionName + " (built-in)");
      staticSegment.setEnabledRange(0, -1, true); // empty static segment
      // Read parameters from the stack
      short[] params = new short[numberOfArguments];
      for (int i = 0; i < numberOfArguments; ++i) {
        params[i] = argSegment.getValueAt(i);
      }
      // Call the built-in implementation
      builtInFunctionsRunner.callBuiltInFunction(functionName, params);
    } else if (address >= 0 || address < program.getSize()) {
      program.setPC(address);
      program.setPC(address); // make sure previouspc isn't pc-1
      // which might happen if the calling
      // function called this function in the
      // last line before the "return" and
      // was declared just before this function.
      // In this case encountering the "function"
      // command will issue an error about
      // "missing return"...
    } else {
      error("Illegal call address");
    }
  }
  // sends a value from the appropriate stack (at sp-1) to the this pointer
  // and increments sp.
  private void popToThisPointer(int stackID) throws ProgramException {
    short value = ram.getValueAt(getSP() - 1);
    if ((value < Definitions.HEAP_START_ADDRESS || value > Definitions.HEAP_END_ADDRESS)
        && value > 0) error("'This' segment must be in the Heap range");

    popToRAM(stackID, Definitions.THIS_POINTER_ADDRESS);
    thisSegment.setEnabledRange(value, Definitions.HEAP_END_ADDRESS, true);
  }
  // sends a value from the appropriate stack (at sp-1) to the that pointer
  // and increments sp.
  private void popToThatPointer(int stackID) throws ProgramException {
    short value = ram.getValueAt(getSP() - 1);
    if (!((value >= Definitions.HEAP_START_ADDRESS && value <= Definitions.HEAP_END_ADDRESS)
        || (value >= Definitions.SCREEN_START_ADDRESS && value <= Definitions.SCREEN_END_ADDRESS)
        || value == 0)) error("'That' segment must be in the Heap or Screen range");

    popToRAM(stackID, Definitions.THAT_POINTER_ADDRESS);
    thatSegment.setEnabledRange(value, Definitions.SCREEN_END_ADDRESS, true);
  }
 /** Initializes the cpu. */
 public void boot() {
   stackSegment.setStartAddress(Definitions.STACK_START_ADDRESS);
   workingStackSegment.setStartAddress(Definitions.STACK_START_ADDRESS);
   localSegment.setEnabledRange(
       Definitions.STACK_START_ADDRESS, Definitions.STACK_END_ADDRESS, true);
   argSegment.setEnabledRange(
       Definitions.STACK_START_ADDRESS, Definitions.STACK_END_ADDRESS, true);
   thisSegment.setEnabledRange(Definitions.HEAP_START_ADDRESS, Definitions.HEAP_END_ADDRESS, true);
   thatSegment.setEnabledRange(
       Definitions.HEAP_START_ADDRESS, Definitions.SCREEN_END_ADDRESS, true);
   staticSegment.setStartAddress(Definitions.VAR_START_ADDRESS);
   staticSegment.setEnabledRange(
       Definitions.VAR_START_ADDRESS, Definitions.VAR_END_ADDRESS - 1, true);
   setSP(Definitions.STACK_START_ADDRESS);
   stackFrames.clear();
   if (builtInFunctionsRunner != null) {
     builtInFunctionsRunner.killAllRunningBuiltInFunctions();
   }
 }
  /** Sets the static segment range according to the the given function (file) name. */
  protected void setStaticRange(String functionName) throws ProgramException {
    int dotLocation = functionName.indexOf(".");
    if (dotLocation == -1) throw new ProgramException("Illegal function name: " + functionName);

    String className = functionName.substring(0, dotLocation);
    int[] range = program.getStaticRange(className);
    if (range == null)
      throw new ProgramException("Function name doesn't match class name: " + functionName);

    staticSegment.setStartAddress(range[0]);
    staticSegment.setEnabledRange(range[0], range[1], true);
  }
  /**
   * Here Starts the code of a function according to the given function name that has the given
   * number of local variables.
   *
   * @param numberOfLocals The number of local variables
   */
  public void function(short numberOfLocals) throws ProgramException {

    short newSP = (short) (getSP() + numberOfLocals);
    checkSP(newSP);
    workingStackSegment.setStartAddress(newSP);

    // disable non relevant range of the local segment - enable only the number
    // of locals of this function.
    localSegment.setEnabledRange(getSP(), newSP - 1, true);

    for (int i = 0; i < numberOfLocals; i++) {
      pushValue(MAIN_STACK, (short) 0);
    }

    String functionName = currentInstruction.getStringArg();

    // adds the new function to the top of the call stack.
    callStack.pushFunction(functionName);

    // sets the static segment range
    setStaticRange(functionName);
  }
  /** Returns the value of the function to the top of the stack. */
  public void returnFromFunction() throws ProgramException {

    // make sure that there's somewhere to return to (old local <> 0)
    if (stackSegment.getValueAt(Definitions.LOCAL_POINTER_ADDRESS) == 0)
      throw new ProgramException(
          "Nowhere to return to in "
              + getCallStack().getTopFunction()
              + "."
              + getCurrentInstruction().getIndexInFunction());

    // done in order to clear the method stack's contents
    workingStackSegment.setStartAddress(getSP());

    bus.send(ram, Definitions.LOCAL_POINTER_ADDRESS, ram, Definitions.R13_ADDRESS); // R13 = lcl
    bus.send(
        stackSegment,
        stackSegment.getValueAt(Definitions.LOCAL_POINTER_ADDRESS) - 5,
        ram,
        Definitions.R14_ADDRESS); // R14 = return address
    bus.send(
        stackSegment,
        getSP() - 1,
        stackSegment,
        ram.getValueAt(Definitions.ARG_POINTER_ADDRESS)); // *arg = return value
    setSP((short) (ram.getValueAt(Definitions.ARG_POINTER_ADDRESS) + 1)); // SP = arg + 1
    bus.send(
        stackSegment,
        ram.getValueAt(Definitions.R13_ADDRESS) - 1,
        ram,
        Definitions.THAT_POINTER_ADDRESS); // that = *(R13 - 1)
    bus.send(
        stackSegment,
        ram.getValueAt(Definitions.R13_ADDRESS) - 2,
        ram,
        Definitions.THIS_POINTER_ADDRESS); // this = *(R13 - 2)
    bus.send(
        stackSegment,
        ram.getValueAt(Definitions.R13_ADDRESS) - 3,
        ram,
        Definitions.ARG_POINTER_ADDRESS); // arg = *(R13 - 3)
    bus.send(
        stackSegment,
        ram.getValueAt(Definitions.R13_ADDRESS) - 4,
        ram,
        Definitions.LOCAL_POINTER_ADDRESS); // lcl = *(R13 - 4)

    // removes the top function from the call stack
    callStack.popFunction();

    // check whether there is a "calling frame"
    if (stackFrames.size() > 0) {
      // retrieve stack frame address of old function
      int frameAddress = ((Integer) stackFrames.lastElement()).intValue();
      stackFrames.removeElementAt(stackFrames.size() - 1);
      workingStackSegment.setStartAddress(frameAddress);

      // disable non relevant range of the local segment - enable only the locals
      // of the function that we returned to.
      localSegment.setEnabledRange(
          Math.max(localSegment.getStartAddress(), Definitions.STACK_START_ADDRESS),
          frameAddress - 1,
          true);

      // enable in the arg segment only the number of args that were sent to the function
      // that we returned to.
      argSegment.setEnabledRange(
          argSegment.getStartAddress(), localSegment.getStartAddress() - 6, true);

      // enable this, that according to their retrieved pointers
      thisSegment.setEnabledRange(
          Math.max(thisSegment.getStartAddress(), Definitions.HEAP_START_ADDRESS),
          Definitions.HEAP_END_ADDRESS,
          true);
      thatSegment.setEnabledRange(
          Math.max(thatSegment.getStartAddress(), Definitions.HEAP_START_ADDRESS),
          Definitions.SCREEN_END_ADDRESS,
          true);
    } /* else {
      	error("Nowhere to return to");
      } */
    // Allow return if we previously had "function" even with no call -
    // For the SimpleFunction test

    short returnAddress = ram.getValueAt(Definitions.R14_ADDRESS);
    if (returnAddress == VMProgram.BUILTIN_FUNCTION_ADDRESS) {
      staticSegment.setEnabledRange(0, -1, true); // empty static segment
      builtInFunctionsRunner.returnToBuiltInFunction(popValue(METHOD_STACK));
    } else if (returnAddress >= 0 && returnAddress < program.getSize()) {
      // sets the static segment range
      if (stackFrames.size() > 0) {
        setStaticRange(callStack.getTopFunction());
      } else {
        staticSegment.setStartAddress(Definitions.VAR_START_ADDRESS);
        staticSegment.setEnabledRange(
            Definitions.VAR_START_ADDRESS, Definitions.VAR_END_ADDRESS - 1, true);
      }
      program.setPC((short) (returnAddress - 1)); // set previousPC correctly
      program.setPC(returnAddress); // pc = *sp
    } else {
      error("Illegal return address");
    }
  }