3 Commits a9c27c6839 ... a20edd8b09

Author SHA1 Message Date
  Bee Hudson a20edd8b09 Add start to intcode transpiler 3 weeks ago
  Bee Hudson 1ffdb5ab02 Add more tests 3 weeks ago
  Bee Hudson 904b77a262 Remove perl file 3 weeks ago
6 changed files with 269 additions and 175 deletions
  1. 10 0
      tests/jumps.icasm
  2. 13 0
      tests/labels.icasm
  3. 4 0
      tests/multiply.icasm
  4. 4 0
      tests/output.icasm
  5. 238 0
      yaict
  6. 0 175
      yaict.pl

+ 10 - 0
tests/jumps.icasm

@@ -0,0 +1,10 @@
+// Countdown from 15 to 0 by increments of 5
+ld 0d20, 5
+ld 0d21, 15
+ld 0d22, -1
+mult 0d20, 0d22, 0d25
+jz 0d21, 14
+add 0d25, 0d21, 0d21
+jnz 1, 4
+out 0d21
+hlt

+ 13 - 0
tests/labels.icasm

@@ -0,0 +1,13 @@
+ld 0d19, 0              // jpt, jpf, eq, lt register
+ld 0d20, 10             // loop counter
+ld 0d30, 0              // lower number
+ld 0d31, 1              // higher number
+ld 0d33, 0              // storage space
+Fibonacci:
+add 0d31, 0, 0d33       // Move higher number to a storage point
+add 0d31, 0d30, 0d31    // Add higher and lower number, and store in higher number slot
+add 0d33, 0, 0d30       // Move stored number to lower number slot
+add 0d20, -1, 0d20      // Subtract 1 from the loop counter
+eq 0d20, 0, 0d19        // Check if the loop counter is 0
+jpf 0d19, Fibonacci + 1 // Loop the function if not
+hlt                     // Otherwise, exit

+ 4 - 0
tests/multiply.icasm

@@ -0,0 +1,4 @@
+ld 0d6, 15
+ld 0d7, 5
+mult 0d6, 0d7, 0d8
+hlt

+ 4 - 0
tests/output.icasm

@@ -0,0 +1,4 @@
+ld 0d10, 44
+out 21
+out 0d10
+hlt

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

+ 0 - 175
yaict.pl

@@ -1,175 +0,0 @@
-#!/bin/perl
-
-use warnings;
-use strict;
-use Data::Dumper;
-
-my $VERSION = 0.01;
-
-# The array of intcodes making up the program
-my @program;
-# A hash of labels and their corresponding starting line number
-my %labels;
-# Opcodes that may be encounted and their corresponding ints
-my %opcodes = (
-    "add"   => "01",
-    "mult"  => "02",
-    "in"    => "03",
-    "out"   => "04",
-    "jpt"   => "05",
-    "jpf"   => "06",
-    "lt"    => "07",
-    "eq"    => "08",
-    "mrb"   => "09",
-    "hlt"   => "99",
-);
-# Additional instructions that don't correspond to opcodes but need to be processed
-# my %instructions = (
-#     # Load
-#     "ld"    => \&load,
-# );
-
-# Load a value into a memory address
-sub load
-{
-    my $loadAddress = shift;
-    my $valueToLoad = shift;
-
-    $program[$loadAddress] = $valueToLoad;
-}
-
-# Parse the assembly to IntCode
-sub parseAssembly
-{
-
-    my @assembly = @_;
-    # The current instruction number
-    my $instructionNumber = 0;
-
-    foreach my $line (@assembly)
-    {
-        $line =~ /^(\w+?:?)(?: (.+))?$/;
-        my $asmInstruction = $1;
-        my @parameters = split(/,\s?/, $2 || "");
-
-        # Store labels by the address of their next instruction
-        if($asmInstruction =~ /:$/)
-        {
-            $labels{substr($asmInstruction, 0, -1)} = ++$instructionNumber;
-            next;
-        }
-
-        if($asmInstruction eq "ld")
-        {
-            my $storeAt = getAddress(shift @parameters);
-            my $value = shift @parameters;
-            load($storeAt, $value);
-        }
-        elsif($opcodes{$asmInstruction})
-        {
-            parseOpcodeAndParams($opcodes{$asmInstruction}, \@parameters, \$instructionNumber);
-        }
-        else
-        {
-            die "Failed to find matching ASM instruction for: $asmInstruction";
-        }
-    }
-}
-
-sub parseOpcodeAndParams
-{
-    # A reference to the current instruction number
-    # Need to assign first due to usage for opcode index
-    my $instructionNumber = $_[2];
-    # The opcode
-    my $opcode = $_[0];
-    # Store program index of opcode now because opcode may be modified in the parameter loop
-    my $opcodeIdx = $$instructionNumber++;
-    # Parameters for the opcode
-    my @parameters = @{ $_[1] };
-
-    # Store at is usually the last parameter, if there are any parameters
-    my $storeAt = $#parameters > 0 ? getAddress(pop(@parameters)) : -1;
-
-    # The amount of digits the opcode is expected to be when the parameter tries to add its parameter mode
-    my $parameterModeIdx = 2;
-
-    foreach my $param (@parameters)
-    {
-        if(isImmediate($param))
-        {
-            if(length($opcode) < $parameterModeIdx)
-            {
-                $opcode = "0$opcode";
-            }
-            $opcode = "1$opcode";
-        }
-        else
-        {
-            $param = getAddress($param);
-        }
-        $parameterModeIdx++;
-
-
-        $program[$$instructionNumber++] = $param;
-        if($$instructionNumber == 9)
-        {
-            print "$opcode, $param";
-            die "Overwriting memory!";
-        }
-    }
-    # Insert opcode back where it belongs
-    $program[$opcodeIdx] = $opcode;
-    if($storeAt > -1)
-    {
-        # Add the storeAt address
-        $program[$$instructionNumber++] = $storeAt;
-    }
-}
-
-# Check if a parameter is Immediate Mode or Position
-#
-# Parameter is considered Position mode if it's prefixed with "0d"
-sub isImmediate
-{
-    return shift =~ /^-?\d+$/;
-}
-
-# Check if a parameter
-sub isLabel
-{
-    $_[0] =~ /(\w+?)(?:\s*+\s*\d*)?/;
-    print "Testing: $_[0]\n";
-    return !!$labels{$1};
-}
-
-# Get the address a value represents
-sub getAddress
-{
-    return (shift =~ s/^0d//r);
-}
-
-sub readFile
-{
-    my @assembly;
-    my $filePath = shift;
-    open(my $fh, "<", $filePath) or die "Failed to open file";
-
-    while(my $line = <$fh>)
-    {
-        chomp $line;
-        # Remove comments from the line
-        $line =~ s/\/\/.+$//;
-        # Remove trailing whitespace
-        $line =~ s/\s+$//;
-        if ($line ne "") {
-            push(@assembly, $line);
-        }
-    }
-
-    return @assembly;
-}
-
-parseAssembly(readFile($ARGV[0]));
-
-print "[".join(",", @program)."]\n";