// Syntax: // ADD $01 ; comments follow a `;` // ADD $FF ; this is direct addressing // ADD ($CC) ; this is indirect addressing // END ; END, CFC, and CHP don't require arguments // ; (a default value of 0 will be used as their operand) // // @label ; create a label // JMP @label ; reference a label const printMemory = require('./print-memory.js'); // 0 = silent // 1 = verbose // 2 = what i'm currently focusing on // 3 = always print const DEBUG = 2; const INITIAL_IP_ADDRESS = 32; const mnemonicsWithOptionalArgs = ['end', 'cfc', 'chp']; const mnemonics2opcodes = { end: { direct: 0, indirect: 0 }, sto: { direct: 1, indirect: 2 }, lda: { direct: 3, indirect: 4 }, add: { direct: 5, indirect: 6 }, sub: { direct: 7, indirect: 8 }, hop: { direct: 9, indirect: 10 }, jmp: { direct: 11, indirect: 12 }, cfc: { direct: 13, indirect: 13 }, chp: { direct: 14, indirect: 14 }, }; function decodeInstructions(str) { let lines = str.split(/\n/); // returns an array of lines let machineCode = new Array(INITIAL_IP_ADDRESS).fill(0); let labels = {}; let byteCursor = INITIAL_IP_ADDRESS; for (let i = 0; i < lines.length; i++) { console.log(); console.group(`Input line ${i}, cursor ${byteCursor}`); dbg(3, `> ${lines[i]}`); let line = stripWhitespaceFromEnds(stripComments(lines[i])); // Handle blank lines if (line.length === 0) { dbg(3, `cursor: ${byteCursor}, new code: none`); dbg(1, 'blank'); console.groupEnd('Input line'); continue; } // Handle labels -- anchors if (line.startsWith('@')) { // TODO: validate label // validateLabel(line); label = line.substring(1); // strip '@' if (label in labels) { labels[label].pointsToByte = byteCursor; } else { labels[label] = { pointsToByte: byteCursor, bytesToReplace: [], }; } dbg(2, `pointsToByte: ${labels[label].pointsToByte}`); dbg(2, `bytesToReplace: ${labels[label].bytesToReplace}`); dbg(3, `cursor: ${byteCursor}, new code: none`); console.groupEnd('Input line'); continue; } let op_arg_array = line.split(" "); // split line into an array of [op, arg] let opName = op_arg_array[0].toLowerCase(); let addressingMode = 'direct'; // Must be "direct" or "indirect" let arg_str = op_arg_array[1]; let arg_num = null; if (typeof arg_str === 'undefined') { // Handle mnemonics without arguments (eg END) ... if (mnemonicsWithOptionalArgs.indexOf(opName) < 0) { console.error(`Missing opcode: ${line}`); throw new Error("Missing opcode"); } arg_num = 0; } else if (arg_str.startsWith('@')) { // Handle mnemonics with pointers to labels // TODO: validate label // validateLabel(line); label = arg_str.substring(1); // strip '@' arg_num = 0; if (label in labels) { dbg(1, `'${label}' already in labels object`); labels[label].bytesToReplace.push(byteCursor + 1); } else { dbg(1, `'${label}' NOT in labels object`); labels[label] = { bytesToReplace: [byteCursor + 1], }; } dbg(2, `pointsToByte: ${labels[label].pointsToByte}`); dbg(2, `bytesToReplace: ${labels[label].bytesToReplace}`); } else if (arg_str.startsWith("(")) { // Handle indirect expressions addressingMode = "indirect"; arg_str = arg_str.replace("(", ""); arg_str = arg_str.replace(")", ""); arg_num = parseInt(arg_str); } else if (arg_str.startsWith("$")) { // Handle direct expressions arg_str = arg_str.replace("$", ""); arg_num = hex2num(arg_str); } else { // Accept decimal i guess arg_num = parseInt(arg_str); } // Decode! op = mnemonics2opcodes[opName][addressingMode]; machineCode.push(op); machineCode.push(arg_num); byteCursor += 2; dbg(3, `cursor: ${byteCursor}, new code: ${op}, ${arg_num}`); console.groupEnd('Input line'); }; dbg(1, ''); dbgGroup(1, 'Memory before filling in label pointers'); dbgExec(1, () => printMemory.printTable(machineCode)); dbgGroupEnd(1, 'Memory before filling in label pointers'); // Backfill label pointers for (let k of Object.keys(labels)) { dbgGroup(2, `@${k}`); let label = labels[k]; dbg(2, `pointsToByte: ${label.pointsToByte}`); dbg(2, `bytesToReplace: ${label.bytesToReplace}`); dbgGroupEnd(2, `label`); for (let j = 0; j < label.bytesToReplace.length; j++) { machineCode[label.bytesToReplace[j]] = label.pointsToByte; } } return new Uint8Array(machineCode); } function stripComments(line) { return line.replace(/;.+/,""); } function stripWhitespaceFromEnds(line) { line = line.replace(/^\s+/,""); line = line.replace(/\s+$/,""); return line; } function hex2num(hex) { return parseInt(hex, 16) }; // Debug helpers const dbg = (lvl, s) => { if (lvl >= DEBUG) console.log(s) }; const dbgGroup = (lvl, s) => { if (lvl >= DEBUG) console.group(s) }; const dbgGroupEnd = (lvl, s) => { if (lvl >= DEBUG) console.groupEnd(s) }; const dbgExec = (lvl, func) => { if (lvl >= DEBUG) func(); } // RUN IT exports.assemble = (str) => { return decodeInstructions(str); }