|
@@ -0,0 +1,238 @@
|
|
|
+#!/usr/bin/node
|
|
|
+
|
|
|
+const fs = require("node:fs");
|
|
|
+
|
|
|
+/**
|
|
|
+ * The tape of integers
|
|
|
+ * @type {number[]}
|
|
|
+ */
|
|
|
+const tape = [];
|
|
|
+/**
|
|
|
+ * The current address of the tape being modified
|
|
|
+ *
|
|
|
+ * Generally, each function that uses the tape pointer will first
|
|
|
+ * progress the pointer by one position.
|
|
|
+ */
|
|
|
+let tapePointer = 0;
|
|
|
+/**
|
|
|
+ * A dictionary of labels and the line numbers they start at
|
|
|
+ */
|
|
|
+const labelDictionary = {};
|
|
|
+/**
|
|
|
+ * The dictionary of opcodes and their values
|
|
|
+ */
|
|
|
+const OP_CODE_DICTIONARY = {
|
|
|
+ ADDITION: "01",
|
|
|
+ MULTIPLY: "02",
|
|
|
+ INPUT: "03",
|
|
|
+ OUTPUT: "04",
|
|
|
+ JUMP_IF_TRUE: "05",
|
|
|
+ JUMP_IF_FALSE: "06",
|
|
|
+ LESS_THAN: "07",
|
|
|
+ EQUALS: "08",
|
|
|
+ MODIFY_RELATIVE_BASE: "09",
|
|
|
+ HALT: "99",
|
|
|
+};
|
|
|
+/**
|
|
|
+ * Dictionary of assembly instructions and their associated functions
|
|
|
+ */
|
|
|
+const ASM_DICTIONARY = {
|
|
|
+ "ld": loadToAddress,
|
|
|
+ "add": insertArithmetic.bind(null, OP_CODE_DICTIONARY.ADDITION),
|
|
|
+ "mult": insertArithmetic.bind(null, OP_CODE_DICTIONARY.MULTIPLY),
|
|
|
+ "in": instructionNotYetImplemented.bind(null, "IN"),
|
|
|
+ "out": insertOutput,
|
|
|
+ "jnz": insertTestJump.bind(null, OP_CODE_DICTIONARY.JUMP_IF_TRUE),
|
|
|
+ "jz": insertTestJump.bind(null, OP_CODE_DICTIONARY.JUMP_IF_FALSE),
|
|
|
+ "slt": instructionNotYetImplemented.bind(null, "SLT"),
|
|
|
+ "seq": instructionNotYetImplemented.bind(null, "SEQ"),
|
|
|
+ "incb": instructionNotYetImplemented.bind(null, "INCB"),
|
|
|
+ "hlt": insertHalt,
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * The main parsing process
|
|
|
+ *
|
|
|
+ * @param {string[][]} assembly The array of lines of assembly and their parameters
|
|
|
+ */
|
|
|
+function transpileAssembly(assembly) {
|
|
|
+ for (const line of assembly) {
|
|
|
+ const instruction = line.shift();
|
|
|
+ if (!ASM_DICTIONARY[instruction]) {
|
|
|
+ throw new Error(`No function defined for the ${instruction} instruction.`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Call the function for this instruction, and pass the rest of the line as parameters
|
|
|
+ ASM_DICTIONARY[instruction](...line);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ASSEMBLY HANDLERS */
|
|
|
+
|
|
|
+/**
|
|
|
+ * Places a value at a given address on the tape
|
|
|
+ *
|
|
|
+ * @param {number} address The address to place the value at
|
|
|
+ * @param {number} value
|
|
|
+ */
|
|
|
+function loadToAddress(address, value) {
|
|
|
+ tape[strToDecimal(address)] = strToDecimal(value);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Add an arthimetic instruction to the tape
|
|
|
+ *
|
|
|
+ * @param {string} opCode The opCode of the arthimetic to perform. Provided by the asmDictionary
|
|
|
+ * @param {string} operand1 The first operand for the operation
|
|
|
+ * @param {string} operand2 The second operand for the operation
|
|
|
+ * @param {string} outputDestination The address to put the result of the operation
|
|
|
+ */
|
|
|
+function insertArithmetic(opCode, operand1, operand2, outputDestination) {
|
|
|
+ if (!isAddress(outputDestination)) {
|
|
|
+ throw new WrongParameterModeError();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add the parameter mode for the two operands
|
|
|
+ opCode = getParameterMode(operand2) + getParameterMode(operand1) + opCode;
|
|
|
+
|
|
|
+ tape[tapePointer++] = strToDecimal(opCode);
|
|
|
+ tape[tapePointer++] = strToDecimal(operand1);
|
|
|
+ tape[tapePointer++] = strToDecimal(operand2);
|
|
|
+ tape[tapePointer++] = strToDecimal(outputDestination);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+function insertInput() {
|
|
|
+
|
|
|
+}
|
|
|
+*/
|
|
|
+
|
|
|
+/**
|
|
|
+ * Insert the output instruction onto the tape
|
|
|
+ *
|
|
|
+ * @param {string} outputParameter The parameter of the value to output. May be an immediate value or an address
|
|
|
+ */
|
|
|
+function insertOutput(outputParameter) {
|
|
|
+ tape[tapePointer++] = strToDecimal(getParameterMode(outputParameter) + OP_CODE_DICTIONARY.OUTPUT);
|
|
|
+ tape[tapePointer++] = strToDecimal(outputParameter);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Insert a jump instruction based on either a true or false test
|
|
|
+ *
|
|
|
+ * @param {"05" | "06"} opcode The opcode to execute
|
|
|
+ * @param {string} testingParameter The parameter to test. Could be an immediate value or an address
|
|
|
+ * @param {string} jumpDestination The address to jump to if the test passes
|
|
|
+ */
|
|
|
+function insertTestJump(opcode, testingParameter, jumpDestination) {
|
|
|
+ tape[tapePointer++] = strToDecimal(getParameterMode(jumpDestination) + getParameterMode(testingParameter) + opcode);
|
|
|
+ tape[tapePointer++] = strToDecimal(testingParameter);
|
|
|
+ tape[tapePointer++] = strToDecimal(jumpDestination);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Add the HALT opcode at the next address
|
|
|
+ */
|
|
|
+function insertHalt() {
|
|
|
+ tape[tapePointer++] = 99;
|
|
|
+}
|
|
|
+
|
|
|
+/* HELPER FUNCTIONS */
|
|
|
+
|
|
|
+/**
|
|
|
+ * Check if a given parameter is, or contains, a label
|
|
|
+ *
|
|
|
+ * @param {string} parameter The parameter to check
|
|
|
+ *
|
|
|
+ * @returns {boolean}
|
|
|
+ */
|
|
|
+function isLabel(parameter) {
|
|
|
+ const labelMatcher = new RegExp("\w+(?:\s*[+]\s*\d*)?");
|
|
|
+ return labelMatcher.test(parameter);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Get the memory address a value represents
|
|
|
+ *
|
|
|
+ * @param {string} parameter A 0d prefixed value
|
|
|
+ *
|
|
|
+ * @returns {string} The memory address
|
|
|
+ */
|
|
|
+function isAddress(parameter) {
|
|
|
+ return !!parameter.match(/^0d/);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Get the parameter mode of a parameter for a function
|
|
|
+ *
|
|
|
+ * @param {string|number} parameter
|
|
|
+ * @returns {"1"|"0"}
|
|
|
+ */
|
|
|
+function getParameterMode(parameter) {
|
|
|
+ return isAddress(parameter) ? "0" : "1";
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Convert a string to a base 10 integer
|
|
|
+ *
|
|
|
+ * @param {string} decimalValue
|
|
|
+ * @returns {number}
|
|
|
+ */
|
|
|
+function strToDecimal(decimalValue) {
|
|
|
+ return parseInt(decimalValue.replace(/^0d/, ""), 10);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Alert that the opcode called hasn't been implemented yet
|
|
|
+ *
|
|
|
+ * @param {string} instruction
|
|
|
+ */
|
|
|
+function instructionNotYetImplemented(instruction) {
|
|
|
+ throw new Error(`The ${instruction} instruction has not yet been implemented`);
|
|
|
+}
|
|
|
+
|
|
|
+/* MAIN FUNCTIONALITY */
|
|
|
+
|
|
|
+/**
|
|
|
+ * Read a file of IntCode Assembly
|
|
|
+ *
|
|
|
+ * @param {string} filename The name of the file to read
|
|
|
+ *
|
|
|
+ * @returns {string[][]} An array of assembly instructions
|
|
|
+ */
|
|
|
+function readFile(filename) {
|
|
|
+ let instructions = fs.readFileSync(filename, "utf8");
|
|
|
+
|
|
|
+ instructions = instructions.split("\n")
|
|
|
+ // Remove comments and trailing spaces
|
|
|
+ .map((line) => line.replace(/\s*(?:\/\/.+)?$/, ""))
|
|
|
+ // Filter out empty lines
|
|
|
+ .filter((line) => !!line)
|
|
|
+ // Split each line into an array of parameters
|
|
|
+ .map((line) => line.split(/,? /))
|
|
|
+ // Find label markers;
|
|
|
+
|
|
|
+ return Array.from(instructions);
|
|
|
+}
|
|
|
+
|
|
|
+function main() {
|
|
|
+ const ret = readFile(process.argv[2]);
|
|
|
+
|
|
|
+ console.log(typeof ret);
|
|
|
+ console.log(ret);
|
|
|
+
|
|
|
+ console.log("Parsing assembly...");
|
|
|
+
|
|
|
+ transpileAssembly(ret);
|
|
|
+
|
|
|
+ console.log("Parsed Assembly: ")
|
|
|
+ console.log(tape.join(", "));
|
|
|
+}
|
|
|
+
|
|
|
+main();
|
|
|
+
|
|
|
+class WrongParameterModeError extends Error {
|
|
|
+ constructor() {
|
|
|
+ super("This parameter for this function is in the wrong parameter mode");
|
|
|
+ }
|
|
|
+}
|