Переглянути джерело

Add start to intcode transpiler

At the time of this commit, the transpiler can handle ADD, MULT, OUT
, JZ, JNZ, and HLT instructions.
Bee Hudson 3 тижнів тому
батько
коміт
a20edd8b09
1 змінених файлів з 238 додано та 0 видалено
  1. 238 0
      yaict

+ 238 - 0
yaict

@@ -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");
+    }
+}