1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-07-08 23:28:55 +00:00
8bitworkshop/gen/worker/assembler.js

402 lines
14 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Assembler = void 0;
const isError = (o) => o.error !== undefined;
function hex(v, nd) {
try {
if (!nd)
nd = 2;
if (nd == 8) {
return hex((v >> 16) & 0xffff, 4) + hex(v & 0xffff, 4);
}
var s = v.toString(16).toUpperCase();
while (s.length < nd)
s = "0" + s;
return s;
}
catch (e) {
return v + "";
}
}
function stringToData(s) {
var data = [];
for (var i = 0; i < s.length; i++) {
data[i] = s.charCodeAt(i);
}
return data;
}
class Assembler {
constructor(spec) {
this.ip = 0;
this.origin = 0;
this.linenum = 0;
this.symbols = {};
this.errors = [];
this.outwords = [];
this.asmlines = [];
this.fixups = [];
this.width = 8;
this.codelen = 0;
this.aborted = false;
this.spec = spec;
if (spec) {
this.preprocessRules();
}
}
rule2regex(rule, vars) {
var s = rule.fmt;
if (!s || !(typeof s === 'string'))
throw Error('Each rule must have a "fmt" string field');
if (!rule.bits || !(rule.bits instanceof Array))
throw Error('Each rule must have a "bits" array field');
var varlist = [];
rule.prefix = s.split(/\s+/)[0];
s = s.replace(/\+/g, '\\+');
s = s.replace(/\*/g, '\\*');
s = s.replace(/\s+/g, '\\s+');
s = s.replace(/\[/g, '\\[');
s = s.replace(/\]/g, '\\]');
s = s.replace(/\(/g, '\\(');
s = s.replace(/\)/g, '\\)');
s = s.replace(/\./g, '\\.');
// TODO: more escapes?
s = s.replace(/~\w+/g, (varname) => {
varname = varname.substr(1);
var v = vars[varname];
varlist.push(varname);
if (!v)
throw Error('Could not find variable definition for "~' + varname + '"');
else if (v.toks)
return '(\\w+)';
else
return '([0-9]+|[$][0-9a-f]+|\\w+)';
});
try {
rule.re = new RegExp('^' + s + '$', 'i');
}
catch (e) {
throw Error("Bad regex for rule \"" + rule.fmt + "\": /" + s + "/ -- " + e);
}
rule.varlist = varlist;
// TODO: check rule constraints
return rule;
}
preprocessRules() {
if (this.spec.width) {
this.width = this.spec.width || 8;
}
for (var rule of this.spec.rules) {
this.rule2regex(rule, this.spec.vars);
}
}
warning(msg, line) {
this.errors.push({ msg: msg, line: line ? line : this.linenum });
}
fatal(msg, line) {
this.warning(msg, line);
this.aborted = true;
}
fatalIf(msg, line) {
if (msg)
this.fatal(msg, line);
}
addBytes(result) {
this.asmlines.push({
line: this.linenum,
offset: this.ip,
nbits: result.nbits
});
var op = result.opcode;
var nb = result.nbits / this.width;
for (var i = 0; i < nb; i++) {
if (this.width < 32)
this.outwords[this.ip++ - this.origin] = (op >> (nb - 1 - i) * this.width) & ((1 << this.width) - 1);
else
this.outwords[this.ip++ - this.origin] = op;
}
}
addWords(data) {
this.asmlines.push({
line: this.linenum,
offset: this.ip,
nbits: this.width * data.length
});
for (var i = 0; i < data.length; i++) {
if (this.width < 32)
this.outwords[this.ip++ - this.origin] = data[i] & ((1 << this.width) - 1);
else
this.outwords[this.ip++ - this.origin] = data[i];
}
}
parseData(toks) {
var data = [];
for (var i = 0; i < toks.length; i++) {
data[i] = this.parseConst(toks[i]);
}
return data;
}
alignIP(align) {
if (align < 1 || align > this.codelen)
this.fatal("Invalid alignment value");
else
this.ip = Math.floor((this.ip + align - 1) / align) * align;
}
parseConst(s, nbits) {
// TODO: check bit length
if (s && s[0] == '$')
return parseInt(s.substr(1), 16);
else
return parseInt(s);
}
swapEndian(x, nbits) {
var y = 0;
while (nbits > 0) {
var n = Math.min(nbits, this.width);
var mask = (1 << n) - 1;
y <<= n;
y |= (x & mask);
x >>>= n;
nbits -= n;
}
return y;
}
buildInstruction(rule, m) {
var opcode = 0;
var oplen = 0;
// iterate over each component of the rule output ("bits")
for (let b of rule.bits) {
let n, x;
// is a string? then it's a bit constant
// TODO
if (typeof b === "string") {
n = b.length;
x = parseInt(b, 2);
}
else {
// is it a slice {a,b,n} or just a number?
var index = typeof b === "number" ? b : b.a;
// it's an indexed variable, look up its variable
var id = m[index + 1];
var v = this.spec.vars[rule.varlist[index]];
if (!v) {
return { error: `Could not find matching identifier for '${m[0]}' index ${index}` };
}
n = v.bits;
var shift = 0;
if (typeof b !== "number") {
n = b.n;
shift = b.b;
}
// is it an enumerated type? look up the index of its keyword
if (v.toks) {
x = v.toks.indexOf(id);
if (x < 0)
return { error: "Can't use '" + id + "' here, only one of: " + v.toks.join(', ') };
}
else {
// otherwise, parse it as a constant
x = this.parseConst(id, n);
// is it a label? add fixup
if (isNaN(x)) {
this.fixups.push({
sym: id, ofs: this.ip, size: v.bits, line: this.linenum,
dstlen: n, dstofs: oplen, srcofs: shift,
endian: v.endian,
iprel: !!v.iprel, ipofs: (v.ipofs + 0), ipmul: v.ipmul || 1
});
x = 0;
}
else {
var mask = (1 << v.bits) - 1;
if ((x & mask) != x)
return { error: "Value " + x + " does not fit in " + v.bits + " bits" };
}
}
// if little endian, we need to swap ordering
if (v.endian == 'little')
x = this.swapEndian(x, v.bits);
// is it an array slice? slice the bits
if (typeof b !== "number") {
x = (x >>> shift) & ((1 << b.n) - 1);
}
}
opcode = (opcode << n) | x;
oplen += n;
}
if (oplen == 0)
this.warning("Opcode had zero length");
else if (oplen > 32)
this.warning("Opcodes > 32 bits not supported");
else if ((oplen % this.width) != 0)
this.warning("Opcode was not word-aligned (" + oplen + " bits)");
return { opcode: opcode, nbits: oplen };
}
loadArch(arch) {
if (this.loadJSON) {
var json = this.loadJSON(arch + ".json");
if (json && json.vars && json.rules) {
this.spec = json;
this.preprocessRules();
}
else {
return ("Could not load arch file '" + arch + ".json'");
}
}
}
parseDirective(tokens) {
var cmd = tokens[0].toLowerCase();
if (cmd == '.define')
this.symbols[tokens[1].toLowerCase()] = { value: tokens[2] };
else if (cmd == '.org')
this.ip = this.origin = parseInt(tokens[1]);
else if (cmd == '.len')
this.codelen = parseInt(tokens[1]);
else if (cmd == '.width')
this.width = parseInt(tokens[1]);
else if (cmd == '.arch')
this.fatalIf(this.loadArch(tokens[1]));
else if (cmd == '.include')
this.fatalIf(this.loadInclude(tokens[1]));
else if (cmd == '.module')
this.fatalIf(this.loadModule(tokens[1]));
else if (cmd == '.data')
this.addWords(this.parseData(tokens.slice(1)));
else if (cmd == '.string')
this.addWords(stringToData(tokens.slice(1).join(' ')));
else if (cmd == '.align')
this.alignIP(this.parseConst(tokens[1]));
else
this.warning("Unrecognized directive: " + tokens);
}
assemble(line) {
this.linenum++;
// remove comments
line = line.replace(/[;].*/g, '').trim();
// is it a directive?
if (line[0] == '.') {
var tokens = line.split(/\s+/);
this.parseDirective(tokens);
return;
}
// make it lowercase
line = line.toLowerCase();
// find labels
line = line.replace(/(\w+):/, (_label, label) => {
this.symbols[label] = { value: this.ip };
return ''; // replace label with blank
});
line = line.trim();
if (line == '')
return; // empty line
// look at each rule in order
if (!this.spec) {
this.fatal("Need to load .arch first");
return;
}
var lastError;
for (var rule of this.spec.rules) {
var m = rule.re.exec(line);
if (m) {
var result = this.buildInstruction(rule, m);
if (!isError(result)) {
this.addBytes(result);
return result;
}
else {
lastError = result.error;
}
}
}
this.warning(lastError ? lastError : ("Could not decode instruction: " + line));
}
applyFixup(fix, sym) {
var ofs = fix.ofs + Math.floor(fix.dstofs / this.width);
var mask = ((1 << fix.size) - 1);
var value = this.parseConst(sym.value + "", fix.dstlen);
if (fix.iprel)
value = (value - fix.ofs) * fix.ipmul - fix.ipofs;
if (fix.srcofs == 0 && (value > mask || value < -mask))
this.warning("Symbol " + fix.sym + " (" + value + ") does not fit in " + fix.dstlen + " bits", fix.line);
//console.log(hex(value,8), fix.srcofs, fix.dstofs, fix.dstlen);
if (fix.srcofs > 0)
value >>>= fix.srcofs;
value &= (1 << fix.dstlen) - 1;
// TODO: make it work for all widths
if (this.width == 32) {
var shift = 32 - fix.dstofs - fix.dstlen;
value <<= shift;
}
// TODO: check range
if (fix.size <= this.width) {
this.outwords[ofs - this.origin] ^= value;
}
else {
// swap if we want big endian (we'll apply in LSB first order)
if (fix.endian == 'big')
value = this.swapEndian(value, fix.size);
// apply multi-byte fixup
while (value) {
if (value & this.outwords[ofs - this.origin]) {
this.warning("Instruction bits overlapped: " + hex(this.outwords[ofs - this.origin], 8), hex(value, 8));
}
else {
this.outwords[ofs - this.origin] ^= value & ((1 << this.width) - 1);
}
value >>>= this.width;
ofs++;
}
}
}
finish() {
// apply fixups
for (var i = 0; i < this.fixups.length; i++) {
var fix = this.fixups[i];
var sym = this.symbols[fix.sym];
if (sym) {
this.applyFixup(fix, sym);
}
else {
this.warning("Symbol '" + fix.sym + "' not found");
}
}
// update asmlines
for (var i = 0; i < this.asmlines.length; i++) {
var al = this.asmlines[i];
al.insns = '';
for (var j = 0; j < al.nbits / this.width; j++) {
var word = this.outwords[al.offset + j - this.origin];
if (j > 0)
al.insns += ' ';
al.insns += hex(word, this.width / 4);
}
}
while (this.outwords.length < this.codelen) {
this.outwords.push(0);
}
this.fixups = [];
return this.state();
}
assembleFile(text) {
var lines = text.split(/\n/g);
for (var i = 0; i < lines.length && !this.aborted; i++) {
try {
this.assemble(lines[i]);
}
catch (e) {
console.log(e);
this.fatal("Exception during assembly: " + e);
}
}
return this.finish();
}
state() {
return { ip: this.ip, line: this.linenum, origin: this.origin, codelen: this.codelen,
intermediate: {},
output: this.outwords,
lines: this.asmlines,
errors: this.errors,
fixups: this.fixups };
}
}
exports.Assembler = Assembler;
//# sourceMappingURL=assembler.js.map