Source: parser/instructionparser.js

Parser.BitsPerImmediate = 16;
Parser.BitsPerShamt = 5;

Parser.parseRdRsRt = function (operandParser) {
    let rd = operandParser.parseWritableRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let rs = operandParser.parseRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let rt = operandParser.parseRegister();

    return {
        '$rd': rd,
        '$rs': rs,
        '$rt': rt
    };
};

Parser.parseRdRs = function (operandParser) {
    let rd = operandParser.parseWritableRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let rs = operandParser.parseRegister();

    return {
        '$rd': rd,
        '$rs': rs
    };
};

Parser.parseRtRsImmSigned = function (operandParser) {
    let rt = operandParser.parseWritableRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let rs = operandParser.parseRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let imm = operandParser.parseSignedConstant(Parser.BitsPerImmediate);

    return {
        '$rt': rt,
        '$rs': rs,
        'imm': imm
    };
};

Parser.parseRtRsImmUnsigned = function (operandParser) {
    let rt = operandParser.parseWritableRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let rs = operandParser.parseRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let imm = operandParser.parseUnsignedConstant(Parser.BitsPerImmediate);

    return {
        '$rt': rt,
        '$rs': rs,
        'imm': imm
    };
};

Parser.parseRdRtShamt = function(operandParser) {
    let rd = operandParser.parseWritableRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let rt = operandParser.parseRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let shamt = operandParser.parseUnsignedConstant(Parser.BitsPerShamt);

    return {
        '$rd': rd,
        '$rt': rt,
        'shamt': shamt
    };
};

Parser.parseLabel = function(operandParser) {
    let label = operandParser.parseLabel();

    return {
        'label': label
    };
};

Parser.parseRs = function(operandParser) {
    let rs = operandParser.parseRegister();

    return {
        '$rs': rs
    };
};

Parser.parseRd = function(operandParser) {
    let rd = operandParser.parseRegister();

    return {
        '$rd': rd
    };
};

Parser.parseRsRt = function (operandParser) {
    let rs = operandParser.parseRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let rt = operandParser.parseRegister();

    return {
        '$rs': rs,
        '$rt': rt
    };
};

Parser.parseRsRtLabel = function (operandParser) {
    let rs = operandParser.parseRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let rt = operandParser.parseRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let label = operandParser.parseLabel();

    return {
        '$rs': rs,
        '$rt': rt,
        'label': label
    };
};

Parser.parseRsLabel = function (operandParser) {
    let rs = operandParser.parseRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let label = operandParser.parseLabel();

    return {
        '$rs': rs,
        'label': label
    };
};

Parser.parseRdAddr = function (operandParser) {
    let rd = operandParser.parseWritableRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let addr = operandParser.parseLoadStoreAddress(Parser.BitsPerImmediate);
    return {
        '$rd': rd,
        'imm': addr.imm,
        '$rs': addr.$rs
    };
};

Parser.parseRtAddr = function (operandParser) {
    let rt = operandParser.parseWritableRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let addr = operandParser.parseLoadStoreAddress(Parser.BitsPerImmediate);
    return {
        '$rt': rt,
        'imm': addr.imm,
        '$rs': addr.$rs
    };
};

Parser.parseRdImm = function (operandParser) {
    let rd = operandParser.parseWritableRegister();
    operandParser.tokenStream.consume(Parser.TokenType.Comma);
    let imm = operandParser.parseConstant(Parser.BitsPerImmediate);
    return {
        '$rd': rd,
        'imm': imm
    };
};

Parser.parseNoArgs = function (operandParser) {
    return undefined;
}

Parser.InstructionParsers = {
    /* Integer Arithmetic Instructions */
    'ADD': Parser.parseRdRsRt,
    'ADDU': Parser.parseRdRsRt,
    'ADDI': Parser.parseRtRsImmSigned,
    'ADDIU': Parser.parseRtRsImmUnsigned,
    'SUB': Parser.parseRdRsRt,
    'SUBU': Parser.parseRdRsRt,
    'MULT': Parser.parseRsRt,
    'MULTU': Parser.parseRsRt,
    'DIV': Parser.parseRsRt,
    'DIVU': Parser.parseRsRt,
    /* Integer Comparison Instructions */
    'SLT': Parser.parseRdRsRt,
    'SLTU': Parser.parseRdRsRt,
    'SLTI': Parser.parseRtRsImmSigned,
    'SLTIU': Parser.parseRtRsImmUnsigned,
    /* Logical Instructions */
    'AND': Parser.parseRdRsRt,
    'ANDI': Parser.parseRtRsImmUnsigned,
    'OR': Parser.parseRdRsRt,
    'ORI': Parser.parseRtRsImmUnsigned,
    'XOR': Parser.parseRdRsRt,
    'XORI': Parser.parseRtRsImmUnsigned,
    'NOR': Parser.parseRdRsRt,
    /* Shift Instructions */
    'SLL': Parser.parseRdRtShamt,
    'SLLV': Parser.parseRdRsRt,
    'SRL': Parser.parseRdRtShamt,
    'SRLV': Parser.parseRdRsRt,
    'SRA': Parser.parseRdRtShamt,
    'SRAV': Parser.parseRdRsRt,
    /* Jump Instructions */
    'B': Parser.parseLabel,
    'J': Parser.parseLabel,
    'JR': Parser.parseRs,
    'JAL': Parser.parseLabel,
    'JALR': Parser.parseRdRs,
    /* Branch Instructions */
    'BEQ': Parser.parseRsRtLabel,
    'BNE': Parser.parseRsRtLabel,
    'BGT': Parser.parseRsRtLabel,
    'BGTU': Parser.parseRsRtLabel,
    'BGE': Parser.parseRsRtLabel,
    'BGEU': Parser.parseRsRtLabel,
    'BLT': Parser.parseRsRtLabel,
    'BLTU': Parser.parseRsRtLabel,
    'BLE': Parser.parseRsRtLabel,
    'BLEU': Parser.parseRsRtLabel,
    /* Branch Instructions after comparison with zero */
    'BEQZ': Parser.parseRsLabel,
    'BNEZ': Parser.parseRsLabel,
    'BGTZ': Parser.parseRsLabel,
    'BGEZ': Parser.parseRsLabel,
    'BGEZAL': Parser.parseRsLabel,
    'BLTZ': Parser.parseRsLabel,
    'BLTZAL': Parser.parseRsLabel,
    'BLEZ': Parser.parseRsLabel,
    /* Register transfer instructions */
    'MFHI': Parser.parseRd,
    'MFLO': Parser.parseRd,
    'MTHI': Parser.parseRs,
    'MTLO': Parser.parseRs,
    /* Load Instructions */
    'LB': Parser.parseRdAddr,
    'LBU': Parser.parseRdAddr,
    'LH': Parser.parseRdAddr,
    'LHU': Parser.parseRdAddr,
    'LW': Parser.parseRdAddr,
    'LWU': Parser.parseRdAddr,
    'LWL': Parser.parseRdAddr,
    'LWR': Parser.parseRdAddr,
    'LUI': Parser.parseRdImm,
    /* Store Instructions */
    'SB': Parser.parseRtAddr,
    'SH': Parser.parseRtAddr,
    'SW': Parser.parseRtAddr,
    'SWL': Parser.parseRtAddr,
    'SWR': Parser.parseRtAddr,
    /* Other Instructions */
    'SYSCALL': Parser.parseNoArgs
};

/** A parser for MIPS instructions and labels
 * @constructor
 * @param {array} symbols An array of pre-defined symbols
 * @param {Parser.TokenStream}  tokenStream The stream of tokens to parse
 */
Parser.InstructionParser = function (tokenStream, symbols) {
    this.tokenStream = tokenStream;
    this.operandParser = new Parser.OperandParser(tokenStream, symbols);

    /**
     * Parse an instruction with its arguments.
     * 
     * @throws Parser.ParseException The instruction must be a valid assembler instruction.
     * @returns {Object}  A dictionary with 'mnemonic' and 'args' set
     */
    this.parseInstruction = function() {
        let token = this.tokenStream.consume(Parser.TokenType.Identifier);
        let mnemonic = token.value.toUpperCase();
        let instructionParser = Parser.InstructionParsers[mnemonic];

        if (instructionParser) {
            let args = instructionParser(this.operandParser);
            return {
                mnemonic: mnemonic,
                args: args
            };
        } else {
            throw new Parser.UnknownInstructionError(token);
        }
    }

    /** Parse a label
     * @returns {String} The label name.
     */
    this.parseLabel = function() {
        let labelToken = this.tokenStream.consume(Parser.TokenType.Identifier);
        this.tokenStream.consume(Parser.TokenType.Colon);
        return labelToken.value;
    }

    /** Parse an optional label
     * @returns {(String|undefined)} The label name or undefined, if no label present.
     */
    this.parseOptionalLabel = function() {
        let that = this;
        return this.tokenStream.tryParsing(function() { return that.parseLabel(); });
    }

    /** Parse a possibly non-empty sequence of labels
     * 
     * @returns {array(Parser.Token)} The sequence of labels.
     */
    this.parseLabels = function() {
        let labels = [];
        let label = this.parseOptionalLabel();
        while (label !== undefined) {
            labels.push(label);
            label = this.parseOptionalLabel();
        }
        return labels;
    }

    /** Parse a sequence of optional labels and an optional instruction. */
    this.parseInstructionLine = function() {
        let labels = this.parseLabels();
        let instr = undefined;
        if (!this.tokenStream.checkNext(Parser.TokenType.EndOfString)) {
            instr = this.parseInstruction();
        }
        this.tokenStream.enforceCompletion();
        return {
            labels: labels,
            instr: instr,
        };
    }

    /** Parse an assignment to a global symbol */
    this.parseSymbolAssignment = function() {
        let name = this.tokenStream.consume(Parser.TokenType.Identifier);
        this.tokenStream.consume(Parser.TokenType.Assignment);
        let value = this.operandParser.exprParser.parseExpression();
        return {
            symbols: [
                {
                    name: name.value,
                    value: value,
                }
            ]
        }
    }

    /** Parse a line */
    this.parseLine = function() {
        if (this.tokenStream.checkNext(Parser.TokenType.Identifier, 0) &&
            this.tokenStream.checkNext(Parser.TokenType.Assignment, 1)) {
            return this.parseSymbolAssignment();
        } else {
            return this.parseInstructionLine();
        }
    }
}

/** Create an {@link Parser.InstructionParser} from a string
 * @param {string}  input The string to parse
 * @param {array} symbols An array of pre-defined symbols
 * @returns {Parser.InstructionParser} The operand parser for parsing the string
 */
Parser.instructionParserFromString = function(text, symbols) {
    let tokenStream = Parser.tokenStreamFromString(text);
    return new Parser.InstructionParser(tokenStream, symbols);
}