csfieldguide/static/interactives/mips-assembler/js/mips-assembler.js
/* Partial MIPS I Assembler based on the work of Alan Hogan
* https://github.com/alanhogan/online-mips-assembler
* Licensed under CC BY-NC-SA 4.0
*/
// Types of instructions
const TYPE_BADREGISTER = -3;
const TYPE_UNSUPPORTED = -2;
const TYPE_INVALID = -1;
const TYPE_UNASSIGNED = 0;
const TYPE_R = 1;
const TYPE_I = 2;
const TYPE_J = 3;
// other constants
const INSTRUCTION_START = 0x00400000;
const DATA_START = 0x00c00000;
const COLOUR_ADDR = "#9FBF8A";
const COLOUR_INSTR = "#ECCC87";
const COLOUR_INPUT = "#8BA1B4";
const COLOUR_BAD = "#C15D5A";
const COLOUR_STR = "#B9C2CE";
const COLOUR_LABEL = "#B58DAE";
const COLOUR_COMMENT = "#CE9178";
// Text constants
const TXT_INPUT = gettext("input");
const TXT_DATA = gettext("DATA IN MEMORY");
const TXT_UNSUPPORTED = gettext("UNSUPPORTED OPERATION");
const TXT_INVALID = gettext("INVALID REGISTER");
const TXT_UNRECOGNISED = gettext("UNRECOGNISED INSTRUCTION");
const TXT_PANIC = gettext("You've triggered the failsafe error control in this interactive, which means the programmer who made it didn't prepare for the specific error in your program. Sorry about that. Double check everything and try again.");
// Required global variables
var LABELS;
var LABELADDRS;
var SHOWCOLOUR;
var SHOWBINARY;
// Stores a backup of the default code and registers button handler functions
$(document).ready(function() {
var basicProgram = $('#basic-example').html();
var advancedProgram = $('#advanced-example').html();
$('#mips-input').val(basicProgram);
$('#assembler-output').html('');
$('#submit-mips').on('click', function () {
try {
assemble();
} catch(err) {
present(TXT_PANIC, false);
}
});
$('#reset-basic').on('click', function () {
$('#mips-input').val(basicProgram);
$('#assembler-output').html('');
});
$('#reset-adv').on('click', function () {
$('#mips-input').val(advancedProgram);
$('#assembler-output').html('');
});
});
/**
* Assembles the code from the mips input box and prints it to the assembler output box
*/
function assemble() {
LABELS = [];
LABELADDRS = [];
var targetIndex;
var showBlank = $('#show-blank').is(':checked');
var showInstr = $('#show-instructions').is(':checked');
SHOWCOLOUR = $('#show-colours').is(':checked');
SHOWBINARY = $('#show-binary').is(':checked');
var printText = "";
var nextInstr = "";
var line;
var input = 0;
var mipsText = $('#mips-input').val();
var instrHex;
var instrType;
var instrArgs = [];
var instructions = [];
var storedText = [];
var storedTextNames = [];
var storedTextAddr = [DATA_START];
var subtext = [];
var instructionAddr = INSTRUCTION_START;
var dataAddr = DATA_START;
var mipsCode = mipsText.split(/\r|\n/);
// Parse the code
for (var x=0; x < mipsCode.length; x++) {
line = mipsCode[x].trim();
if (line.startsWith(".")) {
// Ignore the line
if (showInstr) {
nextInstr = "; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + line + "<br>";
} else {
nextInstr = line + "<br>"
}
if (showBlank) {
instructions.push([TYPE_UNASSIGNED, nextInstr, input, instructionAddr, line]);
}
} else if (line.startsWith("#") || line == "") {
// Interpret as a comment or blank line
if (showInstr) {
nextInstr = "; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(line, COLOUR_COMMENT) + "<br>";
} else {
nextInstr = colour(line, COLOUR_COMMENT) + "<br>";
}
if (showBlank) {
instructions.push([TYPE_UNASSIGNED, nextInstr, input, instructionAddr, line]);
}
} else if (line.includes(" .asciiz ")) {
// Interpret as a string to be stored
var substr = line.match(/.asciiz "(.*)"/);
var colonIndex = line.indexOf(":");
storedText.push(splitEvery(4, substr[1]));
storedTextNames.push(line.substr(0, colonIndex));
storedTextAddr.push(last(storedTextAddr) + last(storedText).length * 4);
if (showInstr) {
nextInstr = "; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(last(storedTextNames), COLOUR_LABEL) + line.substr(colonIndex) + "<br>";
} else {
nextInstr = colour(last(storedTextNames), COLOUR_LABEL) + line.substr(colonIndex) + "<br>";
}
if (showBlank) {
instructions.push([TYPE_UNASSIGNED, nextInstr, input, instructionAddr, line]);
}
} else if (last(line.split("")) == ":") {
// Interpret as a pointer name; e.g. function name or loop point
var name = line.substr(0, line.length - 1);
LABELS.push(name);
LABELADDRS.push(instructionAddr);
if (showInstr) {
nextInstr = colour(rawVal(instructionAddr), COLOUR_ADDR) + ": " + colour(name, COLOUR_LABEL) + " ; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(name, COLOUR_LABEL) + ":<br>";
} else {
nextInstr = colour(name, COLOUR_LABEL) + ":<br>";
}
if (showBlank || showInstr) {
instructions.push([TYPE_UNASSIGNED, nextInstr, input, instructionAddr, line]);
}
} else if (line == "syscall" || line == "SYSCALL") {
// Interpret as a syscall
if (showInstr) {
nextInstr = colour(rawVal(instructionAddr), COLOUR_ADDR) + ": " + colour(rawVal(12), COLOUR_INSTR) + " ; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour("syscall", COLOUR_INSTR) + "<br>";
} else {
nextInstr = colour(rawVal(12), COLOUR_INSTR) + "<br>";
}
instructions.push([TYPE_UNASSIGNED, nextInstr, input, instructionAddr, line]);
instructionAddr += 4;
} else {
// Interpret as a basic instruction
keywords = line.split(" ");
keywords[0] == keywords[0].toLowerCase();
if (keywords[0] == "li" && keywords.length == 3) {
// li is supported with template (li $xy, z == addiu $xy, $zero, z)
keywords[0] = "addiu";
keywords.push(keywords[2]);
keywords[2] = "$zero,";
line = "addiu " + keywords[1] + " " + keywords[2] + " " + keywords[3];
}
if (keywords[0] == "la" && keywords.length == 3) {
// la is supported when split into three instructions
// la $xy, datavarname == addiu $xy, $zero, [high 16 bits of dataaddr]
// sll $xy, $xy, 16
// addiu $xy, $xy, [low 16 bits of dataaddr]
targetIndex = storedTextNames.indexOf(keywords[2]);
var tempTextAddr = storedTextAddr[targetIndex];
var hi = (0xFFFF0000 & tempTextAddr) >> 16;
var lo = 0x0000FFFF & tempTextAddr;
instrArgs = buildInstructionI(["addiu", keywords[1], "$zero,", hi]);
instrArgs.push(input);
instrArgs.push(instructionAddr);
instrArgs.push("addiu " + keywords[1] + " $zero, " + storedTextNames[targetIndex] + "[hi]");
instructions.push(instrArgs);
input++;
instructionAddr += 4;
instrArgs = buildInstructionR(["sll", keywords[1], keywords[1], 16]);
instrArgs.push(input);
instrArgs.push(instructionAddr);
instrArgs.push("sll " + keywords[1] + " " + keywords[1] + " 16");
instructions.push(instrArgs);
input++;
instructionAddr += 4;
keywords[0] = "addiu";
keywords[2] = keywords[1];
keywords.push(lo);
line = "addiu " + keywords[1] + " " + keywords[2] + " " + storedTextNames[targetIndex] + "[lo]";
}
if (keywords[0] == "move" && keywords.length == 3) {
// move is supported with template (move $ab, $cd == add $ab, $zero, $cd)
keywords[0] = "add";
keywords.push(keywords[2]);
keywords[2] = "$zero,";
line = "add " + keywords[1] + " " + keywords[2] + " " + keywords[3];
}
instrType = instructionType(keywords[0]);
switch(instrType) {
case TYPE_R:
instrArgs = buildInstructionR(keywords);
break;
case TYPE_I:
instrArgs = buildInstructionI(keywords);
break;
case TYPE_J:
instrArgs = buildInstructionJ(keywords);
break;
case TYPE_INVALID:
default:
instrArgs = [TYPE_INVALID, null];
}
instrArgs.push(input);
instrArgs.push(instructionAddr);
instrArgs.push(line);
instructions.push(instrArgs);
if (instrArgs[0] in [TYPE_R, TYPE_I, TYPE_J]) {
instructionAddr += 4;
}
}
input++;
nextInstr = "";
}
// Assemble the instructions
for (var z=0; z < instructions.length; z++) {
instrArgs = instructions[z];
var input = nthLast(instrArgs, 3);
var addr = nthLast(instrArgs, 2);
var orig = last(instrArgs);
switch(instrArgs[0]) {
case (TYPE_UNASSIGNED):
printText += instrArgs[1];
break;
case (TYPE_R):
instrHex = hexR(instrArgs[1], instrArgs[2], instrArgs[3], instrArgs[4], instrArgs[5], instrArgs[6]);
if (showInstr) {
printText += colour(rawVal(addr), COLOUR_ADDR) + ": " + colour(rawVal(instrHex), COLOUR_INSTR) + " ; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(orig, COLOUR_INSTR) + "<br>";
} else {
printText += colour(rawVal(instrHex), COLOUR_INSTR) + "<br>";
}
break;
case (TYPE_I):
if (typeof(instrArgs[4]) == "string") {
// Argument is a label that needs addressing
// change to the number of instructions between this and the label
targetIndex = LABELS.indexOf(instrArgs[4]);
instrArgs[4] = (LABELADDRS[targetIndex] - addr) / 4;
}
instrHex = hexI(instrArgs[1], instrArgs[2], instrArgs[3], instrArgs[4]);
if (showInstr) {
printText += colour(rawVal(addr), COLOUR_ADDR) + ": " + colour(rawVal(instrHex), COLOUR_INSTR) + " ; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(orig, COLOUR_INSTR) + "<br>";
} else {
printText += colour(rawVal(instrHex), COLOUR_INSTR) + "<br>";
}
break;
case (TYPE_J):
instrHex = hexJ(instrArgs[1], instrArgs[2]);
if (showInstr) {
printText += colour(rawVal(addr), COLOUR_ADDR) + ": " + colour(rawVal(instrHex), COLOUR_INSTR) + " ; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(orig, COLOUR_INSTR) + "<br>";
} else {
printText += colour(rawVal(instrHex), COLOUR_INSTR) + "<br>";
}
break;
case (TYPE_UNSUPPORTED):
printText += "; " + colour(TXT_UNSUPPORTED, COLOUR_BAD) + ": " + instrArgs[1] + " ; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(orig, COLOUR_INSTR) + "<br>";
break;
case (TYPE_BADREGISTER):
printText += "; " + colour(TXT_INVALID, COLOUR_BAD) + ": " + instrArgs[1] + " ; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(orig, COLOUR_INSTR) + "<br>";
break;
case (TYPE_INVALID):
default:
printText += "; " + colour(TXT_UNRECOGNISED, COLOUR_BAD) + " ; |" + colour(TXT_INPUT + ":" + input, COLOUR_INPUT) + "| " + colour(orig, COLOUR_INSTR) + "<br>";
break;
}
}
// Assemble the data stored in memory
if (storedText.length > 0) {
if (showInstr || showBlank) {
printText += ";<br>; "+ TXT_DATA + "<br>";
}
for (var n=0; n < storedText.length; n++) {
if (showInstr || showBlank) {
printText += "; " + colour(storedTextNames[n], COLOUR_LABEL) + "<br>";
}
subtext = storedText[n];
for (var j=0; j < subtext[0].length; j++) {
if (SHOWBINARY) {
hexString = binOfStr(subtext[0][j], 32);
} else {
hexString = subtext[1][j];
}
if (showInstr) {
nextInstr = colour(rawVal(dataAddr), COLOUR_ADDR) + ": " + colour(hexString, COLOUR_STR) + " ; " + colour(subtext[0][j], COLOUR_STR) + "<br>";
} else {
nextInstr = colour(hexString, COLOUR_STR) + "<br>";
}
dataAddr += 4;
printText += nextInstr;
nextinstr = "";
}
}
}
present(printText, true);
}
/**
* Writes the given text to the Program Output textarea,
* coloured red if not isSuccess
*/
function present(text, isSuccess) {
if (!isSuccess) {
text = colour(text, COLOUR_BAD);
}
$("#assembler-output").html(text);
}
/**
* Returns the last element of the given array
* This equates to the python expression array[-1]
*/
function last(array) {
return array[array.length - 1];
}
/**
* Returns the nth to last element of the given array
* This equates to the python expression array[-n]
*/
function nthLast(array, n) {
return array[array.length - n];
}
/**
* Returns the given string wrapped appropriately to display in the given colour
* If the global SHOWCOLOUR is false, returns the given string uncoloured
*/
function colour(text, colour) {
if (SHOWCOLOUR) {
return '<span style="color:' + colour + '">' + text + '</span>';
} else {
return text;
}
}
/**
* Returns a list of two lists
* 1) a list of strings: the given text split every num characters
* 2) a list of strings: the previous list but each item is the hexadecimal value of each character in the string appended together
* \n is read as a single character even when input as two charaters
*/
function splitEvery(num, text) {
var returnLines = [];
var returnHexes = [];
var subtext = "";
var subhex = "";
var chars = text.split("");
var numchars = 0;
var nextHex;
for (var i=0; i < chars.length; i++) {
if (chars[i] == "\\" && chars[i+1] == "n") {
subtext += chars[i] + chars[i+1];
subhex += "0a";
i++;
} else {
subtext += chars[i];
nextHex = chars[i].charCodeAt(0).toString(16);
subhex += ("0".repeat(2 - nextHex.length) + nextHex);
}
numchars++;
if (numchars >= num) {
returnLines.push(subtext);
returnHexes.push(subhex);
subtext = "";
subhex = "";
numchars = 0;
}
}
// Push the final line of split text, or "" if it was exactly num characters
returnLines.push(subtext);
returnHexes.push(subhex + "0".repeat(8 - subhex.length));
return [returnLines, returnHexes];
}
/**
* Returns a Type R hex of the given instruction, given the hex code of each individual part
*/
function hexR(opcode, rs, rt, rd, shamt, func) {
return (opcode << 26 | rs << 21 | rt << 16 | rd << 11 | shamt << 6 | func);
}
/**
* Returns a Type I hex of the given instruction, given the hex code of each individual part
*/
function hexI(opcode, rs, rd, imm) {
return (opcode << 26 | rs << 21 | rd << 16 | (0xFFFF & imm));
}
/**
* Returns a Type J hex of the given instruction, given the hex code of each individual part
*/
function hexJ(opcode, addr) {
return (opcode << 26 | addr);
}
/**
* If the global value SHOWBINARY is false, returns a string of the given number as a 32-bit hexadecimal
* If the number is larger than 32 bits, a larger number will be returned (see: hexOfInt)
*
* If SHOWBINARY is true, returns a string of the given number as a 32-bit binary value
* If the number is larger than 32 bits, a larger number will be returned (see: binOfInt)
*/
function rawVal(num) {
if (SHOWBINARY) {
return binOfInt(num, 32);
} else {
return hexOfInt(num, 8);
}
}
/**
* Returns the string of an integer as a zero-extended n-character binary value
* If the binary is less than n bits, zeros will be appended to the front
* If the binary is greater than n bits, a larger than n-character string will be returned
* E.g: binOfInt(2, 4) = "0010", binOfint(2, 1) = "10"
*/
function binOfInt(num, n) {
var returnString = num.toString(2);
if (returnString.length < n) {
return "0".repeat(n - returnString.length) + returnString;
} else {
return returnString;
}
}
/**
* Returns the string of another string as a zero-extended n-character binary value
* If the binary is less than n bits, zeros will be appended to the front
* If the binary is greater than n bits, a larger than n-character string will be returned
* /n is interpreted as a single character
*/
function binOfStr(text, n) {
var returnString = "";
var chars = text.split("");
for (var i=0; i < chars.length; i++) {
if (chars[i] == "\\" && chars[i+1] == "n") {
returnString += binOfInt(0x0A, 8);
i++;
} else {
returnString += binOfInt(chars[i].charCodeAt(0), 8);
}
}
if (returnString.length < n) {
return "0".repeat(n - returnString.length) + returnString;
} else {
return returnString;
}
}
/**
* Returns the string of an integer as a zero-extended n-character hex value
* If the hex is less than n/2 bytes, zeros will be appended to the front
* If the hex is greater than n/2 bytes, a larger than n-character string will be returned
* E.g: hexOfInt(20, 4) = "0014", hexOfint(20, 1) = "14"
*/
function hexOfInt(num, n) {
var returnString = num.toString(16);
if (returnString.length < n) {
return "0".repeat(n - returnString.length) + returnString;
} else {
return returnString;
}
}
/**
* Returns the base 10 encoding of the given Type R operation code
*/
function encodingR(opcode) {
var returnVal;
switch(opcode) {
case("sll"):
returnVal = 0; break;
case("jr"):
returnVal = 8; break;
case("add"):
returnVal = 32; break;
case("addu"):
returnVal = 33; break;
case("sub"):
returnVal = 34; break;
case("subu"):
returnVal = 35; break;
case("and"):
returnVal = 36; break;
case("or"):
returnVal = 37; break;
case("xor"):
returnVal = 38; break;
case("nor"):
returnVal = 39; break;
default:
returnVal = TYPE_UNSUPPORTED;
}
return returnVal;
}
/**
* Returns the base 10 encoding of the given Type I operation code
*/
function encodingI(opcode) {
var returnVal;
switch(opcode) {
case("beq"):
returnVal = 4; break;
case("bne"):
returnVal = 5; break;
case("addi"):
returnVal = 8; break;
case("addiu"):
returnVal = 9; break;
case("andi"):
returnVal = 12; break;
case("ori"):
returnVal = 13; break;
case("xori"):
returnVal = 14; break;
default:
returnVal = TYPE_UNSUPPORTED;
}
return returnVal;
}
/**
* Returns the base 10 encoding of the given Type J operation code
*/
function encodingJ(opcode) {
var returnVal;
switch(opcode) {
case("j"):
returnVal = 2; break;
case("jal"):
default:
returnVal = TYPE_UNSUPPORTED;
}
return returnVal;
}
/**
* Returns the integer encoding of the given register
*/
function register(reg) {
var returnVal;
switch(reg) {
case "zero":
case "0":
returnVal = 0; break;
case "at":
returnVal = 1; break;
case "gp":
returnVal = 28; break;
case "sp":
returnVal = 29; break;
case "fp":
returnVal = 30; break;
case "ra":
returnVal = 31; break;
default:
var regArr = reg.split("");
var regChar = regArr[0];
var regNum;
if (regArr.length > 2) {
return -1; // No support for registers > 9
}
regNum = parseInt(regArr[1]);
switch(regChar) {
case("v"):
// must be v0 or v1
if (regNum <= 1) {
returnVal = regNum + 2;
} else {
returnVal = -1;
} break;
case("a"):
// must be a0-a3
if (regNum <= 3) {
returnVal = regNum + 4;
} else {
returnVal = -1;
} break;
case("t"):
// must be t0-t9
if (regNum < 8) {
returnVal = regNum + 8;
} else {
returnVal = regNum + 16;
} break;
case("s"):
// must be s0-s7
if (regNum < 8) {
returnVal = regNum + 16;
} else {
returnVal = -1;
} break;
case("k"):
// must be k0 or k1
if (regNum <= 1) {
returnVal = regNum + 26;
} else {
returnVal = -1;
} break;
default:
returnVal = -1;
}
}
return returnVal;
}
/**
* Returns a list of:
* 1) a TYPE_ code, depending on the success of the operation, TYPE_R if valid
* 2-7) each argument in order for a valid Type-R instruction, or the operation name if invalid
* Input is a list of strings, each being a keyword of the instruction to be interpreted
*/
function buildInstructionR(args) {
var returnList;
var opcode = args[0];
var tempReg;
var dest;
var operands = [0, 0];
var opEncoding = encodingR(opcode);
if (opEncoding == TYPE_UNSUPPORTED) {
return [TYPE_UNSUPPORTED, args[0]];
}
// Get the destination register
if (args.length >= 2 && args[1].startsWith("$")) {
if (last(args[1].split("")) == ",") {
tempReg = args[1].substr(1, args[1].length - 2);
} else {
tempReg = args[1].substr(1);
}
dest = register(tempReg);
} else {
return [TYPE_INVALID, args[0]];
}
if (dest < 0) {
return [TYPE_BADREGISTER, tempReg];
}
// Get the operand arguments and build the hex
if (opcode == "jr") {
// The Jump Register instruction is special in that its two operand arguments are zero
// jr $destreg
if (args.length != 2) {
return [TYPE_INVALID, args[0]];
}
returnList = [TYPE_R, 0, dest, 0, 0, 0, opEncoding];
} else if (opcode == "sll") {
// The only supported Shift instruction is required for the la pseudo instruction
// and requires 4 slightly different arguments
// sll $destreg, $operand1, shift
if (args.length != 4) {
return [TYPE_INVALID, args[0]];
}
if (args[2].startsWith("$") && last(args[2].split("")) == ",") {
tempReg = args[2].substr(1, args[2].length - 2);
} else {
return [TYPE_INVALID, args[0]];
}
operands[0] = register(tempReg);
if (operands[0] < 0) {
return(TYPE_BADREGISTER, tempReg);
}
var shift = parseInt(args[3]);
if (shift > 0) {
returnList = [TYPE_R, 0, 0, operands[0], dest, shift, opEncoding];
} else {
return [TYPE_INVALID, args[0]];
}
} else {
// Remaining supported instructions have exactly 4 arguments
// opcode $destreg, $operand1, $operand2
if (args.length != 4) {
return [TYPE_INVALID, args[0]];
}
if (args[2].startsWith("$") && last(args[2].split("")) == ",") {
tempReg = args[2].substr(1, args[2].length - 2);
} else {
return f[TYPE_INVALID, args[0]];
}
operands[0] = register(tempReg);
if (operands[0] < 0) {
return [TYPE_BADREGISTER, tempReg];
}
if (args[3].startsWith("$")) {
tempReg = args[3].substr(1);
} else {
return [TYPE_INVALID, args[0]];
}
operands[1] = register(tempReg);
if (operands[1] < 0) {
return [TYPE_BADREGISTER, tempReg];
}
returnList = [TYPE_R, 0, operands[0], operands[1], dest, 0, opEncoding];
}
return returnList;
}
/**
* Returns a list of:
* 1) a TYPE_ code, depending on the success of the operation, TYPE_I if valid
* 2-5) each argument in order for a valid Type-I instruction, or the operation name if invalid
* Input is a list of strings, each being a keyword of the instruction to be interpreted
*/
function buildInstructionI(args) {
var returnList;
var opcode = args[0];
var tempReg;
var dest;
var operands = [0, 0];
var opEncoding = encodingI(opcode);
if (opEncoding == TYPE_UNSUPPORTED) {
return [TYPE_UNSUPPORTED, args[0]];
}
// Instructions have exactly 4 arguments
// Get the destination argument
if (args.length == 4 && args[1].startsWith("$")) {
if (last(args[1].split("")) == ",") {
tempReg = args[1].substr(1, args[1].length - 2);
} else {
tempReg = args[1].substr(1)
}
dest = register(tempReg);
} else {
return [TYPE_INVALID, args[0]];
}
if (dest < 0) {
return [TYPE_BADREGISTER, tempReg];
}
// Get the operand arguments and build the hex
if (opcode == "beq" || opcode == "bne") {
// The supported branch instructions work slightly differently
// opcode $operand1, $operand2, desttag
// As such the current value of dest is actually an operand
operands[0] = dest;
if (args[2].startsWith("$") && last(args[2].split("")) == ",") {
tempReg = args[2].substr(1, args[2].length - 2);
} else {
return [TYPE_INVALID, args[0]];
}
operands[1] = register(tempReg);
if (operands[1] < 0) {
return [TYPE_BADREGISTER, tempreg];
}
// beq and bne both reference destinations that haven't yet been parsed
// so return the name of the expected target and deal with it later
returnList = [TYPE_I, opEncoding, operands[0], operands[1], args[3]];
} else {
// Remaining instructions follow the template:
// opcode $destreg, $operand1, immediate
if (args[2].startsWith("$") && last(args[2].split("")) == ",") {
tempreg = args[2].substr(1, args[2].length - 2);
} else {
return [TYPE_INVALID, args[0]];
}
operands[0] = register(tempreg);
if (operands[0] < 0) {
return [TYPE_BADREGISTER, tempReg];
}
operands[1] = parseInt(args[3]);
if (isNaN(operands[1])) {
return [TYPE_INVALID, args[0]];
}
returnList = [TYPE_I, opEncoding, operands[0], dest, operands[1]];
}
return returnList;
}
/**
* Returns a list of:
* 1) a TYPE_ code, depending on the success of the operation, TYPE_J if valid
* 2-3) each argument in order for a valid Type-J instruction, or the operation name if invalid
* Input is a list of strings, each being a keyword of the instruction to be interpreted
*/
function buildInstructionJ(args) {
var returnList;
var opcode = args[0];
var dest;
var targetIndex;
var opEncoding = encodingJ(opcode);
if (opEncoding == TYPE_UNSUPPORTED) {
return [TYPE_UNSUPPORTED, args[0]];
}
// Supported instructions have only 2 arguments
// opcode destaddr
if (args.length != 2) {
return [TYPE_INVALID, args[0]];
}
targetIndex = LABELS.indexOf(args[1])
if (targetIndex < 0) {
return [TYPE_INVALID, args[0]];
}
dest = LABELADDRS[targetIndex];
returnList = [TYPE_J, opEncoding, dest];
return returnList;
}
/**
* Returns the type of instruction based on the given opcode
* Most MIPS I instructions are recognised but only a few are supported
*/
function instructionType(opcode) {
switch(opcode) {
// Type R Shifts
case "sll":
case "srl":
case "sra":
case "sllv":
case "srlv":
case "srav":
// Type R Multiplication & Division
case "mfhi":
case "mthi":
case "mflo":
case "mtlo":
case "mult":
case "multu":
case "div":
case "divu":
// Type R Jumps
case "jr":
case "jalr":
// Type R ALU Instructions
case "add":
case "addu":
case "sub":
case "subu":
case "and":
case "or":
case "xor":
case "nor":
case "slt":
case "sltu":
return TYPE_R; break;
// Type I Loads & Stores
case "lb":
case "lh":
case "lwl":
case "lw":
case "lbu":
case "lhu":
case "lwr":
case "sb":
case "sh":
case "swl":
case "sw":
case "swr":
// Type I ALU Instructions
case "addi":
case "addiu":
case "slti":
case "sltiu":
case "andi":
case "ori":
case "xori":
case "lui":
// Type I Branches
case "bltz":
case "bgez":
case "bltzal":
case "bgezal":
case "beq":
case "bne":
case "blez":
case "bgtz":
return TYPE_I; break;
// Type J Jumps
case "j":
case "jal":
return TYPE_J; break;
// Other
case "break":
default:
return TYPE_INVALID;
}
}