"use strict"; var PLATFORM_PARAMS = { 'mw8080bw': { code_start: 0x0, code_size: 0x2000, data_start: 0x2000, data_size: 0x400, stack_end: 0x2400, }, 'vicdual': { code_start: 0x0, code_size: 0x4020, data_start: 0xe400, data_size: 0x400, stack_end: 0xe800, }, 'galaxian': { code_start: 0x0, code_size: 0x4000, data_start: 0x4000, data_size: 0x400, stack_end: 0x4800, }, 'galaxian-scramble': { code_start: 0x0, code_size: 0x5020, data_start: 0x4000, data_size: 0x400, stack_end: 0x4800, }, 'williams-z80': { code_start: 0x0, code_size: 0x9800, data_start: 0x9800, data_size: 0x2800, stack_end: 0xc000, }, 'vector-z80color': { code_start: 0x0, code_size: 0x8000, data_start: 0xe000, data_size: 0x2000, stack_end: 0x0, }, 'sound_williams-z80': { code_start: 0x0, code_size: 0x4000, data_start: 0x4000, data_size: 0x400, stack_end: 0x8000, }, 'base_z80': { code_start: 0x0, code_size: 0x8000, data_start: 0x8000, data_size: 0x8000, stack_end: 0x0, }, }; var loaded = {} function load(modulename, debug) { if (!loaded[modulename]) { importScripts(modulename+(debug?"."+debug+".js":".js")); loaded[modulename] = 1; } } // shim out window and document objects for security // https://github.com/mbostock/d3/issues/1053 var noop = function() { return new Function(); }; var window = noop(); window.CSSStyleDeclaration = noop(); window.CSSStyleDeclaration.setProperty = noop(); window.Element = noop(); window.Element.setAttribute = noop(); window.Element.setAttributeNS = noop(); window.navigator = noop(); var document = noop(); document.documentElement = noop(); document.documentElement.style = noop(); var fsMeta = {}; var fsBlob = {}; // load filesystems for CC65 and others asynchronously function loadFilesystem(name) { var xhr = new XMLHttpRequest(); xhr.responseType = 'blob'; xhr.open("GET", "fs"+name+".data", false); // synchronous request xhr.send(null); fsBlob[name] = xhr.response; xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.open("GET", "fs"+name+".js.metadata", false); // synchronous request xhr.send(null); fsMeta[name] = xhr.response; console.log("Loaded "+name+" filesystem", fsMeta[name].files.length, 'files', fsBlob[name].size, 'bytes'); } var ATARI_CFG = "FEATURES {\nSTARTADDRESS: default = $9000;\n}\n" + "MEMORY {\n" + " ZP: file = \"\", start = $82, size = $7E, type = rw, define = yes;\n" + " RAM: file = \"\", start = $0200, size = $1e00, define = yes;\n" + " ROM: file = %O, start = $9000, size = $7000;\n" + " ROMV: file = %O, start = $FFFA, size = $0006, fill = yes;\n" + "}\n" + "SEGMENTS {\n" + "ZEROPAGE: load = ZP, type = zp, define = yes;\n" + " STARTUP: load = ROM, type = ro, define = yes;\n" + " ONCE: load = ROM, type = ro, define = yes;\n" + " CODE: load = ROM, type = ro, define = yes;\n" + " DATA: load = RAM, type = rw, define = yes, run = RAM;\n" + " INIT: load = RAM, type = rw, define = yes;\n" + " BSS: load = RAM, type = bss, define = yes;\n" + " HEAP: load = RAM, type = bss, optional = yes;\n" + " RODATA: load = ROM, type = ro;\n" + "}\n" + "FEATURES {\n" + " CONDES: segment = STARTUP,\n" + " type = constructor,\n" + " label = __CONSTRUCTOR_TABLE__,\n" + " count = __CONSTRUCTOR_COUNT__;\n" + " CONDES: segment = STARTUP,\n" + " type = destructor,\n" + " label = __DESTRUCTOR_TABLE__,\n" + " count = __DESTRUCTOR_COUNT__;\n" + "}\n" + "SYMBOLS {\n" + " __STACKSIZE__: type = weak, value = $0400;\n" + " __LC_LAST__: type = weak, value = $0400;\n" + " __LC_START__: type = weak, value = $0400;\n" + "}\n" ; /* + "MEMORY {\n" + " ZP: file = \"\", define = yes, start = $0082, size = $007E;\n" + " MAIN: file = %O, define = yes, start = %S, size = $BC20 - __STACKSIZE__ - __RESERVED_MEMORY__ - %S;\n" + "}\n" + "SEGMENTS {\n" + " ZEROPAGE: load = ZP, type = zp, optional = yes;\n" + " EXTZP: load = ZP, type = zp, optional = yes;\n" + " STARTUP: load = MAIN, type = ro, define = yes, optional = yes;\n" + " LOWCODE: load = MAIN, type = ro, define = yes, optional = yes;\n" + " ONCE: load = MAIN, type = ro, optional = yes;\n" + " CODE: load = MAIN, type = ro, define = yes;\n" + " RODATA: load = MAIN, type = ro, optional = yes;\n" + " DATA: load = MAIN, type = rw, optional = yes;\n" + " BSS: load = MAIN, type = bss, define = yes, optional = yes;\n" + " INIT: load = MAIN, type = bss, optional = yes;\n" + "}\n" + "FEATURES {\n" + " CONDES: type = constructor,\n" + " label = __CONSTRUCTOR_TABLE__,\n" + " count = __CONSTRUCTOR_COUNT__,\n" + " segment = ONCE;\n" + " CONDES: type = destructor,\n" + " label = __DESTRUCTOR_TABLE__,\n" + " count = __DESTRUCTOR_COUNT__,\n" + " segment = RODATA;\n" + " CONDES: type = interruptor,\n" + " label = __INTERRUPTOR_TABLE__,\n" + " count = __INTERRUPTOR_COUNT__,\n" + " segment = RODATA,\n" + " import = __CALLIRQ__;\n" + "}\n"; */ // mount the filesystem at /share function setupFS(FS, name) { FS.mkdir('/share'); FS.mount(FS.filesystems['WORKERFS'], { packages: [{ metadata: fsMeta[name], blob: fsBlob[name] }] }, '/share'); FS.writeFile("/vector-ataricolor.cfg", ATARI_CFG); } var DASM_MAIN_FILENAME = "main.a"; var DASM_PREAMBLE = "\tprocessor 6502\n"; var DASM_PREAMBLE_LINES = 1; var print_fn = function(s) { console.log(s); //console.log(new Error().stack); } // test.c(6) : warning 85: in function main unreferenced local variable : 'x' // main.a (4): error: Unknown Mnemonic 'xxx'. // at 2: warning 190: ISO C forbids an empty source file var re_msvc = /([^(]+)\s*[(](\d+)[)]\s*:\s*(.+?):\s*(.*)/; var re_msvc2 = /\s*(at)\s+(\d+)\s*(:)\s*(.*)/; var msvc_errors; function match_msvc(s) { var matches = re_msvc.exec(s) || re_msvc2.exec(s); if (matches) { var errline = parseInt(matches[2]); msvc_errors.push({ line:errline, file:matches[1], type:matches[3], msg:matches[4] }); } } function makeErrorMatcher(errors, regex, iline, imsg) { return function(s) { var matches = regex.exec(s); if (matches) { errors.push({ line:parseInt(matches[iline]), msg:matches[imsg] }); } } } function extractErrors(regex, strings) { var errors = []; var matcher = makeErrorMatcher(errors, regex, 1, 2); for (var i=0; i0) { lines.push({ line:lastline+1, offset:offset, insns:null }); } } // TODO: check filename too // TODO: better symbol test (word boundaries) for (var key in unresolved) { var pos = restline ? restline.indexOf(key) : line.indexOf(key); if (pos >= 0) { errors.push({ line:linenum, msg:"Unresolved symbol '" + key + "'" }); } } } var errm = re_msvc.exec(line); if (errm) { errors.push({ line:parseInt(errm[2]), msg:errm[4] }) } } return {lines:lines, errors:errors}; } function assembleDASM(code) { load("dasm"); var re_usl = /(\w+)\s+0000\s+[?][?][?][?]/; var unresolved = {}; function match_fn(s) { var matches = re_usl.exec(s); if (matches) { unresolved[matches[1]] = 0; } } var Module = DASM({ noInitialRun:true, print:match_fn }); var FS = Module['FS']; FS.writeFile(DASM_MAIN_FILENAME, DASM_PREAMBLE + code); Module.callMain([DASM_MAIN_FILENAME, "-la.lst"/*, "-v3", "-sa.sym"*/]); var aout = FS.readFile("a.out"); var alst = FS.readFile("a.lst", {'encoding':'utf8'}); //var asym = FS.readFile("a.sym", {'encoding':'utf8'}); var listing = parseDASMListing(alst, unresolved); return { output:aout.slice(2), lines:listing.lines, errors:listing.errors, intermediate:{listing:alst}, }; } // TODO: not quite done function assembleACME(code) { load("acme"); // stderr var re_err2 = /(Error|Warning) - File (.+?), line (\d+) ([^:]+) (.*)/; var errors = []; var errline = 0; function match_fn(s) { var matches = re_err2.exec(s); if (matches) { errors.push({ line:1, // TODO: parseInt(matches[3]), msg:matches[0] // TODO: matches[5] }); } } var Module = ACME({ noInitialRun:true, print:match_fn, printErr:match_fn }); var FS = Module['FS']; FS.writeFile("main.a", code); // TODO: --msvc Module.callMain(["-o", "a.out", "-r", "a.rpt", "-l", "a.sym", "--setpc", "24576", "main.a"]); if (errors.length) { return {errors:errors}; } var aout = FS.readFile("a.out"); var alst = FS.readFile("a.rpt", {'encoding':'utf8'}); var asym = FS.readFile("a.sym", {'encoding':'utf8'}); var listing = parseDASMListing(alst, {}); // TODO return { output:aout, lines:listing.lines, errors:listing.errors, intermediate:{listing:alst, symbols:asym}, }; } function setupStdin(fs, code) { var i = 0; fs.init( function() { return i in line 1 of main.asm null // .org in REL area or directive / mnemonic error var match_asm_re = / <\w> (.+)/; // TODO function match_asm_fn(s) { var matches = match_asm_re.exec(s); if (matches) { var errline = parseInt(matches[2]); msvc_errors.push({ line:1, // TODO: errline, msg:matches[1] }); } } var ASZ80 = sdasz80({ noInitialRun:true, //logReadFiles:true, print:match_asm_fn, printErr:match_asm_fn, }); var FS = ASZ80['FS']; FS.writeFile("main.asm", code, {encoding:'utf8'}); ASZ80.callMain(['-plosgffwy', 'main.asm']); if (msvc_errors.length) { return {errors:msvc_errors}; } objout = FS.readFile("main.rel", {encoding:'utf8'}); lstout = FS.readFile("main.lst", {encoding:'utf8'}); //symout = FS.readFile("main.sym", {encoding:'utf8'}); }{ //?ASlink-Warning-Undefined Global '__divsint' referenced by module 'main' var match_aslink_re = /\?ASlink-(\w+)-(.+)/; function match_aslink_fn(s) { var matches = match_aslink_re.exec(s); if (matches) { msvc_errors.push({ line:1, msg:matches[2] }); } } var LDZ80 = sdldz80({ noInitialRun:true, //logReadFiles:true, print:match_aslink_fn, printErr:match_aslink_fn, }); var FS = LDZ80['FS']; setupFS(FS, 'sdcc'); FS.writeFile("main.rel", objout, {encoding:'utf8'}); FS.writeFile("main.lst", lstout, {encoding:'utf8'}); LDZ80.callMain(['-mjwxyu', '-i', 'main.ihx', '-b', '_CODE=0x'+params.code_start.toString(16), '-b', '_DATA=0x'+params.data_start.toString(16), '-k', '/share/lib/z80', '-l', 'z80', 'main.rel']); var hexout = FS.readFile("main.ihx", {encoding:'utf8'}); var mapout = FS.readFile("main.noi", {encoding:'utf8'}); var rstout = FS.readFile("main.rst", {encoding:'utf8'}); //var dbgout = FS.readFile("main.cdb", {encoding:'utf8'}); // 0000 21 02 00 [10] 52 ld hl, #2 // TODO: offset by start address? var asmlines = parseListing(lstout, /^\s*([0-9A-F]+)\s+([0-9A-F][0-9A-F r]*[0-9A-F])\s+\[([0-9 ]+)\]\s+(\d+) (.*)/i, 4, 1, 2, 5, 3); var srclines = parseSourceLines(lstout, /^\s+\d+ ;:(\d+):/i, /^\s*([0-9A-F]{4})/i); // parse symbol map var symbolmap = {}; for (var s of mapout.split("\n")) { var toks = s.split(" "); if (toks[0] == 'DEF' && !toks[1].startsWith("A$main$")) { symbolmap[toks[1]] = parseInt(toks[2], 16); } } return { output:parseIHX(hexout, params.code_start, params.code_size), lines:asmlines, srclines:srclines, errors:msvc_errors, // TODO? symbolmap:symbolmap, intermediate:{listing:rstout}, }; } } function compileSDCC(code, platform) { var preproc = preprocessMCPP(code, platform); if (preproc.errors) return preproc; else code = preproc.code; load("sdcc"); //console.profile("sdcc"); var params = PLATFORM_PARAMS[platform]; if (!params) throw Error("Platform not supported: " + platform); var SDCC = sdcc({ noInitialRun:true, noFSInit:true, print:print_fn, printErr:match_msvc, TOTAL_MEMORY:256*1024*1024, }); var FS = SDCC['FS']; setupStdin(FS, code); setupFS(FS, 'sdcc'); //FS.writeFile("main.c", code, {encoding:'utf8'}); msvc_errors = []; var t1 = new Date(); SDCC.callMain(['--vc', '--std-sdcc99', '-mz80', //'-Wall', '--c1mode', // '--debug', //'-S', 'main.c', //'--asm=z80asm', '--less-pedantic', ///'--fomit-frame-pointer', '--opt-code-speed', '--oldralloc', // TODO: does this make it fater? //'--cyclomatic', //'--nooverlay','--nogcse','--nolabelopt','--noinvariant','--noinduction','--nojtbound','--noloopreverse','--no-peep','--nolospre', '-o', 'main.asm']); var t2 = new Date(); //console.profileEnd(); //console.log(t2.getTime() - t1.getTime()); /* // ignore if all are warnings (TODO?) var nwarnings = 0; for (var err of msvc_errors) { if (err.type && err.type.startsWith("warning")) nwarnings++; } */ // TODO: preprocessor errors w/ correct file if (msvc_errors.length /* && nwarnings < msvc_errors.length*/) { return {errors:msvc_errors}; } try { var asmout = FS.readFile("main.asm", {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; //asmout = asmout.replace(".area _INITIALIZER",".area _CODE"); } catch (e) { msvc_errors.push({line:1, msg:e+""}); return {errors:msvc_errors}; } var warnings = msvc_errors; try { var result = assemblelinkSDASZ80(asmout, platform, true); } catch (e) { msvc_errors.push({line:1, msg:e+""}); return {errors:msvc_errors}; } result.asmlines = result.lines; result.lines = result.srclines; result.srclines = null; //result.errors = result.errors.concat(warnings); return result; } function assembleXASM6809(code, platform) { load("xasm6809"); var origin = 0; // TODO: configurable var alst = ""; var lasterror = null; msvc_errors = []; function match_fn(s) { alst += s; alst += "\n"; if (lasterror) { var line = parseInt(s.slice(0,5)); msvc_errors.push({ line:line, msg:lasterror }); lasterror = null; } else if (s.startsWith("***** ")) { lasterror = s.slice(6); } } var Module = xasm6809({ noInitialRun:true, //logReadFiles:true, print:match_fn, printErr:print_fn }); var FS = Module['FS']; //setupFS(FS); FS.writeFile("main.asm", code); Module.callMain(["-c", "-l", "-s", "-y", "-o=main.bin", "main.asm"]); try { var aout = FS.readFile("main.bin", {encoding:'binary'}); // 00001 0000 [ 2] 1048 asld var asmlines = parseListing(alst, /^\s*([0-9A-F]+)\s+([0-9A-F]+)\s+\[([0-9 ]+)\]\s+(\d+) (.*)/i, 1, 2, 4, 5, 3); return { output:aout, errors:msvc_errors, lines:asmlines, intermediate:{listing:alst}, }; } catch(e) { return {errors:msvc_errors}; // TODO } } function preprocessMCPP(code, platform) { load("mcpp"); // :2: error: Can't open include file "foo.h" var errors = []; var match_fn = makeErrorMatcher(errors, /:(\d+): (.+)/, 1, 2); var MCPP = mcpp({ noInitialRun:true, noFSInit:true, print:print_fn, printErr:match_fn, }); var FS = MCPP['FS']; setupFS(FS, 'sdcc'); FS.writeFile("main.c", code, {encoding:'utf8'}); MCPP.callMain([ "-D", "__8BITWORKSHOP__", "-D", platform.toUpperCase().replace('-','_'), "-D", "__SDCC_z80", "-I", "/share/include", "-Q", "main.c", "main.i"]); try { var iout = FS.readFile("main.i", {encoding:'utf8'}); iout = iout.replace(/^#line /gm,'\n# '); } catch (e) { errors.push({line:1, msg:e+""}); } try { var errout = FS.readFile("mcpp.err", {encoding:'utf8'}); if (errout.length) { // //main.c:2: error: Can't open include file "stdiosd.h" var errors = extractErrors(/[^:]+:(\d+): (.+)/, errout.split("\n")); if (errors.length == 0) { errors = [{line:1, msg:errout}]; } return {errors: errors}; } } catch (e) { // } return {code:iout}; } function assembleNAKEN(code, platform) { load("naken_asm"); var errors = []; var match_fn = makeErrorMatcher(errors, /Error: (.+) at (.+):(\d+)/, 3, 1); var Module = naken_asm({ noInitialRun:true, //logReadFiles:true, print:match_fn, printErr:print_fn }); var FS = Module['FS']; //setupFS(FS); FS.writeFile("main.asm", code); Module.callMain(["-l", "-b", "main.asm"]); try { var aout = FS.readFile("out.bin", {encoding:'binary'}); var alst = FS.readFile("out.lst", {encoding:'utf8'}); //console.log(alst); // 0x0000: 77 ld (hl),a cycles: 4 var asmlines = parseListing(alst, /^0x([0-9a-f]+):\s+([0-9a-f]+)\s+(.+)cycles: (\d+)/i, 0, 1, 2, 3); return { output:aout, errors:errors, lines:asmlines, intermediate:{listing:alst}, }; } catch(e) { return {errors:errors}; } } var TOOLS = { 'dasm': assembleDASM, 'acme': assembleACME, 'plasm': compilePLASMA, 'cc65': compileCC65, 'ca65': assemblelinkCA65, 'z80asm': assembleZ80ASM, 'sdasz80': assemblelinkSDASZ80, 'sdcc': compileSDCC, 'xasm6809': assembleXASM6809, 'naken': assembleNAKEN, } var TOOL_PRELOADFS = { 'cc65': '65', 'ca65': '65', 'sdasz80': 'sdcc', 'sdcc': 'sdcc', } onmessage = function(e) { // (preload) if (e.data.preload) { var fs = TOOL_PRELOADFS[e.data.preload]; if (fs && !fsMeta[fs]) loadFilesystem(fs); return; } // (code,platform,tool) var code = e.data.code; var platform = e.data.platform; var toolfn = TOOLS[e.data.tool]; if (!toolfn) throw "no tool named " + e.data.tool; var result = toolfn(code, platform); result.params = PLATFORM_PARAMS[platform]; if (result) { postMessage(result); } }