cardiograph-computer/sketches/assembler.js

92 lines
2.7 KiB
JavaScript

// 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)
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 decodeMultipleInstructions(str) {
let lines = str.split(/\n/); // returns an array of lines
let output = [];
lines.forEach( (l) => {
let decoded = decodeInstruction(l);
if (decoded) {
output.push(decoded.op);
output.push(decoded.arg);
}
});
return new Uint8Array(output);
}
/**
* @param {string} line - A line of assembly code
* @returns {(object|false)} Either {op: machineOp, arg: 0}, or false if the line was blank
*/
function decodeInstruction(line) {
line = stripWhitespaceFromEnds(stripComments(line));
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"
// Handle blank lines and mnemonics without arguments (eg END)
if (op_arg_array.length < 2) { // No argument
// handle blank lines, or lines that just contain a comment:
if (line.length === 0) { return false; }
// handle mnemonics that aren't paired with an argument:
if (mnemonicsWithOptionalArgs.indexOf(opName) < 0) {
console.error(`Missing opcode: ${line}`);
throw new Error("Missing opcode");
}
let machineOp = mnemonics2opcodes[opName][addressingMode];
return { op: machineOp, arg: 0 };
}
// Handle mnemonics with arguments (eg ADD $FF)
let arg_str = op_arg_array[1];
if (arg_str.startsWith("(")) {
addressingMode = "indirect";
arg_str = arg_str.replace("(", "");
arg_str = arg_str.replace(")", "");
}
if (arg_str.startsWith("$")) {
arg_str = arg_str.replace("$", "");
arg_num = hex2num(arg_str);
} else {
arg_num = parseInt(arg_str);
}
let machineOp = mnemonics2opcodes[opName][addressingMode];
return { op: machineOp, arg: arg_num };
}
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) };
// RUN IT
exports.assemble = (str) => {
return decodeMultipleInstructions(str);
}