# The core of the assembler (the part of the assembler that actually converts mnemonics to opcodes) is hard to follow due to many if-branchings

For my Bachelor thesis, I made a PicoBlaze Assembler and Emulator in JavaScript. I've discussed it on many Internet forums, and quite a few people have complained that the core of my assembler is hard-to-follow due to lots of if-branchings. So, what are some other ways of making the core of the assembler? Do the cores of good assemblers somehow use polymorphism to avoid that problem?

"use strict";
function formatAsByte(n) {
n = Math.round(n);
if (n < 0 || n > 255) {
alert("Some part of the assembler tried to format the number " + n +
" as a byte, which makes no sense.");
return "ff";
}
let ret = n.toString(16);
while (ret.length < 2)
ret = "0" + ret;
return ret;
}

function formatAsInstruction(n) {
n = Math.round(n);
if (n < 0 || n >= 1 << 18) {
alert("Some part of the assembler tried to format the number " + n +
" as an instruction, which makes no sense.");
return "ff";
}
let ret = n.toString(16);
while (ret.length < 5)
ret = "0" + ret;
return ret;
}

function formatAs4bits(n) {
n = Math.round(n);
if (n < 0 || n >= 1 << 4) {
alert("Some part of the assembler tried to format the number " + n +
" as a single hexadecimal digit, which makes no sense.");
return "f";
}
let ret = n.toString(16);
while (ret.length < 1)
ret = "0" + ret;
return ret;
}

function isDirective(str) {
if (typeof str !== "string") {
'Internal compiler error: The first argument of the "isDirective" function is not a string!');
return false;
}
for (const directive of preprocessor)
if (RegExp("^" + directive + "$", "i").test(str)) return true; if (/:$/.test(str))
return true;
return false;
}

function assemble(parsed, context) {
machineCode = [];
for (let i = 0; i < 4096; i++)
machineCode.push({hex : "00000", line : 0});
for (const node of parsed.children) {
const check_if_the_only_argument_is_register = () => {
// Let's reduce the code repetition a bit by using lambda functions...
if (node.children.length !== 1) {
alert("Line #" + node.lineNumber + ': The AST node "' + node.text +
'" should have exactly 1 child node!');
return;
}
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a valid register name!');
return;
}
};
const check_if_there_are_three_child_nodes_and_the_second_one_is_comma = () => {
if (node.children.length !== 3) {
"Line #" + node.lineNumber + ': The AST node "' + node.text +
'" should have exactly three child nodes (a comma is also a child node).');
return; // This line causes this bug:
// https://github.com/FlatAssembler/PicoBlaze_Simulator_in_JS/issues/18
}
if (node.children[1].text !== ",") {
alert("Line #" + node.lineNumber + ': Expected a comma instead of "' +
node.children[1].text + '"!');
return;
}
};
if (/^address$/i.test(node.text)) address = node.children[0].interpretAsArithmeticExpression(context.constants); else if (/^load$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
// TODO: "bennyboy" from "atheistforums.org" thinks that
// doing this check (whether an argument is a register) again and again
// slows down the assembler significantly, it would be good to
// investigate whether that is true:
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
// If we are moving a constant to a register.
else
node.children[0].getRegisterNumber(context.namedRegisters);
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
formatAsByte(node.children[2].interpretAsArithmeticExpression(
context.constants));
else
node.children[2].getRegisterNumber(context.namedRegisters) + "0";
} else if (/^star$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "1"; if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") // If we are moving a constant to a register. machineCode[address].hex += "7"; else machineCode[address].hex += "6"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); else machineCode[address].hex += node.children[2].getRegisterNumber(context.namedRegisters) + "0"; address++; } else if (/^store$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
if (node.children[2].text === "()")
else
node.children[0].getRegisterNumber(context.namedRegisters);
if (node.children[2].text === "()") {
if (node.children[2].children.length !== 1) {
": The node '()' should have exactly one child!");
return;
}
if (node.children[2].children[0].getRegisterNumber(
context.namedRegisters) === "none") {
alert("Line #" + node.lineNumber + ': "' +
node.children[2].children[0].text + '" is not a register!');
return;
}
node.children[2].children[0].getRegisterNumber(
context.namedRegisters) +
"0";
} else
formatAsByte(node.children[2].interpretAsArithmeticExpression(
context.constants));
} else if (/^fetch$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "0"; if (node.children[2].text === "()") machineCode[address].hex += "a"; else machineCode[address].hex += "b"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].text === "()") { if (node.children[2].children.length !== 1) { alert("Line #" + node.lineNumber + ": The node '()' should have exactly one child!"); return; } if (node.children[2].children[0].getRegisterNumber( context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[2].children[0].text + '" is not a register!'); return; } machineCode[address].hex += node.children[2].children[0].getRegisterNumber( context.namedRegisters) + "0"; } else machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); address++; } else if (/^input$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
if (node.children[2].text === "()")
else
node.children[0].getRegisterNumber(context.namedRegisters);
if (node.children[2].text === "()") {
if (node.children[2].children.length !== 1) {
": The node '()' should have exactly one child!");
return;
}
if (node.children[2].children[0].getRegisterNumber(
context.namedRegisters) === "none") {
alert("Line #" + node.lineNumber + ': "' +
node.children[2].children[0].text + '" is not a register!');
return;
}
node.children[2].children[0].getRegisterNumber(
context.namedRegisters) +
"0";
} else
formatAsByte(node.children[2].interpretAsArithmeticExpression(
context.constants));
} else if (/^output$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "2"; if (node.children[2].text === "()") machineCode[address].hex += "c"; else machineCode[address].hex += "d"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].text === "()") { if (node.children[2].children.length !== 1) { alert("Line #" + node.lineNumber + ": The node '()' should have exactly one child!"); return; } if (node.children[2].children[0].getRegisterNumber( context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[2].children[0].text + '" is not a register!'); return; } machineCode[address].hex += node.children[2].children[0].getRegisterNumber( context.namedRegisters) + "0"; } else machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); address++; } else if (/^outputk$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
"2b" +
formatAsByte(node.children[0].interpretAsArithmeticExpression(
context.constants)) +
formatAs4bits(node.children[2].interpretAsArithmeticExpression(
context.constants));
} else if (/^regbank$/i.test(node.text)) { if (node.children.length !== 1) { alert("Line #" + node.lineNumber + ': The AST node "' + node.text + '" should have exactly one (1) child!'); return; } machineCode[address].line = node.lineNumber; if (node.children[0].text === "a" || node.children[0].text === "A") machineCode[address].hex = "37000"; else if (node.children[0].text === "b" || node.children[0].text === "B") machineCode[address].hex = "37001"; else { alert("Line #" + node.lineNumber + ": Expected 'A' or 'B' instead of '" + node.children[0].text + "'!"); return; } address++; } else if (/^hwbuild$/i.test(node.text)) {
if (node.children.length !== 1) {
alert("Line #" + node.lineNumber + ': The AST node "' + node.text +
'" should have exactly one (1) child!');
return;
}
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
"14" + node.children[0].getRegisterNumber(context.namedRegisters) +
"80";
} else if (/^inst$/i.test(node.text)) { if (node.children.length !== 1) { alert("Line #" + node.lineNumber + ': The AST node "' + node.text + '" should have exactly one (1) child!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = formatAsInstruction( node.children[0].interpretAsArithmeticExpression(context.constants)); address++; } else if (/^jump$/i.test(node.text)) {
if (node.children.length === 1) {
context.constants) === "none") {
alert("Line #" + node.lineNumber + ": Label '" +
node.children[0].text + "' is not declared!");
return;
}
"22" +
} else {
if (node.children.length !== 3) {
"Line #" + node.lineNumber + ": The '" + node.text +
"' node should have either exactly one child node (unconditional jumping) or exactly three (3) child nodes (comma counts as a child node)!");
return;
}
if (node.children[1].text !== ",") {
": Expected a comma (',') instead of '" + node.text + "'!");
return;
}
context.constants) === "none") {
alert("Line #" + node.lineNumber + ": Label '" +
node.children[2].text + "' is not declared!");
return;
}
if (/^z$/i.test(node.children[0].text)) machineCode[address].hex = "32" + node.children[2].getLabelAddress(context.labels, context.constants); else if (/^nz$/i.test(node.children[0].text))
context.constants);
else if (/^c$/i.test(node.children[0].text)) machineCode[address].hex = "3a" + node.children[2].getLabelAddress(context.labels, context.constants); else if (/^nc$/i.test(node.children[0].text))
context.constants);
else {
alert("Line #" + node.lineNumber + ": Invalid flag name '" +
node.children[0].text + "'!");
return;
}
}
} else if (/^jump@$/i.test(node.text)) { if (node.children.length !== 1 || node.children[0].text !== "()") { alert("Line #" + node.lineNumber + ": The '" + node.text + "' node should have exactly one (1) child, and that is '()'"); return; } if (node.children[0].children.length !== 3) { alert( "Line #" + node.lineNumber + ": The '()' node should have exactly three children (now it has " + node.children[0].children.length + ")!"); return; } if (node.children[0].children[1].text !== ",") { alert("Line #" + node.lineNumber + ": Expected a comma (',') instead of '" + node.children[0].children[1].text + "'!"); return; } if (node.children[0].children[0].getRegisterNumber( context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ": '" + node.children[0].children[0].text + "' is not a register name!"); return; } if (node.children[0].children[2].getRegisterNumber( context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ": '" + node.children[0].children[2].text + "' is not a register name!"); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "26" + node.children[0].children[0].getRegisterNumber( context.namedRegisters) + node.children[0].children[2].getRegisterNumber( context.namedRegisters) + "0"; address++; } else if (/^call@$/i.test(node.text)) {
if (node.children.length !== 1 || node.children[0].text !== "()") {
alert("Line #" + node.lineNumber + ": The '" + node.text +
"' node should have exactly one (1) child, and that is '()'");
return;
}
if (node.children[0].children.length !== 3) {
"Line #" + node.lineNumber +
": The '()' node should have exactly three children (now it has " +
node.children[0].children.length + ")!");
return;
}
if (node.children[0].children[1].text !== ",") {
": Expected a comma (',') instead of '" +
node.children[0].children[1].text + "'!");
return;
}
if (node.children[0].children[0].getRegisterNumber(
context.namedRegisters) === "none") {
alert("Line #" + node.lineNumber + ": '" +
node.children[0].children[0].text + "' is not a register name!");
return;
}
if (node.children[0].children[2].getRegisterNumber(
context.namedRegisters) === "none") {
alert("Line #" + node.lineNumber + ": '" +
node.children[0].children[2].text + "' is not a register name!");
return;
}
node.children[0].children[0].getRegisterNumber(
context.namedRegisters) +
node.children[0].children[2].getRegisterNumber(
context.namedRegisters) +
"0";
} else if (/^call$/i.test(node.text)) { machineCode[address].line = node.lineNumber; if (node.children.length === 1) { if (node.children[0].getLabelAddress(context.labels, context.constants) === "none") { alert("Line #" + node.lineNumber + ": Label '" + node.children[0].text + "' is not declared!"); return; } machineCode[address].hex = "20" + node.children[0].getLabelAddress(context.labels, context.constants); } else { if (node.children.length !== 3) { alert( "Line #" + node.lineNumber + ": The '" + node.text + "' node should have either exactly one child node (unconditional jumping) or exactly three (3) child nodes (comma counts as a child node)!"); return; } if (node.children[1].text !== ",") { alert("Line #" + node.lineNumber + ": Expected a comma (',') instead of '" + node.text + "'!"); return; } if (node.children[2].getLabelAddress(context.labels, context.constants) === "none") { alert("Line #" + node.lineNumber + ": Label '" + node.children[2].text + "' is not declared!"); return; } if (/^z$/i.test(node.children[0].text))
context.constants);
else if (/^nz$/i.test(node.children[0].text)) machineCode[address].hex = "34" + node.children[2].getLabelAddress(context.labels, context.constants); else if (/^c$/i.test(node.children[0].text))
context.constants);
else if (/^nc$/i.test(node.children[0].text)) machineCode[address].hex = "3c" + node.children[2].getLabelAddress(context.labels, context.constants); else { alert("Line #" + node.lineNumber + ": Invalid flag name '" + node.children[0].text + "'!"); return; } } address++; } else if (/^return$/i.test(node.text)) {
if (!node.children.length)
else if (node.children.length === 1) {
if (/^z$/i.test(node.children[0].text)) machineCode[address].hex = "31000"; else if (/^nz$/i.test(node.children[0].text))
else if (/^c$/i.test(node.children[0].text)) machineCode[address].hex = "39000"; else if (/^nc$/i.test(node.children[0].text))
else {
alert("Line #" + node.lineNumber + ": Invalid flag name '" +
node.children[0].text + "'");
return;
}
} else {
"Line #" + node.lineNumber + ": The '" + node.text +
"' node should have either exactly zero (0) child nodes or exactly one (1) child node!");
return;
}
} else if (/^add$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "1"; if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") // If we are adding a constant to a register. machineCode[address].hex += "1"; else machineCode[address].hex += "0"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); else machineCode[address].hex += node.children[2].getRegisterNumber(context.namedRegisters) + "0"; address++; } else if (/^addcy?$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
// If we are adding a constant to a register.
else
node.children[0].getRegisterNumber(context.namedRegisters);
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
formatAsByte(node.children[2].interpretAsArithmeticExpression(
context.constants));
else
node.children[2].getRegisterNumber(context.namedRegisters) + "0";
} else if (/^sub$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "1"; if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") // If we are subtracting a constant from a register. machineCode[address].hex += "9"; else machineCode[address].hex += "8"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); else machineCode[address].hex += node.children[2].getRegisterNumber(context.namedRegisters) + "0"; address++; } else if (/^subcy?$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
// If we are subtracting a constant from a register.
else
node.children[0].getRegisterNumber(context.namedRegisters);
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
formatAsByte(node.children[2].interpretAsArithmeticExpression(
context.constants));
else
node.children[2].getRegisterNumber(context.namedRegisters) + "0";
} else if (/^and$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "0"; if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += "3"; else machineCode[address].hex += "2"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); else machineCode[address].hex += node.children[2].getRegisterNumber(context.namedRegisters) + "0"; address++; } else if (/^or$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
else
node.children[0].getRegisterNumber(context.namedRegisters);
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
formatAsByte(node.children[2].interpretAsArithmeticExpression(
context.constants));
else
node.children[2].getRegisterNumber(context.namedRegisters) + "0";
} else if (/^xor$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "0"; if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += "7"; else machineCode[address].hex += "6"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); else machineCode[address].hex += node.children[2].getRegisterNumber(context.namedRegisters) + "0"; address++; } else if (/^test$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
else
node.children[0].getRegisterNumber(context.namedRegisters);
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
formatAsByte(node.children[2].interpretAsArithmeticExpression(
context.constants));
else
node.children[2].getRegisterNumber(context.namedRegisters) + "0";
} else if (/^testcy?$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "0"; if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += "f"; else machineCode[address].hex += "e"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); else machineCode[address].hex += node.children[2].getRegisterNumber(context.namedRegisters) + "0"; address++; } else if (/^comp(are)?$/i.test(node.text)) {
check_if_there_are_three_child_nodes_and_the_second_one_is_comma();
if (node.children[0].getRegisterNumber(context.namedRegisters) ===
"none") {
alert("Line #" + node.lineNumber + ': "' + node.children[0].text +
'" is not a register!');
return;
}
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
else
node.children[0].getRegisterNumber(context.namedRegisters);
if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
formatAsByte(node.children[2].interpretAsArithmeticExpression(
context.constants));
else
node.children[2].getRegisterNumber(context.namedRegisters) + "0";
} else if (/^comp(are)?cy$/i.test(node.text)) { check_if_there_are_three_child_nodes_and_the_second_one_is_comma(); if (node.children[0].getRegisterNumber(context.namedRegisters) === "none") { alert("Line #" + node.lineNumber + ': "' + node.children[0].text + '" is not a register!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "1"; if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += "f"; else machineCode[address].hex += "e"; machineCode[address].hex += node.children[0].getRegisterNumber(context.namedRegisters); if (node.children[2].getRegisterNumber(context.namedRegisters) === "none") machineCode[address].hex += formatAsByte(node.children[2].interpretAsArithmeticExpression( context.constants)); else machineCode[address].hex += node.children[2].getRegisterNumber(context.namedRegisters) + "0"; address++; } else if (/^sl0$/i.test(node.text)) {
check_if_the_only_argument_is_register();
"14" + node.children[0].getRegisterNumber(context.namedRegisters) +
"06";
} else if (/^sl1$/i.test(node.text)) { check_if_the_only_argument_is_register(); machineCode[address].line = node.lineNumber; machineCode[address].hex = "14" + node.children[0].getRegisterNumber(context.namedRegisters) + "07"; address++; } else if (/^slx$/i.test(node.text)) {
check_if_the_only_argument_is_register();
"14" + node.children[0].getRegisterNumber(context.namedRegisters) +
"04";
} else if (/^sla$/i.test(node.text)) { check_if_the_only_argument_is_register(); machineCode[address].line = node.lineNumber; machineCode[address].hex = "14" + node.children[0].getRegisterNumber(context.namedRegisters) + "00"; address++; } else if (/^rl$/i.test(node.text)) {
check_if_the_only_argument_is_register();
"14" + node.children[0].getRegisterNumber(context.namedRegisters) +
"02";
} else if (/^sr0$/i.test(node.text)) { check_if_the_only_argument_is_register(); machineCode[address].line = node.lineNumber; machineCode[address].hex = "14" + node.children[0].getRegisterNumber(context.namedRegisters) + "0e"; address++; } else if (/^sr1$/i.test(node.text)) {
check_if_the_only_argument_is_register();
"14" + node.children[0].getRegisterNumber(context.namedRegisters) +
"0f";
} else if (/^srx$/i.test(node.text)) { check_if_the_only_argument_is_register(); machineCode[address].line = node.lineNumber; machineCode[address].hex = "14" + node.children[0].getRegisterNumber(context.namedRegisters) + "0a"; address++; } else if (/^sra$/i.test(node.text)) {
check_if_the_only_argument_is_register();
"14" + node.children[0].getRegisterNumber(context.namedRegisters) +
"08";
} else if (/^rr$/i.test(node.text)) { check_if_the_only_argument_is_register(); machineCode[address].line = node.lineNumber; machineCode[address].hex = "14" + node.children[0].getRegisterNumber(context.namedRegisters) + "0c"; address++; } else if (/^disable$/i.test(node.text)) {
if (node.children.length !== 1 ||
!/interrupt/i.test(node.children[0].text)) {
alert("Line #" + node.lineNumber + ': The AST node "' + node.text +
'" should have exactly one child node, and that is "interrupt"!');
return;
}
} else if (/^enable$/i.test(node.text)) { if (node.children.length !== 1 || !/interrupt/i.test(node.children[0].text)) { alert("Line #" + node.lineNumber + ': The AST node "' + node.text + '" should have exactly one child node, and that is "interrupt"!'); return; } machineCode[address].line = node.lineNumber; machineCode[address].hex = "28001"; address++; } else if (/^ret(urn)?i$/i.test(node.text)) {
if (node.children.length !== 1) {
alert("Line #" + node.lineNumber + ': The AST node "' + node.text +
'" should have exactly one child node!');
return;
}
if (/^enable$/i.test(node.children[0].text)) machineCode[address].hex = "29001"; else if (/^disable$/i.test(node.children[0].text))
else {
': Expected "ENABLE" or "DISABLE" instead of "' +
node.children[0].text + '"!');
return;
}
} else if (!isDirective(node.text)) {
alert("Line #" + node.lineNumber + ': Sorry about that, the mnemonic "' +
node.text + '" is not implemented.');
}
}
}

• Question TLDR so comment rather than review. Often referred to as "Cognitive Complexity" CC a metric defined by the number of paths through some code. (Eg an if statement represents an alternative path). One way to reduce CC is to use functions as the number of paths within the function are not counted as paths in the calling function. Also lookup code granularity, your main function is way too long (over 1000 lines). A function should never be more than a page in length. And unrelated to CC dont use alert to show compile errors (will make the code so hard to use as a compiler) Oct 25, 2023 at 0:48
• @Blindman67 How should I show compile errors if not via alert? Oct 25, 2023 at 5:43
• How to see alerts if client has blocked alerts? Use the console (eg console.log("Some error")), a DOM list of some type (eg <ul><li>Error at line 1<l/i><li>Compile error at line 10. Too many errors.</li></ul>), Throw an exception (eg throw new RangeError("Unsigned int must be positive")) or save to a file (eg download as text file) Oct 26, 2023 at 0:44

# descriptive name

function formatAsByte(n) {


Consider calling it formatAsHexByte. Alternatively, offer one line of JSDoc.

As it stands, the Gentle Reader is forced to read the body to learn what the contract is. Fortunately it's a short body.

# error sentinel

function formatAsInstruction(n) {
...
return "ff";
...
while (ret.length < 5)
ret = "0" + ret;
...


I just don't get that at all. The contract is to return a well-formed five-digit hex string. But sometimes it's a two-digit hex string? What? Possible copy-n-paste issue from the previous helper.

Is there something special about the all-ones byte so we know it always indicates an error value? Should the error value here be 2**18 - 1 ? Would uniformly returning error as 0 from both helpers improve consistency?

# helper for helpers

function formatAs4bits(n) {


zomg, the same code again. Write formatAsNBits just once, please, and then these three helpers become one-line calls.

# hash map lookup

function isDirective(str) {
...
for (const directive of preprocessor)
...
else if (/^load$/i.test(node.text)) {  These case insensitive comparisons are just distracting. Assign node.text.toLowerCase() to a local variable near top-of-loop, and repeatedly examine that instead. Or write a comparison helper. # cite your references Tell us what spec you're writing against, what spec you're implementing and claim conformance to.  machineCode[address].hex += "7"; else machineCode[address].hex += "6";  I imagine this code is right. Probably. But you've not told me where in the Xilinx docs to lookup these magic numbers, so I wouldn't know where to verify that this is right. And if next year Xilinx releases an errata sheet, or a modified processor that we wish to additionally support, you're not making it easy to trace details from your code into their docs. # listen to what your code is telling you Ok, I'm starting to see a pattern. I can't bear to read any more of this.  } else if (/^star$/i.test(node.text)) {    ...
} else if (/^store$/i.test(node.text)) { ... } else if (/^fetch$/i.test(node.text)) {   ...
} else if (/^input$/i.test(node.text)) { ... } else if (/^output$/i.test(node.text)) {  ...
} else if (/^outputk$/i.test(node.text)) { ... } else if (/^regbank$/i.test(node.text)) { ...
} else if (/^hwbuild$/i.test(node.text)) { ...  This is just not well organized. Put the relevant code into suitably named helper functions, perhaps assemble_star, assemble_store, assemble_fetch ... And rather than repeated case insensitive compares, dispatch via a single probe of a hash map. You want to map from a string opcode to its corresponding assemble function. # copy-pasta Here's the high level advice from this review: Stop pasting! Every time you're tempted to copy-n-paste, pause a moment to consider whether the code is asking for a parameterized helper function. Also, it seems clear that you need a simple rule to follow. So here it is, as simple and direct as I can make it: If you write a function longer than a screenful, refactor it. That is, if you need vertical scrolling to view the code, it's time to Extract Helper. This codebase achieves a subset of its design goals. I would not delegate or accept maintenance tasks in the code's current form. The changes required might be accomplished through gradual "refactoring", supported by the existing test suite. It probably makes more sense to discard this code; call it a learning exercise and with that experience under your belt write some brand new target code which passes existing unit tests. Though there is an already excellent accepted answer, still some things: There is much error handling in functions in the form; parameter normalisation (round) if some error then error message (alert) return some dummy value implementation  Abstraction would be possible - even advisable, but for this limited error processing not a big deal. 1. One solution would be to use exceptions (when feasible): This would short-cut expression trees.  if some error then throw error(message) implementation  And on the global call site:  try { ... } catch (error) { alert(error.message); // Or Console.log }  2. Alternatively is extending the data passed and returned. Say passing a context with a result, and returning a boolean whether parsed. 1. The following might be the major source of criticism: the cascading of cases.  } else if (/^star$/i.test(node.text)) {    ...
} else if (/^store$/i.test(node.text)) { ... ... } else if (/^regbank$/i.test(node.text)) { ...
} else if (/^hwbuild\$/i.test(node.text)) { ...


As said this can be improved. But I see a possible lack of JavaScript knowledge (not important): switch on strings.

    switch (node.text.toLowerCase()) {
case "start":
...
break;
case "fetch":
...
break;
...
default:
...
}