Skip to content

Instantly share code, notes, and snippets.

@qxcv
Last active August 29, 2015 14:20
Show Gist options
  • Save qxcv/790bbd91b497fb408b2e to your computer and use it in GitHub Desktop.
Save qxcv/790bbd91b497fb408b2e to your computer and use it in GitHub Desktop.
MU0 assembler in Javascript
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
<title>MU0 assembler</title>
<style>
body {
font-family: sans-serif;
}
.container {
margin-left: 10%;
margin-right: 10%;
}
textarea {
width: 100%;
height: 150px;
}
</style>
<script>
var MEM_SIZE = 4096;
var OPCODES = {
lda: 0,
sto: 1,
add: 2,
sub: 3,
jmp: 4,
jge: 5,
jne: 6,
stp: 7
};
var TAKES_ARGUMENTS = {
lda: true,
sto: true,
add: true,
sub: true,
jmp: true,
jge: true,
jne: true,
stp: false
};
function inMem(addr) {
return 0 <= addr && addr < MEM_SIZE;
}
function parseLine(line) {
var labelRe = /^[a-zA-Z_]\w*$/;
var sansComment = line.replace(/;.+/, "").trim();
if (!sansComment.length)
return null;
if (sansComment.endsWith(":")) {
// it's a label or label-like thing
var contents = sansComment.slice(0, -1);
if (contents.search(/^(0x[a-fA-F0-9]+|\d+)$/) == 0) {
return {
type: "marker",
value: Number.parseInt(contents),
};
} else {
if (contents.search(labelRe) !== 0)
throw "Invalid label: " + contents;
return {
type: "label",
name: contents,
};
}
}
var splitted = sansComment.split(/\s+/);
if (!(splitted.length == 2 || splitted.length == 1))
throw "Could not parse instruction: " + line;
var toAdd = {
type: "instr",
name: splitted[0]
};
if (splitted.length == 2) {
var arg = splitted[1];
if (arg.search(/^-?(0x[a-fA-F0-9]+|\d+)$/) == 0) {
var argVal = Number.parseInt(arg);
} else {
var argVal = splitted[1];
if (argVal.search(labelRe) !== 0)
throw "Unparseable instruction: " + argVal;
}
toAdd.argument = argVal;
}
return toAdd;
}
function parseASM(asm) {
var lines = asm.split("\n");
var parsed = [];
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var result = parseLine(line);
if (result === null)
continue;
result.line = i + 1;
parsed.push(result);
}
return parsed;
}
function resolveOffsets(parsed) {
// list of instruction dicts, interspersed with nulls for
// padding
var resolvedInstrs = [];
var labels = {};
var currentWord = 0;
for (var i = 0; i < parsed.length; i++) {
var p = parsed[i];
if (p.type === "instr") {
resolvedInstrs.push(p);
currentWord++;
} else if (p.type === "label") {
labels[p.name] = currentWord;
} else if (p.type === "marker") {
if (p.value >= currentWord) {
var diff = p.value - currentWord;
for (var j = 0; j < diff; j++)
resolvedInstrs.push(null);
currentWord = p.value;
} else {
throw "Invalid marker on line " + p.line + ". Goes backwards in memory!";
}
} else {
throw "Invalid line type: " + p.type;
}
}
return [labels, resolvedInstrs];
}
function assemble(text) {
var parsed = parseASM(text);
var offsets = resolveOffsets(parsed);
var labels = offsets[0];
var paddedInstructions = offsets[1];
var rv = []; // list of integers representing instructions
for (var i = 0; i < paddedInstructions.length; i++) {
var instrData = paddedInstructions[i];
if (instrData === null) {
rv.push(0);
continue;
}
var operation = instrData.name.toLowerCase();
if (operation === 'blk') {
var arg = instrData.argument;
if (typeof(arg) !== "number") {
throw "Invalid blk argument on line " + instrData.line + ": " + arg;
}
if (arg < 0) {
// two's complement
var tc = ((~(-arg)) + 1) & 0xFFFF;
rv.push(tc);
} else {
rv.push(arg);
}
} else {
var opcode = OPCODES[operation];
if (opcode === undefined)
throw "Invalid operation on line " + instrData.line + ": " + operation;
var encoded = opcode << 12;
if (TAKES_ARGUMENTS[operation]) {
var arg = instrData.argument;
if (typeof(arg) === "string")
encoded |= labels[arg];
else
encoded |= arg;
} else if (instrData.argument !== undefined) {
throw "Unexpected arugment to unary instruction on line " + instrData.line;
}
rv.push(encoded);
}
}
return rv;
}
function toHex(instr) {
var rv = instr.toString(16);
return "0".repeat(4 - rv.length) + rv;
}
function assembleHandler() {
var asmTextarea = document.getElementById("asm");
var outputTextarea = document.getElementById("out");
try {
var instructions = assemble(asmTextarea.value);
} catch (e) {
outputTextarea.value = "ERROR: " + e;
return;
}
var output = [];
for (var i = 0; i < instructions.length; i++) {
var instr = instructions[i];
output.push(toHex(instr));
}
outputTextarea.value = output.join("\n");
}
</script>
</head>
<body>
<div class=container>
<h1>
MU0 assembler
<button style="float: right;" onclick="assembleHandler();">Assemble</button>
</h1>
<textarea id=asm>; Here's some example ASM. Press 'assemble' to turn it into a .lst file
; Anything on a line after ; is a comment
jmp my_label ; jumping to something
end:
stp
my_label: ; labels should be on lines by themselves
; you can use labels or addresses (decimal or hexadecimal) as instruction arguments, so
lda A ; this line
lda 10 ; does the same thing as this one (label A is at address 10)
lda 0xA ; which does the same thing as this one (A is at address 0xA)
add B
sto C
jmp end ; will terminate after this
0xA: ; this tells the assembler 'put the stuff that follows at address 0xA and onwards
A:
blk 5 ; stores raw data 5 in this cell
B:
blk 6 ; stores 6
C:
blk 7 ; stores 7
</textarea>
<textarea id=out>Output will appear here</textarea>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment