package toy;

/**
 * ToyVirtualMachine is an object that encapsulates the concept of the Toy
 * Machine.  This includes a program counter, set of registers, and a memory
 * array.
 *
 * @author Brian Tsang
 * @version 7.0
 */

public class ToyVirtualMachine
{
    /**
     * The text that is displayed in the reference box during editing and
     * execution.
     */
    public static final String REFERENCE_STRING =
            "INSTRUCTION FORMATS\n" +
            "\n" +
            "             | . . . . | . . . . | . . . . | . . . .|\n" +
            "  Format 1:  | opcode  |    d    |    s    |    t   |\n" +
            "  Format 2:  | opcode  |    d    |       addr       |\n" +
            "\n" +
            "\n" +
            "ARITHMETIC and LOGICAL operations\n" +
            "    1: add              R[d] <- R[s] + R[t]\n" +
            "    2: subtract         R[d] <- R[s] - R[t]\n" +
            "    3: and              R[d] <- R[s] & R[t]\n" +
            "    4: xor              R[d] <- R[s] ^ R[t]\n" +
            "    5: shift left       R[d] <- R[s] << R[t]\n" +
            "    6: shift right      R[d] <- R[s] >> R[t]\n" +
            "\n" +
            "TRANSFER between registers and memory\n" +
            "    7: load address     R[d] <- addr\n" +
            "    8: load             R[d] <- mem[addr]\n" +
            "    9: store            mem[addr] <- R[d]\n" +
            "    A: load indirect    R[d] <- mem[R[t]]\n" +
            "    B: store indirect   mem[R[t]] <- R[d]\n" +
            "\n" +
            "CONTROL\n" +
            "    0: halt             halt\n" +
            "    C: branch zero      if (R[d] == 0) pc <- addr\n" +
            "    D: branch positive  if (R[d] > 0) pc <- addr\n" +
            "    E: jump register    pc <- R[d]\n" +
            "    F: jump and link    R[d] <- pc; pc <- addr\n" +
            "\n" +
            "\n" +
            "R0 always reads 0.\n" +
            "Loads from mem[FF] come from stdin.\n" +
            "Stores to mem[FF] go to stdout.\n";

    /**
     * The size of the memory array.
     * @see #mem
     */
    public static final int MEM_CARDINALITY = 0x100;


    /**
     * The number of registeres.
     * @see #register
     */
    public static final int REGISTER_CARDINALITY = 0x10;


    /**
     * The program counter.
     */
    private short programCtr;
    /**
     * The memory array.
     */
    private ToyWord mem[];
    /**
     * The register array.
     */
    private ToyWord register[];

    /**
     * The standard input stream.
     */
    private ToyWord stdin[];
        /**
         * The index of the next word to be read from stdin.
         * @see #stdin
         */
        private int stdinCtr;
    /**
     * The standard output stream.
     */
    private ToyWord stdout[];
    /**
     * The standard error stream.  This is not a ToyWord array because error
     * messages have to be descriptive (and the real ToyMachine doesn't have
     * a stderr anyway.
     */
    private String stderr;

    /**
     * A boolean to indicate wheter or not the virtual machine is "Done".  This
     * boolean gets set to true when the halt command is executed.
     */
    private boolean isDone;
    /**
     * A boolean to indicate that the stdin buffer is empty and the program just
     * read from stdin.
     */
    private boolean requiresInput;

    /**
     * Constructs a completely blank ToyVirtualMachine.
     */
    public ToyVirtualMachine()
    {
        stdin = new ToyWord[0];
        reset();
    }

    /**
     * Returns true iff an error has been encountered.
     * @return true iff an error has been encountered.
     */
    public boolean hasEncounteredError()
    {
        return stderr.length() > 0;
    }

    /**
     * Returns true iff the Virtual Machine has executed a halt statement.
     * @return true iff the Virtual Machine has executed a halt statement.
     */
    public boolean isDone()
    {
        return isDone;
    }

    /**
     * Returns true iff the stdin buffer is empty and the VM has an executed
     * a command to read from stdin.
     * @return true iff the stdin buffer is empty and the VM has an executed
     * a command to read from stdin.
     */
    public boolean requiresInput()
    {
        return requiresInput;
    }

    /**
     * Returns the program counter.
     * @return the program counter.
     */
    public short getProgramCtr()
    {
        return programCtr;
    }

    /**
     * Change the programCtr.
     * @newProgramCtr the new value of programCtr.
     */
    public void setProgramCtr(short newProgramCtr)
    {
        programCtr = (short)(newProgramCtr & 0xFF);

        if (isDone && (mem[programCtr].getValue() >> 12) != 0)
            isDone = false;
    }

    /**
     * Get the value of a particular register.
     * @param index the index of the requested register.
     * @return the value of the requested register; null if index is invalid.
     */
    public ToyWord getRegister(int index)
    {
        if (index >= 0 && index < REGISTER_CARDINALITY)
            return register[index];
        else
            return null;
    }

    /**
     * Sets the value of a particular register.
     * @param index the index of the register to be changed.
     * @param newRegister the value of the new register.
     */
    public void setRegister(int index, ToyWord newRegister)
    {
        if (index >= 0 && index < MEM_CARDINALITY && newRegister != null)
            register[index] = newRegister;
    }

    /**
     * Equivalent to getMem(getProgramCtr()).
     * @return the section of memory that the program counter points to.
     * @see #getMem(int)
     * @see #getProgramCtr()
     */
    public ToyWord getCurrentMem()
    {
        return getMem(programCtr);
    }

    /**
     * Returns the value at the requested memory sector.
     * @param index the address of the requested memory sector.
     * @return the value at the requested memory sector, 0 if null.
     */
    public ToyWord getMem(int index)
    {
        if (index >= 0 && index < MEM_CARDINALITY)
            return mem[index];
        else
            return null;
    }

    /**
     * Sets the value of the specified memory sector.
     * @param index the address of the specified memory sector.
     * @param newMem the new value of the memory sector.
     */
    public void setMem(int index, ToyWord newMem)
    {
        if (index >= 0 && index < MEM_CARDINALITY && newMem != null)
            mem[index] = newMem;
    }

    /**
     * Returns a *copy* of the standard input stream.
     * @return a *copy* of the standard input stream.
     */
    public ToyWord[] getStdin()
    {
        ToyWord answer[] = new ToyWord[stdin.length];

        for (int ctr = 0; ctr < stdin.length; ctr++)
            answer[ctr] = stdin[ctr];

        return answer;
    }

    /**
     * Sets the standard input stream to the specified value.
     * @param newStdin the new standard input stream.  If null, stdin will be
     * an empty stream.
     */
    public void setStdin(ToyWord newStdin[])
    {
        if (newStdin == null)
            stdin = new ToyWord[0];
        else
            stdin = newStdin;

        if (stdinCtr > stdin.length)
            stdinCtr = stdin.length;

        if (requiresInput && stdinCtr < stdin.length)
            requiresInput = false;
    }

    /**
     * Returns the index of the next ToyWord to be read from stdin.
     * @return the index of the next ToyWord to be read from stdin.
     */
    public int getStdinCtr()
    {
        return stdinCtr;
    }

    /**
     * Returns a *copy* of the standard output stream.
     * @return a *copy* of the standard output stream.
     */
    public ToyWord[] getStdout()
    {
        ToyWord answer[] = new ToyWord[stdout.length];

        for (int ctr = 0; ctr < stdout.length; ctr++)
            answer[ctr] = stdout[ctr];

        return answer;
    }

    /**
     * Returns the standard error stream.
     * @return the standard error stream.
     */
    public String getStderr()
    {
        return stderr;
    }

    /**
     * Returns a core dump very similar to the output of the Toy compiler.
     * @param uninintializedErrorEnabled If true, uninitialized memory sectors
     * and registers will show ???? instead of 0000.
     * @return a core dump very similar to the output of the Toy compiler.
     * @see #getDetailedCoreDump(boolean)
     */
    public String getCoreDump(boolean uninitializedErrorEnabled)
    {
        String answer = "";
        boolean printedPreviousLine = true;

        //dump the programCtr
        answer += "pc: " + ToyWord.toHexString(programCtr) + "\n";

        //dump registers 0 - 7
        answer += "R:  ";
        for (int ctr = 0x0; ctr < 0x8; ctr++)
            answer += register[ctr].toHexString(uninitializedErrorEnabled) + " ";
        answer += "\n";

        //dump registers 8 - F
        answer += "R:  ";
        for (int ctr = 0x8; ctr < 0x10; ctr++)
            answer += register[ctr].toHexString(uninitializedErrorEnabled) + " ";
        answer += "\n\n";

        //dump the low memory areas no matter what
        for (int ctr = 0; ctr < 2; ctr++)
        {
            answer += ToyWord.toHexString((short)(ctr * 8)).substring(2) + ": ";
            for (int ctr2 = 0; ctr2 < 8; ctr2++)
                answer += mem[ctr2 + 8 * ctr].toHexString(uninitializedErrorEnabled) + " ";
            answer += "\n";
        }

        //dump the high memory areas, use an ellipsis (...) whenever necessary
        for (int ctr = 2; ctr < 32; ctr++)
        {
            if (mem[8 * ctr + 0].isDefined() || mem[8 * ctr + 1].isDefined() ||
                mem[8 * ctr + 2].isDefined() || mem[8 * ctr + 3].isDefined() ||
                mem[8 * ctr + 4].isDefined() || mem[8 * ctr + 5].isDefined() ||
                mem[8 * ctr + 6].isDefined() || mem[8 * ctr + 7].isDefined())
            {
                answer += ToyWord.toHexString((short)(ctr * 8)).substring(2) + ": ";
                for (int ctr2 = 0; ctr2 < 8; ctr2++)
                    answer += mem[ctr2 + 8 * ctr].toHexString(uninitializedErrorEnabled) + " ";
                answer += "\n";
                printedPreviousLine = true;
            }
            else
            {
                if (printedPreviousLine)
                {
                    answer += "...\n";
                    printedPreviousLine = false;
                }
            }
        }

        return answer;
    }

    /**
     * Returns a more detailed core dump of the virtual machine where binary
     * and decimal translations of the hexidecimal values are also given.
     * @param uninintializedErrorEnabled If true, uninitialized memory sectors
     * and registers will show ???? instead of 0000.
     * @return a more detailed core dump of the virtual machine where binary
     * and decimal translations of the hexidecimal values are also given.
     * @see #getCoreDump(boolean)
     */
    public String getDetailedCoreDump(boolean uninitializedErrorEnabled)
    {
        String answer = "";
        boolean printedPreviousLine = true;

        //dump the programCtr
        answer += "pc:      " + ToyWord.toString(programCtr) + "\n\n";

        //dump registers
        for (short ctr = 0x0; ctr < 0x10; ctr++)
            answer += "R[" + ToyWord.convertToHexDigit(ctr) + "]:    " +
                register[ctr].toString(uninitializedErrorEnabled) + "\n";

        answer += "\n";

        //dump mem, use an ellipsis (...) whenever necessary
        for (short ctr = 0x0; ctr < 0x100; ctr++)
        {
            if (mem[ctr].isDefined())
            {
                answer += "mem[" + ToyWord.toHexString(ctr).substring(2) + "]: " +
                    mem[ctr].toString(uninitializedErrorEnabled) + "\n";
                printedPreviousLine = true;
            }
            else {
                if (printedPreviousLine)
                {
                    answer += "...\n";
                    printedPreviousLine = false;
                }
            }
        }

        return answer;
    }

    /**
     * Wipes out the memory and registers, resets the programCtr to 0x10, and
     * flushes the streams.
     * @see toy.ToyWorkspace#reset()
     */
    public void reset()
    {
        programCtr = 0x10;

        mem = new ToyWord[MEM_CARDINALITY];
        for (int ctr = 0; ctr < MEM_CARDINALITY; ctr++)
            mem[ctr] = ToyWord.UNDEFINED;

        register = new ToyWord[REGISTER_CARDINALITY];
        register[0] = new ToyWord((short)0);
        for (int ctr = 1; ctr < REGISTER_CARDINALITY; ctr++)
            register[ctr] = ToyWord.UNDEFINED;

        stdin = new ToyWord[0];
        stdinCtr = 0;

        stdout = new ToyWord[0];

        stderr = "";

        isDone = false;
        requiresInput = false;
    }

    /**
     * Executes the command which programCtr points to and increments programCtr
     * if necessary.
     * @param uninitializedErrorEnabled generate an error when an uninitialized
     * memory sector/register is used (as opposed to assuming that everything is
     * initialized to 0000).
     * @param overflowErrorEnabled generate an error when an arithmatic
     * operation results in a number not expressible as a 16-bit two's
     * complement integer (as opposed to cropping off the extra bits).
     * @param outOfBoundsErrorEnabled generate an error when a value in an
     * operation is not valid (as opposed to cropping off the extra bits).
     * Examples of this include attempting to access mem[32BF] by a load
     * indirect call, attempting to assign a value to R[0] by an addition
     * operation, attempting to shift a value by 02AB bits in a left-shift
     * operation, etc...
     */
    public void step(boolean uninitializedErrorEnabled,
                     boolean overflowErrorEnabled,
                     boolean outOfBoundsErrorEnabled)
    {
        short op, rd, rs, rt, addr;

        short oldProgramCtr = programCtr;

        //The step function is big on checking for errors because we want to
        //build a resilient compiler

        try
        {
            //Make sure that the address is defined
            if (!mem[programCtr].isDefined() && uninitializedErrorEnabled)
                throw new ToyException(ToyException.COMMAND_UNINITIALIZED);

            //fetch and parse
            op = (short)((mem[programCtr].getValue() & 0xF000) >> 12);
            rd = (short)((mem[programCtr].getValue() & 0x0F00) >> 8);
            rs = (short)((mem[programCtr].getValue() & 0x00F0) >> 4);
            rt = (short)( mem[programCtr].getValue() & 0x000F);
            addr = (short)(0x10 * rs + rt);

            //increment
            programCtr++;

            //stdin has been moved inside the switch


            //execute (and handle stdin and stdout)
            switch (op)
            {
                //add
                //R[d] <- R[s] + R[t]
                case 0x1:
                    //we could be trying to assign a value to R[0], but don't
                    //throw an error when our command is 1000
                    if (rd == 0 && !(rs == 0 && rt == 0) && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //put R[s] + R[t] in R[d]
                    register[rd] = ToyWord.add(
                        register[rs],
                        register[rt],
                        uninitializedErrorEnabled,
                        overflowErrorEnabled
                        );
                    break;

                //subtract
                //R[d] <- R[s] - R[t]
                case 0x2:
                    //we could be trying to assign a value to R[0]
                    if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //put R[s] - R[t] in R[d]
                    register[rd] = ToyWord.subtract(
                        register[rs],
                        register[rt],
                        uninitializedErrorEnabled,
                        overflowErrorEnabled
                        );
                    break;

                //and
                //R[d] <- R[s] & R[t]
                case 0x3:
                    //we could be trying to assign a value to R[0]
                    if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //put R[s] & R[t] in R[d]
                    register[rd] = ToyWord.and(
                        register[rs],
                        register[rt],
                        uninitializedErrorEnabled
                        );
                    break;

                //xor
                //R[d] <- R[s] ^ R[t]
                case 0x4:
                    //we could be trying to assign a value to R[0]
                    if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //put R[s] ^ R[t] in R[d]
                    register[rd] = ToyWord.xor(
                        register[rs],
                        register[rt],
                        uninitializedErrorEnabled
                        );
                    break;

                //shift left
                //R[d] <- R[s] << t
                case 0x5:
                    //we could be trying to assign a value to R[0]
                    if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //put R[s] << t in R[d]
                    register[rd] = ToyWord.leftShift(
                        register[rs],
                        register[rt],
                        uninitializedErrorEnabled,
                        overflowErrorEnabled
                        );
                    break;

                //shift right
                //R[d] <- R[s] >> t
                case 0x6:
                    //we could be trying to assign a value to R[0]
                    if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //put R[s] >> t in R[d]
                    register[rd] = ToyWord.rightShift(
                        register[rs],
                        register[rt],
                        uninitializedErrorEnabled,
                        overflowErrorEnabled
                        );
                    break;

                //load const
                //R[d] <- addr
                case 0x7:
                    //we could be trying to assign a value to R[0]
                    if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //put addr in R[d]
                    register[rd] = new ToyWord(addr);
                    break;

                //load
                //R[d] <- mem[addr]
                case 0x8:
                    //we could be trying to assign a value to R[0]
                    if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //are we reading from stdin?
                    if (addr == 0xFF)
                    {
                        if (stdinCtr < stdin.length)
                        {
                            //otherwise put the value of stdin into mem[FF]
                            mem[0xFF] = stdin[stdinCtr];
                            stdinCtr++;
                        }
                        else {
                            //if the buffer is too low then
                            //flag the input required field
                            requiresInput = true;

                            //and restore the program ctr
                            programCtr = oldProgramCtr;
                            return;
                        }
                    }
                    //put mem[addr] in R[d]
                    if (!mem[addr].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);
                    register[rd] = mem[addr];

                    break;

                //store
                //mem[addr] <- R[d]
                case 0x9:
                    //put R[d] in mem[addr]
                    if (!register[rd].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);
                    mem[addr] = register[rd];

                    //are we writing to stdout?
                    if (addr == 0xFF)
                    {
                        //append the value of mem[FF] to the stdout array
                        ToyWord oldStdout[] = stdout;
                        stdout = new ToyWord[stdout.length + 1];

                        for (int ctr = 0; ctr < oldStdout.length; ctr++)
                            stdout[ctr] = oldStdout[ctr];

                        stdout[stdout.length - 1] = mem[0xFF];
                    }
                    break;

                //load indirect
                //R[d] <- mem[R[t]]
                case 0xA:
                    //we could be trying to assign a value to R[0]
                    if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //we could be trying to access an undefined R[t]
                    if (!register[rt].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

                    //our R[t] could be out of bounds
                    if ((register[rt].getValue() < 0 ||
                        register[rt].getValue() > MEM_CARDINALITY) &&
                        outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.MEM_OUT_OF_BOUNDS);

                    //are we reading from stdin?
                    if ((register[rt].getValue() & 0xFF) == 0xFF)
                    {
                        if (stdinCtr < stdin.length)
                        {
                            //otherwise put the value of stdin into mem[FF]
                            mem[0xFF] = stdin[stdinCtr];
                            stdinCtr++;
                        }
                        else {
                            //if the buffer is too low then
                            //flag the input required field
                            requiresInput = true;

                            //and restore the program ctr
                            programCtr = oldProgramCtr;
                            return;
                        }
                    }

                    //ok now put the value of mem[R[t]] into R[d]
                    if (!mem[register[rt].getValue() & 0xFF].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);
                    register[rd] = mem[register[rt].getValue() & 0xFF];

                    break;

                //store indirect
                //mem[R[t]] <- R[d]
                case 0xB:
                    //we could be trying to access an undefined R[t]
                    if (!register[rt].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

                    //our R[t] could be out of bounds
                    if ((register[rt].getValue() < 0 ||
                        register[rt].getValue() > MEM_CARDINALITY) &&
                        outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.MEM_OUT_OF_BOUNDS);

                    //put R[d] into mem[R[t]]
                    if (!register[rd].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);
                    mem[register[rt].getValue() & 0xFF] = register[rd];

                    //are we writing to stdout?
                    if ((register[rt].getValue() & 0xFF) == 0xFF)
                    {
                        //append the value of mem[FF] to the stdout array
                        ToyWord oldStdout[] = stdout;
                        stdout = new ToyWord[stdout.length + 1];

                        for (int ctr = 0; ctr < oldStdout.length; ctr++)
                            stdout[ctr] = oldStdout[ctr];

                        stdout[stdout.length - 1] = mem[0xFF];
                    }
                    break;

                //branch zero
                //if (R[d] == 0) pc <- addr
                case 0xC:
                    //we could be testing an uninitialized R[d]
                    if (!register[rd].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

                    //ok jump if R[d] is zero
                    if (register[rd].getValue() == 0)
                        programCtr = addr;
                    break;

                //branch positive
                //if (R[d] > 0) pc <- addr
                case 0xD:
                    //we could be testing an uninitialized R[d]
                    if (!register[rd].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

                    //ok jump if R[d] is positive
                    if (register[rd].getValue() > 0)
                        programCtr = addr;
                    break;

                //jump register
                //pc <- R[d]
                case 0xE:
                    //we could be using an uninitialized R[d]
                    if (!register[rd].isDefined() && uninitializedErrorEnabled)
                        throw new ToyException(ToyException.VARIABLE_UNINITIALIZED);

                    //(we'll test for an invalid pc after this)

                    //ok jump to R[d]
                    programCtr = register[rd].getValue();
                    break;

                //jump and link
                //R[d] <- pc; pc <- addr
                case 0xF:
                    //we could be trying to assign a value to R[0]
                   if (rd == 0 && outOfBoundsErrorEnabled)
                        throw new ToyException(ToyException.REGISTER_OUT_OF_BOUNDS);

                    //ok jump to addr and like R[d]
                    register[rd] = new ToyWord(programCtr);
                    programCtr = addr;
                    break;

                //halt
                case 0:
                    isDone = true;
                    //move the programCtr back
                    programCtr--;
                    break;
            }

            //stdout has been moved inside the switch

            //restore the zeroth register to 0
            if (register[0].getValue() != 0)
                register[0] = ToyWord.parseDecimal(0);

            //check the pc
            if (programCtr > MEM_CARDINALITY)
            {
                if (outOfBoundsErrorEnabled)
                    throw new ToyException(ToyException.PC_OUT_OF_BOUNDS);
                else
                    programCtr = (short)(programCtr & 0xFF);
            }
        }
        catch (Exception e)
        {
            programCtr = oldProgramCtr;

            if (e instanceof ToyException)
            {
                switch (((ToyException)e).getType())
                {
                    case ToyException.COMMAND_UNINITIALIZED:
                        stderr = "A runtime error has occurred at line "
                               + ToyWord.toHexString(programCtr).substring(2,4)
                               +                                        ":                                        \n"
                               + "The line has not been defined, so it cannot be executed (Uninitialized Variable \n"
                               + "Error).                                                                         \n";
                        break;

                    case ToyException.VARIABLE_UNINITIALIZED:
                        stderr = "A runtime error has occurred at line "
                               + ToyWord.toHexString(programCtr).substring(2,4)
                               +                                        ":                                        \n"
                               + "The execution cycle attempted to use an undefined register or memory location   \n"
                               + "(Uninitialized Variable Error).                                                 \n";
                        break;

                    case ToyException.OVERFLOW:
                        stderr = "A runtime error has occurred at line "
                               + ToyWord.toHexString(programCtr).substring(2,4)
                               +                                        ":                                        \n"
                               + "The resulting value of the operation was not between between -32768 and 32767   \n"
                               + "(Overflow Error).                                                               \n";
                        break;

                    case ToyException.SHIFT_OUT_OF_BOUNDS:
                        stderr = "A runtime error has occurred at line "
                               + ToyWord.toHexString(programCtr).substring(2,4)
                               +                                        ":                                        \n"
                               + "The shift operator was used with an invalid shift magnitude; shift magnitudes   \n"
                               + "may only range between 0000 and 000F (Out of Bounds Error).                     \n";
                        break;

                    case ToyException.PC_OUT_OF_BOUNDS:
                        stderr = "A runtime error has occurred at line "
                               + ToyWord.toHexString(programCtr).substring(2,4)
                               +                                        ":                                        \n"
                               + "The execution or fetch-and-increment cycle attempted to assign an invalid value \n"
                               + "to the pc (program counter); memory locations can only range from 00 to FF (Out \n"
                               + "of Bounds Error).                                                               \n";
                        break;

                    case ToyException.REGISTER_OUT_OF_BOUNDS:
                        stderr = "A runtime error has occurred at line "
                               + ToyWord.toHexString(programCtr).substring(2,4)
                               +                                        ":                                        \n"
                               + "The execution cycle attempted to change the zeroth register, which is a constant\n"
                               + "(Out of Bounds Error).                                                          \n";
                        break;

                    case ToyException.MEM_OUT_OF_BOUNDS:
                        stderr = "A runtime error has occurred at line "
                               + ToyWord.toHexString(programCtr).substring(2,4)
                               +                                        ":                                        \n"
                               + "The execution cycle attempted to access an invalid memory location (Out of      \n"
                               + "Bounds Error).                                                                  \n";
                        break;

                    default:
                        stderr = "An unhandled runtime error has occurred:                                        \n"
                               + "This compiler does not understand what went wrong.  Please email your program   \n"
                               + "to btsang@princeton.edu so we can fix this problem.                             \n";
                        e.printStackTrace();
                }
            }
            else {
                stderr = "An unhandled runtime error has occurred:                                        \n"
                       + "This compiler does not understand what went wrong.  Please email your program   \n"
                       + "to btsang@princeton.edu so we can fix this problem.                             \n";
                e.printStackTrace();
            }

            isDone = true;
        }
    }
}
