4
\$\begingroup\$

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") {
    alert(
        '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});
  let address = 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) {
        alert(
            "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:
        // https://atheistforums.org/thread-61911-post-2112572.html#pid2112572
        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")
        // If we are moving 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 (/^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;
      }
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex = "2";
      if (node.children[2].text === "()")
        machineCode[address].hex += "e";
      else
        machineCode[address].hex += "f";
      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 (/^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;
      }
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex = "0";
      if (node.children[2].text === "()")
        machineCode[address].hex += "8";
      else
        machineCode[address].hex += "9";
      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 (/^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();
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex =
          "2b" +
          formatAsByte(node.children[0].interpretAsArithmeticExpression(
              context.constants)) +
          formatAs4bits(node.children[2].interpretAsArithmeticExpression(
              context.constants));
      address++;
    } 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;
      }
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex =
          "14" + node.children[0].getRegisterNumber(context.namedRegisters) +
          "80";
      address++;
    } 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)) {
      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 =
            "22" +
            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))
          machineCode[address].hex =
              "32" + node.children[2].getLabelAddress(context.labels,
                                                      context.constants);
        else if (/^nz$/i.test(node.children[0].text))
          machineCode[address].hex =
              "36" + node.children[2].getLabelAddress(context.labels,
                                                      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))
          machineCode[address].hex =
              "3e" + node.children[2].getLabelAddress(context.labels,
                                                      context.constants);
        else {
          alert("Line #" + node.lineNumber + ": Invalid flag name '" +
                node.children[0].text + "'!");
          return;
        }
      }
      address++;
    } 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) {
        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 = "24" +
                                 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)) {
      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))
          machineCode[address].hex =
              "30" + node.children[2].getLabelAddress(context.labels,
                                                      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))
          machineCode[address].hex =
              "38" + node.children[2].getLabelAddress(context.labels,
                                                      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)) {
      machineCode[address].line = node.lineNumber;
      if (!node.children.length)
        machineCode[address].hex = "25000";
      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))
          machineCode[address].hex = "35000";
        else if (/^c$/i.test(node.children[0].text))
          machineCode[address].hex = "39000";
        else if (/^nc$/i.test(node.children[0].text))
          machineCode[address].hex = "3d000";
        else {
          alert("Line #" + node.lineNumber + ": Invalid flag name '" +
                node.children[0].text + "'");
          return;
        }
      } else {
        alert(
            "Line #" + node.lineNumber + ": The '" + node.text +
            "' node should have either exactly zero (0) child nodes or exactly one (1) child node!");
        return;
      }
      address++;
    } 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;
      }
      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 += "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 (/^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;
      }
      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 += "b";
      else
        machineCode[address].hex += "a";
      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 (/^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;
      }
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex = "0";
      if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
        machineCode[address].hex += "5";
      else
        machineCode[address].hex += "4";
      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 (/^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;
      }
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex = "0";
      if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
        machineCode[address].hex += "d";
      else
        machineCode[address].hex += "c";
      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 (/^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;
      }
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex = "1";
      if (node.children[2].getRegisterNumber(context.namedRegisters) === "none")
        machineCode[address].hex += "d";
      else
        machineCode[address].hex += "c";
      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)?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();
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex =
          "14" + node.children[0].getRegisterNumber(context.namedRegisters) +
          "06";
      address++;
    } 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();
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex =
          "14" + node.children[0].getRegisterNumber(context.namedRegisters) +
          "04";
      address++;
    } 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();
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex =
          "14" + node.children[0].getRegisterNumber(context.namedRegisters) +
          "02";
      address++;
    } 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();
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex =
          "14" + node.children[0].getRegisterNumber(context.namedRegisters) +
          "0f";
      address++;
    } 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();
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex =
          "14" + node.children[0].getRegisterNumber(context.namedRegisters) +
          "08";
      address++;
    } 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;
      }
      machineCode[address].line = node.lineNumber;
      machineCode[address].hex = "28000";
      address++;
    } 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))
        machineCode[address].hex = "29000";
      else {
        alert("Line #" + node.lineNumber +
              ': Expected "ENABLE" or "DISABLE" instead of "' +
              node.children[0].text + '"!');
        return;
      }
      machineCode[address].line = node.lineNumber;
      address++;
    } else if (!isDirective(node.text)) {
      alert("Line #" + node.lineNumber + ': Sorry about that, the mnemonic "' +
            node.text + '" is not implemented.');
    }
  }
}
\$\endgroup\$
3
  • \$\begingroup\$ 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) \$\endgroup\$
    – Blindman67
    Commented Oct 25, 2023 at 0:48
  • \$\begingroup\$ @Blindman67 How should I show compile errors if not via alert? \$\endgroup\$ Commented Oct 25, 2023 at 5:43
  • 1
    \$\begingroup\$ 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) \$\endgroup\$
    – Blindman67
    Commented Oct 26, 2023 at 0:44

2 Answers 2

6
\$\begingroup\$

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)
    if (RegExp("^" + directive + "$", "i").test(str))
      return true;

Ok, we get it, caller should pass in a string. But please, name that string something descriptive. And give us a one-liner JSDoc.

The function name is very nice, making it immediately clear that this is a predicate.

Choosing to avoid .toLowerCase() seems odd, but fine, there's more than one way to accomplish a case insensive compare.

Looping over preprocessor seems more expensive than necessary. Prefer to make just a single probe of a hash map. Populate it with case-smashed entries if need be.


tell us what the code does

function assemble(parsed, context) {

Uggh, that's pretty opaque. I have no idea what the contract is, what parsed looks like, nor context.

Use JSDoc already.

The OP helpfully offers some context that we're implementing a Xilinx PicoBlaze assembler, from which we infer that all instruction opcodes fit into a single byte. It's still not apparent why 2**18 is important in this context.


use exceptions

    const check_if_the_only_argument_is_register = () => {

Thank you for the descriptive name.

The contract confuses me -- we detect either of two bad situations and log that we're not pleased with the input, terrific. But then we just return?!? Carrying on as if everything is fine? I was expecting to see an assertion throw a fatal error. Or possibly a predicate, so caller will change behavior in the error case.

Rather than a verb of "check...", consider using a name that starts with "log..." or "display...", to make its intended side effect clearer.

I'm starting to see some of the node structure. The OP didn't offer any context about which software component did the parsing, and I didn't see any imported libraries near top-of-file giving me hints about where to seek documentation. So I'm pretty much in the dark at this point.


why, not how

    const check_if_there_are_three_child_nodes_and_the_second_one_is_comma = () => {

Kudos for trying to make this descriptive.

But really, this identifier reads like a comment, and the text would be better placed within a comment. (This would be a good identifier if it was part of a unit test suite.)

Isn't there some business term, some domain term, that corresponds to the "node comma node" situation? Is there maybe an EBNF term in some Xilinx grammar that could lend its name here?

It's not yet clear to me why we're defining these functions here, rather than at top-level scope along with the hex formatters.


case smash

    if (/^address$/i.test(node.text))
      ...
    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.

\$\endgroup\$
3
\$\begingroup\$

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:
        ...
    }

A lookup table of functions would be better readable. As already answered.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.