Last active
August 29, 2015 14:20
-
-
Save qxcv/790bbd91b497fb408b2e to your computer and use it in GitHub Desktop.
MU0 assembler in Javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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