123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- #!/usr/bin/node
- const fs = require("node:fs");
- /**
- * The tape of integers
- * @type {number[]}
- */
- let 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 = {};
- /**
- * A dictionary of variables created in the assembly
- */
- const variableDictionary = {};
- /**
- * 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": insertLessThanCheck,
- "seq": insertEqualityCheck,
- "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);
- }
- // Replace variables with address
- placeVariables();
- // Clean the values on the tape
- tape = tape.map((inst) => strToDecimal(inst));
- }
- /* 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) {
- if (isVariable(address)) {
- if (!variableDictionary[address]) {
- variableDictionary[address] = {
- value: value,
- address: null,
- };
- } else {
- variableDictionary[address]["value"] = value;
- }
- }
- else {
- tape[strToDecimal(address)] = 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++] = opCode;
- tape[tapePointer++] = operand1;
- tape[tapePointer++] = operand2;
- tape[tapePointer++] = 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++] = getParameterMode(outputParameter) + OP_CODE_DICTIONARY.OUTPUT;
- tape[tapePointer++] = 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++] = getParameterMode(jumpDestination) + getParameterMode(testingParameter) + opcode;
- tape[tapePointer++] = testingParameter;
- tape[tapePointer++] = jumpDestination;
- }
- /**
- * Insert a less than check onto the tape
- *
- * @param {string} operand1 The first parameter to test. Can be either an immediate value or an address
- * @param {string} operand2 The parameter to test against. Can be either an immediate value or an address
- * @param {string} outputDestination The address to write the output to
- */
- function insertLessThanCheck(operand1, operand2, outputDestination) {
- if (!isAddress(outputDestination)) {
- throw new WrongParameterModeError();
- }
- tape[tapePointer++] = getParameterMode(operand2) + getParameterMode(operand1) + OP_CODE_DICTIONARY.LESS_THAN;
- tape[tapePointer++] = operand1;
- tape[tapePointer++] = operand2;
- tape[tapePointer++] = outputDestination;
- }
- /**
- * Insert an equality check onto the tape
- *
- * @param {string} operand1 The first parameter to test. Can be either an immediate value or an address
- * @param {string} operand2 The parameter to test equality against. Can be either an immediate value or an address
- * @param {string} outputDestination The address to write the output to
- */
- function insertEqualityCheck(operand1, operand2, outputDestination) {
- if (!isAddress(outputDestination)) {
- throw new WrongParameterModeError();
- }
- tape[tapePointer++] = getParameterMode(operand2) + getParameterMode(operand1) + OP_CODE_DICTIONARY.EQUALS;
- tape[tapePointer++] = operand1;
- tape[tapePointer++] = operand2;
- tape[tapePointer++] = outputDestination;
- }
- /**
- * Add the HALT opcode at the next address
- */
- function insertHalt() {
- tape[tapePointer++] = 99;
- }
- /* HELPER FUNCTIONS */
- /**
- * Places variables in the dictionary on the tape
- *
- * Places the values loaded into variables at the end of the tape,
- * and stores those addresses.
- */
- function placeVariables() {
- // move the tape pointer to the end of the tape
- tapePointer = tape.length;
- for (const variable of Object.keys(variableDictionary)) {
- variableDictionary[variable]["address"] = ++tapePointer;
- tape[tapePointer] = variableDictionary[variable]["value"];
- }
- }
- /**
- * 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 isVariable(parameter) || !!parameter.match(/^0d/);
- }
- /**
- * Check if a parameter resembles a variable
- *
- * @param {any} parameter
- * @returns {boolean}
- */
- function isVariable(parameter) {
- if (typeof parameter != "string") { return false; }
- return !!parameter.match(/^\@/);
- }
- /**
- * 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) {
- if (typeof decimalValue == "number") {
- return decimalValue;
- }
- if (isVariable(decimalValue)) { return variableDictionary[decimalValue]["address"]; }
- 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");
- }
- }
|