#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

/* Constants */
#define MAX_LABEL_LENGTH 20
#define MAX_SYMBOL_COUNT 255
#define MAX_LINE_LENGTH 255
#define NUM_OPCODE 30 /*was 28 */
#define PSEUDO_OP_COUNT 3
#define numReg 8
#define ERROR -1
#define VALID 1
#define INT_MAX 2147483647
#define IMD5 0x001F
#define IMDBIT 0x0020
#define OFF6 0x003F
#define OFF9 0x01FF
#define OFF11 0x07FF
#define TRAP8 0x00FF

/* Opcode Ind (for switch) */
#define ADD    0
#define AND    1
#define BRN    2
#define BRZ    3
#define BRP    4
#define BRNZ   5
#define BRNP   6
#define BRZP   7
#define BR     8
#define BRNZP  9
#define HALT  10
#define JMP   11
#define JSR   12
#define JSRR  13
#define LDB   14
#define LDW   15
#define LEA   16
#define NOP   17
#define NOT   18
#define RET   19
#define LSHF  20
#define RSHFL 21
#define RSHFA 22
#define RTI   23
#define STB   24
#define STW   25
#define TRAP  26
#define XOR   27
#define END   28
#define FILL  29

static const char *opCodes[NUM_OPCODE] = {
    "add",
    "and",
    "brn",
    "brz",
    "brp",
    "brnz",
    "brnp",
    "brzp",
    "br",
    "brnzp",
    "halt",
    "jmp",
    "jsr",
    "jsrr",
    "ldb",
    "ldw",
    "lea",
    "nop",
    "not",
    "ret",
    "lshf",
    "rshfl",
    "rshfa",
    "rti",
    "stb",
    "stw",
    "trap",
    "xor",
    ".end",
    ".fill"
};

static const char pseudo_op_codes[PSEUDO_OP_COUNT][8] ={
    ".orig",
    ".end",
    ".fill"
};

static const char *registers[numReg] = {"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7"};

enum {
    DONE, OK, EMPTY_LINE
};

typedef struct {
    int address;
    char label[MAX_LABEL_LENGTH + 1];
} TableEntry;

/* Global Variables */
short numLabels = 0;
TableEntry symbolTable[MAX_SYMBOL_COUNT];
short programDone = 0;
unsigned short currPC = 0;
int startAddr = -1; /* -1 as in False */

/* Prototypes */
int toNum(char *pStr);
int readAndParse(FILE *pInFile, char * pLine, char ** pLabel, char ** pOpcode,
        char ** pArg1, char ** pArg2, char ** pArg3, char ** pArg4);
int isOpcode(char *lStr);
void writeOut(char *filename);
int isLabel(char *label);
unsigned short asmOpCode(char * plabel, char * pOpcode, char * pArg1,
        char * pArg2, char * pArg3, char * pArg4);

int passOne(FILE *infile, FILE *outfile, char * pLine,
        char ** pLabel, char ** pOpcode, char ** pArg1, char ** pArg2,
        char ** pArg3, char ** pArg4);
int passTwo(FILE *infile, FILE *outfile, char * pLine,
        char ** pLabel, char ** pOpcode, char ** pArg1, char ** pArg2,
        char ** pArg3, char ** pArg4);
int getLabelAddr(char *pLabel);
void assignReg(char *lArg1, char *lArg2, char *lArg3, unsigned short *aReg1, unsigned short *aReg2, unsigned short *aReg3);
short getImmediate(char *lArg);

/*
 *  MAIN Loop
 */
int main(int argc, char ** argv) {
    /* Declare Variables */
    char lLine[MAX_LINE_LENGTH + 1], *lLabel, *lOpcode, *lArg1, *lArg2, *lArg3, *lArg4;
    int lRet, i;
    int retval = 0;

    FILE* inFile = NULL;
    FILE* outFile = NULL;
    /* Variables for readAndParse */
    /* argc must be 3 for correct execution */
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <source.asm> <output.obj>\n", argv[0]);
        retval = 4;
        exit(retval);
    }

    /* Open file */
    inFile = fopen(argv[1], "r");
    if (!inFile) {
        fprintf(stderr, "Error: Cannot open input file %s\n", argv[1]);
        retval = 4;
        exit(retval);
    }

    /* Try and open the output file  */
    outFile = fopen(argv[2], "w");
    if (!outFile) {
        fprintf(stderr, "Error: Cannot open output file %s\n", argv[2]);
        retval = 4;
        exit(retval);
    }


    passOne(inFile, outFile, lLine, &lLabel, &lOpcode, &lArg1, &lArg2, &lArg3, &lArg4);
    if (retval) {
        exit(retval);
    }

    /* rewind input file after first pass */
    if (fseek(inFile, 0, SEEK_SET)) {
        retval = 4;
        exit(retval);
    }

    retval = passTwo(inFile, outFile, lLine, &lLabel, &lOpcode, &lArg1, &lArg2, &lArg3, &lArg4);
    if (retval) {
        exit(retval);
    }

    fclose(inFile);
    fclose(outFile);

    return (retval);
}

/*
//////////////////////////////////////////////////////////////////////////
//  Create Symbol Table in Pass 1
//////////////////////////////////////////////////////////////////////////
 */

int passOne(FILE * inFile, FILE *outFile, char * pLine, char ** pLabel, char ** pOpcode, char ** pArg1, char ** pArg2, char ** pArg3, char ** pArg4) {
    int lRet;
    int currAddr;
    int endReached = 0; /*used to make sure a .END was found */
    /* Get rid of anything before .orig, or if we've traversed the whole file */
    do {
        lRet = readAndParse(inFile, pLine, pLabel, pOpcode, pArg1, pArg2, pArg3, pArg4);
    } while ((strcmp(*pOpcode, ".orig") != 0) && (lRet != DONE));

    /* if no .orig, we can't compile, throw error */
    if (lRet == DONE) {
        exit(4);
    }

    /*Get .ORIG addr, strcmp is just for procaution, its not needed */
    if (strcmp(*pOpcode, ".orig") == 0) {
        /*Check if the addr is set */
        if (strcmp(*pArg2, "") != 0) {
            exit(4);
        } else {
            startAddr = toNum(*pArg1);
            currAddr = startAddr; /*Used to keep track of each line */
            /* Throw error ifstart Addr is odd, or outside of [0x3000x 0xFDFF] */
            if (startAddr % 2 != 0 || startAddr > 65023 || startAddr < 3000) {
                exit(3);
            } else {
                fprintf(outFile, "0x%.4X", startAddr);
            }
        }
    } else {
        exit(4);
    } /*if .orig is not given, our while loop above messed up, ERROR */

    /* create symbol table */
    do {
        /* Read next line */
        lRet = readAndParse(inFile, pLine, pLabel, pOpcode, pArg1, pArg2, pArg3, pArg4);
        /* Check for errors/end */
        if (strcmp(*pOpcode, ".orig") == 0) {
            exit(4);
        } /* We already saw .orig, ERROR */
        if (strcmp(*pOpcode, ".end") == 0) {
            lRet = DONE;
            endReached = 1;
        } /* End of file reached */

        /* Parse ASM */
        if (lRet != DONE && lRet != EMPTY_LINE) {
            /* See if we have a label */
            if (strcmp(*pLabel, "") != 0) {
                if (!validLabel(*pLabel)) {
                    exit(4); /* Error with Label */
                } else {
                    strcpy(symbolTable[numLabels].label, *pLabel);
                    symbolTable[numLabels].address = currAddr;
                    numLabels++;
                }
            }
            /* Increment Address as for all non comment/empty lines */
            currAddr += 0x02;
        }
    } while (lRet != DONE);
    /* Check if there was a .end */
    if (!endReached) {
        exit(4);
    }
    return (0); /*Done Pre Processing */
}

/*
//////////////////////////////////////////////////////////////////////////
//  Generate Machine Instructions in Pass2
//////////////////////////////////////////////////////////////////////////
 */
int passTwo(FILE *inFile, FILE *outFile, char *pLine, char **pLabel,
        char **pOpcode, char **pArg1, char **pArg2,
        char **pArg3, char **pArg4) {
    int lRet;
    unsigned short hex;
    currPC = startAddr;
    /* Ignore all until .orig (or end of file) is found */
    do {
        lRet = readAndParse(inFile, pLine, pLabel, pOpcode, pArg1, pArg2, pArg3, pArg4);
    } while ((strcmp(*pOpcode, ".orig") != 0) && (lRet != DONE));

    /* Catch error if no .orig */
    if (lRet == DONE) {
        exit(4);
    }
    /* .origin was just 'found', we already validated it in pass1.
     * Read next line, and start processing */

    do {
        lRet = readAndParse(inFile, pLine, pLabel, pOpcode, pArg1, pArg2, pArg3, pArg4);
        if (lRet != DONE && lRet != EMPTY_LINE) {
            hex = asmOpCode(*pLabel, *pOpcode, *pArg1, *pArg2, *pArg3, *pArg4);
            /* Output the assembled hex to our file, if we haven't reached .end */
            if (!programDone) {
                fprintf(outFile, "\n0x%.4X", hex);
                currPC += 0x02;
            }
        }
    } while (!programDone);

    return (0); /* Done assembling, HAZA! */
}

/*
//////////////////////////////////////////////////////////////////////////
//  Write helper functions here (keep code modular)
//////////////////////////////////////////////////////////////////////////
 */
int getLabelAddr(char *pLabel) {
    int i;
    for (i = 0; i < numLabels; i++) {
        if (strcmp(pLabel, symbolTable[i].label) == 0) {
            return symbolTable[i].address;
        }
    }
    return ERROR;
}

/* checkReg
 *  -Converts String of Reg argument ---> a number
 *    Saves output by use of pointers
 */
void assignReg(char *lArg1, char *lArg2, char *lArg3, unsigned short *aReg1, unsigned short *aReg2, unsigned short *aReg3) {
    int i;
    for (i = 0; i < numReg; i++) {
        if (strcmp(lArg1, registers[i]) == 0) {
            *aReg1 = i;
        }
        if (strcmp(lArg2, registers[i]) == 0) {
            *aReg2 = i;
        }
        if (strcmp(lArg3, registers[i]) == 0) {
            *aReg3 = i;
        }
    }
}

/* getImmediate
 *  -Parses out the number from an immediate Argument, returns the val
 */
short getImmediate(char *lArg) {
    /* Parse out number, or toNum will throw error */
    int imdValue = toNum(lArg);
    /*Throw error for out of bounds imdValue */
    if (imdValue < -16 || imdValue > 15) {
        exit(3);
    }
    return imdValue;
}

/* asmOpCode
 *  - Takes in an instruction, and outputs hex
 */
unsigned short asmOpCode(char * pLabel, char * lOpcode, char * lArg1, char * lArg2, char * lArg3, char * lArg4) {
    int i, opKey, labelAddr;
    unsigned short aReg1 = numReg, aReg2 = numReg, aReg3 = numReg; /* Set to numReg as its upper bound, use for error handling */
    unsigned short hexCode, iBits;
    short imdValue, pcOff, n6Off;
    opKey = isOpcode(lOpcode);
    if (opKey == ERROR) {
        exit(2); /* INVALID OPCODE */
    }
    /* Arg4 should be empty, if it isn't its an error 4 */
    if (strcmp(lArg4, "") != 0) {
        exit(4);
    }

    /* BUILD hexCode */
    switch (opKey) {
        case ADD:
            hexCode = 0x1000;
            /* Convert "R1"..."R7" --> 1...7 */
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            /* Check if manidtory regs are invalid */
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            }
            /* check if we were given an immediate, */
            if (aReg3 == numReg) {
                imdValue = getImmediate(lArg3);
                if (imdValue < -16 || imdValue > 15) {
                    exit(3);
                } /*OVERFLOW*/
                hexCode = (hexCode | IMDBIT | (imdValue & IMD5)) | (aReg1 << 9) | (aReg2 << 6);
            } else {
                return hexCode = (hexCode | aReg3) | (aReg1 << 9) | (aReg2 << 6);
            }
            break;
        case AND:
            hexCode = 0x5000;
            /* Convert "R1"..."R7" --> 1...7 */
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            }
            /* Deal with immediate */
            if (aReg3 == numReg) {
                imdValue = getImmediate(lArg3);
                if (imdValue < -16 || imdValue > 15) {
                    exit(3);
                } /*OVERFLOW*/
                return hexCode = (hexCode | IMDBIT | (imdValue & IMD5)) | (aReg1 << 9) | (aReg2 << 6);
            } else {
                return hexCode = (hexCode | aReg3) | (aReg1 << 9) | (aReg2 << 6);
            }
            break;
        case BRN:
            hexCode = 0x0800;
            /* Make sure only lArg1 is set, any others causes error */
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            /* Get lArg1 label's address */
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            } /*INVALID LABEL*/
            /* Calculate offset */
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            } /*Too great of an offset!!*/
            return hexCode = hexCode | (pcOff >> 1 & OFF9);
            break;
        case BRZ:
            hexCode = 0x0400;
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            } /*INVALID LABEL*/
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            }
            return hexCode = hexCode | (pcOff >> 1 & OFF9);
            break;
        case BRP:
            hexCode = 0x0200;
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            } /*INVALID LABEL*/
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            }
            return hexCode = hexCode | (pcOff >> 1 & OFF9);
        case BRNZ:
            hexCode = 0x0C00;
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            } /*INVALID LABEL*/
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            }
            return hexCode = hexCode | (pcOff >> 1 & OFF9);
            break;
        case BRNP:
            hexCode = 0x0A00;
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            } /*INVALID LABEL*/
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            }
            hexCode = hexCode | (pcOff >> 1 & OFF9);
            break;
        case BRZP:
            hexCode = 0x0200;
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            } /*INVALID LABEL*/
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            }
            return hexCode = hexCode | (pcOff >> 1 & OFF9);
            break;
        case BR:
            hexCode = 0x0E00;
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            } /*INVALID LABEL*/
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            }
            return hexCode = hexCode | (pcOff >> 1 & OFF9);
            break;
        case BRNZP:
            hexCode = 0x0E00;
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            } /*INVALID LABEL*/
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            }
            return hexCode = hexCode | (pcOff >> 1 & OFF9);
            break;
        case HALT:
            hexCode = 0xF025;
            /* Make sure all registers are empty, or throw error */
            if (strcmp(lArg1, "") != 0 | strcmp(lArg2, "") != 0 | strcmp(lArg3, "") != 0) {
                exit(4);
            }
            return hexCode;
            break;
        case JMP:
            hexCode = 0xC000;
            /* Make sure reg2,3 are empty */
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            /* Convert "R1"..."R7" --> 1...7 */
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg) {
                exit(4);
            }
            return hexCode = hexCode | (aReg1 << 6);
            break;
        case JSR:
            hexCode = 0x4800;
            /* Make sure reg2,3 are empty */
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            /* Get label, if invalid, throw error 1 */
            labelAddr = getLabelAddr(lArg1);
            if (labelAddr == ERROR) {
                exit(1);
            }
            /* Calc, pcOffset and check for overflow */
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -1024 || pcOff > 1023) {
                exit(4);
            }
            return hexCode = hexCode | (pcOff >> 1 & OFF11);
            break;
        case JSRR:
            hexCode = 0x4000;
            /* convert "R1"..."R7" --> 1...7 */
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            /* Check for invalid args */
            if (aReg1 == numReg || strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            return hexCode = hexCode | (aReg1 << 6);
            break;
        case LDB:
            hexCode = 0x2000;
            /* get immediate offset */
            n6Off = toNum(lArg3);
            if (n6Off<-32 || n6Off > 31) {
                exit(3);
            } /*Overflow check*/
            /* convert the register strings to ints. */
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            /* Check if reg1 and reg2 were set to a valid register */
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            }
            return hexCode = hexCode | (aReg1 << 9) | (aReg2 << 6) | (n6Off & OFF6);
            break;
        case LDW:
            hexCode = 0x6000;
            /* Get immediate */
            n6Off = toNum(lArg3);
            if (n6Off<-32 || n6Off > 31) {
                exit(3);
            } /*Overflow check*/
            /* Convert strings to registers #s*/
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            /* arg1 and arg2 must be valid registers */
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            }
            return hexCode = hexCode | (aReg1 << 9) | (aReg2 << 6) | (n6Off & OFF6);
            break;
        case LEA:
            hexCode = 0xE000;
            /* Check that arg3 is empty, or throw error */
            if (strcmp(lArg3, "") != 0) {
                exit(4);
            }
            /* Convert arg registers from str to int */
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg) {
                exit(4);
            } /*arg1 must be specified */
            labelAddr = getLabelAddr(lArg2);
            if (labelAddr == ERROR) {
                exit(1);
            } /*invalid label*/
            pcOff = (short) (labelAddr - (currPC + 2));
            if (pcOff < -256 || pcOff > 255) {
                exit(4);
            } /*overflow in command*/
            return hexCode = hexCode | (aReg1 << 9) | (pcOff >> 1 & OFF9);
            break;
        case NOP: /*Do nothing*/
            hexCode = 0x0000;
            /* No arguments should be defined */
            if (strcmp(lArg1, "") != 0, strcmp(lArg2, "") != 0, strcmp(lArg3, "") != 0) {
                exit(4);
            }
            return hexCode;
            break;
        case NOT:
            hexCode = 0x903F;
            if (strcmp(lArg3, "") != 0) {
                exit(4);
            } /* invalid arg*/
            /* convert register strings to ints */
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            } /*register invalid/not set*/
            return hexCode = hexCode | (aReg1 << 9) | (aReg2 << 6);
            break;
        case RET:
            hexCode = 0xC1C0;
            /*No arguments should be specified */
            if (strcmp(lArg1, "") != 0 || strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            return hexCode;
            break;
        case LSHF:
            hexCode = 0xD000;
            imdValue = toNum(lArg3);
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            } /*invalid register # */
            if (imdValue < 0 || imdValue > 15) {
                exit(3);
            } /*invalid imm. shift constant */
            return hexCode = hexCode | (aReg1 << 9) | (aReg2 << 6) | (imdValue & OFF6);
            break;
        case RSHFL:
            hexCode = 0xD000;
            imdValue = toNum(lArg3);
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            } /*invalid register # */
            if (imdValue < 0 || imdValue > 15) {
                exit(3);
            } /*invalid imm. shift constant */
            return hexCode = hexCode | (aReg1 << 9) | (aReg2 << 6) | 0x0010 | (imdValue & OFF6);
            break;
        case RSHFA:
            hexCode = 0xD000;
            imdValue = toNum(lArg3);
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            } /*invalid register # */
            if (imdValue < 0 || imdValue > 15) {
                exit(3);
            } /*invalid imm. shift constant */
            return hexCode = hexCode | (aReg1 << 9) | (aReg2 << 6) | 0x0030 | (imdValue & OFF6);
            break;
        case RTI:
            hexCode = 0x8000;
            if (strcmp(lArg1, "") != 0 || strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            }
            return hexCode;
            break;
        case STB:
            hexCode = 0x3000;
            imdValue = toNum(lArg3);
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            } /*invalid register # */
            if (imdValue < -32 || imdValue > 31) {
                exit(3);
            } /* imdValue overflow! */
            return hexCode = hexCode | (aReg1 << 9) | (aReg2 << 6) | (imdValue & OFF6);
            break;
        case STW:
            hexCode = 0x7000;
            imdValue = toNum(lArg3);
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            } /*invalid register # */
            if (imdValue < -32 || imdValue > 31) {
                exit(3);
            } /* imdValue overflow */
            return hexCode = hexCode | (aReg1 << 9) | (aReg2 << 6) | (imdValue & OFF6);
            break;
        case TRAP:
            hexCode = 0xF000;
            imdValue = toNum(lArg1);
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            } /*Should have no args 2,3 */
            if (imdValue < 0 || imdValue > 255) {
                exit(3);
            } /*overflow */
            return hexCode = hexCode | (TRAP8 & imdValue);
            break;
        case XOR:
            hexCode = 0x9000;
            assignReg(lArg1, lArg2, lArg3, &aReg1, &aReg2, &aReg3);
            if (aReg1 == numReg || aReg2 == numReg) {
                exit(4);
            }
            /* Check if immediate */
            if (aReg3 == numReg) {
                imdValue = toNum(lArg3);
                if (imdValue < -16 || imdValue > 15) {
                    exit(3);
                } /*OVERFLOW*/
                return hexCode = (hexCode | IMDBIT | (imdValue & IMD5)) | (aReg1 << 9) | (aReg2 << 6);
            } else {
                /* No immediate value */
                return hexCode = (hexCode | aReg3) | (aReg1 << 9) | (aReg2 << 6);
            }
            break;
        case END:
            /* PROGRAM DONE!*/
            programDone = 1;
            return 0;
        case FILL:
            if (strcmp(lArg2, "") != 0 || strcmp(lArg3, "") != 0) {
                exit(4);
            } /*Should have no arg2,3 */
            return hexCode = toNum(lArg1);
        default:
            return hexCode = 0; /* Shouldn't happen though */
            break;
    }

}

/* isOpcode
 *  -Checks if the char array is an opcode, else returns ERROR (-1)
 */
int isOpcode(char *lStr) {
    int i;
    for (i = 0; i < NUM_OPCODE; i++) {
        if (strcmp(lStr, opCodes[i]) == 0) {
            return i;
        }
    }
    return ERROR;
}

/* toNum
 *  -Converts dec. hex strr to int
 *  -String format for Decimal: #30 or #-30 and HEX:  x300
 */
int toNum(char * pStr) {
    char * t_ptr;
    char * orig_pStr;
    int t_length, k;
    int lNum, lNeg = 0;
    long int lNumLong;
    orig_pStr = pStr;

    /* Decimal */
    if (*pStr == '#') {
        pStr++;
        if (*pStr == '-') { /* dec is negative */
            lNeg = 1;
            pStr++;
        }
        t_ptr = pStr;
        t_length = strlen(t_ptr);
        for (k = 0; k < t_length; k++) {
            if (!isdigit(*t_ptr)) {
                printf("Error: invalid decimal operand, %s\n", orig_pStr);
                exit(4);
            }
            t_ptr++; \
            }
        lNum = atoi(pStr);
        if (lNeg) {
            lNum = -lNum;
        }
        return lNum;
    } else if (*pStr == 'x') {
        /* hex     */
        pStr++;
        if (*pStr == '-') { /* hex is negative */
            lNeg = 1;
            pStr++;
        }
        t_ptr = pStr;
        t_length = strlen(t_ptr);
        for (k = 0; k < t_length; k++) {
            if (!isxdigit(*t_ptr)) {
                printf("Error: invalid hex operand, %s\n", orig_pStr);
                exit(4);
            }
            t_ptr++;
        }
        lNumLong = strtol(pStr, NULL, 16); /* convert hex string into integer */
        lNum = (lNumLong > INT_MAX) ? INT_MAX : lNumLong;
        if (lNeg) {
            lNum = -lNum;
        }
        return lNum;
    } else {
        /* Neither Hex nor Decimal....ERROR! */
        printf("Error: invalid operand, %s\n", orig_pStr);
        exit(4); /* This has been changed from error code 3 to error code 4, see clarification 12 */
    }
}/* End toNum */

/* Read and Parse
 *  - Parses line as label or opcode.  And parses out its arguments in containers 1-4
 */
int readAndParse(FILE * pInFile, char * pLine, char ** pLabel, char ** pOpcode,
        char ** pArg1, char ** pArg2, char ** pArg3, char ** pArg4) {
    char * lRet, * lPtr;
    int i;
    if (!fgets(pLine, MAX_LINE_LENGTH, pInFile)) {
        return (DONE);
    }

    /* Convert line to lowercase */
    for (i = 0; i < strlen(pLine); i++) {
        pLine[i] = tolower(pLine[i]);
    }

    /* ignore comments */
    *pLabel = *pOpcode = *pArg1 = *pArg2 = *pArg3 = *pArg4 = pLine + strlen(pLine);

    lPtr = pLine;

    while (*lPtr != ';' && *lPtr != '\0' && *lPtr != '\n') {
        lPtr++;
    }

    /* Init lPtr pointer to null escape */
    *lPtr = '\0';

    /* Look if line is empty */
    if (!(lPtr = strtok(pLine, "\t\n ,"))) {
        return (EMPTY_LINE);
    }

    /* Check for Label */
    if (isOpcode(lPtr) == -1 && lPtr[0] != '.') {
        *pLabel = lPtr;
        if (!(lPtr = strtok(NULL, "\t\n ,"))) {
            return ( OK);
        }
    }

    /* Check for opCode, check for Arg1 */
    *pOpcode = lPtr;
    if (!(lPtr = strtok(NULL, "\t\n ,"))) {
        return ( OK);
    }

    /* Save Arg1, and check for Arg2 */
    *pArg1 = lPtr;
    if (!(lPtr = strtok(NULL, "\t\n ,"))) {
        return ( OK);
    }

    /* Save Arg2, and check for Arg3 */
    *pArg2 = lPtr;
    if (!(lPtr = strtok(NULL, "\t\n ,"))) {
        return ( OK);
    }

    /* Save Arg3, and check for Arg4 */
    *pArg3 = lPtr;
    if (!(lPtr = strtok(NULL, "\t\n ,"))) {
        return ( OK);
    }

    /*Save Arg4 */
    *pArg4 = lPtr;

    return (OK);
}/* End Read and parse */

/* validLabel
 *  -Checks to see if the label is 'valid', returns VALID (1) or ERROR (0)
 *  Assumes the label is already lowercase through the use of readAndParse!
 */
int validLabel(char *lStr) {
    int i;

    if (strlen(lStr) <= MAX_LABEL_LENGTH && lStr[0] != 'x') {
        /*Check for any non-alphanumeric chars */
        for (i = 0; i < strlen(lStr); i++) {
            if (!isalnum(lStr[i])) {
                return ERROR;
            }
        }
        /* Check if label is a duplicate */
        for (i = 0; i < numLabels; i++) {
            /* strcmp returns 0 if they are the same!! */
            if (strcmp(symbolTable[i].label, lStr) == 0) {
                return ERROR;
            }
        }
        /* Passed all tests */
        return VALID;
    }
    /* Failed Tests */
    return ERROR;
}