diff --git a/src/common/hdl/vxmlparser.ts b/src/common/hdl/vxmlparser.ts index 12489669..299a09a1 100644 --- a/src/common/hdl/vxmlparser.ts +++ b/src/common/hdl/vxmlparser.ts @@ -1,5 +1,5 @@ -import { HDLError } from "./hdlruntime"; +import { parseXMLPoorly, XMLNode } from "../util"; import { HDLAlwaysBlock, HDLArrayItem, HDLBinop, HDLBlock, HDLConstant, HDLDataType, HDLDataTypeObject, HDLExpr, HDLExtendop, HDLFile, HDLFuncCall, HDLHierarchyDef, HDLInstanceDef, HDLLogicType, HDLModuleDef, HDLNativeType, HDLPort, HDLSensItem, HDLSourceLocation, HDLSourceObject, HDLTriop, HDLUnit, HDLUnop, HDLUnpackArray, HDLValue, HDLVariableDef, HDLVarRef, HDLWhileOp, isArrayType, isBinop, isBlock, isConstExpr, isFuncCall, isLogicType, isTriop, isUnop, isVarDecl, isVarRef } from "./hdltypes"; /** @@ -26,80 +26,6 @@ import { HDLAlwaysBlock, HDLArrayItem, HDLBinop, HDLBlock, HDLConstant, HDLDataT } } -interface XMLNode { - type: string; - text: string | null; - children: XMLNode[]; - attrs: { [id: string]: string }; - obj: any; -} - -type XMLVisitFunction = (node: XMLNode) => any; - -function escapeXML(s: string): string { - if (s.indexOf('&') >= 0) { - return s.replace(/'/g, "'") - .replace(/"/g, '"') - .replace(/>/g, '>') - .replace(/</g, '<') - .replace(/&/g, '&'); - } else { - return s; - } -} - -function parseXMLPoorly(s: string, openfn?: XMLVisitFunction, closefn?: XMLVisitFunction): XMLNode { - const tag_re = /[<]([/]?)([?a-z_-]+)([^>]*)[>]+|(\s*[^<]+)/gi; - const attr_re = /\s*(\w+)="(.*?)"\s*/gi; - var fm: RegExpMatchArray; - var stack: XMLNode[] = []; - var top: XMLNode; - - function closetop() { - top = stack.pop(); - if (top == null || top.type != ident) throw new CompileError(null, "mismatch close tag: " + ident); - if (closefn) { - top.obj = closefn(top); - } - if (stack.length == 0) throw new CompileError(null, "close tag without open: " + ident); - stack[stack.length - 1].children.push(top); - } - function parseattrs(as: string): { [id: string]: string } { - var am; - var attrs = {}; - if (as != null) { - while (am = attr_re.exec(as)) { - attrs[am[1]] = escapeXML(am[2]); - } - } - return attrs; - } - while (fm = tag_re.exec(s)) { - var [_m0, close, ident, attrs, content] = fm; - //console.log(stack.length, close, ident, attrs, content); - if (close) { - closetop(); - } else if (ident) { - var node = { type: ident, text: null, children: [], attrs: parseattrs(attrs), obj: null }; - stack.push(node); - if (attrs) { - parseattrs(attrs); - } - if (openfn) { - node.obj = openfn(node); - } - if (attrs && attrs.endsWith('/')) closetop(); - } else if (content != null) { - if (stack.length == 0) throw new CompileError(null, "content without element"); - var txt = escapeXML(content as string).trim(); - if (txt.length) stack[stack.length - 1].text = txt; - } - } - if (stack.length != 1) throw new CompileError(null, "tag not closed"); - if (stack[0].type != '?xml') throw new CompileError(null, "?xml needs to be first element"); - return top; -} - export class VerilogXMLParser implements HDLUnit { files: { [id: string]: HDLFile } = {}; diff --git a/src/common/util.ts b/src/common/util.ts index 18995598..680ac4de 100644 --- a/src/common/util.ts +++ b/src/common/util.ts @@ -538,3 +538,83 @@ export function parseBool(s : string) : boolean { if (s == 'true' || s == '1') return true; return s ? true : false; } + +/// + +export class XMLParseError extends Error { +} + +export interface XMLNode { + type: string; + text: string | null; + children: XMLNode[]; + attrs: { [id: string]: string }; + obj: any; +} + +export type XMLVisitFunction = (node: XMLNode) => any; + +function escapeXML(s: string): string { + if (s.indexOf('&') >= 0) { + return s.replace(/'/g, "'") + .replace(/"/g, '"') + .replace(/>/g, '>') + .replace(/</g, '<') + .replace(/&/g, '&'); + } else { + return s; + } +} + +export function parseXMLPoorly(s: string, openfn?: XMLVisitFunction, closefn?: XMLVisitFunction): XMLNode { + const tag_re = /[<]([/]?)([?a-z_-]+)([^>]*)[>]+|(\s*[^<]+)/gi; + const attr_re = /\s*(\w+)="(.*?)"\s*/gi; + var fm: RegExpMatchArray; + var stack: XMLNode[] = []; + var top: XMLNode; + + function closetop() { + top = stack.pop(); + if (top == null || top.type != ident) throw new XMLParseError("mismatch close tag: " + ident); + if (closefn) { + top.obj = closefn(top); + } + if (stack.length == 0) throw new XMLParseError("close tag without open: " + ident); + stack[stack.length - 1].children.push(top); + } + function parseattrs(as: string): { [id: string]: string } { + var am; + var attrs = {}; + if (as != null) { + while (am = attr_re.exec(as)) { + attrs[am[1]] = escapeXML(am[2]); + } + } + return attrs; + } + while (fm = tag_re.exec(s)) { + var [_m0, close, ident, attrs, content] = fm; + //console.log(stack.length, close, ident, attrs, content); + if (close) { + closetop(); + } else if (ident) { + var node = { type: ident, text: null, children: [], attrs: parseattrs(attrs), obj: null }; + stack.push(node); + if (attrs) { + parseattrs(attrs); + } + if (openfn) { + node.obj = openfn(node); + } + if (attrs && attrs.endsWith('/')) closetop(); + } else if (content != null) { + if (stack.length == 0) throw new XMLParseError("content without element"); + var txt = escapeXML(content as string).trim(); + if (txt.length) stack[stack.length - 1].text = txt; + } + } + if (stack.length != 1) throw new XMLParseError("tag not closed"); + if (stack[0].type != '?xml') throw new XMLParseError("?xml needs to be first element"); + return top; +} + diff --git a/src/worker/tools/arm.ts b/src/worker/tools/arm.ts new file mode 100644 index 00000000..b51bab66 --- /dev/null +++ b/src/worker/tools/arm.ts @@ -0,0 +1,239 @@ + +import { hex } from "../../common/util"; +import { WorkerResult, CodeListingMap, WorkerError, SourceLine } from "../../common/workertypes"; +import { anyTargetChanged, BuildStep, BuildStepResult, emglobal, EmscriptenModule, execMain, gatherFiles, getPrefix, getWorkFileAsString, loadNative, makeErrorMatcher, moduleInstFn, populateFiles, putWorkFile, re_crlf, staleFiles } from "../workermain" + +export function assembleARMIPS(step: BuildStep): WorkerResult { + loadNative("armips"); + var errors = []; + gatherFiles(step, { mainFilePath: "main.asm" }); + var objpath = "main.bin"; + var lstpath = step.prefix + ".lst"; + var sympath = step.prefix + ".sym"; + //test.armips(3) error: Parse error '.arm' + var error_fn = makeErrorMatcher(errors, /^(.+?)\((\d+)\)\s+(fatal error|error|warning):\s+(.+)/, 2, 4, step.path, 1); + + if (staleFiles(step, [objpath])) { + var args = [step.path, '-temp', lstpath, '-sym', sympath, '-erroronwarning']; + var armips: EmscriptenModule = emglobal.armips({ + instantiateWasm: moduleInstFn('armips'), + noInitialRun: true, + print: error_fn, + printErr: error_fn, + }); + + var FS = armips.FS; + var code = getWorkFileAsString(step.path); + code = `.arm.little :: .create "${objpath}",0 :: ${code} + .close`; + putWorkFile(step.path, code); + populateFiles(step, FS); + execMain(step, armips, args); + if (errors.length) + return { errors: errors }; + + var objout = FS.readFile(objpath, { encoding: 'binary' }) as Uint8Array; + putWorkFile(objpath, objout); + if (!anyTargetChanged(step, [objpath])) + return; + + var symbolmap = {}; + var segments = []; + var listings: CodeListingMap = {}; + var lstout = FS.readFile(lstpath, { encoding: 'utf8' }) as string; + var lines = lstout.split(re_crlf); + //00000034 .word 0x11223344 ; /vidfill.armips line 25 + var re_asmline = /^([0-9A-F]+) (.+?); [/](.+?) line (\d+)/; + var lastofs = -1; + for (var line of lines) { + var m; + if (m = re_asmline.exec(line)) { + var path = m[3]; + var path2 = getPrefix(path) + '.lst'; // TODO: don't rename listing + var lst = listings[path2]; + if (lst == null) { lst = listings[path2] = { lines: [] }; } + var ofs = parseInt(m[1], 16); + if (lastofs == ofs) { + lst.lines.pop(); // get rid of duplicate offset + } else if (ofs > lastofs) { + var lastline = lst.lines[lst.lines.length - 1]; + if (lastline && !lastline.insns) { + var insns = objout.slice(lastofs, ofs).reverse(); + lastline.insns = Array.from(insns).map((b) => hex(b, 2)).join(''); + } + } + lst.lines.push({ + path: path, + line: parseInt(m[4]), + offset: ofs + }); + lastofs = ofs; + } + } + //listings[lstpath] = {lines:lstlines, text:lstout}; + + var symout = FS.readFile(sympath, { encoding: 'utf8' }) as string; + //0000000C loop2 + //00000034 .dbl:0004 + var re_symline = /^([0-9A-F]+)\s+(.+)/; + for (var line of symout.split(re_crlf)) { + var m; + if (m = re_symline.exec(line)) { + symbolmap[m[2]] = parseInt(m[1], 16); + } + } + + return { + output: objout, //.slice(0), + listings: listings, + errors: errors, + symbolmap: symbolmap, + segments: segments + }; + } +} + +export function assembleVASMARM(step: BuildStep): BuildStepResult { + loadNative("vasmarm_std"); + /// error 2 in line 8 of "gfxtest.c": unknown mnemonic + /// error 3007: undefined symbol + /// TODO: match undefined symbols + var re_err1 = /^(fatal error|error|warning)? (\d+) in line (\d+) of "(.+)": (.+)/; + var re_err2 = /^(fatal error|error|warning)? (\d+): (.+)/; + var re_undefsym = /symbol <(.+?)>/; + var errors: WorkerError[] = []; + var undefsyms = []; + function findUndefinedSymbols(line: string) { + // find undefined symbols in line + undefsyms.forEach((sym) => { + if (line.indexOf(sym) >= 0) { + errors.push({ + path: curpath, + line: curline, + msg: "Undefined symbol: " + sym, + }) + } + }); + } + function match_fn(s) { + let matches = re_err1.exec(s); + if (matches) { + errors.push({ + line: parseInt(matches[3]), + path: matches[4], + msg: matches[5], + }); + } else { + matches = re_err2.exec(s); + if (matches) { + let m = re_undefsym.exec(matches[3]); + if (m) { + undefsyms.push(m[1]); + } else { + errors.push({ + line: 0, + msg: s, + }); + } + } else { + console.log(s); + } + } + } + + gatherFiles(step, { mainFilePath: "main.asm" }); + var objpath = step.prefix + ".bin"; + var lstpath = step.prefix + ".lst"; + + if (staleFiles(step, [objpath])) { + var args = ['-Fbin', '-m7tdmi', '-x', '-wfail', step.path, '-o', objpath, '-L', lstpath]; + var vasm: EmscriptenModule = emglobal.vasm({ + instantiateWasm: moduleInstFn('vasmarm_std'), + noInitialRun: true, + print: match_fn, + printErr: match_fn, + }); + + var FS = vasm.FS; + populateFiles(step, FS); + execMain(step, vasm, args); + if (errors.length) { + return { errors: errors }; + } + + if (undefsyms.length == 0) { + var objout = FS.readFile(objpath, { encoding: 'binary' }); + putWorkFile(objpath, objout); + if (!anyTargetChanged(step, [objpath])) + return; + } + + var lstout = FS.readFile(lstpath, { encoding: 'utf8' }); + // 00:00000018 023020E0 14: eor r3, r0, r2 + // Source: "vidfill.vasm" + // 00: ".text" (0-40) + // LOOP 00:00000018 + // STACK S:20010000 + var symbolmap = {}; + var segments = []; // TODO + var listings: CodeListingMap = {}; + // TODO: parse listings + var re_asmline = /^(\d+):([0-9A-F]+)\s+([0-9A-F ]+)\s+(\d+)([:M])/; + var re_secline = /^(\d+):\s+"(.+)"/; + var re_nameline = /^Source:\s+"(.+)"/; + var re_symline = /^(\w+)\s+(\d+):([0-9A-F]+)/; + var re_emptyline = /^\s+(\d+)([:M])/; + var curpath = step.path; + var curline = 0; + var sections = {}; + // map file and section indices -> names + var lines: string[] = lstout.split(re_crlf); + // parse lines + var lstlines: SourceLine[] = []; + for (var line of lines) { + var m; + if (m = re_secline.exec(line)) { + sections[m[1]] = m[2]; + } else if (m = re_nameline.exec(line)) { + curpath = m[1]; + } else if (m = re_symline.exec(line)) { + symbolmap[m[1]] = parseInt(m[3], 16); + } else if (m = re_asmline.exec(line)) { + if (m[5] == ':') { + curline = parseInt(m[4]); + } else { + // TODO: macro line + } + lstlines.push({ + path: curpath, + line: curline, + offset: parseInt(m[2], 16), + insns: m[3].replaceAll(' ', '') + }); + findUndefinedSymbols(line); + } else if (m = re_emptyline.exec(line)) { + curline = parseInt(m[1]); + findUndefinedSymbols(line); + } else { + //console.log(line); + } + } + listings[lstpath] = { lines: lstlines, text: lstout }; + // catch-all if no error generated + if (undefsyms.length && errors.length == 0) { + errors.push({ + line: 0, + msg: 'Undefined symbols: ' + undefsyms.join(', ') + }) + } + + return { + output: objout, //.slice(0x34), + listings: listings, + errors: errors, + symbolmap: symbolmap, + segments: segments + }; + } +} + diff --git a/src/worker/tools/cc65.ts b/src/worker/tools/cc65.ts new file mode 100644 index 00000000..99c58039 --- /dev/null +++ b/src/worker/tools/cc65.ts @@ -0,0 +1,303 @@ + +import { getRootBasePlatform } from "../../common/util"; +import { CodeListingMap, WorkerError } from "../../common/workertypes"; +import { re_crlf, BuildStepResult, anyTargetChanged, execMain, gatherFiles, msvcErrorMatcher, populateEntry, populateExtraFiles, populateFiles, print_fn, putWorkFile, setupFS, staleFiles, BuildStep, emglobal, loadNative, moduleInstFn, fixParamsWithDefines, store } from "../workermain"; +import { EmscriptenModule } from "../workermain" + + +/* +000000r 1 .segment "CODE" +000000r 1 .proc _rasterWait: near +000000r 1 ; int main() { return mul2(2); } +000000r 1 .dbg line, "main.c", 3 +000014r 1 .dbg func, "main", "00", extern, "_main" +000000r 1 A2 00 ldx #$00 +00B700 1 BOOT2: +00B700 1 A2 01 ldx #1 ;track +00B725 1 00 IBLASTDRVN: .byte 0 +00B726 1 xx xx IBSECSZ: .res 2 +00BA2F 1 2A 2B E8 2C HEX "2A2BE82C2D2E2F303132F0F133343536" +*/ +function parseCA65Listing(code, symbols, params, dbg) { + var segofs = 0; + var offset = 0; + var dbgLineMatch = /^([0-9A-F]+)([r]?)\s+(\d+)\s+[.]dbg\s+(\w+), "([^"]+)", (.+)/; + var funcLineMatch = /"(\w+)", (\w+), "(\w+)"/; + var insnLineMatch = /^([0-9A-F]+)([r]?)\s{1,2}(\d+)\s{1,2}([0-9A-Frx ]{11})\s+(.*)/; + var segMatch = /[.]segment\s+"(\w+)"/i; + var lines = []; + var linenum = 0; + // TODO: only does .c functions, not all .s files + for (var line of code.split(re_crlf)) { + var dbgm = dbgLineMatch.exec(line); + if (dbgm && dbgm[1]) { + var dbgtype = dbgm[4]; + offset = parseInt(dbgm[1], 16); + if (dbgtype == 'func') { + var funcm = funcLineMatch.exec(dbgm[6]); + if (funcm) { + var funcofs = symbols[funcm[3]]; + if (typeof funcofs === 'number') { + segofs = funcofs - offset; + //console.log(funcm[3], funcofs, '-', offset); + } + } + } + } + if (dbg) { + if (dbgm && dbgtype == 'line') { + lines.push({ + // TODO: sourcefile + line: parseInt(dbgm[6]), + offset: offset + segofs, + insns: null + }); + } + } else { + var linem = insnLineMatch.exec(line); + var topfile = linem && linem[3] == '1'; + if (topfile) linenum++; + if (topfile && linem[1]) { + var offset = parseInt(linem[1], 16); + var insns = linem[4].trim(); + if (insns.length) { + // take back one to honor the long .byte line + if (linem[5].length == 0) { + linenum--; + } else { + lines.push({ + line: linenum, + offset: offset + segofs, + insns: insns, + iscode: true // TODO: can't really tell unless we parse it + }); + } + } else { + var sym = linem[5]; + var segm = sym && segMatch.exec(sym); + if (segm && segm[1]) { + var symofs = symbols['__' + segm[1] + '_RUN__']; + if (typeof symofs === 'number') { + segofs = symofs; + //console.log(sym, symofs, '-', offset); + } + } else if (sym.endsWith(':') && !sym.startsWith('@')) { + var symofs = symbols[sym.substring(0, sym.length - 1)]; + if (typeof symofs === 'number') { + segofs = symofs - offset; + //console.log(sym, segofs, symofs, offset); + } + } + } + } + } + } + return lines; +} + +export function assembleCA65(step: BuildStep): BuildStepResult { + loadNative("ca65"); + var errors = []; + gatherFiles(step, { mainFilePath: "main.s" }); + var objpath = step.prefix + ".o"; + var lstpath = step.prefix + ".lst"; + if (staleFiles(step, [objpath, lstpath])) { + var objout, lstout; + var CA65: EmscriptenModule = emglobal.ca65({ + instantiateWasm: moduleInstFn('ca65'), + noInitialRun: true, + //logReadFiles:true, + print: print_fn, + printErr: msvcErrorMatcher(errors), + }); + var FS = CA65.FS; + setupFS(FS, '65-' + getRootBasePlatform(step.platform)); + populateFiles(step, FS); + fixParamsWithDefines(step.path, step.params); + var args = ['-v', '-g', '-I', '/share/asminc', '-o', objpath, '-l', lstpath, step.path]; + args.unshift.apply(args, ["-D", "__8BITWORKSHOP__=1"]); + if (step.mainfile) { + args.unshift.apply(args, ["-D", "__MAIN__=1"]); + } + execMain(step, CA65, args); + if (errors.length) + return { errors: errors }; + objout = FS.readFile(objpath, { encoding: 'binary' }); + lstout = FS.readFile(lstpath, { encoding: 'utf8' }); + putWorkFile(objpath, objout); + putWorkFile(lstpath, lstout); + } + return { + linktool: "ld65", + files: [objpath, lstpath], + args: [objpath] + }; +} + +export function linkLD65(step: BuildStep): BuildStepResult { + loadNative("ld65"); + var params = step.params; + gatherFiles(step); + var binpath = "main"; + if (staleFiles(step, [binpath])) { + var errors = []; + var LD65: EmscriptenModule = emglobal.ld65({ + instantiateWasm: moduleInstFn('ld65'), + noInitialRun: true, + //logReadFiles:true, + print: print_fn, + printErr: function (s) { errors.push({ msg: s, line: 0 }); } + }); + var FS = LD65.FS; + setupFS(FS, '65-' + getRootBasePlatform(step.platform)); + populateFiles(step, FS); + populateExtraFiles(step, FS, params.extra_link_files); + // populate .cfg file, if it is a custom one + if (store.hasFile(params.cfgfile)) { + populateEntry(FS, params.cfgfile, store.getFileEntry(params.cfgfile), null); + } + var libargs = params.libargs || []; + var cfgfile = params.cfgfile; + var args = ['--cfg-path', '/share/cfg', + '--lib-path', '/share/lib', + '-C', cfgfile, + '-Ln', 'main.vice', + //'--dbgfile', 'main.dbg', // TODO: get proper line numbers + '-o', 'main', '-m', 'main.map'].concat(step.args, libargs); + //console.log(args); + execMain(step, LD65, args); + if (errors.length) + return { errors: errors }; + var aout = FS.readFile("main", { encoding: 'binary' }); + var mapout = FS.readFile("main.map", { encoding: 'utf8' }); + var viceout = FS.readFile("main.vice", { encoding: 'utf8' }); + //var dbgout = FS.readFile("main.dbg", {encoding:'utf8'}); + putWorkFile("main", aout); + putWorkFile("main.map", mapout); + putWorkFile("main.vice", viceout); + // return unchanged if no files changed + if (!anyTargetChanged(step, ["main", "main.map", "main.vice"])) + return; + // parse symbol map (TODO: omit segments, constants) + var symbolmap = {}; + for (var s of viceout.split("\n")) { + var toks = s.split(" "); + if (toks[0] == 'al') { + let ident = toks[2].substr(1); + if (ident.length != 5 || !ident.startsWith('L')) { // no line numbers + let ofs = parseInt(toks[1], 16); + symbolmap[ident] = ofs; + } + } + } + // build segment map + var seg_re = /^__(\w+)_SIZE__$/; + // TODO: move to Platform class + var segments = []; + segments.push({ name: 'CPU Stack', start: 0x100, size: 0x100, type: 'ram' }); + segments.push({ name: 'CPU Vectors', start: 0xfffa, size: 0x6, type: 'rom' }); + // TODO: CHR, banks, etc + for (let ident in symbolmap) { + let m = seg_re.exec(ident); + if (m) { + let seg = m[1]; + let segstart = symbolmap['__' + seg + '_RUN__'] || symbolmap['__' + seg + '_START__']; + let segsize = symbolmap['__' + seg + '_SIZE__']; + let seglast = symbolmap['__' + seg + '_LAST__']; + if (segstart >= 0 && segsize > 0 && !seg.startsWith('PRG') && seg != 'RAM') { // TODO + var type = null; + if (seg.startsWith('CODE') || seg == 'STARTUP' || seg == 'RODATA' || seg.endsWith('ROM')) type = 'rom'; + else if (seg == 'ZP' || seg == 'DATA' || seg == 'BSS' || seg.endsWith('RAM')) type = 'ram'; + segments.push({ name: seg, start: segstart, size: segsize, last: seglast, type: type }); + } + } + } + // build listings + var listings: CodeListingMap = {}; + for (var fn of step.files) { + if (fn.endsWith('.lst')) { + var lstout = FS.readFile(fn, { encoding: 'utf8' }); + lstout = lstout.split('\n\n')[1] || lstout; // remove header + var asmlines = parseCA65Listing(lstout, symbolmap, params, false); + var srclines = parseCA65Listing(lstout, symbolmap, params, true); + putWorkFile(fn, lstout); + // TODO: you have to get rid of all source lines to get asm listing + listings[fn] = { + asmlines: srclines.length ? asmlines : null, + lines: srclines.length ? srclines : asmlines, + text: lstout + }; + } + } + return { + output: aout, //.slice(0), + listings: listings, + errors: errors, + symbolmap: symbolmap, + segments: segments + }; + } +} + +export function compileCC65(step: BuildStep): BuildStepResult { + loadNative("cc65"); + var params = step.params; + // stderr + var re_err1 = /(.*?)[(](\d+)[)].*?: (.+)/; + var errors: WorkerError[] = []; + var errline = 0; + function match_fn(s) { + console.log(s); + var matches = re_err1.exec(s); + if (matches) { + errline = parseInt(matches[2]); + errors.push({ + line: errline, + msg: matches[3], + path: matches[1] + }); + } + } + gatherFiles(step, { mainFilePath: "main.c" }); + var destpath = step.prefix + '.s'; + if (staleFiles(step, [destpath])) { + var CC65: EmscriptenModule = emglobal.cc65({ + instantiateWasm: moduleInstFn('cc65'), + noInitialRun: true, + //logReadFiles:true, + print: print_fn, + printErr: match_fn, + }); + var FS = CC65.FS; + setupFS(FS, '65-' + getRootBasePlatform(step.platform)); + populateFiles(step, FS); + fixParamsWithDefines(step.path, params); + var args = [ + '-I', '/share/include', + '-I', '.', + "-D", "__8BITWORKSHOP__", + ]; + if (params.define) { + params.define.forEach((x) => args.push('-D' + x)); + } + if (step.mainfile) { + args.unshift.apply(args, ["-D", "__MAIN__"]); + } + var customArgs = params.extra_compiler_args || ['-T', '-g', '-Oirs', '-Cl']; + args = args.concat(customArgs, args); + args.push(step.path); + //console.log(args); + execMain(step, CC65, args); + if (errors.length) + return { errors: errors }; + var asmout = FS.readFile(destpath, { encoding: 'utf8' }); + putWorkFile(destpath, asmout); + } + return { + nexttool: "ca65", + path: destpath, + args: [destpath], + files: [destpath], + }; +} + diff --git a/src/worker/tools/dasm.ts b/src/worker/tools/dasm.ts new file mode 100644 index 00000000..d6a9c928 --- /dev/null +++ b/src/worker/tools/dasm.ts @@ -0,0 +1,311 @@ +import { CodeListingMap, WorkerError } from "../../common/workertypes"; +import { re_crlf, BuildStep, BuildStepResult, load, msvcErrorMatcher, emglobal, populateFiles, execMain, putWorkFile, anyTargetChanged, re_msvc, gatherFiles, getWorkFileAsString, print_fn, setupFS, setupStdin, staleFiles } from "../workermain"; +import { EmscriptenModule } from "../workermain" + +function parseDASMListing(lstpath: string, lsttext: string, listings: CodeListingMap, errors: WorkerError[], unresolved: {}) { + // TODO: this gets very slow + // TODO: macros that are on adjacent lines don't get offset addresses + // 4 08ee a9 00 start lda #01workermain.js:23:5 + let lineMatch = /\s*(\d+)\s+(\S+)\s+([0-9a-f]+)\s+([?0-9a-f][?0-9a-f ]+)?\s+(.+)?/i; + let equMatch = /\bequ\b/i; + let macroMatch = /\bMAC\s+(\S+)?/i; + let lastline = 0; + let macros = {}; + let lstline = 0; + let lstlist = listings[lstpath]; + for (let line of lsttext.split(re_crlf)) { + lstline++; + let linem = lineMatch.exec(line + " "); + if (linem && linem[1] != null) { + let linenum = parseInt(linem[1]); + let filename = linem[2]; + let offset = parseInt(linem[3], 16); + let insns = linem[4]; + let restline = linem[5]; + if (insns && insns.startsWith('?')) insns = null; + // don't use listing yet + if (lstlist && lstlist.lines) { + lstlist.lines.push({ + line: lstline, + offset: offset, + insns: insns, + iscode: true, + }); + } + // inside of a file? + let lst = listings[filename]; + if (lst) { + var lines = lst.lines; + // look for MAC statement + let macmatch = macroMatch.exec(restline); + if (macmatch) { + macros[macmatch[1]] = { line: parseInt(linem[1]), file: linem[2].toLowerCase() }; + } + else if (insns && restline && !restline.match(equMatch)) { + lines.push({ + line: linenum, + offset: offset, + insns: insns, + iscode: restline[0] != '.' + }); + } + lastline = linenum; + } else { + // inside of macro? + let mac = macros[filename.toLowerCase()]; + // macro invocation in main file + if (mac && linenum == 0) { + lines.push({ + line: lastline + 1, + offset: offset, + insns: insns, + iscode: true + }); + } + if (insns && mac) { + let maclst = listings[mac.file]; + if (maclst && maclst.lines) { + maclst.lines.push({ + path: mac.file, + line: mac.line + linenum, + offset: offset, + insns: insns, + iscode: true + }); + } + // TODO: a listing file can't include other files + } else { + // inside of macro or include file + if (insns && linem[3] && lastline > 0) { + lines.push({ + line: lastline + 1, + offset: offset, + insns: null + }); + } + } + } + // TODO: better symbol test (word boundaries) + // TODO: ignore IFCONST and IFNCONST usage + for (let key in unresolved) { + let l = restline || line; + // find the identifier substring + let pos = l.indexOf(key); + if (pos >= 0) { + // strip the comment, if any + let cmt = l.indexOf(';'); + if (cmt < 0 || cmt > pos) { + // make sure identifier is flanked by non-word chars + if (new RegExp("\\b" + key + "\\b").exec(l)) { + errors.push({ + path: filename, + line: linenum, + msg: "Unresolved symbol '" + key + "'" + }); + } + } + } + } + } + let errm = re_msvc.exec(line); + if (errm) { + errors.push({ + path: errm[1], + line: parseInt(errm[2]), + msg: errm[4] + }) + } + } +} + +export function assembleDASM(step: BuildStep): BuildStepResult { + load("dasm"); + var re_usl = /(\w+)\s+0000\s+[?][?][?][?]/; + var unresolved = {}; + var errors = []; + var errorMatcher = msvcErrorMatcher(errors); + function match_fn(s: string) { + // TODO: what if s is not string? (startsWith is not a function) + var matches = re_usl.exec(s); + if (matches) { + var key = matches[1]; + if (key != 'NO_ILLEGAL_OPCODES') { // TODO + unresolved[matches[1]] = 0; + } + } else if (s.startsWith("Warning:")) { + errors.push({ line: 0, msg: s.substr(9) }); + } else if (s.startsWith("unable ")) { + errors.push({ line: 0, msg: s }); + } else if (s.startsWith("segment: ")) { + errors.push({ line: 0, msg: "Segment overflow: " + s.substring(9) }); + } else if (s.toLowerCase().indexOf('error:') >= 0) { + errors.push({ line: 0, msg: s.trim() }); + } else { + errorMatcher(s); + } + } + var Module: EmscriptenModule = emglobal.DASM({ + noInitialRun: true, + print: match_fn + }); + var FS = Module.FS; + populateFiles(step, FS, { + mainFilePath: 'main.a' + }); + var binpath = step.prefix + '.bin'; + var lstpath = step.prefix + '.lst'; + var sympath = step.prefix + '.sym'; + execMain(step, Module, [step.path, '-f3', + "-l" + lstpath, + "-o" + binpath, + "-s" + sympath]); + var alst = FS.readFile(lstpath, { 'encoding': 'utf8' }); + // parse main listing, get errors and listings for each file + var listings: CodeListingMap = {}; + //listings[lstpath] = {lines:[], text:alst}; + for (let path of step.files) { + listings[path] = { lines: [] }; + } + parseDASMListing(lstpath, alst, listings, errors, unresolved); + if (errors.length) { + return { errors: errors }; + } + // read binary rom output and symbols + var aout, asym; + aout = FS.readFile(binpath); + try { + asym = FS.readFile(sympath, { 'encoding': 'utf8' }); + } catch (e) { + console.log(e); + errors.push({ line: 0, msg: "No symbol table generated, maybe segment overflow?" }); + return { errors: errors } + } + putWorkFile(binpath, aout); + putWorkFile(lstpath, alst); + putWorkFile(sympath, asym); + // return unchanged if no files changed + // TODO: what if listing or symbols change? + if (!anyTargetChanged(step, [binpath/*, lstpath, sympath*/])) + return; + var symbolmap = {}; + for (var s of asym.split("\n")) { + var toks = s.split(/\s+/); + if (toks && toks.length >= 2 && !toks[0].startsWith('-')) { + symbolmap[toks[0]] = parseInt(toks[1], 16); + } + } + // for bataribasic (TODO) + if (step['bblines']) { + let lst = listings[step.path]; + if (lst) { + lst.asmlines = lst.lines; + lst.text = alst; + lst.lines = []; + } + } + return { + output: aout, + listings: listings, + errors: errors, + symbolmap: symbolmap, + }; +} + + +function preprocessBatariBasic(code: string): string { + load("bbpreprocess"); + var bbout = ""; + function addbbout_fn(s) { + bbout += s; + bbout += "\n"; + } + var BBPRE: EmscriptenModule = emglobal.preprocess({ + noInitialRun: true, + //logReadFiles:true, + print: addbbout_fn, + printErr: print_fn, + noFSInit: true, + }); + var FS = BBPRE.FS; + setupStdin(FS, code); + BBPRE.callMain([]); + console.log("preprocess " + code.length + " -> " + bbout.length + " bytes"); + return bbout; +} + +export function compileBatariBasic(step: BuildStep): BuildStepResult { + load("bb2600basic"); + var params = step.params; + // stdout + var asmout = ""; + function addasmout_fn(s) { + asmout += s; + asmout += "\n"; + } + // stderr + var re_err1 = /[(](\d+)[)]:?\s*(.+)/; + var errors = []; + var errline = 0; + function match_fn(s) { + console.log(s); + var matches = re_err1.exec(s); + if (matches) { + errline = parseInt(matches[1]); + errors.push({ + line: errline, + msg: matches[2] + }); + } + } + gatherFiles(step, { mainFilePath: "main.bas" }); + var destpath = step.prefix + '.asm'; + if (staleFiles(step, [destpath])) { + var BB: EmscriptenModule = emglobal.bb2600basic({ + noInitialRun: true, + //logReadFiles:true, + print: addasmout_fn, + printErr: match_fn, + noFSInit: true, + TOTAL_MEMORY: 64 * 1024 * 1024, + }); + var FS = BB.FS; + populateFiles(step, FS); + // preprocess, pipe file to stdin + var code = getWorkFileAsString(step.path); + code = preprocessBatariBasic(code); + setupStdin(FS, code); + setupFS(FS, '2600basic'); + execMain(step, BB, ["-i", "/share", step.path]); + if (errors.length) + return { errors: errors }; + // build final assembly output from include file list + var includesout = FS.readFile("includes.bB", { encoding: 'utf8' }); + var redefsout = FS.readFile("2600basic_variable_redefs.h", { encoding: 'utf8' }); + var includes = includesout.trim().split("\n"); + var combinedasm = ""; + var splitasm = asmout.split("bB.asm file is split here"); + for (var incfile of includes) { + var inctext; + if (incfile == "bB.asm") + inctext = splitasm[0]; + else if (incfile == "bB2.asm") + inctext = splitasm[1]; + else + inctext = FS.readFile("/share/includes/" + incfile, { encoding: 'utf8' }); + console.log(incfile, inctext.length); + combinedasm += "\n\n;;;" + incfile + "\n\n"; + combinedasm += inctext; + } + // TODO: ; bB.asm file is split here + putWorkFile(destpath, combinedasm); + putWorkFile("2600basic.h", FS.readFile("/share/includes/2600basic.h")); + putWorkFile("2600basic_variable_redefs.h", redefsout); + } + return { + nexttool: "dasm", + path: destpath, + args: [destpath], + files: [destpath, "2600basic.h", "2600basic_variable_redefs.h"], + bblines: true, + }; +} \ No newline at end of file diff --git a/src/worker/tools/m6502.ts b/src/worker/tools/m6502.ts new file mode 100644 index 00000000..3b4dceaa --- /dev/null +++ b/src/worker/tools/m6502.ts @@ -0,0 +1,239 @@ + +import { WorkerError, CodeListingMap } from "../../common/workertypes"; +import { anyTargetChanged, BuildStep, BuildStepResult, emglobal, EmscriptenModule, execMain, gatherFiles, loadNative, makeErrorMatcher, moduleInstFn, parseListing, populateFiles, print_fn, putWorkFile, staleFiles } from "../workermain" + + +// http://www.nespowerpak.com/nesasm/ +export function assembleNESASM(step: BuildStep): BuildStepResult { + loadNative("nesasm"); + var re_filename = /\#\[(\d+)\]\s+(\S+)/; + var re_insn = /\s+(\d+)\s+([0-9A-F]+):([0-9A-F]+)/; + var re_error = /\s+(.+)/; + var errors: WorkerError[] = []; + var state = 0; + var lineno = 0; + var filename; + function match_fn(s) { + var m; + switch (state) { + case 0: + m = re_filename.exec(s); + if (m) { + filename = m[2]; + } + m = re_insn.exec(s); + if (m) { + lineno = parseInt(m[1]); + state = 1; + } + break; + case 1: + m = re_error.exec(s); + if (m) { + errors.push({ path: filename, line: lineno, msg: m[1] }); + state = 0; + } + break; + } + } + var Module: EmscriptenModule = emglobal.nesasm({ + instantiateWasm: moduleInstFn('nesasm'), + noInitialRun: true, + print: match_fn + }); + var FS = Module.FS; + populateFiles(step, FS, { + mainFilePath: 'main.a' + }); + var binpath = step.prefix + '.nes'; + var lstpath = step.prefix + '.lst'; + var sympath = step.prefix + '.fns'; + execMain(step, Module, [step.path, '-s', "-l", "2"]); + // parse main listing, get errors and listings for each file + var listings: CodeListingMap = {}; + try { + var alst = FS.readFile(lstpath, { 'encoding': 'utf8' }); + // 16 00:C004 8E 17 40 STX $4017 ; disable APU frame IRQ + var asmlines = parseListing(alst, /^\s*(\d+)\s+([0-9A-F]+):([0-9A-F]+)\s+([0-9A-F ]+?) (.*)/i, 1, 3, 4); + putWorkFile(lstpath, alst); + listings[lstpath] = { + lines: asmlines, + text: alst + }; + } catch (e) { + // + } + if (errors.length) { + return { errors: errors }; + } + // read binary rom output and symbols + var aout, asym; + aout = FS.readFile(binpath); + try { + asym = FS.readFile(sympath, { 'encoding': 'utf8' }); + } catch (e) { + console.log(e); + errors.push({ line: 0, msg: "No symbol table generated, maybe missing ENDM or segment overflow?" }); + return { errors: errors } + } + putWorkFile(binpath, aout); + putWorkFile(sympath, asym); + if (alst) putWorkFile(lstpath, alst); // listing optional (use LIST) + // return unchanged if no files changed + if (!anyTargetChanged(step, [binpath, sympath])) + return; + // parse symbols + var symbolmap = {}; + for (var s of asym.split("\n")) { + if (!s.startsWith(';')) { + var m = /(\w+)\s+=\s+[$]([0-9A-F]+)/.exec(s); + if (m) { + symbolmap[m[1]] = parseInt(m[2], 16); + } + } + } + return { + output: aout, + listings: listings, + errors: errors, + symbolmap: symbolmap, + }; +} + + +/* +------+-------------------+-------------+----+---------+------+-----------------------+------------------------------------------------------------------- +Line | # File Line | Line Type | MX | Reloc | Size | Address Object Code | Source Code +------+-------------------+-------------+----+---------+------+-----------------------+------------------------------------------------------------------- + 1 | 1 zap.asm 1 | Unknown | ?? | | -1 | 00/FFFF | broak + 2 | 1 zap.asm 2 | Comment | ?? | | -1 | 00/FFFF | * SPACEGAME + + => [Error] Impossible to decode address mode for instruction 'BNE KABOOM!' (line 315, file 'zap.asm') : The number of element in 'KABOOM!' is even (should be value [operator value [operator value]...]). + => [Error] Unknown line 'foo' in source file 'zap.asm' (line 315) + => Creating Object file 'pcs.bin' + => Creating Output file 'pcs.bin_S01__Output.txt' + +*/ +export function assembleMerlin32(step: BuildStep): BuildStepResult { + loadNative("merlin32"); + var errors = []; + var lstfiles = []; + gatherFiles(step, { mainFilePath: "main.lnk" }); + var objpath = step.prefix + ".bin"; + if (staleFiles(step, [objpath])) { + var args = ['-v', step.path]; + var merlin32: EmscriptenModule = emglobal.merlin32({ + instantiateWasm: moduleInstFn('merlin32'), + noInitialRun: true, + print: (s: string) => { + var m = /\s*=>\s*Creating Output file '(.+?)'/.exec(s); + if (m) { + lstfiles.push(m[1]); + } + var errpos = s.indexOf('Error'); + if (errpos >= 0) { + s = s.slice(errpos + 6).trim(); + var mline = /\bline (\d+)\b/.exec(s); + var mpath = /\bfile '(.+?)'/.exec(s); + errors.push({ + line: parseInt(mline[1]) || 0, + msg: s, + path: mpath[1] || step.path, + }); + } + }, + printErr: print_fn, + }); + var FS = merlin32.FS; + populateFiles(step, FS); + execMain(step, merlin32, args); + if (errors.length) + return { errors: errors }; + + var errout = null; + try { + errout = FS.readFile("error_output.txt", { encoding: 'utf8' }); + } catch (e) { + // + } + + var objout = FS.readFile(objpath, { encoding: 'binary' }); + putWorkFile(objpath, objout); + if (!anyTargetChanged(step, [objpath])) + return; + + var symbolmap = {}; + var segments = []; + var listings: CodeListingMap = {}; + lstfiles.forEach((lstfn) => { + var lst = FS.readFile(lstfn, { encoding: 'utf8' }) as string; + lst.split('\n').forEach((line) => { + var toks = line.split(/\s*\|\s*/); + if (toks && toks[6]) { + var toks2 = toks[1].split(/\s+/); + var toks3 = toks[6].split(/[:/]/, 4); + var path = toks2[1]; + if (path && toks2[2] && toks3[1]) { + var lstline = { + line: parseInt(toks2[2]), + offset: parseInt(toks3[1].trim(), 16), + insns: toks3[2], + cycles: null, + iscode: false // TODO + }; + var lst = listings[path]; + if (!lst) listings[path] = lst = { lines: [] }; + lst.lines.push(lstline); + //console.log(path,toks2,toks3); + } + } + }); + }); + return { + output: objout, //.slice(0), + listings: listings, + errors: errors, + symbolmap: symbolmap, + segments: segments + }; + } +} + +// README.md:2:5: parse error, expected: statement or variable assignment, integer variable, variable assignment +export function compileFastBasic(step: BuildStep): BuildStepResult { + // TODO: fastbasic-fp? + loadNative("fastbasic-int"); + var params = step.params; + gatherFiles(step, { mainFilePath: "main.fb" }); + var destpath = step.prefix + '.s'; + var errors = []; + if (staleFiles(step, [destpath])) { + var fastbasic: EmscriptenModule = emglobal.fastbasic({ + instantiateWasm: moduleInstFn('fastbasic-int'), + noInitialRun: true, + print: print_fn, + printErr: makeErrorMatcher(errors, /(.+?):(\d+):(\d+):\s*(.+)/, 2, 4, step.path, 1), + }); + var FS = fastbasic.FS; + populateFiles(step, FS); + var libfile = 'fastbasic-int.lib' + params.libargs = [libfile]; + params.cfgfile = params.fastbasic_cfgfile; + //params.extra_compile_args = ["--asm-define", "NO_SMCODE"]; + params.extra_link_files = [libfile, params.cfgfile]; + //fixParamsWithDefines(step.path, params); + var args = [step.path, destpath]; + execMain(step, fastbasic, args); + if (errors.length) + return { errors: errors }; + var asmout = FS.readFile(destpath, { encoding: 'utf8' }); + putWorkFile(destpath, asmout); + } + return { + nexttool: "ca65", + path: destpath, + args: [destpath], + files: [destpath], + }; +} + diff --git a/src/worker/tools/m6809.ts b/src/worker/tools/m6809.ts new file mode 100644 index 00000000..2e7eefc7 --- /dev/null +++ b/src/worker/tools/m6809.ts @@ -0,0 +1,253 @@ +import { CodeListingMap, WorkerError } from "../../common/workertypes"; +import { BuildStep, BuildStepResult, load, emglobal, print_fn, populateFiles, execMain, putWorkFile, parseListing, loadNative, gatherFiles, staleFiles, moduleInstFn, getWorkFileAsString, preprocessMCPP, fixParamsWithDefines, msvcErrorMatcher, populateExtraFiles, anyTargetChanged, parseSourceLines } from "../workermain"; +import { EmscriptenModule } from "../workermain"; + +// http://datapipe-blackbeltsystems.com/windows/flex/asm09.html +export function assembleXASM6809(step: BuildStep): BuildStepResult { + load("xasm6809"); + var alst = ""; + var lasterror = null; + var errors = []; + function match_fn(s) { + alst += s; + alst += "\n"; + if (lasterror) { + var line = parseInt(s.slice(0, 5)) || 0; + errors.push({ + line: line, + msg: lasterror + }); + lasterror = null; + } + else if (s.startsWith("***** ")) { + lasterror = s.slice(6); + } + } + var Module: EmscriptenModule = emglobal.xasm6809({ + noInitialRun: true, + //logReadFiles:true, + print: match_fn, + printErr: print_fn + }); + var FS = Module.FS; + //setupFS(FS); + populateFiles(step, FS, { + mainFilePath: 'main.asm' + }); + var binpath = step.prefix + '.bin'; + var lstpath = step.prefix + '.lst'; // in stdout + execMain(step, Module, ["-c", "-l", "-s", "-y", "-o=" + binpath, step.path]); + if (errors.length) + return { errors: errors }; + var aout = FS.readFile(binpath, { encoding: 'binary' }); + if (aout.length == 0) { + console.log(alst); + errors.push({ line: 0, msg: "Empty output file" }); + return { errors: errors }; + } + putWorkFile(binpath, aout); + putWorkFile(lstpath, alst); + // TODO: symbol map + //mond09 0000 + var symbolmap = {}; + //00005 W 0003 [ 8] A6890011 lda >PALETTE,x + //00012 0011 0C0203 fcb 12,2,3 + var asmlines = parseListing(alst, /^\s*([0-9]+) .+ ([0-9A-F]+)\s+\[([0-9 ]+)\]\s+([0-9A-F]+) (.*)/i, 1, 2, 4, 3); + var listings: CodeListingMap = {}; + listings[step.prefix + '.lst'] = { lines: asmlines, text: alst }; + return { + output: aout, + listings: listings, + errors: errors, + symbolmap: symbolmap, + }; +} + +export function compileCMOC(step: BuildStep): BuildStepResult { + loadNative("cmoc"); + var params = step.params; + // stderr + var re_err1 = /^[/]*([^:]*):(\d+): (.+)$/; + var errors: WorkerError[] = []; + var errline = 0; + function match_fn(s) { + var matches = re_err1.exec(s); + if (matches) { + errors.push({ + line: parseInt(matches[2]), + msg: matches[3], + path: matches[1] || step.path + }); + } else { + console.log(s); + } + } + gatherFiles(step, { mainFilePath: "main.c" }); + var destpath = step.prefix + '.s'; + if (staleFiles(step, [destpath])) { + var args = ['-S', '-Werror', '-V', + '-I/share/include', + '-I.', + step.path]; + var CMOC: EmscriptenModule = emglobal.cmoc({ + instantiateWasm: moduleInstFn('cmoc'), + noInitialRun: true, + //logReadFiles:true, + print: match_fn, + printErr: match_fn, + }); + // load source file and preprocess + var code = getWorkFileAsString(step.path); + var preproc = preprocessMCPP(step, null); + if (preproc.errors) { + return { errors: preproc.errors } + } + else code = preproc.code; + // set up filesystem + var FS = CMOC.FS; + //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); + populateFiles(step, FS); + FS.writeFile(step.path, code); + fixParamsWithDefines(step.path, params); + if (params.extra_compile_args) { + args.unshift.apply(args, params.extra_compile_args); + } + execMain(step, CMOC, args); + if (errors.length) + return { errors: errors }; + var asmout = FS.readFile(destpath, { encoding: 'utf8' }); + putWorkFile(destpath, asmout); + } + return { + nexttool: "lwasm", + path: destpath, + args: [destpath], + files: [destpath], + }; +} + +export function assembleLWASM(step: BuildStep): BuildStepResult { + loadNative("lwasm"); + var errors = []; + gatherFiles(step, { mainFilePath: "main.s" }); + var objpath = step.prefix + ".o"; + var lstpath = step.prefix + ".lst"; + if (staleFiles(step, [objpath, lstpath])) { + var objout, lstout; + var args = ['-9', '--obj', '-I/share/asminc', '-o' + objpath, '-l' + lstpath, step.path]; + var LWASM: EmscriptenModule = emglobal.lwasm({ + instantiateWasm: moduleInstFn('lwasm'), + noInitialRun: true, + //logReadFiles:true, + print: print_fn, + printErr: msvcErrorMatcher(errors), + }); + var FS = LWASM.FS; + //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); + populateFiles(step, FS); + fixParamsWithDefines(step.path, step.params); + execMain(step, LWASM, args); + if (errors.length) + return { errors: errors }; + objout = FS.readFile(objpath, { encoding: 'binary' }); + lstout = FS.readFile(lstpath, { encoding: 'utf8' }); + putWorkFile(objpath, objout); + putWorkFile(lstpath, lstout); + } + return { + linktool: "lwlink", + files: [objpath, lstpath], + args: [objpath] + }; +} + +export function linkLWLINK(step: BuildStep): BuildStepResult { + loadNative("lwlink"); + var params = step.params; + gatherFiles(step); + var binpath = "main"; + if (staleFiles(step, [binpath])) { + var errors = []; + var LWLINK: EmscriptenModule = emglobal.lwlink({ + instantiateWasm: moduleInstFn('lwlink'), + noInitialRun: true, + //logReadFiles:true, + print: print_fn, + printErr: function (s) { + if (s.startsWith("Warning:")) + console.log(s); + else + errors.push({ msg: s, line: 0 }); + } + }); + var FS = LWLINK.FS; + //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); + populateFiles(step, FS); + populateExtraFiles(step, FS, params.extra_link_files); + var libargs = params.extra_link_args || []; + var args = [ + '-L.', + '--entry=program_start', + '--raw', + '--output=main', + '--map=main.map'].concat(libargs, step.args); + console.log(args); + execMain(step, LWLINK, args); + if (errors.length) + return { errors: errors }; + var aout = FS.readFile("main", { encoding: 'binary' }); + var mapout = FS.readFile("main.map", { encoding: 'utf8' }); + putWorkFile("main", aout); + putWorkFile("main.map", mapout); + // return unchanged if no files changed + if (!anyTargetChanged(step, ["main", "main.map"])) + return; + // parse symbol map + //console.log(mapout); + var symbolmap = {}; + var segments = []; + for (var s of mapout.split("\n")) { + var toks = s.split(" "); + // TODO: use regex + if (toks[0] == 'Symbol:') { + let ident = toks[1]; + let ofs = parseInt(toks[4], 16); + if (ident && ofs >= 0 && !ident.startsWith("l_") && !/^L\d+$/.test(ident)) { + symbolmap[ident] = ofs; + } + } + else if (toks[0] == 'Section:') { + let seg = toks[1]; + let segstart = parseInt(toks[5], 16); + let segsize = parseInt(toks[7], 16); + segments.push({ name: seg, start: segstart, size: segsize }); + } + } + // build listings + var listings: CodeListingMap = {}; + for (var fn of step.files) { + if (fn.endsWith('.lst')) { + // TODO + var lstout = FS.readFile(fn, { encoding: 'utf8' }); + var asmlines = parseListing(lstout, /^([0-9A-F]+)\s+([0-9A-F]+)\s+[(]\s*(.+?)[)]:(\d+) (.*)/i, 4, 1, 2, 3); + // * Line //threed.c:117: init of variable e + var srclines = parseSourceLines(lstout, /Line .+?:(\d+)/i, /^([0-9A-F]{4})/i); + putWorkFile(fn, lstout); + // TODO: you have to get rid of all source lines to get asm listing + listings[fn] = { + asmlines: srclines.length ? asmlines : null, + lines: srclines.length ? srclines : asmlines, + text: lstout + }; + } + } + return { + output: aout, //.slice(0), + listings: listings, + errors: errors, + symbolmap: symbolmap, + segments: segments + }; + } +} + diff --git a/src/worker/tools/misc.ts b/src/worker/tools/misc.ts new file mode 100644 index 00000000..a15288d6 --- /dev/null +++ b/src/worker/tools/misc.ts @@ -0,0 +1,191 @@ +import { Segment, CodeListingMap, WorkerResult, WorkerError } from "../../common/workertypes"; +import { BuildStep, BuildStepResult, setupRequireFunction, load, emglobal, getWorkFileAsString, loadNative, gatherFiles, staleFiles, msvcErrorMatcher, moduleInstFn, setupFS, populateFiles, execMain, putWorkFile, anyTargetChanged, parseListing, print_fn, makeErrorMatcher, populateExtraFiles } from "../workermain"; +import { EmscriptenModule } from "../workermain" +import * as basic_compiler from '../../common/basic/compiler'; +import { parseXMLPoorly } from "../../common/util"; + +export function translateShowdown(step: BuildStep): BuildStepResult { + setupRequireFunction(); + load("showdown.min"); + var showdown = emglobal['showdown']; + var converter = new showdown.Converter({ + tables: 'true', + smoothLivePreview: 'true', + requireSpaceBeforeHeadingText: 'true', + emoji: 'true', + }); + var code = getWorkFileAsString(step.path); + var html = converter.makeHtml(code); + delete emglobal['require']; + return { + output: html + }; +} + +export function compileInform6(step: BuildStep): BuildStepResult { + loadNative("inform"); + var errors = []; + gatherFiles(step, { mainFilePath: "main.inf" }); + var objpath = step.prefix + ".z5"; + if (staleFiles(step, [objpath])) { + var errorMatcher = msvcErrorMatcher(errors); + var lstout = ""; + var match_fn = (s: string) => { + if (s.indexOf("Error:") >= 0) { + errorMatcher(s); + } else { + lstout += s; + lstout += "\n"; + } + } + // TODO: step.path must end in '.inf' or error + var args = ['-afjnops', '-v5', '-Cu', '-E1', '-k', '+/share/lib', step.path]; + var inform: EmscriptenModule = emglobal.inform({ + instantiateWasm: moduleInstFn('inform'), + noInitialRun: true, + //logReadFiles:true, + print: match_fn, + printErr: match_fn, + }); + var FS = inform.FS; + setupFS(FS, 'inform'); + populateFiles(step, FS); + //fixParamsWithDefines(step.path, step.params); + execMain(step, inform, args); + if (errors.length) + return { errors: errors }; + var objout = FS.readFile(objpath, { encoding: 'binary' }); + putWorkFile(objpath, objout); + if (!anyTargetChanged(step, [objpath])) + return; + + // parse debug XML + var symbolmap = {}; + var segments: Segment[] = []; + var entitymap = { + // number -> string + 'object': {}, 'property': {}, 'attribute': {}, 'constant': {}, 'global-variable': {}, 'routine': {}, + }; + var dbgout = FS.readFile("gameinfo.dbg", { encoding: 'utf8' }); + var xmlroot = parseXMLPoorly(dbgout); + //console.log(xmlroot); + var segtype = "ram"; + xmlroot.children.forEach((node) => { + switch (node.type) { + case 'global-variable': + case 'routine': + var ident = node.children.find((c, v) => c.type == 'identifier').text; + var address = parseInt(node.children.find((c, v) => c.type == 'address').text); + symbolmap[ident] = address; + entitymap[node.type][address] = ident; + break; + case 'object': + case 'property': + case 'attribute': + var ident = node.children.find((c, v) => c.type == 'identifier').text; + var value = parseInt(node.children.find((c, v) => c.type == 'value').text); + //entitymap[node.type][ident] = value; + entitymap[node.type][value] = ident; + //symbolmap[ident] = address | 0x1000000; + break; + case 'story-file-section': + var name = node.children.find((c, v) => c.type == 'type').text; + var address = parseInt(node.children.find((c, v) => c.type == 'address').text); + var endAddress = parseInt(node.children.find((c, v) => c.type == 'end-address').text); + if (name == "grammar table") segtype = "rom"; + segments.push({ name: name, start: address, size: endAddress - address, type: segtype }); + } + }); + // parse listing + var listings: CodeListingMap = {}; + // 35 +00015 <*> call_vs long_19 location long_424 -> sp + var lines = parseListing(lstout, /\s*(\d+)\s+[+]([0-9a-f]+)\s+([<*>]*)\s*(\w+)\s+(.+)/i, -1, 2, 4); + var lstpath = step.prefix + '.lst'; + listings[lstpath] = { lines: [], asmlines: lines, text: lstout }; + return { + output: objout, //.slice(0), + listings: listings, + errors: errors, + symbolmap: symbolmap, + segments: segments, + debuginfo: entitymap, + }; + } +} + +export function compileBASIC(step: BuildStep): WorkerResult { + var jsonpath = step.path + ".json"; + gatherFiles(step); + if (staleFiles(step, [jsonpath])) { + var parser = new basic_compiler.BASICParser(); + var code = getWorkFileAsString(step.path); + try { + var ast = parser.parseFile(code, step.path); + } catch (e) { + console.log(e); + if (parser.errors.length == 0) throw e; + } + if (parser.errors.length) { + return { errors: parser.errors }; + } + // put AST into JSON (sans source locations) to see if it has changed + var json = JSON.stringify(ast, (key, value) => { return (key == '$loc' ? undefined : value) }); + putWorkFile(jsonpath, json); + if (anyTargetChanged(step, [jsonpath])) return { + output: ast, + listings: parser.getListings(), + }; + } +} + +export function compileWiz(step: BuildStep): WorkerResult { + loadNative("wiz"); + var params = step.params; + gatherFiles(step, { mainFilePath: "main.wiz" }); + var destpath = step.prefix + (params.wiz_rom_ext || ".bin"); + var errors: WorkerError[] = []; + if (staleFiles(step, [destpath])) { + var wiz: EmscriptenModule = emglobal.wiz({ + instantiateWasm: moduleInstFn('wiz'), + noInitialRun: true, + print: print_fn, + //test.wiz:2: error: expected statement, but got identifier `test` + printErr: makeErrorMatcher(errors, /(.+?):(\d+):\s*(.+)/, 2, 3, step.path, 1), + }); + var FS = wiz.FS; + setupFS(FS, 'wiz'); + populateFiles(step, FS); + populateExtraFiles(step, FS, params.extra_compile_files); + const FWDIR = '/share/common'; + var args = [ + '-o', destpath, + '-I', FWDIR + '/' + (params.wiz_inc_dir || step.platform), + '-s', 'wla', + '--color=none', + step.path]; + args.push('--system', params.wiz_sys_type || params.arch); + execMain(step, wiz, args); + if (errors.length) + return { errors: errors }; + var binout = FS.readFile(destpath, { encoding: 'binary' }); + putWorkFile(destpath, binout); + var dbgout = FS.readFile(step.prefix + '.sym', { encoding: 'utf8' }); + var symbolmap = {}; + for (var s of dbgout.split("\n")) { + var toks = s.split(/ /); + // 00:4008 header.basic_start + if (toks && toks.length >= 2) { + var tokrange = toks[0].split(':'); + var start = parseInt(tokrange[1], 16); + var sym = toks[1]; + symbolmap[sym] = start; + } + } + return { + output: binout, //.slice(0), + errors: errors, + symbolmap: symbolmap, + }; + } +} + diff --git a/src/worker/tools/sdcc.ts b/src/worker/tools/sdcc.ts new file mode 100644 index 00000000..2f53be94 --- /dev/null +++ b/src/worker/tools/sdcc.ts @@ -0,0 +1,290 @@ +import { CodeListingMap } from "../../common/workertypes"; +import { BuildStep, BuildStepResult, loadNative, gatherFiles, staleFiles, emglobal, moduleInstFn, populateFiles, execMain, putWorkFile, setupFS, populateExtraFiles, anyTargetChanged, parseListing, print_fn, msvcErrorMatcher, getWorkFileAsString, setupStdin, preprocessMCPP, parseSourceLines } from "../workermain"; +import { EmscriptenModule } from "../workermain" + +function hexToArray(s, ofs) { + var buf = new ArrayBuffer(s.length / 2); + var arr = new Uint8Array(buf); + for (var i = 0; i < arr.length; i++) { + arr[i] = parseInt(s.slice(i * 2 + ofs, i * 2 + ofs + 2), 16); + } + return arr; +} + +function parseIHX(ihx, rom_start, rom_size, errors) { + var output = new Uint8Array(new ArrayBuffer(rom_size)); + var high_size = 0; + for (var s of ihx.split("\n")) { + if (s[0] == ':') { + var arr = hexToArray(s, 1); + var count = arr[0]; + var address = (arr[1] << 8) + arr[2] - rom_start; + var rectype = arr[3]; + //console.log(rectype,address.toString(16),count,arr); + if (rectype == 0) { + for (var i = 0; i < count; i++) { + var b = arr[4 + i]; + output[i + address] = b; + } + if (i + address > high_size) high_size = i + address; + } else if (rectype == 1) { + break; + } else { + console.log(s); // unknown record type + } + } + } + // TODO: return ROM anyway? + if (high_size > rom_size) { + //errors.push({line:0, msg:"ROM size too large: 0x" + high_size.toString(16) + " > 0x" + rom_size.toString(16)}); + } + return output; +} + +export function assembleSDASZ80(step: BuildStep): BuildStepResult { + loadNative("sdasz80"); + var objout, lstout, symout; + var errors = []; + gatherFiles(step, { mainFilePath: "main.asm" }); + var objpath = step.prefix + ".rel"; + var lstpath = step.prefix + ".lst"; + if (staleFiles(step, [objpath, lstpath])) { + //?ASxxxx-Error- in line 1 of main.asm null + // .org in REL area or directive / mnemonic error + // ?ASxxxx-Error- in line 1627 of cosmic.asm + // missing or improper operators, terminators, or delimiters + var match_asm_re1 = / in line (\d+) of (\S+)/; // TODO + var match_asm_re2 = / <\w> (.+)/; // TODO + var errline = 0; + var errpath = step.path; + var match_asm_fn = (s: string) => { + var m = match_asm_re1.exec(s); + if (m) { + errline = parseInt(m[1]); + errpath = m[2]; + } else { + m = match_asm_re2.exec(s); + if (m) { + errors.push({ + line: errline, + path: errpath, + msg: m[1] + }); + } + } + } + var ASZ80: EmscriptenModule = emglobal.sdasz80({ + instantiateWasm: moduleInstFn('sdasz80'), + noInitialRun: true, + //logReadFiles:true, + print: match_asm_fn, + printErr: match_asm_fn, + }); + var FS = ASZ80.FS; + populateFiles(step, FS); + execMain(step, ASZ80, ['-plosgffwy', step.path]); + if (errors.length) { + return { errors: errors }; + } + objout = FS.readFile(objpath, { encoding: 'utf8' }); + lstout = FS.readFile(lstpath, { encoding: 'utf8' }); + putWorkFile(objpath, objout); + putWorkFile(lstpath, lstout); + } + return { + linktool: "sdldz80", + files: [objpath, lstpath], + args: [objpath] + }; + //symout = FS.readFile("main.sym", {encoding:'utf8'}); +} + +export function linkSDLDZ80(step: BuildStep) { + loadNative("sdldz80"); + var errors = []; + gatherFiles(step); + var binpath = "main.ihx"; + if (staleFiles(step, [binpath])) { + //?ASlink-Warning-Undefined Global '__divsint' referenced by module 'main' + var match_aslink_re = /\?ASlink-(\w+)-(.+)/; + var match_aslink_fn = (s: string) => { + var matches = match_aslink_re.exec(s); + if (matches) { + errors.push({ + line: 0, + msg: matches[2] + }); + } + } + var params = step.params; + var LDZ80: EmscriptenModule = emglobal.sdldz80({ + instantiateWasm: moduleInstFn('sdldz80'), + noInitialRun: true, + //logReadFiles:true, + print: match_aslink_fn, + printErr: match_aslink_fn, + }); + var FS = LDZ80.FS; + setupFS(FS, 'sdcc'); + populateFiles(step, FS); + populateExtraFiles(step, FS, params.extra_link_files); + // TODO: coleco hack so that -u flag works + if (step.platform.startsWith("coleco")) { + FS.writeFile('crt0.rel', FS.readFile('/share/lib/coleco/crt0.rel', { encoding: 'utf8' })); + FS.writeFile('crt0.lst', '\n'); // TODO: needed so -u flag works + } + var args = ['-mjwxyu', + '-i', 'main.ihx', // TODO: main? + '-b', '_CODE=0x' + params.code_start.toString(16), + '-b', '_DATA=0x' + params.data_start.toString(16), + '-k', '/share/lib/z80', + '-l', 'z80']; + if (params.extra_link_args) + args.push.apply(args, params.extra_link_args); + args.push.apply(args, step.args); + //console.log(args); + execMain(step, LDZ80, args); + var hexout = FS.readFile("main.ihx", { encoding: 'utf8' }); + var noiout = FS.readFile("main.noi", { encoding: 'utf8' }); + putWorkFile("main.ihx", hexout); + putWorkFile("main.noi", noiout); + // return unchanged if no files changed + if (!anyTargetChanged(step, ["main.ihx", "main.noi"])) + return; + // parse binary file + var binout = parseIHX(hexout, params.rom_start !== undefined ? params.rom_start : params.code_start, params.rom_size, errors); + if (errors.length) { + return { errors: errors }; + } + // parse listings + var listings: CodeListingMap = {}; + for (var fn of step.files) { + if (fn.endsWith('.lst')) { + var rstout = FS.readFile(fn.replace('.lst', '.rst'), { encoding: 'utf8' }); + // 0000 21 02 00 [10] 52 ld hl, #2 + var asmlines = parseListing(rstout, /^\s*([0-9A-F]{4})\s+([0-9A-F][0-9A-F r]*[0-9A-F])\s+\[([0-9 ]+)\]?\s+(\d+) (.*)/i, 4, 1, 2, 3); + var srclines = parseSourceLines(rstout, /^\s+\d+ ;:(\d+):/i, /^\s*([0-9A-F]{4})/i); + putWorkFile(fn, rstout); + // TODO: you have to get rid of all source lines to get asm listing + listings[fn] = { + asmlines: srclines.length ? asmlines : null, + lines: srclines.length ? srclines : asmlines, + text: rstout + }; + } + } + // parse symbol map + var symbolmap = {}; + for (var s of noiout.split("\n")) { + var toks = s.split(" "); + if (toks[0] == 'DEF' && !toks[1].startsWith("A$")) { + symbolmap[toks[1]] = parseInt(toks[2], 16); + } + } + // build segment map + var seg_re = /^s__(\w+)$/; + var segments = []; + // TODO: use stack params for stack segment + for (let ident in symbolmap) { + let m = seg_re.exec(ident); + if (m) { + let seg = m[1]; + let segstart = symbolmap[ident]; // s__SEG + let segsize = symbolmap['l__' + seg]; // l__SEG + if (segstart >= 0 && segsize > 0) { + var type = null; + if (['INITIALIZER', 'GSINIT', 'GSFINAL'].includes(seg)) type = 'rom'; + else if (seg.startsWith('CODE')) type = 'rom'; + else if (['DATA', 'INITIALIZED'].includes(seg)) type = 'ram'; + if (type == 'rom' || segstart > 0) // ignore HEADER0, CABS0, etc (TODO?) + segments.push({ name: seg, start: segstart, size: segsize, type: type }); + } + } + } + return { + output: binout, + listings: listings, + errors: errors, + symbolmap: symbolmap, + segments: segments + }; + } +} + +export function compileSDCC(step: BuildStep): BuildStepResult { + + gatherFiles(step, { + mainFilePath: "main.c" // not used + }); + var outpath = step.prefix + ".asm"; + if (staleFiles(step, [outpath])) { + var errors = []; + var params = step.params; + loadNative('sdcc'); + var SDCC: EmscriptenModule = emglobal.sdcc({ + instantiateWasm: moduleInstFn('sdcc'), + noInitialRun: true, + noFSInit: true, + print: print_fn, + printErr: msvcErrorMatcher(errors), + //TOTAL_MEMORY:256*1024*1024, + }); + var FS = SDCC.FS; + populateFiles(step, FS); + // load source file and preprocess + var code = getWorkFileAsString(step.path); + var preproc = preprocessMCPP(step, 'sdcc'); + if (preproc.errors) { + return { errors: preproc.errors }; + } + else code = preproc.code; + // pipe file to stdin + setupStdin(FS, code); + setupFS(FS, 'sdcc'); + var args = ['--vc', '--std-sdcc99', '-mz80', //'-Wall', + '--c1mode', + //'--debug', + //'-S', 'main.c', + //'--asm=sdasz80', + //'--reserve-regs-iy', + '--less-pedantic', + ///'--fomit-frame-pointer', + //'--opt-code-speed', + //'--max-allocs-per-node', '1000', + //'--cyclomatic', + //'--nooverlay', + //'--nogcse', + //'--nolabelopt', + //'--noinvariant', + //'--noinduction', + //'--nojtbound', + //'--noloopreverse', + '-o', outpath]; + // if "#pragma opt_code" found do not disable optimziations + if (!/^\s*#pragma\s+opt_code/m.exec(code)) { + args.push.apply(args, [ + '--oldralloc', + '--no-peep', + '--nolospre' + ]); + } + if (params.extra_compile_args) { + args.push.apply(args, params.extra_compile_args); + } + execMain(step, SDCC, args); + // TODO: preprocessor errors w/ correct file + if (errors.length /* && nwarnings < msvc_errors.length*/) { + return { errors: errors }; + } + // massage the asm output + var asmout = FS.readFile(outpath, { encoding: 'utf8' }); + asmout = " .area _HOME\n .area _CODE\n .area _INITIALIZER\n .area _DATA\n .area _INITIALIZED\n .area _BSEG\n .area _BSS\n .area _HEAP\n" + asmout; + putWorkFile(outpath, asmout); + } + return { + nexttool: "sdasz80", + path: outpath, + args: [outpath], + files: [outpath], + }; +} diff --git a/src/worker/tools/verilog.ts b/src/worker/tools/verilog.ts new file mode 100644 index 00000000..a3e29c59 --- /dev/null +++ b/src/worker/tools/verilog.ts @@ -0,0 +1,310 @@ + +// TODO: must be a better way to do all this + +import { WorkerError, CodeListingMap, SourceLocation } from "../../common/workertypes"; +import { Assembler } from "../assembler"; +import * as vxmlparser from '../../common/hdl/vxmlparser'; +import { getWorkFileAsString, BuildStep, BuildStepResult, gatherFiles, loadNative, staleFiles, makeErrorMatcher, emglobal, moduleInstFn, print_fn, populateFiles, execMain, putWorkFile, anyTargetChanged, endtime, getWASMMemory, starttime, populateExtraFiles, setupFS } from "../workermain"; +import { EmscriptenModule } from "../workermain" + +function detectModuleName(code: string) { + var m = /^\s*module\s+(\w+_top)\b/m.exec(code) + || /^\s*module\s+(top|t)\b/m.exec(code) + || /^\s*module\s+(\w+)\b/m.exec(code); + return m ? m[1] : null; +} + +function detectTopModuleName(code: string) { + var topmod = detectModuleName(code) || "top"; + var m = /^\s*module\s+(\w+?_top)/m.exec(code); + if (m && m[1]) topmod = m[1]; + return topmod; +} + +// cached stuff (TODO) +var jsasm_module_top; +var jsasm_module_output; +var jsasm_module_key; + +function compileJSASM(asmcode: string, platform, options, is_inline) { + var asm = new Assembler(null); + var includes = []; + asm.loadJSON = (filename: string) => { + var jsontext = getWorkFileAsString(filename); + if (!jsontext) throw Error("could not load " + filename); + return JSON.parse(jsontext); + }; + asm.loadInclude = (filename) => { + if (!filename.startsWith('"') || !filename.endsWith('"')) + return 'Expected filename in "double quotes"'; + filename = filename.substr(1, filename.length - 2); + includes.push(filename); + }; + var loaded_module = false; + asm.loadModule = (top_module: string) => { + // compile last file in list + loaded_module = true; + var key = top_module + '/' + includes; + if (jsasm_module_key != key) { + jsasm_module_key = key; + jsasm_module_output = null; + } + jsasm_module_top = top_module; + var main_filename = includes[includes.length - 1]; + // TODO: take out .asm dependency + var voutput = compileVerilator({ platform: platform, files: includes, path: main_filename, tool: 'verilator' }); + if (voutput) + jsasm_module_output = voutput; + return null; // no error + } + var result = asm.assembleFile(asmcode); + if (loaded_module && jsasm_module_output) { + // errors? return them + if (jsasm_module_output.errors && jsasm_module_output.errors.length) + return jsasm_module_output; + // return program ROM array + var asmout = result.output; + // TODO: unify + result.output = jsasm_module_output.output; + // TODO: typecheck this garbage + (result as any).output.program_rom = asmout; + // TODO: not cpu_platform__DOT__program_rom anymore, make const + (result as any).output.program_rom_variable = jsasm_module_top + "$program_rom"; + (result as any).listings = {}; + (result as any).listings[options.path] = { lines: result.lines }; + return result; + } else { + return result; + } +} + +export function compileJSASMStep(step: BuildStep): BuildStepResult { + gatherFiles(step); + var code = getWorkFileAsString(step.path); + var platform = step.platform || 'verilog'; + return compileJSASM(code, platform, step, false); +} + +function compileInlineASM(code: string, platform, options, errors, asmlines) { + code = code.replace(/__asm\b([\s\S]+?)\b__endasm\b/g, function (s, asmcode, index) { + var firstline = code.substr(0, index).match(/\n/g).length; + var asmout = compileJSASM(asmcode, platform, options, true); + if (asmout.errors && asmout.errors.length) { + for (var i = 0; i < asmout.errors.length; i++) { + asmout.errors[i].line += firstline; + errors.push(asmout.errors[i]); + } + return ""; + } else if (asmout.output) { + let s = ""; + var out = asmout.output; + for (var i = 0; i < out.length; i++) { + if (i > 0) { + s += ","; + if ((i & 0xff) == 0) s += "\n"; + } + s += 0 | out[i]; + } + if (asmlines) { + var al = asmout.lines; + for (var i = 0; i < al.length; i++) { + al[i].line += firstline; + asmlines.push(al[i]); + } + } + return s; + } + }); + return code; +} + +export function compileVerilator(step: BuildStep): BuildStepResult { + loadNative("verilator_bin"); + var platform = step.platform || 'verilog'; + var errors: WorkerError[] = []; + gatherFiles(step); + // compile verilog if files are stale + if (staleFiles(step, [xmlPath])) { + // TODO: %Error: Specified --top-module 'ALU' isn't at the top level, it's under another cell 'cpu' + // TODO: ... Use "/* verilator lint_off BLKSEQ */" and lint_on around source to disable this message. + var match_fn = makeErrorMatcher(errors, /%(.+?): (.+?):(\d+)?[:]?\s*(.+)/i, 3, 4, step.path, 2); + var verilator_mod: EmscriptenModule = emglobal.verilator_bin({ + instantiateWasm: moduleInstFn('verilator_bin'), + noInitialRun: true, + noExitRuntime: true, + print: print_fn, + printErr: match_fn, + wasmMemory: getWASMMemory(), // reuse memory + //INITIAL_MEMORY:256*1024*1024, + }); + var code = getWorkFileAsString(step.path); + var topmod = detectTopModuleName(code); + var FS = verilator_mod.FS; + var listings: CodeListingMap = {}; + // process inline assembly, add listings where found + populateFiles(step, FS, { + mainFilePath: step.path, + processFn: (path, code) => { + if (typeof code === 'string') { + let asmlines = []; + code = compileInlineASM(code, platform, step, errors, asmlines); + if (asmlines.length) { + listings[path] = { lines: asmlines }; + } + } + return code; + } + }); + starttime(); + var xmlPath = `obj_dir/V${topmod}.xml`; + try { + var args = ["--cc", "-O3", + "-DEXT_INLINE_ASM", "-DTOPMOD__" + topmod, "-D__8BITWORKSHOP__", + "-Wall", + "-Wno-DECLFILENAME", "-Wno-UNUSED", "-Wno-EOFNEWLINE", "-Wno-PROCASSWIRE", + "--x-assign", "fast", "--noassert", "--pins-sc-biguint", + "--debug-check", // for XML output + "--top-module", topmod, step.path] + execMain(step, verilator_mod, args); + } catch (e) { + console.log(e); + errors.push({ line: 0, msg: "Compiler internal error: " + e }); + } + endtime("compile"); + // remove boring errors + errors = errors.filter(function (e) { return !/Exiting due to \d+/.exec(e.msg); }, errors); + errors = errors.filter(function (e) { return !/Use ["][/][*]/.exec(e.msg); }, errors); + if (errors.length) { + return { errors: errors }; + } + starttime(); + var xmlParser = new vxmlparser.VerilogXMLParser(); + try { + var xmlContent = FS.readFile(xmlPath, { encoding: 'utf8' }); + var xmlScrubbed = xmlContent.replace(/ fl=".+?" loc=".+?"/g, ''); + // TODO: this squelches the .asm listing + //listings[step.prefix + '.xml'] = {lines:[],text:xmlContent}; + putWorkFile(xmlPath, xmlScrubbed); // don't detect changes in source position + if (!anyTargetChanged(step, [xmlPath])) + return; + xmlParser.parse(xmlContent); + } catch (e) { + console.log(e, e.stack); + if (e.$loc != null) { + let $loc = e.$loc as SourceLocation; + errors.push({ msg: "" + e, path: $loc.path, line: $loc.line }); + } else { + errors.push({ line: 0, msg: "" + e }); + } + return { errors: errors, listings: listings }; + } finally { + endtime("parse"); + } + return { + output: xmlParser, + errors: errors, + listings: listings, + }; + } +} + +// TODO: test +export function compileYosys(step: BuildStep): BuildStepResult { + loadNative("yosys"); + var code = step.code; + var errors = []; + var match_fn = makeErrorMatcher(errors, /ERROR: (.+?) in line (.+?[.]v):(\d+)[: ]+(.+)/i, 3, 4, step.path); + starttime(); + var yosys_mod: EmscriptenModule = emglobal.yosys({ + instantiateWasm: moduleInstFn('yosys'), + noInitialRun: true, + print: print_fn, + printErr: match_fn, + }); + endtime("create module"); + var topmod = detectTopModuleName(code); + var FS = yosys_mod.FS; + FS.writeFile(topmod + ".v", code); + starttime(); + try { + execMain(step, yosys_mod, ["-q", "-o", topmod + ".json", "-S", topmod + ".v"]); + } catch (e) { + console.log(e); + endtime("compile"); + return { errors: errors }; + } + endtime("compile"); + //TODO: filename in errors + if (errors.length) return { errors: errors }; + try { + var json_file = FS.readFile(topmod + ".json", { encoding: 'utf8' }); + var json = JSON.parse(json_file); + console.log(json); + return { output: json, errors: errors }; // TODO + } catch (e) { + console.log(e); + return { errors: errors }; + } +} + +export function compileSilice(step: BuildStep): BuildStepResult { + loadNative("silice"); + var params = step.params; + gatherFiles(step, { mainFilePath: "main.ice" }); + var destpath = step.prefix + '.v'; + var errors: WorkerError[] = []; + var errfile: string; + var errline: number; + if (staleFiles(step, [destpath])) { + //[preprocessor] 97] attempt to concatenate a nil value (global 'addrW') + var match_fn = (s: string) => { + s = (s as any).replaceAll(/\033\[\d+\w/g, ''); + var mf = /file:\s*(\w+)/.exec(s); + var ml = /line:\s+(\d+)/.exec(s); + var preproc = /\[preprocessor\] (\d+)\] (.+)/.exec(s); + if (mf) errfile = mf[1]; + else if (ml) errline = parseInt(ml[1]); + else if (preproc) { + errors.push({ path: step.path, line: parseInt(preproc[1]), msg: preproc[2] }); + } + else if (errfile && errline && s.length > 1) { + if (s.length > 2) { + errors.push({ path: errfile + ".ice", line: errline, msg: s }); + } else { + errfile = null; + errline = null; + } + } + else console.log(s); + } + var silice: EmscriptenModule = emglobal.silice({ + instantiateWasm: moduleInstFn('silice'), + noInitialRun: true, + print: match_fn, + printErr: match_fn, + }); + var FS = silice.FS; + setupFS(FS, 'Silice'); + populateFiles(step, FS); + populateExtraFiles(step, FS, params.extra_compile_files); + const FWDIR = '/share/frameworks'; + var args = [ + '-D', 'NTSC=1', + '--frameworks_dir', FWDIR, + '-f', `/8bitworkshop.v`, + '-o', destpath, + step.path]; + execMain(step, silice, args); + if (errors.length) + return { errors: errors }; + var vout = FS.readFile(destpath, { encoding: 'utf8' }); + putWorkFile(destpath, vout); + } + return { + nexttool: "verilator", + path: destpath, + args: [destpath], + files: [destpath], + }; +} + diff --git a/src/worker/tools/x86.ts b/src/worker/tools/x86.ts new file mode 100644 index 00000000..96842e78 --- /dev/null +++ b/src/worker/tools/x86.ts @@ -0,0 +1,124 @@ +import { WorkerError, CodeListingMap } from "../../common/workertypes"; +import { BuildStep, BuildStepResult, loadNative, gatherFiles, staleFiles, emglobal, moduleInstFn, getWorkFileAsString, preprocessMCPP, populateFiles, fixParamsWithDefines, execMain, putWorkFile, print_fn, msvcErrorMatcher, anyTargetChanged, parseListing } from "../workermain"; +import { EmscriptenModule } from "../workermain" + +// http://www.techhelpmanual.com/829-program_startup___exit.html +export function compileSmallerC(step: BuildStep): BuildStepResult { + loadNative("smlrc"); + var params = step.params; + // stderr + var re_err1 = /^Error in "[/]*(.+)" [(](\d+):(\d+)[)]/; + var errors: WorkerError[] = []; + var errline = 0; + var errpath = step.path; + function match_fn(s) { + var matches = re_err1.exec(s); + if (matches) { + errline = parseInt(matches[2]); + errpath = matches[1]; + } else { + errors.push({ + line: errline, + msg: s, + path: errpath, + }); + } + } + gatherFiles(step, { mainFilePath: "main.c" }); + var destpath = step.prefix + '.asm'; + if (staleFiles(step, [destpath])) { + var args = ['-seg16', + //'-nobss', + '-no-externs', + step.path, destpath]; + var smlrc: EmscriptenModule = emglobal.smlrc({ + instantiateWasm: moduleInstFn('smlrc'), + noInitialRun: true, + //logReadFiles:true, + print: match_fn, + printErr: match_fn, + }); + // load source file and preprocess + var code = getWorkFileAsString(step.path); + var preproc = preprocessMCPP(step, null); + if (preproc.errors) { + return { errors: preproc.errors }; + } + else code = preproc.code; + // set up filesystem + var FS = smlrc.FS; + //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); + populateFiles(step, FS); + FS.writeFile(step.path, code); + fixParamsWithDefines(step.path, params); + if (params.extra_compile_args) { + args.unshift.apply(args, params.extra_compile_args); + } + execMain(step, smlrc, args); + if (errors.length) + return { errors: errors }; + var asmout = FS.readFile(destpath, { encoding: 'utf8' }); + putWorkFile(destpath, asmout); + } + return { + nexttool: "yasm", + path: destpath, + args: [destpath], + files: [destpath], + }; +} + +export function assembleYASM(step: BuildStep): BuildStepResult { + loadNative("yasm"); + var errors = []; + gatherFiles(step, { mainFilePath: "main.asm" }); + var objpath = step.prefix + ".exe"; + var lstpath = step.prefix + ".lst"; + var mappath = step.prefix + ".map"; + if (staleFiles(step, [objpath])) { + var args = ['-X', 'vc', + '-a', 'x86', '-f', 'dosexe', '-p', 'nasm', + '-D', 'freedos', + //'-g', 'dwarf2', + //'-I/share/asminc', + '-o', objpath, '-l', lstpath, '--mapfile=' + mappath, + step.path]; + // return yasm/*.ready*/ + var YASM: EmscriptenModule = emglobal.yasm({ + instantiateWasm: moduleInstFn('yasm'), + noInitialRun: true, + //logReadFiles:true, + print: print_fn, + printErr: msvcErrorMatcher(errors), + }); + var FS = YASM.FS; + //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); + populateFiles(step, FS); + //fixParamsWithDefines(step.path, step.params); + execMain(step, YASM, args); + if (errors.length) + return { errors: errors }; + var objout, lstout, mapout; + objout = FS.readFile(objpath, { encoding: 'binary' }); + lstout = FS.readFile(lstpath, { encoding: 'utf8' }); + mapout = FS.readFile(mappath, { encoding: 'utf8' }); + putWorkFile(objpath, objout); + putWorkFile(lstpath, lstout); + //putWorkFile(mappath, mapout); + if (!anyTargetChanged(step, [objpath])) + return; + var symbolmap = {}; + var segments = []; + var lines = parseListing(lstout, /\s*(\d+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+(.+)/i, 1, 2, 3); + var listings: CodeListingMap = {}; + listings[lstpath] = { lines: lines, text: lstout }; + return { + output: objout, //.slice(0), + listings: listings, + errors: errors, + symbolmap: symbolmap, + segments: segments + }; + } +} + diff --git a/src/worker/tools/z80.ts b/src/worker/tools/z80.ts new file mode 100644 index 00000000..39ccea37 --- /dev/null +++ b/src/worker/tools/z80.ts @@ -0,0 +1,67 @@ + +import { CodeListingMap } from "../../common/workertypes"; +import { anyTargetChanged, BuildStep, BuildStepResult, emglobal, EmscriptenModule, execMain, gatherFiles, loadNative, makeErrorMatcher, moduleInstFn, parseListing, populateFiles, print_fn, putWorkFile, staleFiles } from "../workermain" + + +export function assembleZMAC(step: BuildStep): BuildStepResult { + loadNative("zmac"); + var hexout, lstout, binout; + var errors = []; + var params = step.params; + gatherFiles(step, { mainFilePath: "main.asm" }); + var lstpath = step.prefix + ".lst"; + var binpath = step.prefix + ".cim"; + if (staleFiles(step, [binpath, lstpath])) { + /* + error1.asm(4) : 'l18d4' Undeclared + JP L18D4 + + error1.asm(11): warning: 'foobar' treated as label (instruction typo?) + Add a colon or move to first column to stop this warning. + 1 errors (see listing if no diagnostics appeared here) + */ + var ZMAC: EmscriptenModule = emglobal.zmac({ + instantiateWasm: moduleInstFn('zmac'), + noInitialRun: true, + //logReadFiles:true, + print: print_fn, + printErr: makeErrorMatcher(errors, /([^( ]+)\s*[(](\d+)[)]\s*:\s*(.+)/, 2, 3, step.path), + }); + var FS = ZMAC.FS; + populateFiles(step, FS); + // TODO: don't know why CIM (hexary) doesn't work + execMain(step, ZMAC, ['-z', '-c', '--oo', 'lst,cim', step.path]); + if (errors.length) { + return { errors: errors }; + } + lstout = FS.readFile("zout/" + lstpath, { encoding: 'utf8' }); + binout = FS.readFile("zout/" + binpath, { encoding: 'binary' }); + putWorkFile(binpath, binout); + putWorkFile(lstpath, lstout); + if (!anyTargetChanged(step, [binpath, lstpath])) + return; + // 230: 1739+7+x 017A 1600 L017A: LD D,00h + var lines = parseListing(lstout, /\s*(\d+):\s*([0-9a-f]+)\s+([0-9a-f]+)\s+(.+)/i, 1, 2, 3); + var listings: CodeListingMap = {}; + listings[lstpath] = { lines: lines }; + // parse symbol table + var symbolmap = {}; + var sympos = lstout.indexOf('Symbol Table:'); + if (sympos > 0) { + var symout = lstout.slice(sympos + 14); + symout.split('\n').forEach(function (l) { + var m = l.match(/(\S+)\s+([= ]*)([0-9a-f]+)/i); + if (m) { + symbolmap[m[1]] = parseInt(m[3], 16); + } + }); + } + return { + output: binout, + listings: listings, + errors: errors, + symbolmap: symbolmap + }; + } +} + diff --git a/src/worker/workermain.ts b/src/worker/workermain.ts index 7b76550e..a5080ff9 100644 --- a/src/worker/workermain.ts +++ b/src/worker/workermain.ts @@ -1,12 +1,9 @@ -/// import type { WorkerResult, WorkerBuildStep, WorkerMessage, WorkerError, SourceLine, CodeListingMap, Segment, SourceLocation } from "../common/workertypes"; import { getBasePlatform, getRootBasePlatform, hex } from "../common/util"; -import { Assembler } from "./assembler"; -import * as vxmlparser from '../common/hdl/vxmlparser'; -import * as basic_compiler from '../common/basic/compiler'; -interface EmscriptenModule { +/// +export interface EmscriptenModule { callMain: (args: string[]) => void; FS : any; // TODO } @@ -16,7 +13,7 @@ declare function postMessage(msg); const ENVIRONMENT_IS_WEB = typeof window === 'object'; const ENVIRONMENT_IS_WORKER = typeof importScripts === 'function'; -const emglobal : any = ENVIRONMENT_IS_WORKER ? self : ENVIRONMENT_IS_WEB ? window : global; +export const emglobal : any = ENVIRONMENT_IS_WORKER ? self : ENVIRONMENT_IS_WEB ? window : global; // simple CommonJS module loader // TODO: relative paths for dependencies @@ -44,7 +41,7 @@ var CACHE_WASM_MODULES = true; // if false, use asm.js only // TODO: which modules need this? var wasmMemory; -function getWASMMemory() { +export function getWASMMemory() { if (wasmMemory == null) { wasmMemory = new WebAssembly.Memory({ 'initial': 1024, // 64MB @@ -68,7 +65,7 @@ function getWASMModule(module_id:string) { return module; } // function for use with instantiateWasm -function moduleInstFn(module_id:string) { +export function moduleInstFn(module_id:string) { return function(imports,ri) { var mod = getWASMModule(module_id); var inst = new WebAssembly.Instance(mod, imports); @@ -352,8 +349,8 @@ var PLATFORM_PARAMS = { PLATFORM_PARAMS['sms-sms-libcv'] = PLATFORM_PARAMS['sms-sg1000-libcv']; var _t1; -function starttime() { _t1 = new Date(); } -function endtime(msg) { var _t2 = new Date(); console.log(msg, _t2.getTime() - _t1.getTime(), "ms"); } +export function starttime() { _t1 = new Date(); } +export function endtime(msg) { var _t2 = new Date(); console.log(msg, _t2.getTime() - _t1.getTime(), "ms"); } /// working file store and build steps @@ -383,7 +380,7 @@ export interface WorkerNextToolResult { bblines?: boolean } -interface BuildStep extends WorkerBuildStep { +export interface BuildStep extends WorkerBuildStep { files? : string[] args? : string[] nextstep? : BuildStep @@ -443,7 +440,7 @@ class FileWorkingStore { } } -var store = new FileWorkingStore(); +export var store = new FileWorkingStore(); /// @@ -565,15 +562,15 @@ function compareData(a:FileData, b:FileData) : boolean { } } -function putWorkFile(path:string, data:FileData) { +export function putWorkFile(path:string, data:FileData) { return store.putFile(path, data); } -function getWorkFileAsString(path:string) : string { +export function getWorkFileAsString(path:string) : string { return store.getFileAsString(path); } -function populateEntry(fs, path:string, entry:FileEntry, options:BuildOptions) { +export function populateEntry(fs, path:string, entry:FileEntry, options:BuildOptions) { var data = entry.data; if (options && options.processFn) { data = options.processFn(path, data); @@ -594,7 +591,7 @@ function populateEntry(fs, path:string, entry:FileEntry, options:BuildOptions) { } // can call multiple times (from populateFiles) -function gatherFiles(step:BuildStep, options?:BuildOptions) : number { +export function gatherFiles(step:BuildStep, options?:BuildOptions) : number { var maxts = 0; if (step.files) { for (var i=0; i 0) ? s.substring(0, pos) : s; } -function populateFiles(step:BuildStep, fs, options?:BuildOptions) { +export function populateFiles(step:BuildStep, fs, options?:BuildOptions) { gatherFiles(step, options); if (!step.files) throw Error("call gatherFiles() first"); for (var i=0; i { @@ -879,7 +874,7 @@ function parseListing(code:string, lineMatch, iline:number, ioffset:number, iins return lines; } -function parseSourceLines(code:string, lineMatch, offsetMatch) { +export function parseSourceLines(code:string, lineMatch, offsetMatch) { var lines = []; var lastlinenum = 0; for (var line of code.split(re_crlf)) { @@ -901,458 +896,14 @@ function parseSourceLines(code:string, lineMatch, offsetMatch) { return lines; } -function parseDASMListing(lstpath:string, lsttext:string, listings:CodeListingMap, errors:WorkerError[], unresolved:{}) { - // TODO: this gets very slow - // TODO: macros that are on adjacent lines don't get offset addresses - // 4 08ee a9 00 start lda #01workermain.js:23:5 - let lineMatch = /\s*(\d+)\s+(\S+)\s+([0-9a-f]+)\s+([?0-9a-f][?0-9a-f ]+)?\s+(.+)?/i; - let equMatch = /\bequ\b/i; - let macroMatch = /\bMAC\s+(\S+)?/i; - let lastline = 0; - let macros = {}; - let lstline = 0; - let lstlist = listings[lstpath]; - for (let line of lsttext.split(re_crlf)) { - lstline++; - let linem = lineMatch.exec(line + " "); - if (linem && linem[1] != null) { - let linenum = parseInt(linem[1]); - let filename = linem[2]; - let offset = parseInt(linem[3], 16); - let insns = linem[4]; - let restline = linem[5]; - if (insns && insns.startsWith('?')) insns = null; - // don't use listing yet - if (lstlist && lstlist.lines) { - lstlist.lines.push({ - line:lstline, - offset:offset, - insns:insns, - iscode:true, - }); - } - // inside of a file? - let lst = listings[filename]; - if (lst) { - var lines = lst.lines; - // look for MAC statement - let macmatch = macroMatch.exec(restline); - if (macmatch) { - macros[macmatch[1]] = {line:parseInt(linem[1]), file:linem[2].toLowerCase()}; - } - else if (insns && restline && !restline.match(equMatch)) { - lines.push({ - line:linenum, - offset:offset, - insns:insns, - iscode:restline[0] != '.' - }); - } - lastline = linenum; - } else { - // inside of macro? - let mac = macros[filename.toLowerCase()]; - // macro invocation in main file - if (mac && linenum == 0) { - lines.push({ - line:lastline+1, - offset:offset, - insns:insns, - iscode:true - }); - } - if (insns && mac) { - let maclst = listings[mac.file]; - if (maclst && maclst.lines) { - maclst.lines.push({ - path:mac.file, - line:mac.line+linenum, - offset:offset, - insns:insns, - iscode:true - }); - } - // TODO: a listing file can't include other files - } else { - // inside of macro or include file - if (insns && linem[3] && lastline>0) { - lines.push({ - line:lastline+1, - offset:offset, - insns:null - }); - } - } - } - // TODO: better symbol test (word boundaries) - // TODO: ignore IFCONST and IFNCONST usage - for (let key in unresolved) { - let l = restline || line; - // find the identifier substring - let pos = l.indexOf(key); - if (pos >= 0) { - // strip the comment, if any - let cmt = l.indexOf(';'); - if (cmt < 0 || cmt > pos) { - // make sure identifier is flanked by non-word chars - if (new RegExp("\\b"+key+"\\b").exec(l)) { - errors.push({ - path:filename, - line:linenum, - msg:"Unresolved symbol '" + key + "'" - }); - } - } - } - } - } - let errm = re_msvc.exec(line); - if (errm) { - errors.push({ - path:errm[1], - line:parseInt(errm[2]), - msg:errm[4] - }) - } - } -} - -function assembleDASM(step:BuildStep) : BuildStepResult { - load("dasm"); - var re_usl = /(\w+)\s+0000\s+[?][?][?][?]/; - var unresolved = {}; - var errors = []; - var errorMatcher = msvcErrorMatcher(errors); - function match_fn(s:string) { - // TODO: what if s is not string? (startsWith is not a function) - var matches = re_usl.exec(s); - if (matches) { - var key = matches[1]; - if (key != 'NO_ILLEGAL_OPCODES') { // TODO - unresolved[matches[1]] = 0; - } - } else if (s.startsWith("Warning:")) { - errors.push({line:0, msg:s.substr(9)}); - } else if (s.startsWith("unable ")) { - errors.push({line:0, msg:s}); - } else if (s.startsWith("segment: ")) { - errors.push({line:0, msg:"Segment overflow: "+s.substring(9)}); - } else if (s.toLowerCase().indexOf('error:') >= 0) { - errors.push({line:0, msg:s.trim()}); - } else { - errorMatcher(s); - } - } - var Module : EmscriptenModule = emglobal.DASM({ - noInitialRun:true, - print:match_fn - }); - var FS = Module.FS; - populateFiles(step, FS, { - mainFilePath:'main.a' - }); - var binpath = step.prefix+'.bin'; - var lstpath = step.prefix+'.lst'; - var sympath = step.prefix+'.sym'; - execMain(step, Module, [step.path, '-f3', - "-l"+lstpath, - "-o"+binpath, - "-s"+sympath ]); - var alst = FS.readFile(lstpath, {'encoding':'utf8'}); - // parse main listing, get errors and listings for each file - var listings : CodeListingMap = {}; - //listings[lstpath] = {lines:[], text:alst}; - for (let path of step.files) { - listings[path] = {lines:[]}; - } - parseDASMListing(lstpath, alst, listings, errors, unresolved); - if (errors.length) { - return {errors:errors}; - } - // read binary rom output and symbols - var aout, asym; - aout = FS.readFile(binpath); - try { - asym = FS.readFile(sympath, {'encoding':'utf8'}); - } catch (e) { - console.log(e); - errors.push({line:0,msg:"No symbol table generated, maybe segment overflow?"}); - return {errors:errors} - } - putWorkFile(binpath, aout); - putWorkFile(lstpath, alst); - putWorkFile(sympath, asym); - // return unchanged if no files changed - // TODO: what if listing or symbols change? - if (!anyTargetChanged(step, [binpath/*, lstpath, sympath*/])) - return; - var symbolmap = {}; - for (var s of asym.split("\n")) { - var toks = s.split(/\s+/); - if (toks && toks.length >= 2 && !toks[0].startsWith('-')) { - symbolmap[toks[0]] = parseInt(toks[1], 16); - } - } - // for bataribasic (TODO) - if (step['bblines']) { - let lst = listings[step.path]; - if (lst) { - lst.asmlines = lst.lines; - lst.text = alst; - lst.lines = []; - } - } - return { - output:aout, - listings:listings, - errors:errors, - symbolmap:symbolmap, - }; -} - -function setupStdin(fs, code:string) { +export function setupStdin(fs, code:string) { var i = 0; fs.init( function() { return i= 0 && segsize > 0 && !seg.startsWith('PRG') && seg != 'RAM') { // TODO - var type = null; - if (seg.startsWith('CODE') || seg == 'STARTUP' || seg == 'RODATA' || seg.endsWith('ROM')) type = 'rom'; - else if (seg == 'ZP' || seg == 'DATA' || seg == 'BSS' || seg.endsWith('RAM')) type = 'ram'; - segments.push({name:seg, start:segstart, size:segsize, last:seglast, type:type}); - } - } - } - // build listings - var listings : CodeListingMap = {}; - for (var fn of step.files) { - if (fn.endsWith('.lst')) { - var lstout = FS.readFile(fn, {encoding:'utf8'}); - lstout = lstout.split('\n\n')[1] || lstout; // remove header - var asmlines = parseCA65Listing(lstout, symbolmap, params, false); - var srclines = parseCA65Listing(lstout, symbolmap, params, true); - putWorkFile(fn, lstout); - // TODO: you have to get rid of all source lines to get asm listing - listings[fn] = { - asmlines:srclines.length ? asmlines : null, - lines:srclines.length ? srclines : asmlines, - text:lstout - }; - } - } - return { - output:aout, //.slice(0), - listings:listings, - errors:errors, - symbolmap:symbolmap, - segments:segments - }; - } -} - -function fixParamsWithDefines(path:string, params){ +export function fixParamsWithDefines(path:string, params){ var libargs = params.libargs; if (path && libargs) { var code = getWorkFileAsString(path); @@ -1395,361 +946,12 @@ function fixParamsWithDefines(path:string, params){ } } -function compileCC65(step:BuildStep) : BuildStepResult { - loadNative("cc65"); - var params = step.params; - // stderr - var re_err1 = /(.*?)[(](\d+)[)].*?: (.+)/; - var errors : WorkerError[] = []; - var errline = 0; - function match_fn(s) { - console.log(s); - var matches = re_err1.exec(s); - if (matches) { - errline = parseInt(matches[2]); - errors.push({ - line:errline, - msg:matches[3], - path:matches[1] - }); - } - } - gatherFiles(step, {mainFilePath:"main.c"}); - var destpath = step.prefix + '.s'; - if (staleFiles(step, [destpath])) { - var CC65 : EmscriptenModule = emglobal.cc65({ - instantiateWasm: moduleInstFn('cc65'), - noInitialRun:true, - //logReadFiles:true, - print:print_fn, - printErr:match_fn, - }); - var FS = CC65.FS; - setupFS(FS, '65-'+getRootBasePlatform(step.platform)); - populateFiles(step, FS); - fixParamsWithDefines(step.path, params); - var args = [ - '-I', '/share/include', - '-I', '.', - "-D", "__8BITWORKSHOP__", - ]; - if (params.define) { - params.define.forEach((x) => args.push('-D'+x)); - } - if (step.mainfile) { - args.unshift.apply(args, ["-D", "__MAIN__"]); - } - var customArgs = params.extra_compiler_args || ['-T', '-g', '-Oirs', '-Cl']; - args = args.concat(customArgs, args); - args.push(step.path); - //console.log(args); - execMain(step, CC65, args); - if (errors.length) - return {errors:errors}; - var asmout = FS.readFile(destpath, {encoding:'utf8'}); - putWorkFile(destpath, asmout); - } - return { - nexttool:"ca65", - path:destpath, - args:[destpath], - files:[destpath], - }; -} - -function hexToArray(s, ofs) { - var buf = new ArrayBuffer(s.length/2); - var arr = new Uint8Array(buf); - for (var i=0; i high_size) high_size = i+address; - } else if (rectype == 1) { - break; - } else { - console.log(s); // unknown record type - } - } - } - // TODO: return ROM anyway? - if (high_size > rom_size) { - //errors.push({line:0, msg:"ROM size too large: 0x" + high_size.toString(16) + " > 0x" + rom_size.toString(16)}); - } - return output; -} - -function assembleSDASZ80(step:BuildStep) : BuildStepResult { - loadNative("sdasz80"); - var objout, lstout, symout; - var errors = []; - gatherFiles(step, {mainFilePath:"main.asm"}); - var objpath = step.prefix + ".rel"; - var lstpath = step.prefix + ".lst"; - if (staleFiles(step, [objpath, lstpath])) { - //?ASxxxx-Error- in line 1 of main.asm null - // .org in REL area or directive / mnemonic error - // ?ASxxxx-Error- in line 1627 of cosmic.asm - // missing or improper operators, terminators, or delimiters - var match_asm_re1 = / in line (\d+) of (\S+)/; // TODO - var match_asm_re2 = / <\w> (.+)/; // TODO - var errline = 0; - var errpath = step.path; - var match_asm_fn = (s:string) => { - var m = match_asm_re1.exec(s); - if (m) { - errline = parseInt(m[1]); - errpath = m[2]; - } else { - m = match_asm_re2.exec(s); - if (m) { - errors.push({ - line:errline, - path:errpath, - msg:m[1] - }); - } - } - } - var ASZ80 : EmscriptenModule = emglobal.sdasz80({ - instantiateWasm: moduleInstFn('sdasz80'), - noInitialRun:true, - //logReadFiles:true, - print:match_asm_fn, - printErr:match_asm_fn, - }); - var FS = ASZ80.FS; - populateFiles(step, FS); - execMain(step, ASZ80, ['-plosgffwy', step.path]); - if (errors.length) { - return {errors:errors}; - } - objout = FS.readFile(objpath, {encoding:'utf8'}); - lstout = FS.readFile(lstpath, {encoding:'utf8'}); - putWorkFile(objpath, objout); - putWorkFile(lstpath, lstout); - } - return { - linktool:"sdldz80", - files:[objpath, lstpath], - args:[objpath] - }; - //symout = FS.readFile("main.sym", {encoding:'utf8'}); -} - -function linkSDLDZ80(step:BuildStep) -{ - loadNative("sdldz80"); - var errors = []; - gatherFiles(step); - var binpath = "main.ihx"; - if (staleFiles(step, [binpath])) { - //?ASlink-Warning-Undefined Global '__divsint' referenced by module 'main' - var match_aslink_re = /\?ASlink-(\w+)-(.+)/; - var match_aslink_fn = (s:string) => { - var matches = match_aslink_re.exec(s); - if (matches) { - errors.push({ - line:0, - msg:matches[2] - }); - } - } - var params = step.params; - var LDZ80 : EmscriptenModule = emglobal.sdldz80({ - instantiateWasm: moduleInstFn('sdldz80'), - noInitialRun:true, - //logReadFiles:true, - print:match_aslink_fn, - printErr:match_aslink_fn, - }); - var FS = LDZ80.FS; - setupFS(FS, 'sdcc'); - populateFiles(step, FS); - populateExtraFiles(step, FS, params.extra_link_files); - // TODO: coleco hack so that -u flag works - if (step.platform.startsWith("coleco")) { - FS.writeFile('crt0.rel', FS.readFile('/share/lib/coleco/crt0.rel', {encoding:'utf8'})); - FS.writeFile('crt0.lst', '\n'); // TODO: needed so -u flag works - } - var args = ['-mjwxyu', - '-i', 'main.ihx', // TODO: main? - '-b', '_CODE=0x'+params.code_start.toString(16), - '-b', '_DATA=0x'+params.data_start.toString(16), - '-k', '/share/lib/z80', - '-l', 'z80']; - if (params.extra_link_args) - args.push.apply(args, params.extra_link_args); - args.push.apply(args, step.args); - //console.log(args); - execMain(step, LDZ80, args); - var hexout = FS.readFile("main.ihx", {encoding:'utf8'}); - var noiout = FS.readFile("main.noi", {encoding:'utf8'}); - putWorkFile("main.ihx", hexout); - putWorkFile("main.noi", noiout); - // return unchanged if no files changed - if (!anyTargetChanged(step, ["main.ihx", "main.noi"])) - return; - // parse binary file - var binout = parseIHX(hexout, params.rom_start!==undefined?params.rom_start:params.code_start, params.rom_size, errors); - if (errors.length) { - return {errors:errors}; - } - // parse listings - var listings : CodeListingMap = {}; - for (var fn of step.files) { - if (fn.endsWith('.lst')) { - var rstout = FS.readFile(fn.replace('.lst','.rst'), {encoding:'utf8'}); - // 0000 21 02 00 [10] 52 ld hl, #2 - var asmlines = parseListing(rstout, /^\s*([0-9A-F]{4})\s+([0-9A-F][0-9A-F r]*[0-9A-F])\s+\[([0-9 ]+)\]?\s+(\d+) (.*)/i, 4, 1, 2, 3); - var srclines = parseSourceLines(rstout, /^\s+\d+ ;:(\d+):/i, /^\s*([0-9A-F]{4})/i); - putWorkFile(fn, rstout); - // TODO: you have to get rid of all source lines to get asm listing - listings[fn] = { - asmlines:srclines.length ? asmlines : null, - lines:srclines.length ? srclines : asmlines, - text:rstout - }; - } - } - // parse symbol map - var symbolmap = {}; - for (var s of noiout.split("\n")) { - var toks = s.split(" "); - if (toks[0] == 'DEF' && !toks[1].startsWith("A$")) { - symbolmap[toks[1]] = parseInt(toks[2], 16); - } - } - // build segment map - var seg_re = /^s__(\w+)$/; - var segments = []; - // TODO: use stack params for stack segment - for (let ident in symbolmap) { - let m = seg_re.exec(ident); - if (m) { - let seg = m[1]; - let segstart = symbolmap[ident]; // s__SEG - let segsize = symbolmap['l__'+seg]; // l__SEG - if (segstart >= 0 && segsize > 0) { - var type = null; - if (['INITIALIZER','GSINIT','GSFINAL'].includes(seg)) type = 'rom'; - else if (seg.startsWith('CODE')) type = 'rom'; - else if (['DATA','INITIALIZED'].includes(seg)) type = 'ram'; - if (type == 'rom' || segstart > 0) // ignore HEADER0, CABS0, etc (TODO?) - segments.push({name:seg, start:segstart, size:segsize, type:type}); - } - } - } - return { - output:binout, - listings:listings, - errors:errors, - symbolmap:symbolmap, - segments:segments - }; - } -} - -function compileSDCC(step:BuildStep) : BuildStepResult { - - gatherFiles(step, { - mainFilePath:"main.c" // not used - }); - var outpath = step.prefix + ".asm"; - if (staleFiles(step, [outpath])) { - var errors = []; - var params = step.params; - loadNative('sdcc'); - var SDCC : EmscriptenModule = emglobal.sdcc({ - instantiateWasm: moduleInstFn('sdcc'), - noInitialRun:true, - noFSInit:true, - print:print_fn, - printErr:msvcErrorMatcher(errors), - //TOTAL_MEMORY:256*1024*1024, - }); - var FS = SDCC.FS; - populateFiles(step, FS); - // load source file and preprocess - var code = getWorkFileAsString(step.path); - var preproc = preprocessMCPP(step, 'sdcc'); - if (preproc.errors) { - return { errors: preproc.errors }; - } - else code = preproc.code; - // pipe file to stdin - setupStdin(FS, code); - setupFS(FS, 'sdcc'); - var args = ['--vc', '--std-sdcc99', '-mz80', //'-Wall', - '--c1mode', - //'--debug', - //'-S', 'main.c', - //'--asm=sdasz80', - //'--reserve-regs-iy', - '--less-pedantic', - ///'--fomit-frame-pointer', - //'--opt-code-speed', - //'--max-allocs-per-node', '1000', - //'--cyclomatic', - //'--nooverlay', - //'--nogcse', - //'--nolabelopt', - //'--noinvariant', - //'--noinduction', - //'--nojtbound', - //'--noloopreverse', - '-o', outpath]; - // if "#pragma opt_code" found do not disable optimziations - if (!/^\s*#pragma\s+opt_code/m.exec(code)) { - args.push.apply(args, [ - '--oldralloc', - '--no-peep', - '--nolospre' - ]); - } - if (params.extra_compile_args) { - args.push.apply(args, params.extra_compile_args); - } - execMain(step, SDCC, args); - // TODO: preprocessor errors w/ correct file - if (errors.length /* && nwarnings < msvc_errors.length*/) { - return {errors:errors}; - } - // massage the asm output - var asmout = FS.readFile(outpath, {encoding:'utf8'}); - asmout = " .area _HOME\n .area _CODE\n .area _INITIALIZER\n .area _DATA\n .area _INITIALIZED\n .area _BSEG\n .area _BSS\n .area _HEAP\n" + asmout; - putWorkFile(outpath, asmout); - } - return { - nexttool:"sdasz80", - path:outpath, - args:[outpath], - files:[outpath], - }; -} function makeCPPSafe(s:string) : string { return s.replace(/[^A-Za-z0-9_]/g,'_'); } -function preprocessMCPP(step:BuildStep, filesys:string) { +export function preprocessMCPP(step:BuildStep, filesys:string) { load("mcpp"); var platform = step.platform; var params = PLATFORM_PARAMS[getBasePlatform(platform)]; @@ -1802,409 +1004,7 @@ function preprocessMCPP(step:BuildStep, filesys:string) { return {code:iout}; } -// TODO: must be a better way to do all this - -function detectModuleName(code:string) { - var m = /^\s*module\s+(\w+_top)\b/m.exec(code) - || /^\s*module\s+(top|t)\b/m.exec(code) - || /^\s*module\s+(\w+)\b/m.exec(code); - return m ? m[1] : null; -} - -function detectTopModuleName(code:string) { - var topmod = detectModuleName(code) || "top"; - var m = /^\s*module\s+(\w+?_top)/m.exec(code); - if (m && m[1]) topmod = m[1]; - return topmod; -} - -// cached stuff (TODO) -var jsasm_module_top; -var jsasm_module_output; -var jsasm_module_key; - -function compileJSASM(asmcode:string, platform, options, is_inline) { - var asm = new Assembler(null); - var includes = []; - asm.loadJSON = (filename:string) => { - var jsontext = getWorkFileAsString(filename); - if (!jsontext) throw Error("could not load " + filename); - return JSON.parse(jsontext); - }; - asm.loadInclude = (filename) => { - if (!filename.startsWith('"') || !filename.endsWith('"')) - return 'Expected filename in "double quotes"'; - filename = filename.substr(1, filename.length-2); - includes.push(filename); - }; - var loaded_module = false; - asm.loadModule = (top_module : string) => { - // compile last file in list - loaded_module = true; - var key = top_module + '/' + includes; - if (jsasm_module_key != key) { - jsasm_module_key = key; - jsasm_module_output = null; - } - jsasm_module_top = top_module; - var main_filename = includes[includes.length-1]; - // TODO: take out .asm dependency - var voutput = compileVerilator({platform:platform, files:includes, path:main_filename, tool:'verilator'}); - if (voutput) - jsasm_module_output = voutput; - return null; // no error - } - var result = asm.assembleFile(asmcode); - if (loaded_module && jsasm_module_output) { - // errors? return them - if (jsasm_module_output.errors && jsasm_module_output.errors.length) - return jsasm_module_output; - // return program ROM array - var asmout = result.output; - // TODO: unify - result.output = jsasm_module_output.output; - // TODO: typecheck this garbage - (result as any).output.program_rom = asmout; - // TODO: not cpu_platform__DOT__program_rom anymore, make const - (result as any).output.program_rom_variable = jsasm_module_top + "$program_rom"; - (result as any).listings = {}; - (result as any).listings[options.path] = {lines:result.lines}; - return result; - } else { - return result; - } -} - -function compileJSASMStep(step:BuildStep) : BuildStepResult { - gatherFiles(step); - var code = getWorkFileAsString(step.path); - var platform = step.platform || 'verilog'; - return compileJSASM(code, platform, step, false); -} - -function compileInlineASM(code:string, platform, options, errors, asmlines) { - code = code.replace(/__asm\b([\s\S]+?)\b__endasm\b/g, function(s,asmcode,index) { - var firstline = code.substr(0,index).match(/\n/g).length; - var asmout = compileJSASM(asmcode, platform, options, true); - if (asmout.errors && asmout.errors.length) { - for (var i=0; i0) { - s += ","; - if ((i & 0xff) == 0) s += "\n"; - } - s += 0|out[i]; - } - if (asmlines) { - var al = asmout.lines; - for (var i=0; i { - if (typeof code === 'string') { - let asmlines = []; - code = compileInlineASM(code, platform, step, errors, asmlines); - if (asmlines.length) { - listings[path] = {lines:asmlines}; - } - } - return code; - } - }); - starttime(); - var xmlPath = `obj_dir/V${topmod}.xml`; - try { - var args = ["--cc", "-O3", - "-DEXT_INLINE_ASM", "-DTOPMOD__"+topmod, "-D__8BITWORKSHOP__", - "-Wall", - "-Wno-DECLFILENAME", "-Wno-UNUSED", "-Wno-EOFNEWLINE", "-Wno-PROCASSWIRE", - "--x-assign", "fast", "--noassert", "--pins-sc-biguint", - "--debug-check", // for XML output - "--top-module", topmod, step.path] - execMain(step, verilator_mod, args); - } catch (e) { - console.log(e); - errors.push({line:0,msg:"Compiler internal error: " + e}); - } - endtime("compile"); - // remove boring errors - errors = errors.filter(function(e) { return !/Exiting due to \d+/.exec(e.msg); }, errors); - errors = errors.filter(function(e) { return !/Use ["][/][*]/.exec(e.msg); }, errors); - if (errors.length) { - return {errors:errors}; - } - starttime(); - var xmlParser = new vxmlparser.VerilogXMLParser(); - try { - var xmlContent = FS.readFile(xmlPath, {encoding:'utf8'}); - var xmlScrubbed = xmlContent.replace(/ fl=".+?" loc=".+?"/g, ''); - // TODO: this squelches the .asm listing - //listings[step.prefix + '.xml'] = {lines:[],text:xmlContent}; - putWorkFile(xmlPath, xmlScrubbed); // don't detect changes in source position - if (!anyTargetChanged(step, [xmlPath])) - return; - xmlParser.parse(xmlContent); - } catch(e) { - console.log(e, e.stack); - if (e.$loc != null) { - let $loc = e.$loc as SourceLocation; - errors.push({msg:""+e, path:$loc.path, line:$loc.line}); - } else { - errors.push({line:0,msg:""+e}); - } - return {errors:errors, listings:listings}; - } finally { - endtime("parse"); - } - return { - output: xmlParser, - errors: errors, - listings: listings, - }; - } -} - -// TODO: test -function compileYosys(step:BuildStep) : BuildStepResult { - loadNative("yosys"); - var code = step.code; - var errors = []; - var match_fn = makeErrorMatcher(errors, /ERROR: (.+?) in line (.+?[.]v):(\d+)[: ]+(.+)/i, 3, 4, step.path); - starttime(); - var yosys_mod : EmscriptenModule = emglobal.yosys({ - instantiateWasm: moduleInstFn('yosys'), - noInitialRun:true, - print:print_fn, - printErr:match_fn, - }); - endtime("create module"); - var topmod = detectTopModuleName(code); - var FS = yosys_mod.FS; - FS.writeFile(topmod+".v", code); - starttime(); - try { - execMain(step, yosys_mod, ["-q", "-o", topmod+".json", "-S", topmod+".v"]); - } catch (e) { - console.log(e); - endtime("compile"); - return {errors:errors}; - } - endtime("compile"); - //TODO: filename in errors - if (errors.length) return {errors:errors}; - try { - var json_file = FS.readFile(topmod+".json", {encoding:'utf8'}); - var json = JSON.parse(json_file); - console.log(json); - return {output:json, errors:errors}; // TODO - } catch(e) { - console.log(e); - return {errors:errors}; - } -} - -function assembleZMAC(step:BuildStep) : BuildStepResult { - loadNative("zmac"); - var hexout, lstout, binout; - var errors = []; - var params = step.params; - gatherFiles(step, {mainFilePath:"main.asm"}); - var lstpath = step.prefix + ".lst"; - var binpath = step.prefix + ".cim"; - if (staleFiles(step, [binpath, lstpath])) { - /* -error1.asm(4) : 'l18d4' Undeclared - JP L18D4 - -error1.asm(11): warning: 'foobar' treated as label (instruction typo?) - Add a colon or move to first column to stop this warning. -1 errors (see listing if no diagnostics appeared here) - */ - var ZMAC : EmscriptenModule = emglobal.zmac({ - instantiateWasm: moduleInstFn('zmac'), - noInitialRun:true, - //logReadFiles:true, - print:print_fn, - printErr:makeErrorMatcher(errors, /([^( ]+)\s*[(](\d+)[)]\s*:\s*(.+)/, 2, 3, step.path), - }); - var FS = ZMAC.FS; - populateFiles(step, FS); - // TODO: don't know why CIM (hexary) doesn't work - execMain(step, ZMAC, ['-z', '-c', '--oo', 'lst,cim', step.path]); - if (errors.length) { - return {errors:errors}; - } - lstout = FS.readFile("zout/"+lstpath, {encoding:'utf8'}); - binout = FS.readFile("zout/"+binpath, {encoding:'binary'}); - putWorkFile(binpath, binout); - putWorkFile(lstpath, lstout); - if (!anyTargetChanged(step, [binpath, lstpath])) - return; - // 230: 1739+7+x 017A 1600 L017A: LD D,00h - var lines = parseListing(lstout, /\s*(\d+):\s*([0-9a-f]+)\s+([0-9a-f]+)\s+(.+)/i, 1, 2, 3); - var listings : CodeListingMap = {}; - listings[lstpath] = {lines:lines}; - // parse symbol table - var symbolmap = {}; - var sympos = lstout.indexOf('Symbol Table:'); - if (sympos > 0) { - var symout = lstout.slice(sympos+14); - symout.split('\n').forEach(function(l) { - var m = l.match(/(\S+)\s+([= ]*)([0-9a-f]+)/i); - if (m) { - symbolmap[m[1]] = parseInt(m[3],16); - } - }); - } - return { - output:binout, - listings:listings, - errors:errors, - symbolmap:symbolmap - }; - } -} - -function preprocessBatariBasic(code:string) : string { - load("bbpreprocess"); - var bbout = ""; - function addbbout_fn(s) { - bbout += s; - bbout += "\n"; - } - var BBPRE : EmscriptenModule = emglobal.preprocess({ - noInitialRun:true, - //logReadFiles:true, - print:addbbout_fn, - printErr:print_fn, - noFSInit:true, - }); - var FS = BBPRE.FS; - setupStdin(FS, code); - BBPRE.callMain([]); - console.log("preprocess " + code.length + " -> " + bbout.length + " bytes"); - return bbout; -} - -function compileBatariBasic(step:BuildStep) : BuildStepResult { - load("bb2600basic"); - var params = step.params; - // stdout - var asmout = ""; - function addasmout_fn(s) { - asmout += s; - asmout += "\n"; - } - // stderr - var re_err1 = /[(](\d+)[)]:?\s*(.+)/; - var errors = []; - var errline = 0; - function match_fn(s) { - console.log(s); - var matches = re_err1.exec(s); - if (matches) { - errline = parseInt(matches[1]); - errors.push({ - line:errline, - msg:matches[2] - }); - } - } - gatherFiles(step, {mainFilePath:"main.bas"}); - var destpath = step.prefix + '.asm'; - if (staleFiles(step, [destpath])) { - var BB : EmscriptenModule = emglobal.bb2600basic({ - noInitialRun:true, - //logReadFiles:true, - print:addasmout_fn, - printErr:match_fn, - noFSInit:true, - TOTAL_MEMORY:64*1024*1024, - }); - var FS = BB.FS; - populateFiles(step, FS); - // preprocess, pipe file to stdin - var code = getWorkFileAsString(step.path); - code = preprocessBatariBasic(code); - setupStdin(FS, code); - setupFS(FS, '2600basic'); - execMain(step, BB, ["-i", "/share", step.path]); - if (errors.length) - return {errors:errors}; - // build final assembly output from include file list - var includesout = FS.readFile("includes.bB", {encoding:'utf8'}); - var redefsout = FS.readFile("2600basic_variable_redefs.h", {encoding:'utf8'}); - var includes = includesout.trim().split("\n"); - var combinedasm = ""; - var splitasm = asmout.split("bB.asm file is split here"); - for (var incfile of includes) { - var inctext; - if (incfile=="bB.asm") - inctext = splitasm[0]; - else if (incfile=="bB2.asm") - inctext = splitasm[1]; - else - inctext = FS.readFile("/share/includes/"+incfile, {encoding:'utf8'}); - console.log(incfile, inctext.length); - combinedasm += "\n\n;;;" + incfile + "\n\n"; - combinedasm += inctext; - } - // TODO: ; bB.asm file is split here - putWorkFile(destpath, combinedasm); - putWorkFile("2600basic.h", FS.readFile("/share/includes/2600basic.h")); - putWorkFile("2600basic_variable_redefs.h", redefsout); - } - return { - nexttool:"dasm", - path:destpath, - args:[destpath], - files:[destpath, "2600basic.h", "2600basic_variable_redefs.h"], - bblines:true, - }; -} - -function setupRequireFunction() { +export function setupRequireFunction() { var exports = {}; exports['jsdom'] = { JSDOM: function(a,b) { @@ -2217,1152 +1017,53 @@ function setupRequireFunction() { } } -function translateShowdown(step:BuildStep) : BuildStepResult { - setupRequireFunction(); - load("showdown.min"); - var showdown = emglobal['showdown']; - var converter = new showdown.Converter({ - tables:'true', - smoothLivePreview:'true', - requireSpaceBeforeHeadingText:'true', - emoji:'true', - }); - var code = getWorkFileAsString(step.path); - var html = converter.makeHtml(code); - delete emglobal['require']; - return { - output:html - }; -} - -// http://datapipe-blackbeltsystems.com/windows/flex/asm09.html -function assembleXASM6809(step:BuildStep) : BuildStepResult { - load("xasm6809"); - var alst = ""; - var lasterror = null; - var errors = []; - function match_fn(s) { - alst += s; - alst += "\n"; - if (lasterror) { - var line = parseInt(s.slice(0,5)) || 0; - errors.push({ - line:line, - msg:lasterror - }); - lasterror = null; - } - else if (s.startsWith("***** ")) { - lasterror = s.slice(6); - } - } - var Module : EmscriptenModule = emglobal.xasm6809({ - noInitialRun:true, - //logReadFiles:true, - print:match_fn, - printErr:print_fn - }); - var FS = Module.FS; - //setupFS(FS); - populateFiles(step, FS, { - mainFilePath:'main.asm' - }); - var binpath = step.prefix + '.bin'; - var lstpath = step.prefix + '.lst'; // in stdout - execMain(step, Module, ["-c", "-l", "-s", "-y", "-o="+binpath, step.path]); - if (errors.length) - return {errors:errors}; - var aout = FS.readFile(binpath, {encoding:'binary'}); - if (aout.length == 0) { - console.log(alst); - errors.push({line:0, msg:"Empty output file"}); - return {errors:errors}; - } - putWorkFile(binpath, aout); - putWorkFile(lstpath, alst); - // TODO: symbol map - //mond09 0000 - var symbolmap = {}; - //00005 W 0003 [ 8] A6890011 lda >PALETTE,x - //00012 0011 0C0203 fcb 12,2,3 - var asmlines = parseListing(alst, /^\s*([0-9]+) .+ ([0-9A-F]+)\s+\[([0-9 ]+)\]\s+([0-9A-F]+) (.*)/i, 1, 2, 4, 3); - var listings : CodeListingMap = {}; - listings[step.prefix+'.lst'] = {lines:asmlines, text:alst}; - return { - output:aout, - listings:listings, - errors:errors, - symbolmap:symbolmap, - }; -} - -// http://www.nespowerpak.com/nesasm/ -function assembleNESASM(step:BuildStep) : BuildStepResult { - loadNative("nesasm"); - var re_filename = /\#\[(\d+)\]\s+(\S+)/; - var re_insn = /\s+(\d+)\s+([0-9A-F]+):([0-9A-F]+)/; - var re_error = /\s+(.+)/; - var errors : WorkerError[] = []; - var state = 0; - var lineno = 0; - var filename; - function match_fn(s) { - var m; - switch (state) { - case 0: - m = re_filename.exec(s); - if (m) { - filename = m[2]; - } - m = re_insn.exec(s); - if (m) { - lineno = parseInt(m[1]); - state = 1; - } - break; - case 1: - m = re_error.exec(s); - if (m) { - errors.push({path:filename, line:lineno, msg:m[1]}); - state = 0; - } - break; - } - } - var Module : EmscriptenModule = emglobal.nesasm({ - instantiateWasm: moduleInstFn('nesasm'), - noInitialRun:true, - print:match_fn - }); - var FS = Module.FS; - populateFiles(step, FS, { - mainFilePath:'main.a' - }); - var binpath = step.prefix+'.nes'; - var lstpath = step.prefix+'.lst'; - var sympath = step.prefix+'.fns'; - execMain(step, Module, [step.path, '-s', "-l", "2" ]); - // parse main listing, get errors and listings for each file - var listings : CodeListingMap = {}; - try { - var alst = FS.readFile(lstpath, {'encoding':'utf8'}); - // 16 00:C004 8E 17 40 STX $4017 ; disable APU frame IRQ - var asmlines = parseListing(alst, /^\s*(\d+)\s+([0-9A-F]+):([0-9A-F]+)\s+([0-9A-F ]+?) (.*)/i, 1, 3, 4); - putWorkFile(lstpath, alst); - listings[lstpath] = { - lines:asmlines, - text:alst - }; - } catch (e) { - // - } - if (errors.length) { - return {errors:errors}; - } - // read binary rom output and symbols - var aout, asym; - aout = FS.readFile(binpath); - try { - asym = FS.readFile(sympath, {'encoding':'utf8'}); - } catch (e) { - console.log(e); - errors.push({line:0,msg:"No symbol table generated, maybe missing ENDM or segment overflow?"}); - return {errors:errors} - } - putWorkFile(binpath, aout); - putWorkFile(sympath, asym); - if (alst) putWorkFile(lstpath, alst); // listing optional (use LIST) - // return unchanged if no files changed - if (!anyTargetChanged(step, [binpath, sympath])) - return; - // parse symbols - var symbolmap = {}; - for (var s of asym.split("\n")) { - if (!s.startsWith(';')) { - var m = /(\w+)\s+=\s+[$]([0-9A-F]+)/.exec(s); - if (m) { - symbolmap[m[1]] = parseInt(m[2], 16); - } - } - } - return { - output:aout, - listings:listings, - errors:errors, - symbolmap:symbolmap, - }; -} - -function compileCMOC(step:BuildStep) : BuildStepResult { - loadNative("cmoc"); - var params = step.params; - // stderr - var re_err1 = /^[/]*([^:]*):(\d+): (.+)$/; - var errors : WorkerError[] = []; - var errline = 0; - function match_fn(s) { - var matches = re_err1.exec(s); - if (matches) { - errors.push({ - line:parseInt(matches[2]), - msg:matches[3], - path:matches[1] || step.path - }); - } else { - console.log(s); - } - } - gatherFiles(step, {mainFilePath:"main.c"}); - var destpath = step.prefix + '.s'; - if (staleFiles(step, [destpath])) { - var args = ['-S', '-Werror', '-V', - '-I/share/include', - '-I.', - step.path]; - var CMOC : EmscriptenModule = emglobal.cmoc({ - instantiateWasm: moduleInstFn('cmoc'), - noInitialRun:true, - //logReadFiles:true, - print:match_fn, - printErr:match_fn, - }); - // load source file and preprocess - var code = getWorkFileAsString(step.path); - var preproc = preprocessMCPP(step, null); - if (preproc.errors) { - return {errors: preproc.errors} - } - else code = preproc.code; - // set up filesystem - var FS = CMOC.FS; - //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); - populateFiles(step, FS); - FS.writeFile(step.path, code); - fixParamsWithDefines(step.path, params); - if (params.extra_compile_args) { - args.unshift.apply(args, params.extra_compile_args); - } - execMain(step, CMOC, args); - if (errors.length) - return {errors:errors}; - var asmout = FS.readFile(destpath, {encoding:'utf8'}); - putWorkFile(destpath, asmout); - } - return { - nexttool:"lwasm", - path:destpath, - args:[destpath], - files:[destpath], - }; -} - -function assembleLWASM(step:BuildStep) : BuildStepResult { - loadNative("lwasm"); - var errors = []; - gatherFiles(step, {mainFilePath:"main.s"}); - var objpath = step.prefix+".o"; - var lstpath = step.prefix+".lst"; - if (staleFiles(step, [objpath, lstpath])) { - var objout, lstout; - var args = ['-9', '--obj', '-I/share/asminc', '-o'+objpath, '-l'+lstpath, step.path]; - var LWASM : EmscriptenModule = emglobal.lwasm({ - instantiateWasm: moduleInstFn('lwasm'), - noInitialRun:true, - //logReadFiles:true, - print:print_fn, - printErr:msvcErrorMatcher(errors), - }); - var FS = LWASM.FS; - //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); - populateFiles(step, FS); - fixParamsWithDefines(step.path, step.params); - execMain(step, LWASM, args); - if (errors.length) - return {errors:errors}; - objout = FS.readFile(objpath, {encoding:'binary'}); - lstout = FS.readFile(lstpath, {encoding:'utf8'}); - putWorkFile(objpath, objout); - putWorkFile(lstpath, lstout); - } - return { - linktool:"lwlink", - files:[objpath, lstpath], - args:[objpath] - }; -} - -function linkLWLINK(step:BuildStep) : BuildStepResult { - loadNative("lwlink"); - var params = step.params; - gatherFiles(step); - var binpath = "main"; - if (staleFiles(step, [binpath])) { - var errors = []; - var LWLINK : EmscriptenModule = emglobal.lwlink({ - instantiateWasm: moduleInstFn('lwlink'), - noInitialRun:true, - //logReadFiles:true, - print:print_fn, - printErr:function(s) { - if (s.startsWith("Warning:")) - console.log(s); - else - errors.push({msg:s,line:0}); - } - }); - var FS = LWLINK.FS; - //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); - populateFiles(step, FS); - populateExtraFiles(step, FS, params.extra_link_files); - var libargs = params.extra_link_args || []; - var args = [ - '-L.', - '--entry=program_start', - '--raw', - '--output=main', - '--map=main.map'].concat(libargs, step.args); - console.log(args); - execMain(step, LWLINK, args); - if (errors.length) - return {errors:errors}; - var aout = FS.readFile("main", {encoding:'binary'}); - var mapout = FS.readFile("main.map", {encoding:'utf8'}); - putWorkFile("main", aout); - putWorkFile("main.map", mapout); - // return unchanged if no files changed - if (!anyTargetChanged(step, ["main", "main.map"])) - return; - // parse symbol map - //console.log(mapout); - var symbolmap = {}; - var segments = []; - for (var s of mapout.split("\n")) { - var toks = s.split(" "); - // TODO: use regex - if (toks[0] == 'Symbol:') { - let ident = toks[1]; - let ofs = parseInt(toks[4], 16); - if (ident && ofs >= 0 && !ident.startsWith("l_") && !/^L\d+$/.test(ident)) { - symbolmap[ident] = ofs; - } - } - else if (toks[0] == 'Section:') { - let seg = toks[1]; - let segstart = parseInt(toks[5], 16); - let segsize = parseInt(toks[7], 16); - segments.push({name:seg, start:segstart, size:segsize}); - } - } - // build listings - var listings : CodeListingMap = {}; - for (var fn of step.files) { - if (fn.endsWith('.lst')) { - // TODO - var lstout = FS.readFile(fn, {encoding:'utf8'}); - var asmlines = parseListing(lstout, /^([0-9A-F]+)\s+([0-9A-F]+)\s+[(]\s*(.+?)[)]:(\d+) (.*)/i, 4, 1, 2, 3); - // * Line //threed.c:117: init of variable e - var srclines = parseSourceLines(lstout, /Line .+?:(\d+)/i, /^([0-9A-F]{4})/i); - putWorkFile(fn, lstout); - // TODO: you have to get rid of all source lines to get asm listing - listings[fn] = { - asmlines:srclines.length ? asmlines : null, - lines:srclines.length ? srclines : asmlines, - text:lstout - }; - } - } - return { - output:aout, //.slice(0), - listings:listings, - errors:errors, - symbolmap:symbolmap, - segments:segments - }; - } -} - -// http://www.techhelpmanual.com/829-program_startup___exit.html -function compileSmallerC(step:BuildStep) : BuildStepResult { - loadNative("smlrc"); - var params = step.params; - // stderr - var re_err1 = /^Error in "[/]*(.+)" [(](\d+):(\d+)[)]/; - var errors : WorkerError[] = []; - var errline = 0; - var errpath = step.path; - function match_fn(s) { - var matches = re_err1.exec(s); - if (matches) { - errline = parseInt(matches[2]); - errpath = matches[1]; - } else { - errors.push({ - line:errline, - msg:s, - path:errpath, - }); - } - } - gatherFiles(step, {mainFilePath:"main.c"}); - var destpath = step.prefix + '.asm'; - if (staleFiles(step, [destpath])) { - var args = ['-seg16', - //'-nobss', - '-no-externs', - step.path, destpath]; - var smlrc : EmscriptenModule = emglobal.smlrc({ - instantiateWasm: moduleInstFn('smlrc'), - noInitialRun:true, - //logReadFiles:true, - print:match_fn, - printErr:match_fn, - }); - // load source file and preprocess - var code = getWorkFileAsString(step.path); - var preproc = preprocessMCPP(step, null); - if (preproc.errors) { - return {errors: preproc.errors}; - } - else code = preproc.code; - // set up filesystem - var FS = smlrc.FS; - //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); - populateFiles(step, FS); - FS.writeFile(step.path, code); - fixParamsWithDefines(step.path, params); - if (params.extra_compile_args) { - args.unshift.apply(args, params.extra_compile_args); - } - execMain(step, smlrc, args); - if (errors.length) - return {errors:errors}; - var asmout = FS.readFile(destpath, {encoding:'utf8'}); - putWorkFile(destpath, asmout); - } - return { - nexttool:"yasm", - path:destpath, - args:[destpath], - files:[destpath], - }; -} - -function assembleYASM(step:BuildStep) : BuildStepResult { - loadNative("yasm"); - var errors = []; - gatherFiles(step, {mainFilePath:"main.asm"}); - var objpath = step.prefix+".exe"; - var lstpath = step.prefix+".lst"; - var mappath = step.prefix+".map"; - if (staleFiles(step, [objpath])) { - var args = [ '-X', 'vc', - '-a', 'x86', '-f', 'dosexe', '-p', 'nasm', - '-D', 'freedos', - //'-g', 'dwarf2', - //'-I/share/asminc', - '-o', objpath, '-l', lstpath, '--mapfile='+mappath, - step.path]; - // return yasm/*.ready*/ - var YASM : EmscriptenModule = emglobal.yasm({ - instantiateWasm: moduleInstFn('yasm'), - noInitialRun:true, - //logReadFiles:true, - print:print_fn, - printErr:msvcErrorMatcher(errors), - }); - var FS = YASM.FS; - //setupFS(FS, '65-'+getRootBasePlatform(step.platform)); - populateFiles(step, FS); - //fixParamsWithDefines(step.path, step.params); - execMain(step, YASM, args); - if (errors.length) - return {errors:errors}; - var objout, lstout, mapout; - objout = FS.readFile(objpath, {encoding:'binary'}); - lstout = FS.readFile(lstpath, {encoding:'utf8'}); - mapout = FS.readFile(mappath, {encoding:'utf8'}); - putWorkFile(objpath, objout); - putWorkFile(lstpath, lstout); - //putWorkFile(mappath, mapout); - if (!anyTargetChanged(step, [objpath])) - return; - var symbolmap = {}; - var segments = []; - var lines = parseListing(lstout, /\s*(\d+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+(.+)/i, 1, 2, 3); - var listings : CodeListingMap = {}; - listings[lstpath] = {lines:lines, text:lstout}; - return { - output:objout, //.slice(0), - listings:listings, - errors:errors, - symbolmap:symbolmap, - segments:segments - }; - } -} - -interface XMLNode { - type: string; - text: string | null; - children: XMLNode[]; -} - -function parseXMLPoorly(s: string) : XMLNode { - var re = /[<]([/]?)([?a-z_-]+)([^>]*)[>]+|(\s*[^<]+)/gi; - var m : RegExpMatchArray; - //var i=0; - var stack : XMLNode[] = []; - while (m = re.exec(s)) { - var [_m0,close,ident,attrs,content] = m; - //if (i++<100) console.log(close,ident,attrs,content); - if (close) { - var top = stack.pop(); - if (top.type != ident) throw "mismatch close tag: " + ident; - stack[stack.length-1].children.push(top); - } else if (ident) { - stack.push({type:ident, text:null, children:[]}); - } else if (content != null) { - stack[stack.length-1].text = (content as string).trim(); - } - } - return top; -} - -function compileInform6(step:BuildStep) : BuildStepResult { - loadNative("inform"); - var errors = []; - gatherFiles(step, {mainFilePath:"main.inf"}); - var objpath = step.prefix+".z5"; - if (staleFiles(step, [objpath])) { - var errorMatcher = msvcErrorMatcher(errors); - var lstout = ""; - var match_fn = (s: string) => { - if (s.indexOf("Error:") >= 0) { - errorMatcher(s); - } else { - lstout += s; - lstout += "\n"; - } - } - // TODO: step.path must end in '.inf' or error - var args = [ '-afjnops', '-v5', '-Cu', '-E1', '-k', '+/share/lib', step.path ]; - var inform : EmscriptenModule = emglobal.inform({ - instantiateWasm: moduleInstFn('inform'), - noInitialRun:true, - //logReadFiles:true, - print:match_fn, - printErr:match_fn, - }); - var FS = inform.FS; - setupFS(FS, 'inform'); - populateFiles(step, FS); - //fixParamsWithDefines(step.path, step.params); - execMain(step, inform, args); - if (errors.length) - return {errors:errors}; - var objout = FS.readFile(objpath, {encoding:'binary'}); - putWorkFile(objpath, objout); - if (!anyTargetChanged(step, [objpath])) - return; - - // parse debug XML - var symbolmap = {}; - var segments : Segment[] = []; - var entitymap = { - // number -> string - 'object':{}, 'property':{}, 'attribute':{}, 'constant':{}, 'global-variable':{}, 'routine':{}, - }; - var dbgout = FS.readFile("gameinfo.dbg", {encoding:'utf8'}); - var xmlroot = parseXMLPoorly(dbgout); - //console.log(xmlroot); - var segtype = "ram"; - xmlroot.children.forEach((node) => { - switch (node.type) { - case 'global-variable': - case 'routine': - var ident = node.children.find((c,v) => c.type=='identifier').text; - var address = parseInt(node.children.find((c,v) => c.type=='address').text); - symbolmap[ident] = address; - entitymap[node.type][address] = ident; - break; - case 'object': - case 'property': - case 'attribute': - var ident = node.children.find((c,v) => c.type=='identifier').text; - var value = parseInt(node.children.find((c,v) => c.type=='value').text); - //entitymap[node.type][ident] = value; - entitymap[node.type][value] = ident; - //symbolmap[ident] = address | 0x1000000; - break; - case 'story-file-section': - var name = node.children.find((c,v) => c.type=='type').text; - var address = parseInt(node.children.find((c,v) => c.type=='address').text); - var endAddress = parseInt(node.children.find((c,v) => c.type=='end-address').text); - if (name == "grammar table") segtype = "rom"; - segments.push({name:name, start:address, size:endAddress-address, type:segtype}); - } - }); - // parse listing - var listings : CodeListingMap = {}; - // 35 +00015 <*> call_vs long_19 location long_424 -> sp - var lines = parseListing(lstout, /\s*(\d+)\s+[+]([0-9a-f]+)\s+([<*>]*)\s*(\w+)\s+(.+)/i, -1, 2, 4); - var lstpath = step.prefix + '.lst'; - listings[lstpath] = {lines:[], asmlines:lines, text:lstout}; - return { - output:objout, //.slice(0), - listings:listings, - errors:errors, - symbolmap:symbolmap, - segments:segments, - debuginfo:entitymap, - }; - } -} - -/* -------+-------------------+-------------+----+---------+------+-----------------------+------------------------------------------------------------------- - Line | # File Line | Line Type | MX | Reloc | Size | Address Object Code | Source Code -------+-------------------+-------------+----+---------+------+-----------------------+------------------------------------------------------------------- - 1 | 1 zap.asm 1 | Unknown | ?? | | -1 | 00/FFFF | broak - 2 | 1 zap.asm 2 | Comment | ?? | | -1 | 00/FFFF | * SPACEGAME - - => [Error] Impossible to decode address mode for instruction 'BNE KABOOM!' (line 315, file 'zap.asm') : The number of element in 'KABOOM!' is even (should be value [operator value [operator value]...]). - => [Error] Unknown line 'foo' in source file 'zap.asm' (line 315) - => Creating Object file 'pcs.bin' - => Creating Output file 'pcs.bin_S01__Output.txt' - -*/ -function assembleMerlin32(step:BuildStep) : BuildStepResult { - loadNative("merlin32"); - var errors = []; - var lstfiles = []; - gatherFiles(step, {mainFilePath:"main.lnk"}); - var objpath = step.prefix+".bin"; - if (staleFiles(step, [objpath])) { - var args = [ '-v', step.path ]; - var merlin32 : EmscriptenModule = emglobal.merlin32({ - instantiateWasm: moduleInstFn('merlin32'), - noInitialRun:true, - print:(s:string) => { - var m = /\s*=>\s*Creating Output file '(.+?)'/.exec(s); - if (m) { - lstfiles.push(m[1]); - } - var errpos = s.indexOf('Error'); - if (errpos >= 0) { - s = s.slice(errpos+6).trim(); - var mline = /\bline (\d+)\b/.exec(s); - var mpath = /\bfile '(.+?)'/.exec(s); - errors.push({ - line:parseInt(mline[1]) || 0, - msg:s, - path:mpath[1] || step.path, - }); - } - }, - printErr:print_fn, - }); - var FS = merlin32.FS; - populateFiles(step, FS); - execMain(step, merlin32, args); - if (errors.length) - return {errors:errors}; - - var errout = null; - try { - errout = FS.readFile("error_output.txt", {encoding:'utf8'}); - } catch (e) { - // - } - - var objout = FS.readFile(objpath, {encoding:'binary'}); - putWorkFile(objpath, objout); - if (!anyTargetChanged(step, [objpath])) - return; - - var symbolmap = {}; - var segments = []; - var listings : CodeListingMap = {}; - lstfiles.forEach((lstfn) => { - var lst = FS.readFile(lstfn, {encoding:'utf8'}) as string; - lst.split('\n').forEach((line) => { - var toks = line.split(/\s*\|\s*/); - if (toks && toks[6]) { - var toks2 = toks[1].split(/\s+/); - var toks3 = toks[6].split(/[:/]/, 4); - var path = toks2[1]; - if (path && toks2[2] && toks3[1]) { - var lstline = { - line:parseInt(toks2[2]), - offset:parseInt(toks3[1].trim(),16), - insns:toks3[2], - cycles:null, - iscode:false // TODO - }; - var lst = listings[path]; - if (!lst) listings[path] = lst = {lines:[]}; - lst.lines.push(lstline); - //console.log(path,toks2,toks3); - } - } - }); - }); - return { - output:objout, //.slice(0), - listings:listings, - errors:errors, - symbolmap:symbolmap, - segments:segments - }; - } -} - -// README.md:2:5: parse error, expected: statement or variable assignment, integer variable, variable assignment -function compileFastBasic(step:BuildStep) : BuildStepResult { - // TODO: fastbasic-fp? - loadNative("fastbasic-int"); - var params = step.params; - gatherFiles(step, {mainFilePath:"main.fb"}); - var destpath = step.prefix + '.s'; - var errors = []; - if (staleFiles(step, [destpath])) { - var fastbasic : EmscriptenModule = emglobal.fastbasic({ - instantiateWasm: moduleInstFn('fastbasic-int'), - noInitialRun:true, - print:print_fn, - printErr:makeErrorMatcher(errors, /(.+?):(\d+):(\d+):\s*(.+)/, 2, 4, step.path, 1), - }); - var FS = fastbasic.FS; - populateFiles(step, FS); - var libfile = 'fastbasic-int.lib' - params.libargs = [libfile]; - params.cfgfile = params.fastbasic_cfgfile; - //params.extra_compile_args = ["--asm-define", "NO_SMCODE"]; - params.extra_link_files = [libfile, params.cfgfile]; - //fixParamsWithDefines(step.path, params); - var args = [step.path, destpath]; - execMain(step, fastbasic, args); - if (errors.length) - return {errors:errors}; - var asmout = FS.readFile(destpath, {encoding:'utf8'}); - putWorkFile(destpath, asmout); - } - return { - nexttool:"ca65", - path:destpath, - args:[destpath], - files:[destpath], - }; -} - -function compileBASIC(step:BuildStep) : WorkerResult { - var jsonpath = step.path + ".json"; - gatherFiles(step); - if (staleFiles(step, [jsonpath])) { - var parser = new basic_compiler.BASICParser(); - var code = getWorkFileAsString(step.path); - try { - var ast = parser.parseFile(code, step.path); - } catch (e) { - console.log(e); - if (parser.errors.length == 0) throw e; - } - if (parser.errors.length) { - return {errors: parser.errors}; - } - // put AST into JSON (sans source locations) to see if it has changed - var json = JSON.stringify(ast, (key,value) => { return (key=='$loc'?undefined:value) }); - putWorkFile(jsonpath, json); - if (anyTargetChanged(step, [jsonpath])) return { - output: ast, - listings: parser.getListings(), - }; - } -} - -function compileSilice(step:BuildStep) : BuildStepResult { - loadNative("silice"); - var params = step.params; - gatherFiles(step, {mainFilePath:"main.ice"}); - var destpath = step.prefix + '.v'; - var errors : WorkerError[] = []; - var errfile : string; - var errline : number; - if (staleFiles(step, [destpath])) { - //[preprocessor] 97] attempt to concatenate a nil value (global 'addrW') - var match_fn = (s: string) => { - s = (s as any).replaceAll(/\033\[\d+\w/g, ''); - var mf = /file:\s*(\w+)/.exec(s); - var ml = /line:\s+(\d+)/.exec(s); - var preproc = /\[preprocessor\] (\d+)\] (.+)/.exec(s); - if (mf) errfile = mf[1]; - else if (ml) errline = parseInt(ml[1]); - else if (preproc) { - errors.push({path:step.path, line:parseInt(preproc[1]), msg:preproc[2]}); - } - else if (errfile && errline && s.length > 1) { - if (s.length > 2) { - errors.push({path:errfile+".ice", line:errline, msg:s}); - } else { - errfile = null; - errline = null; - } - } - else console.log(s); - } - var silice : EmscriptenModule = emglobal.silice({ - instantiateWasm: moduleInstFn('silice'), - noInitialRun:true, - print:match_fn, - printErr:match_fn, - }); - var FS = silice.FS; - setupFS(FS, 'Silice'); - populateFiles(step, FS); - populateExtraFiles(step, FS, params.extra_compile_files); - const FWDIR = '/share/frameworks'; - var args = [ - '-D', 'NTSC=1', - '--frameworks_dir', FWDIR, - '-f', `/8bitworkshop.v`, - '-o', destpath, - step.path]; - execMain(step, silice, args); - if (errors.length) - return {errors:errors}; - var vout = FS.readFile(destpath, {encoding:'utf8'}); - putWorkFile(destpath, vout); - } - return { - nexttool:"verilator", - path:destpath, - args:[destpath], - files:[destpath], - }; -} - -function compileWiz(step:BuildStep) : WorkerResult { - loadNative("wiz"); - var params = step.params; - gatherFiles(step, {mainFilePath:"main.wiz"}); - var destpath = step.prefix + (params.wiz_rom_ext || ".bin"); - var errors : WorkerError[] = []; - if (staleFiles(step, [destpath])) { - var wiz : EmscriptenModule = emglobal.wiz({ - instantiateWasm: moduleInstFn('wiz'), - noInitialRun:true, - print:print_fn, - //test.wiz:2: error: expected statement, but got identifier `test` - printErr:makeErrorMatcher(errors, /(.+?):(\d+):\s*(.+)/, 2, 3, step.path, 1), - }); - var FS = wiz.FS; - setupFS(FS, 'wiz'); - populateFiles(step, FS); - populateExtraFiles(step, FS, params.extra_compile_files); - const FWDIR = '/share/common'; - var args = [ - '-o', destpath, - '-I', FWDIR + '/' + (params.wiz_inc_dir || step.platform), - '-s', 'wla', - '--color=none', - step.path]; - args.push('--system', params.wiz_sys_type || params.arch); - execMain(step, wiz, args); - if (errors.length) - return {errors:errors}; - var binout = FS.readFile(destpath, {encoding:'binary'}); - putWorkFile(destpath, binout); - var dbgout = FS.readFile(step.prefix + '.sym', {encoding:'utf8'}); - var symbolmap = {}; - for (var s of dbgout.split("\n")) { - var toks = s.split(/ /); - // 00:4008 header.basic_start - if (toks && toks.length >= 2) { - var tokrange = toks[0].split(':'); - var start = parseInt(tokrange[1], 16); - var sym = toks[1]; - symbolmap[sym] = start; - } - } - return { - output:binout, //.slice(0), - errors:errors, - symbolmap:symbolmap, - }; - } -} - -function assembleARMIPS(step:BuildStep) : WorkerResult { - loadNative("armips"); - var errors = []; - gatherFiles(step, {mainFilePath:"main.asm"}); - var objpath = "main.bin"; - var lstpath = step.prefix + ".lst"; - var sympath = step.prefix + ".sym"; - //test.armips(3) error: Parse error '.arm' - var error_fn = makeErrorMatcher(errors, /^(.+?)\((\d+)\)\s+(fatal error|error|warning):\s+(.+)/, 2, 4, step.path, 1); - - if (staleFiles(step, [objpath])) { - var args = [ step.path, '-temp', lstpath, '-sym', sympath, '-erroronwarning' ]; - var armips : EmscriptenModule = emglobal.armips({ - instantiateWasm: moduleInstFn('armips'), - noInitialRun:true, - print:error_fn, - printErr:error_fn, - }); - - var FS = armips.FS; - var code = getWorkFileAsString(step.path); - code = `.arm.little :: .create "${objpath}",0 :: ${code} -.close`; - putWorkFile(step.path, code); - populateFiles(step, FS); - execMain(step, armips, args); - if (errors.length) - return {errors:errors}; - - var objout = FS.readFile(objpath, {encoding:'binary'}) as Uint8Array; - putWorkFile(objpath, objout); - if (!anyTargetChanged(step, [objpath])) - return; - - var symbolmap = {}; - var segments = []; - var listings : CodeListingMap = {}; - var lstout = FS.readFile(lstpath, {encoding:'utf8'}) as string; - var lines = lstout.split(re_crlf); - //00000034 .word 0x11223344 ; /vidfill.armips line 25 - var re_asmline = /^([0-9A-F]+) (.+?); [/](.+?) line (\d+)/; - var lastofs = -1; - for (var line of lines) { - var m; - if (m = re_asmline.exec(line)) { - var path = m[3]; - var path2 = getPrefix(path) + '.lst'; // TODO: don't rename listing - var lst = listings[path2]; - if (lst == null) { lst = listings[path2] = {lines:[]}; } - var ofs = parseInt(m[1], 16); - if (lastofs == ofs) { - lst.lines.pop(); // get rid of duplicate offset - } else if (ofs > lastofs) { - var lastline = lst.lines[lst.lines.length-1]; - if (lastline && !lastline.insns) { - var insns = objout.slice(lastofs, ofs).reverse(); - lastline.insns = Array.from(insns).map((b) => hex(b,2)).join(''); - } - } - lst.lines.push({ - path: path, - line: parseInt(m[4]), - offset: ofs - }); - lastofs = ofs; - } - } - //listings[lstpath] = {lines:lstlines, text:lstout}; - - var symout = FS.readFile(sympath, {encoding:'utf8'}) as string; - //0000000C loop2 - //00000034 .dbl:0004 - var re_symline = /^([0-9A-F]+)\s+(.+)/; - for (var line of symout.split(re_crlf)) { - var m; - if (m = re_symline.exec(line)) { - symbolmap[m[2]] = parseInt(m[1], 16); - } - } - - return { - output:objout, //.slice(0), - listings:listings, - errors:errors, - symbolmap:symbolmap, - segments:segments - }; - } -} - -function assembleVASMARM(step:BuildStep) : BuildStepResult { - loadNative("vasmarm_std"); - /// error 2 in line 8 of "gfxtest.c": unknown mnemonic - /// error 3007: undefined symbol - /// TODO: match undefined symbols - var re_err1 = /^(fatal error|error|warning)? (\d+) in line (\d+) of "(.+)": (.+)/; - var re_err2 = /^(fatal error|error|warning)? (\d+): (.+)/; - var re_undefsym = /symbol <(.+?)>/; - var errors : WorkerError[] = []; - var undefsyms = []; - function findUndefinedSymbols(line:string) { - // find undefined symbols in line - undefsyms.forEach((sym) => { - if (line.indexOf(sym) >= 0) { - errors.push({ - path:curpath, - line:curline, - msg:"Undefined symbol: " + sym, - }) - } - }); - } - function match_fn(s) { - let matches = re_err1.exec(s); - if (matches) { - errors.push({ - line:parseInt(matches[3]), - path:matches[4], - msg:matches[5], - }); - } else { - matches = re_err2.exec(s); - if (matches) { - let m = re_undefsym.exec(matches[3]); - if (m) { - undefsyms.push(m[1]); - } else { - errors.push({ - line:0, - msg:s, - }); - } - } else { - console.log(s); - } - } - } - - gatherFiles(step, {mainFilePath:"main.asm"}); - var objpath = step.prefix+".bin"; - var lstpath = step.prefix+".lst"; - - if (staleFiles(step, [objpath])) { - var args = [ '-Fbin', '-m7tdmi', '-x', '-wfail', step.path, '-o', objpath, '-L', lstpath ]; - var vasm : EmscriptenModule = emglobal.vasm({ - instantiateWasm: moduleInstFn('vasmarm_std'), - noInitialRun:true, - print:match_fn, - printErr:match_fn, - }); - - var FS = vasm.FS; - populateFiles(step, FS); - execMain(step, vasm, args); - if (errors.length) { - return {errors:errors}; - } - - if (undefsyms.length == 0) { - var objout = FS.readFile(objpath, {encoding:'binary'}); - putWorkFile(objpath, objout); - if (!anyTargetChanged(step, [objpath])) - return; - } - - var lstout = FS.readFile(lstpath, {encoding:'utf8'}); - // 00:00000018 023020E0 14: eor r3, r0, r2 - // Source: "vidfill.vasm" - // 00: ".text" (0-40) - // LOOP 00:00000018 - // STACK S:20010000 - var symbolmap = {}; - var segments = []; // TODO - var listings : CodeListingMap = {}; - // TODO: parse listings - var re_asmline = /^(\d+):([0-9A-F]+)\s+([0-9A-F ]+)\s+(\d+)([:M])/; - var re_secline = /^(\d+):\s+"(.+)"/; - var re_nameline = /^Source:\s+"(.+)"/; - var re_symline = /^(\w+)\s+(\d+):([0-9A-F]+)/; - var re_emptyline = /^\s+(\d+)([:M])/; - var curpath = step.path; - var curline = 0; - var sections = {}; - // map file and section indices -> names - var lines : string[] = lstout.split(re_crlf); - // parse lines - var lstlines : SourceLine[] = []; - for (var line of lines) { - var m; - if (m = re_secline.exec(line)) { - sections[m[1]] = m[2]; - } else if (m = re_nameline.exec(line)) { - curpath = m[1]; - } else if (m = re_symline.exec(line)) { - symbolmap[m[1]] = parseInt(m[3], 16); - } else if (m = re_asmline.exec(line)) { - if (m[5] == ':') { - curline = parseInt(m[4]); - } else { - // TODO: macro line - } - lstlines.push({ - path: curpath, - line: curline, - offset: parseInt(m[2], 16), - insns: m[3].replaceAll(' ','') - }); - findUndefinedSymbols(line); - } else if (m = re_emptyline.exec(line)) { - curline = parseInt(m[1]); - findUndefinedSymbols(line); - } else { - //console.log(line); - } - } - listings[lstpath] = {lines:lstlines, text:lstout}; - // catch-all if no error generated - if (undefsyms.length && errors.length == 0) { - errors.push({ - line: 0, - msg: 'Undefined symbols: ' + undefsyms.join(', ') - }) - } - - return { - output:objout, //.slice(0x34), - listings:listings, - errors:errors, - symbolmap:symbolmap, - segments:segments - }; - } -} - //////////////////////////// +import * as misc from './tools/misc' +import * as cc65 from './tools/cc65' +import * as dasm from './tools/dasm' +import * as sdcc from './tools/sdcc' +import * as verilog from './tools/verilog' +import * as m6809 from './tools/m6809' +import * as m6502 from './tools/m6502' +import * as z80 from './tools/z80' +import * as x86 from './tools/x86' +import * as arm from './tools/arm' + var TOOLS = { - 'dasm': assembleDASM, + 'dasm': dasm.assembleDASM, //'acme': assembleACME, //'plasm': compilePLASMA, - 'cc65': compileCC65, - 'ca65': assembleCA65, - 'ld65': linkLD65, + 'cc65': cc65.compileCC65, + 'ca65': cc65.assembleCA65, + 'ld65': cc65.linkLD65, //'z80asm': assembleZ80ASM, //'sccz80': compileSCCZ80, - 'sdasz80': assembleSDASZ80, - 'sdldz80': linkSDLDZ80, - 'sdcc': compileSDCC, - 'xasm6809': assembleXASM6809, - 'cmoc': compileCMOC, - 'lwasm': assembleLWASM, - 'lwlink': linkLWLINK, + 'sdasz80': sdcc.assembleSDASZ80, + 'sdldz80': sdcc.linkSDLDZ80, + 'sdcc': sdcc.compileSDCC, + 'xasm6809': m6809.assembleXASM6809, + 'cmoc': m6809.compileCMOC, + 'lwasm': m6809.assembleLWASM, + 'lwlink': m6809.linkLWLINK, //'naken': assembleNAKEN, - 'verilator': compileVerilator, - 'yosys': compileYosys, - //'caspr': compileCASPR, - 'jsasm': compileJSASMStep, - 'zmac': assembleZMAC, - 'nesasm': assembleNESASM, - 'smlrc': compileSmallerC, - 'yasm': assembleYASM, - 'bataribasic': compileBatariBasic, - 'markdown': translateShowdown, - 'inform6': compileInform6, - 'merlin32': assembleMerlin32, - 'fastbasic': compileFastBasic, - 'basic': compileBASIC, - 'silice': compileSilice, - 'wiz': compileWiz, - 'armips': assembleARMIPS, - 'vasmarm': assembleVASMARM, + 'verilator': verilog.compileVerilator, + 'yosys': verilog.compileYosys, + 'jsasm': verilog.compileJSASMStep, + 'zmac': z80.assembleZMAC, + 'nesasm': m6502.assembleNESASM, + 'smlrc': x86.compileSmallerC, + 'yasm': x86.assembleYASM, + 'bataribasic': dasm.compileBatariBasic, + 'markdown': misc.translateShowdown, + 'inform6': misc.compileInform6, + 'merlin32': m6502.assembleMerlin32, + 'fastbasic': m6502.compileFastBasic, + 'basic': misc.compileBASIC, + 'silice': verilog.compileSilice, + 'wiz': misc.compileWiz, + 'armips': arm.assembleARMIPS, + 'vasmarm': arm.assembleVASMARM, } var TOOL_PRELOADFS = {