type AssemblerVar = { bits : number, toks : string[], iprel? : boolean, ipofs? : number } type AssemblerRule = { fmt : string, bits : (string | number)[], // added at runtime re? : RegExp, prefix? : string, varlist? : string[] } type AssemblerVarList = {[name:string] : AssemblerVar}; type AssemblerLine = {line:number, offset:number, nbits:number, insns?:string}; type AssemblerFixup = { sym:string, ofs:number, bitlen:number, bitofs:number, line:number, iprel:boolean, ipofs:number }; type AssemblerSpec = { name : string, width : number, vars : AssemblerVarList, rules : AssemblerRule[] } type AssemblerInstruction = {opcode:number, nbits : number}; type AssemblerLineResult = {error:string} | AssemblerInstruction; type AssemblerError = {msg:string, line:number}; type AssemblerState = { ip: number, line: number, origin: number, codelen: number, intermediate: any, output: number[], lines: AssemblerLine[], errors: AssemblerError[], fixups: AssemblerFixup[] } var Assembler = function(spec : AssemblerSpec) { var self = this; var ip = 0; var origin = 0; var linenum = 0; var symbols : {[name:string] : {value:number}} = {}; var errors : AssemblerError[] = []; var outwords : number[] = []; var asmlines : AssemblerLine[] = []; var fixups : AssemblerFixup[] = []; var width = 8; var codelen = 0; var aborted = false; function rule2regex(rule : AssemblerRule, vars : AssemblerVarList) { var s = rule.fmt; 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, '\\.'); // TODO: more escapes? s = s.replace(/~\w+/g, function(varname:string) { varname = varname.substr(1); var v = vars[varname]; varlist.push(varname); if (!v) throw Error("Could not find rule for ~" + varname); else if (v.toks) return '(\\w+)'; else return '([0-9]+|[$][0-9a-f]+|\\w+)'; }); rule.re = new RegExp('^'+s+'$', 'i'); rule.varlist = varlist; // TODO: check rule constraints return rule; } function preprocessRules() { if (spec.width) width = spec.width|0; for (var i=0; i> (nb-1-i)*width) & ((1< codelen) fatal("Invalid alignment value"); else ip = Math.floor((ip+align-1)/align)*align; } function parseConst(s:string, nbits?:number) : number { // TODO: check bit length if (s.startsWith("$")) return parseInt(s.substr(1), 16); else return parseInt(s); } self.buildInstruction = function(rule:AssemblerRule, m:string[]) : AssemblerLineResult { var opcode = 0; var oplen = 0; // iterate over each component of the rule output ("bits") for (var i=0; i 32) warning("Opcodes > 32 bits not supported"); else if ((oplen % width) != 0) warning("Opcode was not word-aligned (" + oplen + " bits)"); return {opcode:opcode, nbits:oplen}; } self.loadArch = function(arch:string) { if (self.loadJSON) { var json = self.loadJSON(arch + ".json"); if (json && json.vars && json.rules) { spec = json; preprocessRules(); } else { fatal("Could not load arch file '" + arch + ".json'"); } } } function parseDirective(tokens) { var cmd = tokens[0].toLowerCase(); if (cmd == '.define') symbols[tokens[1].toLowerCase()] = {value:tokens[2]}; else if (cmd == '.org') ip = origin = parseInt(tokens[1]); else if (cmd == '.len') codelen = parseInt(tokens[1]); else if (cmd == '.width') width = parseInt(tokens[1]); else if (cmd == '.arch') fatalIf(self.loadArch(tokens[1])); else if (cmd == '.include') fatalIf(self.loadInclude(tokens[1])); else if (cmd == '.module') fatalIf(self.loadModule(tokens[1])); else if (cmd == '.data') addWords(parseData(tokens.slice(1))); else if (cmd == '.string') addWords(stringToData(tokens.slice(1).join(' '))); else if (cmd == '.align') alignIP(parseConst(tokens[1])); else warning("Unrecognized directive: " + tokens); } self.assemble = function(line) : AssemblerInstruction { linenum++; // remove comments line = line.replace(/[;].*/g, '').trim(); // is it a directive? if (line[0] == '.') { var tokens = line.split(/\s+/); parseDirective(tokens); return; } // make it lowercase line = line.toLowerCase(); // find labels line = line.replace(/(\w+):/, function(_label, label) { symbols[label] = {value:ip}; return ''; // replace label with blank }); line = line.trim(); if (line == '') return; // empty line // look at each rule in order if (!spec) { fatal("Need to load .arch first"); return; } var lastError; for (var i=0; i mask || value < -mask) warning("Symbol " + fix.sym + " (" + value + ") does not fit in " + fix.bitlen + " bits", fix.line); value &= mask; // TODO: check range // TODO: span multiple words? outwords[ofs - origin] ^= value; // TODO: << shift? } else { warning("Symbol '" + fix.sym + "' not found"); } } // update asmlines for (var i=0; i0) al.insns += ' '; al.insns += hex(word,width/4); } } while (outwords.length < codelen) { outwords.push(0); } fixups = []; return self.state(); } self.assembleFile = function(text) : AssemblerState { var lines = text.split(/\n/g); for (var i=0; i