yaict 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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 boolean value to determine whether a label is waiting to receive an address
  17. */
  18. let labelStartActive = false;
  19. /**
  20. * A dictionary of labels and the line numbers they start at
  21. */
  22. const labelDictionary = {
  23. /** Stores labels that haven't had their address found yet */
  24. _toSet: undefined,
  25. };
  26. /**
  27. * A dictionary of variables created in the assembly
  28. */
  29. const variableDictionary = {};
  30. /**
  31. * The dictionary of opcodes and their values
  32. */
  33. const OP_CODE_DICTIONARY = {
  34. ADDITION: "01",
  35. MULTIPLY: "02",
  36. INPUT: "03",
  37. OUTPUT: "04",
  38. JUMP_IF_TRUE: "05",
  39. JUMP_IF_FALSE: "06",
  40. LESS_THAN: "07",
  41. EQUALS: "08",
  42. MODIFY_RELATIVE_BASE: "09",
  43. HALT: "99",
  44. };
  45. /**
  46. * Dictionary of assembly instructions and their associated functions
  47. */
  48. const ASM_DICTIONARY = {
  49. "ld": loadToAddress,
  50. "add": insertArithmetic.bind(null, OP_CODE_DICTIONARY.ADDITION),
  51. "mult": insertArithmetic.bind(null, OP_CODE_DICTIONARY.MULTIPLY),
  52. "in": instructionNotYetImplemented.bind(null, "IN"),
  53. "out": insertOutput,
  54. "jnz": insertTestJump.bind(null, OP_CODE_DICTIONARY.JUMP_IF_TRUE),
  55. "jz": insertTestJump.bind(null, OP_CODE_DICTIONARY.JUMP_IF_FALSE),
  56. "slt": insertLessThanCheck,
  57. "seq": insertEqualityCheck,
  58. "incb": instructionNotYetImplemented.bind(null, "INCB"),
  59. "hlt": insertHalt,
  60. };
  61. /**
  62. * The main parsing process
  63. *
  64. * @param {string[][]} assembly The array of lines of assembly and their parameters
  65. */
  66. function transpileAssembly(assembly) {
  67. for (const line of assembly) {
  68. let instruction = line.shift();
  69. // Check if the "instruction" is actually a label start
  70. if (isLabelStart(instruction)) {
  71. // Store the label in the dictionary, sans the colon
  72. labelDictionary._toSet = instruction.substring(0, instruction.length -1);
  73. labelStartActive = true;
  74. // If the line only contained the label start, continue to the next line
  75. if (line.length == 0) { continue; }
  76. // Otherwise, grab the next next string, which must be the instruction
  77. instruction = line.shift();
  78. }
  79. else if (!ASM_DICTIONARY[instruction]) {
  80. throw new Error(`No function defined for the ${instruction} instruction.`);
  81. }
  82. // If a label is waiting to be set, the tape pointer is currently where
  83. // the next instruction is going to be put, so we can set the label's
  84. // pointer here
  85. setLabelStart();
  86. // Call the function for this instruction, and pass the rest of the line as parameters
  87. ASM_DICTIONARY[instruction](...line);
  88. }
  89. // Replace variables with address
  90. placeVariables();
  91. // Clean the values on the tape
  92. tape = tape.map((inst) => strToDecimal(inst));
  93. }
  94. /* ASSEMBLY HANDLERS */
  95. /**
  96. * Places a value at a given address on the tape
  97. *
  98. * @param {number} address The address to place the value at
  99. * @param {number} value
  100. */
  101. function loadToAddress(address, value) {
  102. if (isVariable(address)) {
  103. if (!variableDictionary[address]) {
  104. variableDictionary[address] = {
  105. value: value,
  106. address: null,
  107. };
  108. } else {
  109. variableDictionary[address]["value"] = value;
  110. }
  111. }
  112. else {
  113. tape[strToDecimal(address)] = value;
  114. }
  115. }
  116. /**
  117. * Add an arthimetic instruction to the tape
  118. *
  119. * @param {string} opCode The opCode of the arthimetic to perform. Provided by the asmDictionary
  120. * @param {string} operand1 The first operand for the operation
  121. * @param {string} operand2 The second operand for the operation
  122. * @param {string} outputDestination The address to put the result of the operation
  123. */
  124. function insertArithmetic(opCode, operand1, operand2, outputDestination) {
  125. if (!isAddress(outputDestination)) {
  126. throw new WrongParameterModeError();
  127. }
  128. // Add the parameter mode for the two operands
  129. opCode = getParameterMode(operand2) + getParameterMode(operand1) + opCode;
  130. tape[tapePointer++] = opCode;
  131. tape[tapePointer++] = operand1;
  132. tape[tapePointer++] = operand2;
  133. tape[tapePointer++] = outputDestination;
  134. }
  135. /*
  136. function insertInput() {
  137. }
  138. */
  139. /**
  140. * Insert the output instruction onto the tape
  141. *
  142. * @param {string} outputParameter The parameter of the value to output. May be an immediate value or an address
  143. */
  144. function insertOutput(outputParameter) {
  145. tape[tapePointer++] = getParameterMode(outputParameter) + OP_CODE_DICTIONARY.OUTPUT;
  146. tape[tapePointer++] = outputParameter;
  147. }
  148. /**
  149. * Insert a jump instruction based on either a true or false test
  150. *
  151. * @param {"05" | "06"} opcode The opcode to execute
  152. * @param {string} testingParameter The parameter to test. Could be an immediate value or an address
  153. * @param {string} jumpDestination The address to jump to if the test passes
  154. */
  155. function insertTestJump(opcode, testingParameter, jumpDestination, offset) {
  156. tape[tapePointer++] = getParameterMode(jumpDestination) + getParameterMode(testingParameter) + opcode;
  157. tape[tapePointer++] = testingParameter;
  158. tape[tapePointer++] = validateJumpDestination(jumpDestination, offset);
  159. }
  160. /**
  161. * Insert a less than check onto the tape
  162. *
  163. * @param {string} operand1 The first parameter to test. Can be either an immediate value or an address
  164. * @param {string} operand2 The parameter to test against. Can be either an immediate value or an address
  165. * @param {string} outputDestination The address to write the output to
  166. */
  167. function insertLessThanCheck(operand1, operand2, outputDestination) {
  168. if (!isAddress(outputDestination)) {
  169. throw new WrongParameterModeError();
  170. }
  171. tape[tapePointer++] = getParameterMode(operand2) + getParameterMode(operand1) + OP_CODE_DICTIONARY.LESS_THAN;
  172. tape[tapePointer++] = operand1;
  173. tape[tapePointer++] = operand2;
  174. tape[tapePointer++] = outputDestination;
  175. }
  176. /**
  177. * Insert an equality check onto the tape
  178. *
  179. * @param {string} operand1 The first parameter to test. Can be either an immediate value or an address
  180. * @param {string} operand2 The parameter to test equality against. Can be either an immediate value or an address
  181. * @param {string} outputDestination The address to write the output to
  182. */
  183. function insertEqualityCheck(operand1, operand2, outputDestination) {
  184. if (!isAddress(outputDestination)) {
  185. throw new WrongParameterModeError();
  186. }
  187. tape[tapePointer++] = getParameterMode(operand2) + getParameterMode(operand1) + OP_CODE_DICTIONARY.EQUALS;
  188. tape[tapePointer++] = operand1;
  189. tape[tapePointer++] = operand2;
  190. tape[tapePointer++] = outputDestination;
  191. }
  192. /**
  193. * Add the HALT opcode at the next address
  194. */
  195. function insertHalt() {
  196. tape[tapePointer++] = 99;
  197. }
  198. /* HELPER FUNCTIONS */
  199. /**
  200. * Places variables in the dictionary on the tape
  201. *
  202. * Places the values loaded into variables at the end of the tape,
  203. * and stores those addresses.
  204. */
  205. function placeVariables() {
  206. // move the tape pointer to the end of the tape
  207. tapePointer = tape.length;
  208. for (const variable of Object.keys(variableDictionary)) {
  209. variableDictionary[variable]["address"] = ++tapePointer;
  210. tape[tapePointer] = variableDictionary[variable]["value"];
  211. }
  212. }
  213. /**
  214. * Check if a given string matches a label start definition
  215. *
  216. * @param {string} parameter The parameter to check
  217. *
  218. * @returns {boolean}
  219. */
  220. function isLabelStart(parameter) {
  221. return /^\w+:$/.test(parameter);
  222. }
  223. /**
  224. * Confirm that a label exists
  225. *
  226. * @param {string} labelName The possible label name to check
  227. *
  228. * @returns {boolean}
  229. */
  230. function validateLabel(labelName) {
  231. return labelDictionary.hasOwnProperty(labelName);
  232. }
  233. /**
  234. * Get the address to jump to
  235. *
  236. * @param {string} jumpDestination The destination to jump to. Could be either a memory address or
  237. * @param {number} [offset] An offset for the jump destination. Only used if the jump destination is a label
  238. *
  239. * @returns {string|number} The address to jump to
  240. */
  241. function validateJumpDestination(jumpDestination, offset = 0) {
  242. if (isAddress(jumpDestination)) { return jumpDestination; }
  243. if (validateLabel(jumpDestination)) {
  244. return labelDictionary[jumpDestination] + offset
  245. }
  246. return;
  247. }
  248. /**
  249. * Get the memory address a value represents
  250. *
  251. * @param {string} parameter A 0d prefixed value
  252. *
  253. * @returns {string} The memory address
  254. */
  255. function isAddress(parameter) {
  256. return isVariable(parameter) || !!parameter.match(/^0d/);
  257. }
  258. /**
  259. * Check if a parameter resembles a variable
  260. *
  261. * @param {any} parameter
  262. * @returns {boolean}
  263. */
  264. function isVariable(parameter) {
  265. if (typeof parameter != "string") { return false; }
  266. return !!parameter.match(/^\@/);
  267. }
  268. /**
  269. * Get the parameter mode of a parameter for a function
  270. *
  271. * @param {string|number} parameter
  272. * @returns {"1"|"0"}
  273. */
  274. function getParameterMode(parameter) {
  275. return isAddress(parameter) ? "0" : "1";
  276. }
  277. /**
  278. * Convert a string to a base 10 integer
  279. *
  280. * @param {string} decimalValue
  281. * @returns {number}
  282. */
  283. function strToDecimal(decimalValue) {
  284. if (typeof decimalValue == "number") {
  285. return decimalValue;
  286. }
  287. if (isVariable(decimalValue)) { return variableDictionary[decimalValue]["address"]; }
  288. return parseInt(decimalValue.replace(/^0d/, ""), 10);
  289. }
  290. /**
  291. * Alert that the opcode called hasn't been implemented yet
  292. *
  293. * @param {string} instruction
  294. */
  295. function instructionNotYetImplemented(instruction) {
  296. throw new Error(`The ${instruction} instruction has not yet been implemented`);
  297. }
  298. /**
  299. * Assigns a label waiting an address to the current pointer position
  300. *
  301. * @returns {void}
  302. */
  303. function setLabelStart() {
  304. if (labelDictionary._toSet === undefined) { return };
  305. labelDictionary[labelDictionary._toSet] = tapePointer;
  306. labelDictionary._toSet = undefined;
  307. }
  308. /* MAIN FUNCTIONALITY */
  309. /**
  310. * Read a file of IntCode Assembly
  311. *
  312. * @param {string} filename The name of the file to read
  313. *
  314. * @returns {string[][]} An array of assembly instructions
  315. */
  316. function readFile(filename) {
  317. let instructions = fs.readFileSync(filename, "utf8");
  318. instructions = instructions.split("\n")
  319. // Remove comments and trailing spaces
  320. .map((line) => line.replace(/\s*(?:\/\/.+)?$/, ""))
  321. // Filter out empty lines
  322. .filter((line) => !!line)
  323. // Split each line into an array of parameters
  324. .map((line) => line.split(/,? /))
  325. // Find label markers;
  326. return Array.from(instructions);
  327. }
  328. function main() {
  329. const ret = readFile(process.argv[2]);
  330. console.log("Assembly provided: ", ret);
  331. console.log("Parsing assembly...");
  332. transpileAssembly(ret);
  333. console.log("Parsed IntCode: ", tape.join(", "));
  334. console.log("Labels:", labelDictionary);
  335. }
  336. main();
  337. class WrongParameterModeError extends Error {
  338. constructor() {
  339. super("This parameter for this function is in the wrong parameter mode");
  340. }
  341. }