yaict 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. #!/usr/bin/node
  2. const fs = require("node:fs");
  3. /**
  4. * The tape of integers
  5. * @type {number[]}
  6. */
  7. let tape = [];
  8. /**
  9. * The current address of the tape being modified
  10. *
  11. * Generally, each function that uses the tape pointer will first
  12. * progress the pointer by one position.
  13. */
  14. let tapePointer = 0;
  15. /**
  16. * A dictionary of labels and the line numbers they start at
  17. */
  18. const labelDictionary = {};
  19. /**
  20. * A dictionary of variables created in the assembly
  21. */
  22. const variableDictionary = {};
  23. /**
  24. * The dictionary of opcodes and their values
  25. */
  26. const OP_CODE_DICTIONARY = {
  27. ADDITION: "01",
  28. MULTIPLY: "02",
  29. INPUT: "03",
  30. OUTPUT: "04",
  31. JUMP_IF_TRUE: "05",
  32. JUMP_IF_FALSE: "06",
  33. LESS_THAN: "07",
  34. EQUALS: "08",
  35. MODIFY_RELATIVE_BASE: "09",
  36. HALT: "99",
  37. };
  38. /**
  39. * Dictionary of assembly instructions and their associated functions
  40. */
  41. const ASM_DICTIONARY = {
  42. "ld": loadToAddress,
  43. "add": insertArithmetic.bind(null, OP_CODE_DICTIONARY.ADDITION),
  44. "mult": insertArithmetic.bind(null, OP_CODE_DICTIONARY.MULTIPLY),
  45. "in": instructionNotYetImplemented.bind(null, "IN"),
  46. "out": insertOutput,
  47. "jnz": insertTestJump.bind(null, OP_CODE_DICTIONARY.JUMP_IF_TRUE),
  48. "jz": insertTestJump.bind(null, OP_CODE_DICTIONARY.JUMP_IF_FALSE),
  49. "slt": insertLessThanCheck,
  50. "seq": insertEqualityCheck,
  51. "incb": instructionNotYetImplemented.bind(null, "INCB"),
  52. "hlt": insertHalt,
  53. };
  54. /**
  55. * The main parsing process
  56. *
  57. * @param {string[][]} assembly The array of lines of assembly and their parameters
  58. */
  59. function transpileAssembly(assembly) {
  60. for (const line of assembly) {
  61. const instruction = line.shift();
  62. if (!ASM_DICTIONARY[instruction]) {
  63. throw new Error(`No function defined for the ${instruction} instruction.`);
  64. }
  65. // Call the function for this instruction, and pass the rest of the line as parameters
  66. ASM_DICTIONARY[instruction](...line);
  67. }
  68. // Replace variables with address
  69. placeVariables();
  70. // Clean the values on the tape
  71. tape = tape.map((inst) => strToDecimal(inst));
  72. }
  73. /* ASSEMBLY HANDLERS */
  74. /**
  75. * Places a value at a given address on the tape
  76. *
  77. * @param {number} address The address to place the value at
  78. * @param {number} value
  79. */
  80. function loadToAddress(address, value) {
  81. if (isVariable(address)) {
  82. if (!variableDictionary[address]) {
  83. variableDictionary[address] = {
  84. value: value,
  85. address: null,
  86. };
  87. } else {
  88. variableDictionary[address]["value"] = value;
  89. }
  90. }
  91. else {
  92. tape[strToDecimal(address)] = value;
  93. }
  94. }
  95. /**
  96. * Add an arthimetic instruction to the tape
  97. *
  98. * @param {string} opCode The opCode of the arthimetic to perform. Provided by the asmDictionary
  99. * @param {string} operand1 The first operand for the operation
  100. * @param {string} operand2 The second operand for the operation
  101. * @param {string} outputDestination The address to put the result of the operation
  102. */
  103. function insertArithmetic(opCode, operand1, operand2, outputDestination) {
  104. if (!isAddress(outputDestination)) {
  105. throw new WrongParameterModeError();
  106. }
  107. // Add the parameter mode for the two operands
  108. opCode = getParameterMode(operand2) + getParameterMode(operand1) + opCode;
  109. tape[tapePointer++] = opCode;
  110. tape[tapePointer++] = operand1;
  111. tape[tapePointer++] = operand2;
  112. tape[tapePointer++] = outputDestination;
  113. }
  114. /*
  115. function insertInput() {
  116. }
  117. */
  118. /**
  119. * Insert the output instruction onto the tape
  120. *
  121. * @param {string} outputParameter The parameter of the value to output. May be an immediate value or an address
  122. */
  123. function insertOutput(outputParameter) {
  124. tape[tapePointer++] = getParameterMode(outputParameter) + OP_CODE_DICTIONARY.OUTPUT;
  125. tape[tapePointer++] = outputParameter;
  126. }
  127. /**
  128. * Insert a jump instruction based on either a true or false test
  129. *
  130. * @param {"05" | "06"} opcode The opcode to execute
  131. * @param {string} testingParameter The parameter to test. Could be an immediate value or an address
  132. * @param {string} jumpDestination The address to jump to if the test passes
  133. */
  134. function insertTestJump(opcode, testingParameter, jumpDestination) {
  135. tape[tapePointer++] = getParameterMode(jumpDestination) + getParameterMode(testingParameter) + opcode;
  136. tape[tapePointer++] = testingParameter;
  137. tape[tapePointer++] = jumpDestination;
  138. }
  139. /**
  140. * Insert a less than check onto the tape
  141. *
  142. * @param {string} operand1 The first parameter to test. Can be either an immediate value or an address
  143. * @param {string} operand2 The parameter to test against. Can be either an immediate value or an address
  144. * @param {string} outputDestination The address to write the output to
  145. */
  146. function insertLessThanCheck(operand1, operand2, outputDestination) {
  147. if (!isAddress(outputDestination)) {
  148. throw new WrongParameterModeError();
  149. }
  150. tape[tapePointer++] = getParameterMode(operand2) + getParameterMode(operand1) + OP_CODE_DICTIONARY.LESS_THAN;
  151. tape[tapePointer++] = operand1;
  152. tape[tapePointer++] = operand2;
  153. tape[tapePointer++] = outputDestination;
  154. }
  155. /**
  156. * Insert an equality check onto the tape
  157. *
  158. * @param {string} operand1 The first parameter to test. Can be either an immediate value or an address
  159. * @param {string} operand2 The parameter to test equality against. Can be either an immediate value or an address
  160. * @param {string} outputDestination The address to write the output to
  161. */
  162. function insertEqualityCheck(operand1, operand2, outputDestination) {
  163. if (!isAddress(outputDestination)) {
  164. throw new WrongParameterModeError();
  165. }
  166. tape[tapePointer++] = getParameterMode(operand2) + getParameterMode(operand1) + OP_CODE_DICTIONARY.EQUALS;
  167. tape[tapePointer++] = operand1;
  168. tape[tapePointer++] = operand2;
  169. tape[tapePointer++] = outputDestination;
  170. }
  171. /**
  172. * Add the HALT opcode at the next address
  173. */
  174. function insertHalt() {
  175. tape[tapePointer++] = 99;
  176. }
  177. /* HELPER FUNCTIONS */
  178. /**
  179. * Places variables in the dictionary on the tape
  180. *
  181. * Places the values loaded into variables at the end of the tape,
  182. * and stores those addresses.
  183. */
  184. function placeVariables() {
  185. // move the tape pointer to the end of the tape
  186. tapePointer = tape.length;
  187. for (const variable of Object.keys(variableDictionary)) {
  188. variableDictionary[variable]["address"] = ++tapePointer;
  189. tape[tapePointer] = variableDictionary[variable]["value"];
  190. }
  191. }
  192. /**
  193. * Check if a given parameter is, or contains, a label
  194. *
  195. * @param {string} parameter The parameter to check
  196. *
  197. * @returns {boolean}
  198. */
  199. function isLabel(parameter) {
  200. const labelMatcher = new RegExp("\w+(?:\s*[+]\s*\d*)?");
  201. return labelMatcher.test(parameter);
  202. }
  203. /**
  204. * Get the memory address a value represents
  205. *
  206. * @param {string} parameter A 0d prefixed value
  207. *
  208. * @returns {string} The memory address
  209. */
  210. function isAddress(parameter) {
  211. return isVariable(parameter) || !!parameter.match(/^0d/);
  212. }
  213. /**
  214. * Check if a parameter resembles a variable
  215. *
  216. * @param {any} parameter
  217. * @returns {boolean}
  218. */
  219. function isVariable(parameter) {
  220. if (typeof parameter != "string") { return false; }
  221. return !!parameter.match(/^\@/);
  222. }
  223. /**
  224. * Get the parameter mode of a parameter for a function
  225. *
  226. * @param {string|number} parameter
  227. * @returns {"1"|"0"}
  228. */
  229. function getParameterMode(parameter) {
  230. return isAddress(parameter) ? "0" : "1";
  231. }
  232. /**
  233. * Convert a string to a base 10 integer
  234. *
  235. * @param {string} decimalValue
  236. * @returns {number}
  237. */
  238. function strToDecimal(decimalValue) {
  239. if (typeof decimalValue == "number") {
  240. return decimalValue;
  241. }
  242. if (isVariable(decimalValue)) { return variableDictionary[decimalValue]["address"]; }
  243. return parseInt(decimalValue.replace(/^0d/, ""), 10);
  244. }
  245. /**
  246. * Alert that the opcode called hasn't been implemented yet
  247. *
  248. * @param {string} instruction
  249. */
  250. function instructionNotYetImplemented(instruction) {
  251. throw new Error(`The ${instruction} instruction has not yet been implemented`);
  252. }
  253. /* MAIN FUNCTIONALITY */
  254. /**
  255. * Read a file of IntCode Assembly
  256. *
  257. * @param {string} filename The name of the file to read
  258. *
  259. * @returns {string[][]} An array of assembly instructions
  260. */
  261. function readFile(filename) {
  262. let instructions = fs.readFileSync(filename, "utf8");
  263. instructions = instructions.split("\n")
  264. // Remove comments and trailing spaces
  265. .map((line) => line.replace(/\s*(?:\/\/.+)?$/, ""))
  266. // Filter out empty lines
  267. .filter((line) => !!line)
  268. // Split each line into an array of parameters
  269. .map((line) => line.split(/,? /))
  270. // Find label markers;
  271. return Array.from(instructions);
  272. }
  273. function main() {
  274. const ret = readFile(process.argv[2]);
  275. console.log(typeof ret);
  276. console.log(ret);
  277. console.log("Parsing assembly...");
  278. transpileAssembly(ret);
  279. console.log("Parsed Assembly: ")
  280. console.log(tape.join(", "));
  281. }
  282. main();
  283. class WrongParameterModeError extends Error {
  284. constructor() {
  285. super("This parameter for this function is in the wrong parameter mode");
  286. }
  287. }