(() => { var __defProp = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; // src/common/util.ts function hex(v, nd) { if (!nd) nd = 2; if (nd == 8) { return hex(v >> 16 & 65535, 4) + hex(v & 65535, 4); } else { return toradix(v, nd, 16); } } function toradix(v, nd, radix) { try { var s = v.toString(radix).toUpperCase(); while (s.length < nd) s = "0" + s; return s; } catch (e) { return v + ""; } } function getBasePlatform(platform) { return platform.split(".")[0]; } function getRootPlatform(platform) { return platform.split("-")[0]; } function getRootBasePlatform(platform) { return getRootPlatform(getBasePlatform(platform)); } var XMLParseError = class extends Error { }; function escapeXML(s) { if (s.indexOf("&") >= 0) { return s.replace(/'/g, "'").replace(/"/g, '"').replace(/>/g, ">").replace(/</g, "<").replace(/&/g, "&"); } else { return s; } } function parseXMLPoorly(s, openfn, closefn) { const tag_re = /[<]([/]?)([?a-z_-]+)([^>]*)[>]+|(\s*[^<]+)/gi; const attr_re = /\s*(\w+)="(.*?)"\s*/gi; var fm; var stack = []; var top; 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) { var am; var attrs2 = {}; if (as != null) { while (am = attr_re.exec(as)) { attrs2[am[1]] = escapeXML(am[2]); } } return attrs2; } while (fm = tag_re.exec(s)) { var [_m0, close, ident, attrs, content] = fm; 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).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; } // src/common/basic/compiler.ts var CompileError = class extends Error { constructor(msg, loc) { super(msg); Object.setPrototypeOf(this, CompileError.prototype); this.$loc = loc; } }; var re_toks = /([0-9.]+[E][+-]?\d+|\d+[.][E0-9]*|[.][E0-9]+)|[0]*(\d+)|&([OH][0-9A-F]+)|(['].*)|([A-Z_]\w*[$]?)|(".*?")|([<>]?[=<>#])|(\*\*)|([-+*/^,;:()\[\]\?\\])|(\S+)|(\s+)/gi; var TokenType; (function(TokenType3) { TokenType3[TokenType3["EOL"] = 0] = "EOL"; TokenType3[TokenType3["Float"] = 1] = "Float"; TokenType3[TokenType3["Int"] = 2] = "Int"; TokenType3[TokenType3["HexOctalInt"] = 3] = "HexOctalInt"; TokenType3[TokenType3["Remark"] = 4] = "Remark"; TokenType3[TokenType3["Ident"] = 5] = "Ident"; TokenType3[TokenType3["String"] = 6] = "String"; TokenType3[TokenType3["Relational"] = 7] = "Relational"; TokenType3[TokenType3["DoubleStar"] = 8] = "DoubleStar"; TokenType3[TokenType3["Operator"] = 9] = "Operator"; TokenType3[TokenType3["CatchAll"] = 10] = "CatchAll"; TokenType3[TokenType3["Whitespace"] = 11] = "Whitespace"; TokenType3[TokenType3["_LAST"] = 12] = "_LAST"; })(TokenType || (TokenType = {})); var OPERATORS = { "IMP": { f: "bimp", p: 4 }, "EQV": { f: "beqv", p: 5 }, "XOR": { f: "bxor", p: 6 }, "OR": { f: "bor", p: 7 }, "AND": { f: "band", p: 8 }, "||": { f: "lor", p: 17 }, "&&": { f: "land", p: 18 }, "=": { f: "eq", p: 50 }, "==": { f: "eq", p: 50 }, "<>": { f: "ne", p: 50 }, "><": { f: "ne", p: 50 }, "!=": { f: "ne", p: 50 }, "#": { f: "ne", p: 50 }, "<": { f: "lt", p: 50 }, ">": { f: "gt", p: 50 }, "<=": { f: "le", p: 50 }, ">=": { f: "ge", p: 50 }, "MIN": { f: "min", p: 75 }, "MAX": { f: "max", p: 75 }, "+": { f: "add", p: 100 }, "-": { f: "sub", p: 100 }, "%": { f: "mod", p: 140 }, "MOD": { f: "mod", p: 140 }, "\\": { f: "idiv", p: 150 }, "*": { f: "mul", p: 200 }, "/": { f: "div", p: 200 }, "^": { f: "pow", p: 300 }, "**": { f: "pow", p: 300 } }; function getOperator(op) { return OPERATORS[op]; } function getPrecedence(tok) { switch (tok.type) { case 9: case 8: case 7: case 5: let op = getOperator(tok.str); if (op) return op.p; } return -1; } function isEOS(tok) { return tok.type == 0 || tok.type == 4 || tok.str == ":" || tok.str == "ELSE"; } function stripQuotes(s) { return s.substr(1, s.length - 2); } function isLiteral(arg) { return arg.value != null; } function isLookup(arg) { return arg.name != null; } function isBinOp(arg) { return arg.op != null && arg.left != null && arg.right != null; } function isUnOp(arg) { return arg.op != null && arg.expr != null; } function mergeLocs(a, b) { return { line: Math.min(a.line, b.line), start: Math.min(a.start, b.start), end: Math.max(a.end, b.end), label: a.label || b.label, path: a.path || b.path }; } var BASICParser = class { constructor() { this.opts = DIALECTS["DEFAULT"]; this.maxlinelen = 255; this.optionCount = 0; this.lineno = 0; this.curlabel = null; this.stmts = []; this.labels = {}; this.targets = {}; this.errors = []; this.listings = {}; this.vardefs = {}; this.varrefs = {}; this.fnrefs = {}; this.scopestack = []; this.elseifcount = 0; } addError(msg, loc) { var tok = this.lasttoken || this.peekToken(); if (!loc) loc = tok.$loc; this.errors.push({ path: loc.path, line: loc.line, label: this.curlabel, start: loc.start, end: loc.end, msg }); } compileError(msg, loc, loc2) { this.addError(msg, loc); throw new CompileError(msg, loc); } dialectError(what, loc) { this.compileError(`${what} in this dialect of BASIC (${this.opts.dialectName}).`, loc); } dialectErrorNoSupport(what, loc) { this.compileError(`You can't use ${what} in this dialect of BASIC (${this.opts.dialectName}).`, loc); } consumeToken() { var tok = this.lasttoken = this.tokens.shift() || this.eol; return tok; } expectToken(str, msg) { var tok = this.consumeToken(); var tokstr = tok.str; if (str != tokstr) { this.compileError(msg || `There should be a "${str}" here.`); } return tok; } expectTokens(strlist, msg) { var tok = this.consumeToken(); var tokstr = tok.str; if (strlist.indexOf(tokstr) < 0) { this.compileError(msg || `There should be a ${strlist.map((s) => `"${s}"`).join(" or ")} here.`); } return tok; } peekToken(lookahead) { var tok = this.tokens[lookahead || 0]; return tok ? tok : this.eol; } pushbackToken(tok) { this.tokens.unshift(tok); } parseOptLabel() { let tok = this.consumeToken(); switch (tok.type) { case 5: if (this.opts.optionalLabels || tok.str == "OPTION") { if (this.peekToken().str == ":" && !this.supportsCommand(tok.str)) { this.consumeToken(); } else { this.pushbackToken(tok); break; } } else this.dialectError(`Each line must begin with a line number`); case 2: this.addLabel(tok.str); return; case 3: case 1: this.compileError(`Line numbers must be positive integers.`); break; case 9: if (this.supportsCommand(tok.str) && this.validKeyword(tok.str)) { this.pushbackToken(tok); break; } default: if (this.opts.optionalLabels) this.compileError(`A line must start with a line number, command, or label.`); else this.compileError(`A line must start with a line number.`); case 4: break; } this.addLabel("#" + this.lineno); } getPC() { return this.stmts.length; } addStatement(stmt, cmdtok, endtok) { if (endtok == null) endtok = this.peekToken(); stmt.$loc = { path: cmdtok.$loc.path, line: cmdtok.$loc.line, start: cmdtok.$loc.start, end: endtok.$loc.start, label: this.curlabel, offset: this.stmts.length }; this.modifyScope(stmt); this.stmts.push(stmt); } addLabel(str, offset) { if (this.labels[str] != null) this.compileError(`There's a duplicated label named "${str}".`); this.labels[str] = this.getPC() + (offset || 0); this.curlabel = str; this.tokens.forEach((tok) => tok.$loc.label = str); } parseFile(file, path) { this.path = path; var txtlines = file.split(/\n|\r\n?/); txtlines.forEach((line) => this.parseLine(line)); var program = { opts: this.opts, stmts: this.stmts, labels: this.labels }; this.checkAll(program); this.listings[path] = this.generateListing(file, program); return program; } parseLine(line) { try { this.tokenize(line); this.parse(); } catch (e) { if (!(e instanceof CompileError)) throw e; } } _tokenize(line) { let splitre = this.opts.optionalWhitespace && new RegExp("(" + this.opts.validKeywords.map((s) => `${s}`).join("|") + ")"); var lastTokType = 10; var m; while (m = re_toks.exec(line)) { for (var i = 1; i <= lastTokType; i++) { let s = m[i]; if (s != null) { let loc = { path: this.path, line: this.lineno, start: m.index, end: m.index + s.length }; if (this.opts.asciiOnly && !/^[\x00-\x7F]*$/.test(s)) this.dialectErrorNoSupport(`non-ASCII characters`); if (i == 5 || i == 3 || this.opts.uppercaseOnly) { s = s.toUpperCase(); if (s == "DATA") lastTokType = 11; if (s == "DATA") splitre = null; if (s == "OPTION") splitre = null; if (lastTokType == 10 && s.startsWith("REM")) { s = "REM"; lastTokType = 0; } } if (s == "[" || s == "]") { if (!this.opts.squareBrackets) this.dialectErrorNoSupport(`square brackets`); if (s == "[") s = "("; if (s == "]") s = ")"; } if (splitre && i == 5) { var splittoks = s.split(splitre).filter((s2) => s2 != ""); if (splittoks.length > 1) { splittoks.forEach((ss) => { if (/^[0-9]+$/.test(ss)) i = 2; else if (/^[A-Z_]\w*[$]?$/.test(ss)) i = 5; else this.compileError(`Try adding whitespace before "${ss}".`); this.tokens.push({ str: ss, type: i, $loc: loc }); }); s = null; } } if (s) this.tokens.push({ str: s, type: i, $loc: loc }); break; } } } } tokenize(line) { this.lineno++; this.tokens = []; this.eol = { type: 0, str: "", $loc: { path: this.path, line: this.lineno, start: line.length } }; if (line.length > this.maxlinelen) this.compileError(`A line should be no more than ${this.maxlinelen} characters long.`); this._tokenize(line); } parse() { if (this.tokens.length) { this.parseOptLabel(); if (this.tokens.length) { this.parseCompoundStatement(); } var next = this.peekToken(); if (!isEOS(next)) this.compileError(`Expected end of line or ':'`, next.$loc); this.curlabel = null; } } parseCompoundStatement() { if (this.opts.multipleStmtsPerLine) { this.parseList(this.parseStatement, ":"); } else { this.parseList(this.parseStatement, "\0"); if (this.peekToken().str == ":") this.dialectErrorNoSupport(`multiple statements on a line`); } } validKeyword(keyword) { return this.opts.validKeywords && this.opts.validKeywords.indexOf(keyword) < 0 ? null : keyword; } validFunction(funcname) { return this.opts.validFunctions && this.opts.validFunctions.indexOf(funcname) < 0 ? null : funcname; } supportsCommand(cmd) { if (cmd == "?") return this.stmt__PRINT; else return this["stmt__" + cmd]; } parseStatement() { if (this.opts.optionalWhitespace && this.peekToken().str == ":") return null; var cmdtok = this.consumeToken(); var cmd = cmdtok.str; var stmt; switch (cmdtok.type) { case 4: if (cmdtok.str.startsWith("'") && !this.opts.tickComments) this.dialectErrorNoSupport(`tick comments`); return null; case 9: if (cmd == this.validKeyword("?")) cmd = "PRINT"; case 5: if (cmd == "REM") return null; if (cmd == "GO" && this.peekToken().str == "TO") { this.consumeToken(); cmd = "GOTO"; } else if (cmd == "GO" && this.peekToken().str == "SUB") { this.consumeToken(); cmd = "GOSUB"; } var fn = this.supportsCommand(cmd); if (fn) { if (this.validKeyword(cmd) == null) this.dialectErrorNoSupport(`the ${cmd} statement`); stmt = fn.bind(this)(); break; } else if (this.peekToken().str == "=" || this.peekToken().str == "(") { if (!this.opts.optionalLet) this.dialectError(`Assignments must have a preceding LET`); this.pushbackToken(cmdtok); stmt = this.stmt__LET(); break; } else { this.compileError(`I don't understand the command "${cmd}".`); } case 0: if (this.opts.optionalWhitespace) return null; default: this.compileError(`There should be a command here.`); return null; } if (stmt != null) this.addStatement(stmt, cmdtok); return stmt; } modifyScope(stmt) { if (this.opts.compiledBlocks) { var cmd = stmt.command; if (cmd == "FOR" || cmd == "WHILE" || cmd == "SUB") { this.scopestack.push(this.getPC()); } else if (cmd == "NEXT") { this.popScope(stmt, "FOR"); } else if (cmd == "WEND") { this.popScope(stmt, "WHILE"); } } } popScope(close, open) { var popidx = this.scopestack.pop(); var popstmt = popidx != null ? this.stmts[popidx] : null; if (popstmt == null) this.compileError(`There's a ${close.command} without a matching ${open}.`, close.$loc); else if (popstmt.command != open) this.compileError(`There's a ${close.command} paired with ${popstmt.command}, but it should be paired with ${open}.`, close.$loc, popstmt.$loc); else if (close.command == "NEXT" && !this.opts.optionalNextVar && close.lexpr.name != popstmt.lexpr.name) this.compileError(`This NEXT statement is matched with the wrong FOR variable (${close.lexpr.name}).`, close.$loc, popstmt.$loc); close.startpc = popidx; popstmt.endpc = this.getPC(); } popIfThenScope(nextpc) { var popidx = this.scopestack.pop(); var popstmt = popidx != null ? this.stmts[popidx] : null; if (popstmt == null) this.compileError(`There's an END IF without a matching IF or ELSE.`); if (popstmt.command == "ELSE") { popstmt.endpc = this.getPC(); this.popIfThenScope(popidx + 1); } else if (popstmt.command == "IF") { popstmt.endpc = nextpc != null ? nextpc : this.getPC(); } else { this.compileError(`There's an END IF paired with a ${popstmt.command}, not IF or ELSE.`, this.lasttoken.$loc, popstmt.$loc); } } parseVarSubscriptOrFunc() { var tok = this.consumeToken(); switch (tok.type) { case 5: let args = null; if (this.peekToken().str == "(") { this.expectToken("("); args = this.parseExprList(); this.expectToken(")", `There should be another expression or a ")" here.`); } var loc = mergeLocs(tok.$loc, this.lasttoken.$loc); var valtype = this.exprTypeForSubscript(tok.str, args, loc); return { valtype, name: tok.str, args, $loc: loc }; default: this.compileError(`There should be a variable name here.`); break; } } parseLexpr() { var lexpr = this.parseVarSubscriptOrFunc(); this.vardefs[lexpr.name] = lexpr; this.validateVarName(lexpr); return lexpr; } parseForNextLexpr() { var lexpr = this.parseLexpr(); if (lexpr.args || lexpr.name.endsWith("$")) this.compileError(`A FOR ... NEXT loop can only use numeric variables.`, lexpr.$loc); return lexpr; } parseList(parseFunc, delim) { var sep; var list = []; do { var el = parseFunc.bind(this)(); if (el != null) list.push(el); sep = this.consumeToken(); } while (sep.str == delim); this.pushbackToken(sep); return list; } parseLexprList() { return this.parseList(this.parseLexpr, ","); } parseExprList() { return this.parseList(this.parseExpr, ","); } parseLabelList() { return this.parseList(this.parseLabel, ","); } parseLabel() { if (this.opts.computedGoto) { var expr = this.parseExpr(); if (isLiteral(expr)) this.targets[expr.value] = this.lasttoken.$loc; return expr; } else { var tok = this.consumeToken(); switch (tok.type) { case 5: if (!this.opts.optionalLabels) this.dialectError(`All labels must be line numbers`); case 2: var label = tok.str; this.targets[label] = tok.$loc; return { valtype: "label", value: label }; default: var what = this.opts.optionalLabels ? "label or line number" : "line number"; this.compileError(`There should be a ${what} here.`); } } } parseDatumList() { return this.parseList(this.parseDatum, ","); } parseDatum() { var tok = this.consumeToken(); while (tok.type == 11) tok = this.consumeToken(); if (isEOS(tok)) this.compileError(`There should be a datum here.`); if (tok.type <= 3) { return this.parseValue(tok); } if (tok.str == "-" && this.peekToken().type <= 3) { tok = this.consumeToken(); return { valtype: "number", value: -this.parseValue(tok).value }; } if (tok.str == "+" && this.peekToken().type <= 3) { tok = this.consumeToken(); return this.parseValue(tok); } var s = ""; while (!isEOS(tok) && tok.str != ",") { s += this.parseValue(tok).value; tok = this.consumeToken(); } this.pushbackToken(tok); return { valtype: "string", value: s }; } parseValue(tok) { switch (tok.type) { case 3: if (!this.opts.hexOctalConsts) this.dialectErrorNoSupport(`hex/octal constants`); let base = tok.str.startsWith("H") ? 16 : 8; return { valtype: "number", value: parseInt(tok.str.substr(1), base) }; case 2: case 1: return { valtype: "number", value: this.parseNumber(tok.str) }; case 6: return { valtype: "string", value: stripQuotes(tok.str) }; default: return { valtype: "string", value: tok.str }; } } parsePrimary() { let tok = this.consumeToken(); switch (tok.type) { case 3: case 2: case 1: case 6: return this.parseValue(tok); case 5: if (tok.str == "NOT") { let expr = this.parsePrimary(); return { valtype: "number", op: this.opts.bitwiseLogic ? "bnot" : "lnot", expr }; } else { this.pushbackToken(tok); return this.parseVarSubscriptOrFunc(); } case 9: if (tok.str == "(") { let expr = this.parseExpr(); this.expectToken(")", `There should be another expression or a ")" here.`); return expr; } else if (tok.str == "-") { let expr = this.parsePrimary(); return { valtype: "number", op: "neg", expr }; } else if (tok.str == "+") { return this.parsePrimary(); } default: this.compileError(`The expression is incomplete.`); return; } } parseNumber(str) { var n = parseFloat(str); if (isNaN(n)) this.compileError(`The number ${str} is not a valid floating-point number.`); if (this.opts.checkOverflow && !isFinite(n)) this.compileError(`The number ${str} is too big to fit into a floating-point value.`); return n; } parseExpr1(left, minPred) { let look = this.peekToken(); while (getPrecedence(look) >= minPred) { let op = this.consumeToken(); if (this.opts.validOperators && this.opts.validOperators.indexOf(op.str) < 0) this.dialectErrorNoSupport(`the "${op.str}" operator`); let right = this.parsePrimary(); look = this.peekToken(); while (getPrecedence(look) > getPrecedence(op)) { right = this.parseExpr1(right, getPrecedence(look)); look = this.peekToken(); } var opfn = getOperator(op.str).f; if (!this.opts.bitwiseLogic && op.str == "AND") opfn = "land"; if (!this.opts.bitwiseLogic && op.str == "OR") opfn = "lor"; var valtype = this.exprTypeForOp(opfn, left, right, op); left = { valtype, op: opfn, left, right }; } return left; } parseExpr() { var startloc = this.peekToken().$loc; var expr = this.parseExpr1(this.parsePrimary(), 0); var endloc = this.lasttoken.$loc; expr.$loc = mergeLocs(startloc, endloc); return expr; } parseExprWithType(expecttype) { var expr = this.parseExpr(); if (expr.valtype != expecttype) this.compileError(`There should be a ${expecttype} here, but this expression evaluates to a ${expr.valtype}.`, expr.$loc); return expr; } validateVarName(lexpr) { switch (this.opts.varNaming) { case "A": if (!/^[A-Z]$/i.test(lexpr.name)) this.dialectErrorNoSupport(`variable names other than a single letter`); break; case "A1": if (lexpr.args == null && !/^[A-Z][0-9]?[$]?$/i.test(lexpr.name)) this.dialectErrorNoSupport(`variable names other than a letter followed by an optional digit`); if (lexpr.args != null && !/^[A-Z]?[$]?$/i.test(lexpr.name)) this.dialectErrorNoSupport(`array names other than a single letter`); break; case "A1$": if (!/^[A-Z][0-9]?[$]?$/i.test(lexpr.name)) this.dialectErrorNoSupport(`variable names other than a letter followed by an optional digit`); break; case "AA": if (lexpr.args == null && !/^[A-Z][A-Z0-9]?[$]?$/i.test(lexpr.name)) this.dialectErrorNoSupport(`variable names other than a letter followed by an optional letter or digit`); break; case "*": break; } } visitExpr(expr, callback) { if (isBinOp(expr)) { this.visitExpr(expr.left, callback); this.visitExpr(expr.right, callback); } if (isUnOp(expr)) { this.visitExpr(expr.expr, callback); } if (isLookup(expr) && expr.args != null) { for (var arg of expr.args) this.visitExpr(arg, callback); } callback(expr); } exprTypeForOp(fnname, left, right, optok) { if (left.valtype == "string" || right.valtype == "string") { if (fnname == "add") { if (this.opts.stringConcat) return "string"; else this.dialectErrorNoSupport(`the "+" operator to concatenate strings`, optok.$loc); } else if (fnname.length != 2) this.compileError(`You can't do math on strings until they're converted to numbers.`, optok.$loc); } return "number"; } exprTypeForSubscript(fnname, args, loc) { args = args || []; var defs = BUILTIN_MAP[fnname]; if (defs != null) { if (!this.validFunction(fnname)) this.dialectErrorNoSupport(`the ${fnname} function`, loc); for (var def of defs) { if (args.length == def.args.length) return def.result; } this.compileError(`The ${fnname} function takes ${def.args.length} arguments, but ${args.length} are given.`, loc); } this.varrefs[fnname] = loc; return fnname.endsWith("$") ? "string" : "number"; } stmt__LET() { var lexprs = [this.parseLexpr()]; this.expectToken("="); while (this.opts.chainAssignments && this.peekToken().type == 5 && this.peekToken(1).str == "=") { lexprs.push(this.parseLexpr()); this.expectToken("="); } var right = this.parseExprWithType(lexprs[0].valtype); return { command: "LET", lexprs, right }; } stmt__PRINT() { var sep, lastsep; var list = []; do { sep = this.peekToken(); if (isEOS(sep)) { break; } else if (sep.str == ";") { this.consumeToken(); lastsep = sep; } else if (sep.str == ",") { this.consumeToken(); list.push({ value: " " }); lastsep = sep; } else { list.push(this.parseExpr()); lastsep = null; } } while (true); if (!(lastsep && (lastsep.str == ";" || sep.str != ","))) { list.push({ value: "\n" }); } return { command: "PRINT", args: list }; } stmt__GOTO() { return this.__GO("GOTO"); } stmt__GOSUB() { return this.__GO("GOSUB"); } __GO(cmd) { var expr = this.parseLabel(); if (this.peekToken().str == this.validKeyword("OF")) { this.expectToken("OF"); let newcmd = cmd == "GOTO" ? "ONGOTO" : "ONGOSUB"; return { command: newcmd, expr, labels: this.parseLabelList() }; } else { return { command: cmd, label: expr }; } } stmt__IF() { var cmdtok = this.lasttoken; var cond = this.parseExprWithType("number"); var ifstmt = { command: "IF", cond }; this.addStatement(ifstmt, cmdtok); var thengoto = this.expectTokens(["THEN", "GOTO", "GO"]); if (thengoto.str == "GO") this.expectToken("TO"); if (this.opts.multilineIfThen && isEOS(this.peekToken())) { this.scopestack.push(this.getPC() - 1); } else { this.parseGotoOrStatements(); if (this.peekToken().str == "ELSE") { this.expectToken("ELSE"); ifstmt.endpc = this.getPC() + 1; this.stmt__ELSE(); } else { ifstmt.endpc = this.getPC(); } } } stmt__ELSE() { var elsestmt = { command: "ELSE" }; this.addStatement(elsestmt, this.lasttoken); var nexttok = this.peekToken(); if (this.opts.multilineIfThen && isEOS(nexttok)) { this.scopestack.push(this.getPC() - 1); } else if (this.opts.multilineIfThen && nexttok.str == "IF") { this.scopestack.push(this.getPC() - 1); this.parseGotoOrStatements(); this.elseifcount++; } else { this.parseGotoOrStatements(); elsestmt.endpc = this.getPC(); } } parseGotoOrStatements() { var lineno = this.peekToken(); if (lineno.type == 2) { this.parseLabel(); var gotostmt = { command: "GOTO", label: { valtype: "label", value: lineno.str } }; this.addStatement(gotostmt, lineno); } else { this.parseCompoundStatement(); } } stmt__FOR() { var lexpr = this.parseForNextLexpr(); this.expectToken("="); var init = this.parseExprWithType("number"); this.expectToken("TO"); var targ = this.parseExprWithType("number"); if (this.peekToken().str == "STEP") { this.consumeToken(); var step = this.parseExprWithType("number"); } return { command: "FOR", lexpr, initial: init, target: targ, step }; } stmt__NEXT() { var lexpr = null; if (!this.opts.optionalNextVar || !isEOS(this.peekToken())) { lexpr = this.parseForNextLexpr(); if (this.opts.multipleNextVars && this.peekToken().str == ",") { this.consumeToken(); this.tokens.unshift({ type: 5, str: "NEXT", $loc: this.peekToken().$loc }); this.tokens.unshift({ type: 9, str: ":", $loc: this.peekToken().$loc }); } } return { command: "NEXT", lexpr }; } stmt__WHILE() { var cond = this.parseExprWithType("number"); return { command: "WHILE", cond }; } stmt__WEND() { return { command: "WEND" }; } stmt__DIM() { var lexprs = this.parseLexprList(); lexprs.forEach((arr) => { if (arr.args == null || arr.args.length == 0) this.compileError(`An array defined by DIM must have at least one dimension.`); else if (arr.args.length > this.opts.maxDimensions) this.dialectErrorNoSupport(`arrays with more than ${this.opts.maxDimensions} dimensionals`); for (var arrdim of arr.args) { if (arrdim.valtype != "number") this.compileError(`Array dimensions must be numeric.`, arrdim.$loc); if (isLiteral(arrdim) && arrdim.value < this.opts.defaultArrayBase) this.compileError(`An array dimension cannot be less than ${this.opts.defaultArrayBase}.`, arrdim.$loc); } }); return { command: "DIM", args: lexprs }; } stmt__INPUT() { var prompt = this.consumeToken(); var promptstr; if (prompt.type == 6) { this.expectTokens([";", ","]); promptstr = stripQuotes(prompt.str); } else { this.pushbackToken(prompt); promptstr = ""; } return { command: "INPUT", prompt: { valtype: "string", value: promptstr }, args: this.parseLexprList() }; } stmt__ENTER() { var timeout = this.parseExpr(); this.expectToken(","); var elapsed = this.parseLexpr(); this.expectToken(","); return { command: "INPUT", prompt: null, args: this.parseLexprList(), timeout, elapsed }; } stmt__DATA() { return { command: "DATA", datums: this.parseDatumList() }; } stmt__READ() { return { command: "READ", args: this.parseLexprList() }; } stmt__RESTORE() { var label = null; if (this.opts.restoreWithLabel && !isEOS(this.peekToken())) label = this.parseLabel(); return { command: "RESTORE", label }; } stmt__RETURN() { return { command: "RETURN" }; } stmt__STOP() { return { command: "STOP" }; } stmt__END() { if (this.opts.multilineIfThen && this.scopestack.length) { let endtok = this.expectTokens(["IF", "SUB"]); if (endtok.str == "IF") { this.popIfThenScope(); while (this.elseifcount--) this.popIfThenScope(); this.elseifcount = 0; } else if (endtok.str == "SUB") { this.addStatement({ command: "RETURN" }, endtok); this.popScope({ command: "END" }, "SUB"); } } else { return { command: "END" }; } } stmt__ON() { var expr = this.parseExprWithType("number"); var gotok = this.consumeToken(); var cmd = { GOTO: "ONGOTO", THEN: "ONGOTO", GOSUB: "ONGOSUB" }[gotok.str]; if (!cmd) this.compileError(`There should be a GOTO or GOSUB here.`); var labels = this.parseLabelList(); return { command: cmd, expr, labels }; } stmt__DEF() { var lexpr = this.parseVarSubscriptOrFunc(); if (lexpr.args && lexpr.args.length > this.opts.maxDefArgs) this.compileError(`There can be no more than ${this.opts.maxDefArgs} arguments to a function or subscript.`, lexpr.$loc); if (!lexpr.name.startsWith("FN")) this.compileError(`Functions defined with DEF must begin with the letters "FN".`, lexpr.$loc); this.markVarDefs(lexpr); this.expectToken("="); var func = this.parseExpr(); this.visitExpr(func, (expr) => { if (isLookup(expr) && expr.name.startsWith("FN")) { if (!this.fnrefs[lexpr.name]) this.fnrefs[lexpr.name] = []; this.fnrefs[lexpr.name].push(expr.name); } }); this.checkCallGraph(lexpr.name, new Set()); return { command: "DEF", lexpr, def: func }; } stmt__SUB() { var lexpr = this.parseVarSubscriptOrFunc(); this.markVarDefs(lexpr); this.addLabel(lexpr.name, 1); return { command: "SUB", lexpr }; } stmt__CALL() { return { command: "CALL", call: this.parseVarSubscriptOrFunc() }; } markVarDefs(lexpr) { this.vardefs[lexpr.name] = lexpr; if (lexpr.args != null) for (let arg of lexpr.args) { if (isLookup(arg) && arg.args == null) this.vardefs[arg.name] = arg; else this.compileError(`A definition can only define symbols, not expressions.`); } } checkCallGraph(name, visited) { if (visited.has(name)) this.compileError(`There was a cycle in the function definition graph for ${name}.`); visited.add(name); var refs = this.fnrefs[name] || []; for (var ref of refs) this.checkCallGraph(ref, visited); visited.delete(name); } stmt__POP() { return { command: "POP" }; } stmt__GET() { var lexpr = this.parseLexpr(); return { command: "GET", lexpr }; } stmt__CLEAR() { return { command: "CLEAR" }; } stmt__RANDOMIZE() { return { command: "RANDOMIZE" }; } stmt__CHANGE() { var src = this.parseExpr(); this.expectToken("TO"); var dest = this.parseLexpr(); if (dest.valtype == src.valtype) this.compileError(`CHANGE can only convert strings to numeric arrays, or vice-versa.`, mergeLocs(src.$loc, dest.$loc)); return { command: "CHANGE", src, dest }; } stmt__CONVERT() { var src = this.parseExpr(); this.expectToken("TO"); var dest = this.parseLexpr(); if (dest.valtype == src.valtype) this.compileError(`CONVERT can only convert strings to numbers, or vice-versa.`, mergeLocs(src.$loc, dest.$loc)); return { command: "CONVERT", src, dest }; } stmt__OPTION() { this.optionCount++; var tokname = this.consumeToken(); var optname = tokname.str.toUpperCase(); if (tokname.type != 5) this.compileError(`There must be a name after the OPTION statement.`); var tokarg = this.consumeToken(); var arg = tokarg.str.toUpperCase(); switch (optname) { case "DIALECT": if (this.optionCount > 1) this.compileError(`OPTION DIALECT must be the first OPTION statement in the file.`, tokname.$loc); let dname = arg || ""; if (dname == "") this.compileError(`OPTION DIALECT requires a dialect name.`, tokname.$loc); let dialect = DIALECTS[dname.toUpperCase()]; if (dialect) this.opts = dialect; else this.compileError(`${dname} is not a valid dialect.`); break; case "BASE": let base = parseInt(arg); if (base == 0 || base == 1) this.opts.defaultArrayBase = base; else this.compileError("OPTION BASE can only be 0 or 1."); break; case "CPUSPEED": if (!(this.opts.commandsPerSec = Math.min(1e7, arg == "MAX" ? Infinity : parseFloat(arg)))) this.compileError(`OPTION CPUSPEED takes a positive number or MAX.`); break; default: let propname = Object.getOwnPropertyNames(this.opts).find((n) => n.toUpperCase() == optname); if (propname == null) this.compileError(`${optname} is not a valid option.`, tokname.$loc); if (arg == null) this.compileError(`OPTION ${optname} requires a parameter.`); switch (typeof this.opts[propname]) { case "boolean": this.opts[propname] = arg.toUpperCase().startsWith("T") || arg > 0; return; case "number": this.opts[propname] = parseFloat(arg); return; case "string": this.opts[propname] = arg; return; case "object": if (Array.isArray(this.opts[propname]) && arg == "ALL") { this.opts[propname] = null; return; } this.compileError(`OPTION ${optname} ALL is the only option supported.`); } break; } return { command: "OPTION", optname, optargs: [arg] }; } generateListing(file, program) { var srclines = []; var laststmt; program.stmts.forEach((stmt, idx) => { laststmt = stmt; srclines.push(stmt.$loc); }); if (this.opts.endStmtRequired && (laststmt == null || laststmt.command != "END")) this.dialectError(`All programs must have a final END statement`); return { lines: srclines }; } getListings() { return this.listings; } checkAll(program) { this.checkLabels(); this.checkScopes(); this.checkVarRefs(); } checkLabels() { for (let targ in this.targets) { if (this.labels[targ] == null) { var what = this.opts.optionalLabels && isNaN(parseInt(targ)) ? "label named" : "line number"; this.addError(`There isn't a ${what} ${targ}.`, this.targets[targ]); } } } checkScopes() { if (this.opts.compiledBlocks && this.scopestack.length) { var open = this.stmts[this.scopestack.pop()]; var close = { FOR: "NEXT", WHILE: "WEND", IF: "END IF", SUB: "END SUB" }; this.compileError(`Don't forget to add a matching ${close[open.command]} statement.`, open.$loc); } } checkVarRefs() { if (!this.opts.defaultValues) { for (var varname in this.varrefs) { if (this.vardefs[varname] == null) this.compileError(`The variable ${varname} isn't defined anywhere in the program.`, this.varrefs[varname]); } } } }; var ECMA55_MINIMAL = { dialectName: "ECMA55", asciiOnly: true, uppercaseOnly: true, optionalLabels: false, optionalWhitespace: false, multipleStmtsPerLine: false, varNaming: "A1", staticArrays: true, sharedArrayNamespace: true, defaultArrayBase: 0, defaultArraySize: 11, defaultValues: false, stringConcat: false, maxDimensions: 2, maxDefArgs: 255, maxStringLength: 255, tickComments: false, hexOctalConsts: false, validKeywords: [ "BASE", "DATA", "DEF", "DIM", "END", "FOR", "GO", "GOSUB", "GOTO", "IF", "INPUT", "LET", "NEXT", "ON", "OPTION", "PRINT", "RANDOMIZE", "READ", "REM", "RESTORE", "RETURN", "STEP", "STOP", "THEN", "TO" ], validFunctions: [ "ABS", "ATN", "COS", "EXP", "INT", "LOG", "RND", "SGN", "SIN", "SQR", "TAB", "TAN" ], validOperators: [ "=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/", "^" ], printZoneLength: 15, numericPadding: true, checkOverflow: true, testInitialFor: true, optionalNextVar: false, multipleNextVars: false, bitwiseLogic: false, checkOnGotoIndex: true, computedGoto: false, restoreWithLabel: false, squareBrackets: false, arraysContainChars: false, endStmtRequired: true, chainAssignments: false, optionalLet: false, compiledBlocks: true }; var DARTMOUTH_4TH_EDITION = { dialectName: "DARTMOUTH4", asciiOnly: true, uppercaseOnly: true, optionalLabels: false, optionalWhitespace: false, multipleStmtsPerLine: false, varNaming: "A1", staticArrays: true, sharedArrayNamespace: false, defaultArrayBase: 0, defaultArraySize: 11, defaultValues: false, stringConcat: false, maxDimensions: 2, maxDefArgs: 255, maxStringLength: 255, tickComments: true, hexOctalConsts: false, validKeywords: [ "BASE", "DATA", "DEF", "DIM", "END", "FOR", "GO", "GOSUB", "GOTO", "IF", "INPUT", "LET", "NEXT", "ON", "OPTION", "PRINT", "RANDOMIZE", "READ", "REM", "RESTORE", "RETURN", "STEP", "STOP", "THEN", "TO", "CHANGE", "MAT", "RANDOM", "RESTORE$", "RESTORE*" ], validFunctions: [ "ABS", "ATN", "COS", "EXP", "INT", "LOG", "RND", "SGN", "SIN", "SQR", "TAB", "TAN", "TRN", "INV", "DET", "NUM", "ZER" ], validOperators: [ "=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/", "^" ], printZoneLength: 15, numericPadding: true, checkOverflow: true, testInitialFor: true, optionalNextVar: false, multipleNextVars: false, bitwiseLogic: false, checkOnGotoIndex: true, computedGoto: false, restoreWithLabel: false, squareBrackets: false, arraysContainChars: false, endStmtRequired: true, chainAssignments: true, optionalLet: false, compiledBlocks: true }; var TINY_BASIC = { dialectName: "TINY", asciiOnly: true, uppercaseOnly: true, optionalLabels: false, optionalWhitespace: false, multipleStmtsPerLine: false, varNaming: "A", staticArrays: false, sharedArrayNamespace: true, defaultArrayBase: 0, defaultArraySize: 0, defaultValues: true, stringConcat: false, maxDimensions: 0, maxDefArgs: 255, maxStringLength: 255, tickComments: false, hexOctalConsts: false, validKeywords: [ "OPTION", "PRINT", "IF", "THEN", "GOTO", "INPUT", "LET", "GOSUB", "RETURN", "CLEAR", "END" ], validFunctions: [], validOperators: [ "=", "<>", "><", "<", ">", "<=", ">=", "+", "-", "*", "/" ], printZoneLength: 1, numericPadding: false, checkOverflow: false, testInitialFor: false, optionalNextVar: false, multipleNextVars: false, bitwiseLogic: false, checkOnGotoIndex: false, computedGoto: true, restoreWithLabel: false, squareBrackets: false, arraysContainChars: false, endStmtRequired: false, chainAssignments: false, optionalLet: false, compiledBlocks: false }; var HP_TIMESHARED_BASIC = { dialectName: "HP2000", asciiOnly: true, uppercaseOnly: true, optionalLabels: false, optionalWhitespace: false, multipleStmtsPerLine: true, varNaming: "A1$", staticArrays: true, sharedArrayNamespace: false, defaultArrayBase: 1, defaultArraySize: 11, defaultValues: false, stringConcat: false, maxDimensions: 2, maxDefArgs: 255, maxStringLength: 255, tickComments: false, hexOctalConsts: false, validKeywords: [ "BASE", "DATA", "DEF", "DIM", "END", "FOR", "GO", "GOSUB", "GOTO", "IF", "INPUT", "LET", "NEXT", "OPTION", "PRINT", "RANDOMIZE", "READ", "REM", "RESTORE", "RETURN", "STEP", "STOP", "THEN", "TO", "ENTER", "MAT", "CONVERT", "OF", "IMAGE", "USING" ], validFunctions: [ "ABS", "ATN", "BRK", "COS", "CTL", "EXP", "INT", "LEN", "LIN", "LOG", "NUM", "POS", "RND", "SGN", "SIN", "SPA", "SQR", "TAB", "TAN", "TIM", "TYP", "UPS$", "NFORMAT$" ], validOperators: [ "=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/", "^", "**", "#", "NOT", "AND", "OR", "MIN", "MAX" ], printZoneLength: 15, numericPadding: true, checkOverflow: false, testInitialFor: true, optionalNextVar: false, multipleNextVars: false, bitwiseLogic: false, checkOnGotoIndex: false, computedGoto: true, restoreWithLabel: true, squareBrackets: true, arraysContainChars: true, endStmtRequired: true, chainAssignments: true, optionalLet: true, compiledBlocks: true, maxArrayElements: 5e3 }; var DEC_BASIC_11 = { dialectName: "DEC11", asciiOnly: true, uppercaseOnly: true, optionalLabels: false, optionalWhitespace: false, multipleStmtsPerLine: false, varNaming: "A1", staticArrays: true, sharedArrayNamespace: false, defaultArrayBase: 0, defaultArraySize: 11, defaultValues: true, stringConcat: true, maxDimensions: 2, maxDefArgs: 255, maxStringLength: 255, tickComments: false, hexOctalConsts: false, validKeywords: [ "OPTION", "DATA", "DEF", "DIM", "END", "FOR", "STEP", "GOSUB", "GOTO", "GO", "TO", "IF", "THEN", "INPUT", "LET", "NEXT", "ON", "PRINT", "RANDOMIZE", "READ", "REM", "RESET", "RESTORE", "RETURN", "STOP" ], validFunctions: [ "ABS", "ATN", "COS", "EXP", "INT", "LOG", "LOG10", "PI", "RND", "SGN", "SIN", "SQR", "TAB", "ASC", "BIN", "CHR$", "CLK$", "DAT$", "LEN", "OCT", "POS", "SEG$", "STR$", "TRM$", "VAL", "NFORMAT$" ], validOperators: [ "=", "<>", "><", "<", ">", "<=", ">=", "+", "-", "*", "/", "^" ], printZoneLength: 14, numericPadding: true, checkOverflow: true, testInitialFor: true, optionalNextVar: false, multipleNextVars: false, bitwiseLogic: false, checkOnGotoIndex: true, computedGoto: false, restoreWithLabel: false, squareBrackets: false, arraysContainChars: false, endStmtRequired: false, chainAssignments: false, optionalLet: true, compiledBlocks: true }; var DEC_BASIC_PLUS = { dialectName: "DECPLUS", asciiOnly: true, uppercaseOnly: false, optionalLabels: false, optionalWhitespace: false, multipleStmtsPerLine: true, varNaming: "A1", staticArrays: true, sharedArrayNamespace: false, defaultArrayBase: 0, defaultArraySize: 11, defaultValues: true, stringConcat: true, maxDimensions: 2, maxDefArgs: 255, maxStringLength: 255, tickComments: true, hexOctalConsts: false, validKeywords: [ "OPTION", "REM", "LET", "DIM", "RANDOM", "RANDOMIZE", "IF", "THEN", "ELSE", "FOR", "TO", "STEP", "WHILE", "UNTIL", "NEXT", "DEF", "ON", "GOTO", "GOSUB", "RETURN", "CHANGE", "READ", "DATA", "RESTORE", "PRINT", "USING", "INPUT", "LINE", "NAME", "AS", "ERROR", "RESUME", "CHAIN", "STOP", "END", "MAT", "UNLESS", "SLEEP", "WAIT" ], validFunctions: [ "ABS", "ATN", "COS", "EXP", "INT", "LOG", "LOG10", "PI", "RND", "SGN", "SIN", "SQR", "TAB", "TAN", "POS", "TAB", "ASCII", "CHR$", "CVT%$", "CVTF$", "CVT$%", "CVT$F", "LEFT$", "RIGHT$", "MID$", "LEN", "INSTR", "SPACE$", "NUM$", "VAL", "XLATE", "DATE$", "TIME$", "TIME", "ERR", "ERL", "SWAP%", "RAD$", "NFORMAT$" ], validOperators: [ "=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/", "^", "**", "==", "NOT", "AND", "OR", "XOR", "IMP", "EQV" ], printZoneLength: 14, numericPadding: true, checkOverflow: true, testInitialFor: true, optionalNextVar: false, multipleNextVars: false, bitwiseLogic: false, checkOnGotoIndex: true, computedGoto: false, restoreWithLabel: false, squareBrackets: false, arraysContainChars: false, endStmtRequired: false, chainAssignments: false, optionalLet: true, compiledBlocks: true }; var BASICODE = { dialectName: "BASICODE", asciiOnly: true, uppercaseOnly: false, optionalLabels: false, optionalWhitespace: true, multipleStmtsPerLine: true, varNaming: "AA", staticArrays: true, sharedArrayNamespace: false, defaultArrayBase: 0, defaultArraySize: 11, defaultValues: false, stringConcat: true, maxDimensions: 2, maxDefArgs: 255, maxStringLength: 255, tickComments: false, hexOctalConsts: false, validKeywords: [ "BASE", "DATA", "DEF", "DIM", "END", "FOR", "GO", "GOSUB", "GOTO", "IF", "INPUT", "LET", "NEXT", "ON", "OPTION", "PRINT", "READ", "REM", "RESTORE", "RETURN", "STEP", "STOP", "THEN", "TO", "AND", "NOT", "OR" ], validFunctions: [ "ABS", "ASC", "ATN", "CHR$", "COS", "EXP", "INT", "LEFT$", "LEN", "LOG", "MID$", "RIGHT$", "SGN", "SIN", "SQR", "TAB", "TAN", "VAL" ], validOperators: [ "=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/", "^", "AND", "NOT", "OR" ], printZoneLength: 15, numericPadding: true, checkOverflow: true, testInitialFor: true, optionalNextVar: false, multipleNextVars: false, bitwiseLogic: false, checkOnGotoIndex: true, computedGoto: false, restoreWithLabel: false, squareBrackets: false, arraysContainChars: false, endStmtRequired: false, chainAssignments: false, optionalLet: true, compiledBlocks: false }; var ALTAIR_BASIC41 = { dialectName: "ALTAIR41", asciiOnly: true, uppercaseOnly: true, optionalLabels: false, optionalWhitespace: true, multipleStmtsPerLine: true, varNaming: "*", staticArrays: false, sharedArrayNamespace: true, defaultArrayBase: 0, defaultArraySize: 11, defaultValues: true, stringConcat: true, maxDimensions: 128, maxDefArgs: 255, maxStringLength: 255, tickComments: false, hexOctalConsts: false, validKeywords: [ "OPTION", "CONSOLE", "DATA", "DEF", "DEFUSR", "DIM", "END", "ERASE", "ERROR", "FOR", "GOTO", "GOSUB", "IF", "THEN", "ELSE", "INPUT", "LET", "LINE", "PRINT", "LPRINT", "USING", "NEXT", "ON", "OUT", "POKE", "READ", "REM", "RESTORE", "RESUME", "RETURN", "STOP", "SWAP", "TROFF", "TRON", "WAIT", "TO", "STEP", "AND", "NOT", "OR", "XOR", "IMP", "EQV", "MOD", "RANDOMIZE" ], validFunctions: [ "ABS", "ASC", "ATN", "CDBL", "CHR$", "CINT", "COS", "ERL", "ERR", "EXP", "FIX", "FRE", "HEX$", "INP", "INSTR", "INT", "LEFT$", "LEN", "LOG", "LPOS", "MID$", "OCT$", "POS", "RIGHT$", "RND", "SGN", "SIN", "SPACE$", "SPC", "SQR", "STR$", "STRING$", "TAB", "TAN", "USR", "VAL", "VARPTR" ], validOperators: [ "=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/", "^", "\\", "AND", "NOT", "OR", "XOR", "IMP", "EQV", "MOD" ], printZoneLength: 15, numericPadding: true, checkOverflow: true, testInitialFor: false, optionalNextVar: true, multipleNextVars: true, bitwiseLogic: true, checkOnGotoIndex: false, computedGoto: false, restoreWithLabel: false, squareBrackets: false, arraysContainChars: false, endStmtRequired: false, chainAssignments: false, optionalLet: true, compiledBlocks: false }; var APPLESOFT_BASIC = { dialectName: "APPLESOFT", asciiOnly: true, uppercaseOnly: false, optionalLabels: false, optionalWhitespace: true, multipleStmtsPerLine: true, varNaming: "*", staticArrays: false, sharedArrayNamespace: false, defaultArrayBase: 0, defaultArraySize: 11, defaultValues: true, stringConcat: true, maxDimensions: 88, maxDefArgs: 1, maxStringLength: 255, tickComments: false, hexOctalConsts: false, validKeywords: [ "OPTION", "CLEAR", "LET", "DIM", "DEF", "GOTO", "GOSUB", "RETURN", "ON", "POP", "FOR", "NEXT", "IF", "THEN", "END", "STOP", "ONERR", "RESUME", "PRINT", "INPUT", "GET", "HOME", "HTAB", "VTAB", "INVERSE", "FLASH", "NORMAL", "TEXT", "GR", "COLOR", "PLOT", "HLIN", "VLIN", "HGR", "HGR2", "HPLOT", "HCOLOR", "AT", "DATA", "READ", "RESTORE", "REM", "TRACE", "NOTRACE", "TO", "STEP", "AND", "NOT", "OR" ], validFunctions: [ "ABS", "ATN", "COS", "EXP", "INT", "LOG", "RND", "SGN", "SIN", "SQR", "TAN", "LEN", "LEFT$", "MID$", "RIGHT$", "STR$", "VAL", "CHR$", "ASC", "FRE", "SCRN", "PDL", "PEEK", "POS" ], validOperators: [ "=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/", "^", "AND", "NOT", "OR" ], printZoneLength: 16, numericPadding: false, checkOverflow: true, testInitialFor: false, optionalNextVar: true, multipleNextVars: true, bitwiseLogic: false, checkOnGotoIndex: false, computedGoto: false, restoreWithLabel: false, squareBrackets: false, arraysContainChars: false, endStmtRequired: false, chainAssignments: false, optionalLet: true, compiledBlocks: false }; var BASIC80 = { dialectName: "BASIC80", asciiOnly: true, uppercaseOnly: false, optionalLabels: false, optionalWhitespace: true, multipleStmtsPerLine: true, varNaming: "*", staticArrays: false, sharedArrayNamespace: true, defaultArrayBase: 0, defaultArraySize: 11, defaultValues: true, stringConcat: true, maxDimensions: 255, maxDefArgs: 255, maxStringLength: 255, tickComments: true, hexOctalConsts: true, validKeywords: [ "OPTION", "CONSOLE", "DATA", "DEF", "DEFUSR", "DIM", "END", "ERASE", "ERROR", "FOR", "GOTO", "GOSUB", "IF", "THEN", "ELSE", "INPUT", "LET", "LINE", "PRINT", "LPRINT", "USING", "NEXT", "ON", "OUT", "POKE", "READ", "REM", "RESTORE", "RESUME", "RETURN", "STOP", "SWAP", "TROFF", "TRON", "WAIT", "CALL", "CHAIN", "COMMON", "WHILE", "WEND", "WRITE", "RANDOMIZE", "TO", "STEP", "AND", "NOT", "OR", "XOR", "IMP", "EQV", "MOD" ], validFunctions: [ "ABS", "ASC", "ATN", "CDBL", "CHR$", "CINT", "COS", "CSNG", "CVI", "CVS", "CVD", "EOF", "EXP", "FIX", "FRE", "HEX$", "INP", "INPUT$", "INSTR", "INT", "LEFT$", "LEN", "LOC", "LOG", "LPOS", "MID$", "MKI$", "MKS$", "MKD$", "OCT$", "PEEK", "POS", "RIGHT$", "RND", "SGN", "SIN", "SPACE$", "SPC", "SQR", "STR$", "STRING$", "TAB", "TAN", "USR", "VAL", "VARPTR" ], validOperators: [ "=", "<>", "<", ">", "<=", ">=", "+", "-", "*", "/", "^", "\\", "AND", "NOT", "OR", "XOR", "IMP", "EQV", "MOD" ], printZoneLength: 14, numericPadding: true, checkOverflow: false, testInitialFor: true, optionalNextVar: true, multipleNextVars: true, bitwiseLogic: true, checkOnGotoIndex: false, computedGoto: false, restoreWithLabel: true, squareBrackets: false, arraysContainChars: false, endStmtRequired: false, chainAssignments: false, optionalLet: true, compiledBlocks: false }; var MODERN_BASIC = { dialectName: "MODERN", asciiOnly: false, uppercaseOnly: false, optionalLabels: true, optionalWhitespace: false, multipleStmtsPerLine: true, varNaming: "*", staticArrays: false, sharedArrayNamespace: false, defaultArrayBase: 0, defaultArraySize: 0, defaultValues: false, stringConcat: true, maxDimensions: 255, maxDefArgs: 255, maxStringLength: 2048, tickComments: true, hexOctalConsts: true, validKeywords: null, validFunctions: null, validOperators: null, printZoneLength: 16, numericPadding: false, checkOverflow: true, testInitialFor: true, optionalNextVar: true, multipleNextVars: true, bitwiseLogic: true, checkOnGotoIndex: true, computedGoto: false, restoreWithLabel: true, squareBrackets: true, arraysContainChars: false, endStmtRequired: false, chainAssignments: true, optionalLet: true, compiledBlocks: true, multilineIfThen: true }; var BUILTIN_DEFS = [ ["ABS", ["number"], "number"], ["ASC", ["string"], "number"], ["ATN", ["number"], "number"], ["CHR$", ["number"], "string"], ["CINT", ["number"], "number"], ["COS", ["number"], "number"], ["COT", ["number"], "number"], ["CTL", ["number"], "string"], ["EXP", ["number"], "number"], ["FIX", ["number"], "number"], ["HEX$", ["number"], "string"], ["INSTR", ["number", "string", "string"], "number"], ["INSTR", ["string", "string"], "number"], ["INT", ["number"], "number"], ["LEFT$", ["string", "number"], "string"], ["LEN", ["string"], "number"], ["LIN", ["number"], "string"], ["LOG", ["number"], "number"], ["LOG10", ["number"], "number"], ["MID$", ["string", "number"], "string"], ["MID$", ["string", "number", "number"], "string"], ["OCT$", ["number"], "string"], ["PI", [], "number"], ["POS", ["number"], "number"], ["POS", ["string", "string"], "number"], ["RIGHT$", ["string", "number"], "string"], ["RND", [], "number"], ["RND", ["number"], "number"], ["ROUND", ["number"], "number"], ["SGN", ["number"], "number"], ["SIN", ["number"], "number"], ["SPACE$", ["number"], "string"], ["SPC", ["number"], "string"], ["SQR", ["number"], "number"], ["STR$", ["number"], "string"], ["STRING$", ["number", "number"], "string"], ["STRING$", ["number", "string"], "string"], ["TAB", ["number"], "string"], ["TAN", ["number"], "number"], ["TIM", ["number"], "number"], ["TIMER", [], "number"], ["UPS$", ["string"], "string"], ["VAL", ["string"], "number"], ["LPAD$", ["string", "number"], "string"], ["RPAD$", ["string", "number"], "string"], ["NFORMAT$", ["number", "number"], "string"] ]; var BUILTIN_MAP = {}; BUILTIN_DEFS.forEach((def, idx) => { let [name, args, result] = def; if (!BUILTIN_MAP[name]) BUILTIN_MAP[name] = []; BUILTIN_MAP[name].push({ args, result }); }); var DIALECTS = { "DEFAULT": MODERN_BASIC, "DARTMOUTH": DARTMOUTH_4TH_EDITION, "DARTMOUTH4": DARTMOUTH_4TH_EDITION, "ALTAIR": ALTAIR_BASIC41, "ALTAIR4": ALTAIR_BASIC41, "ALTAIR41": ALTAIR_BASIC41, "TINY": TINY_BASIC, "ECMA55": ECMA55_MINIMAL, "MINIMAL": ECMA55_MINIMAL, "HP": HP_TIMESHARED_BASIC, "HPB": HP_TIMESHARED_BASIC, "HPTSB": HP_TIMESHARED_BASIC, "HP2000": HP_TIMESHARED_BASIC, "HPBASIC": HP_TIMESHARED_BASIC, "HPACCESS": HP_TIMESHARED_BASIC, "DEC11": DEC_BASIC_11, "DEC": DEC_BASIC_PLUS, "DECPLUS": DEC_BASIC_PLUS, "BASICPLUS": DEC_BASIC_PLUS, "BASICODE": BASICODE, "APPLESOFT": APPLESOFT_BASIC, "BASIC80": BASIC80, "MODERN": MODERN_BASIC }; // src/worker/tools/misc.ts function translateShowdown(step) { 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 }; } function compileInform6(step) { 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) => { if (s.indexOf("Error:") >= 0) { errorMatcher(s); } else { lstout += s; lstout += "\n"; } }; var args = ["-afjnops", "-v5", "-Cu", "-E1", "-k", "+/share/lib", step.path]; var inform = emglobal.inform({ instantiateWasm: moduleInstFn("inform"), noInitialRun: true, print: match_fn, printErr: match_fn }); var FS = inform.FS; setupFS(FS, "inform"); populateFiles(step, FS); execMain(step, inform, args); if (errors.length) return { errors }; var objout = FS.readFile(objpath, { encoding: "binary" }); putWorkFile(objpath, objout); if (!anyTargetChanged(step, [objpath])) return; var symbolmap = {}; var segments = []; var entitymap = { "object": {}, "property": {}, "attribute": {}, "constant": {}, "global-variable": {}, "routine": {} }; var dbgout = FS.readFile("gameinfo.dbg", { encoding: "utf8" }); var xmlroot = parseXMLPoorly(dbgout); 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][value] = ident; 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, start: address, size: endAddress - address, type: segtype }); } }); var listings = {}; 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, listings, errors, symbolmap, segments, debuginfo: entitymap }; } } function compileBASIC(step) { var jsonpath = step.path + ".json"; gatherFiles(step); if (staleFiles(step, [jsonpath])) { var parser = new 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 }; } var json = JSON.stringify(ast, (key, value) => { return key == "$loc" ? void 0 : value; }); putWorkFile(jsonpath, json); if (anyTargetChanged(step, [jsonpath])) return { output: ast, listings: parser.getListings() }; } } function compileWiz(step) { loadNative("wiz"); var params = step.params; gatherFiles(step, { mainFilePath: "main.wiz" }); var destpath = step.prefix + (params.wiz_rom_ext || ".bin"); var errors = []; if (staleFiles(step, [destpath])) { var wiz = emglobal.wiz({ instantiateWasm: moduleInstFn("wiz"), noInitialRun: true, print: print_fn, 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 }; 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(/ /); 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, errors, symbolmap }; } } // src/worker/tools/cc65.ts function parseCA65Listing(code, symbols, segments, params, dbg, listings) { 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 origlines = []; var lines = origlines; var linenum = 0; let curpath = ""; 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); curpath = dbgm[5]; if (curpath && listings) { let l = listings[curpath]; if (!l) l = listings[curpath] = { lines: [] }; lines = l.lines; } if (dbgtype == "func") { var funcm = funcLineMatch.exec(dbgm[6]); if (funcm) { var funcofs = symbols[funcm[3]]; if (typeof funcofs === "number") { segofs = funcofs - offset; } } } } if (dbg && dbgm && dbgtype == "line") { lines.push({ path: dbgm[5], line: parseInt(dbgm[6]), offset: offset + segofs, insns: null }); } linenum++; var linem = insnLineMatch.exec(line); var topfile = linem && linem[3] == "1"; if (topfile && linem[1]) { var offset = parseInt(linem[1], 16); var insns = linem[4].trim(); if (insns.length) { if (!dbg) { lines.push({ path: curpath, line: linenum, offset: offset + segofs, insns, iscode: true }); } } else { var sym = linem[5]; if (sym.endsWith(":") && !sym.startsWith("@")) { var symofs = symbols[sym.substring(0, sym.length - 1)]; if (typeof symofs === "number") { segofs = symofs - offset; } } } } } return origlines; } function assembleCA65(step) { 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 = emglobal.ca65({ instantiateWasm: moduleInstFn("ca65"), noInitialRun: true, print: print_fn, printErr: makeErrorMatcher(errors, /(.+?):(\d+): (.+)/, 2, 3, step.path, 1) }); 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) { let listings = {}; return { errors, listings }; } 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] }; } function linkLD65(step) { var _a; loadNative("ld65"); var params = step.params; gatherFiles(step); var binpath = "main"; if (staleFiles(step, [binpath])) { var errors = []; var LD65 = emglobal.ld65({ instantiateWasm: moduleInstFn("ld65"), noInitialRun: true, print: print_fn, printErr: function(s2) { errors.push({ msg: s2, line: 0 }); } }); var FS = LD65.FS; setupFS(FS, "65-" + getRootBasePlatform(step.platform)); populateFiles(step, FS); populateExtraFiles(step, FS, params.extra_link_files); 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", "-o", "main", "-m", "main.map" ].concat(step.args, libargs); execMain(step, LD65, args); if (errors.length) return { errors }; var aout = FS.readFile("main", { encoding: "binary" }); var mapout = FS.readFile("main.map", { encoding: "utf8" }); var viceout = FS.readFile("main.vice", { encoding: "utf8" }); putWorkFile("main", aout); putWorkFile("main.map", mapout); putWorkFile("main.vice", viceout); if (!anyTargetChanged(step, ["main", "main.map", "main.vice"])) return; 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")) { let ofs = parseInt(toks[1], 16); symbolmap[ident] = ofs; } } } var segments = []; segments.push({ name: "CPU Stack", start: 256, size: 256, type: "ram" }); segments.push({ name: "CPU Vectors", start: 65530, size: 6, type: "rom" }); let re_seglist = /(\w+)\s+([0-9A-F]+)\s+([0-9A-F]+)\s+([0-9A-F]+)\s+([0-9A-F]+)/; let parseseglist = false; let m; for (let s2 of mapout.split("\n")) { if (parseseglist && (m = re_seglist.exec(s2))) { let seg = m[1]; let start = parseInt(m[2], 16); let size = parseInt(m[4], 16); let type = ""; 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, size, type }); } if (s2 == "Segment list:") parseseglist = true; if (s2 == "") parseseglist = false; } var listings = {}; for (var fn of step.files) { if (fn.endsWith(".lst")) { var lstout = FS.readFile(fn, { encoding: "utf8" }); lstout = lstout.split("\n\n")[1] || lstout; putWorkFile(fn, lstout); console.log(step); let isECS = ((_a = step.debuginfo) == null ? void 0 : _a.entities) != null; if (isECS) { var asmlines = []; var srclines = parseCA65Listing(lstout, symbolmap, segments, params, true, listings); listings[fn] = { lines: [], text: lstout }; } else { var asmlines = parseCA65Listing(lstout, symbolmap, segments, params, false); var srclines = parseCA65Listing(lstout, symbolmap, segments, params, true); listings[fn] = { asmlines: srclines.length ? asmlines : null, lines: srclines.length ? srclines : asmlines, text: lstout }; } } } return { output: aout, listings, errors, symbolmap, segments }; } } function compileCC65(step) { loadNative("cc65"); var params = step.params; var re_err1 = /(.*?):(\d+): (.+)/; var errors = []; 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 = emglobal.cc65({ instantiateWasm: moduleInstFn("cc65"), noInitialRun: 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", "-W", "-pointer-sign,-no-effect"]; args = args.concat(customArgs, args); args.push(step.path); execMain(step, CC65, args); if (errors.length) return { errors }; var asmout = FS.readFile(destpath, { encoding: "utf8" }); putWorkFile(destpath, asmout); } return { nexttool: "ca65", path: destpath, args: [destpath], files: [destpath] }; } // src/worker/tools/dasm.ts function parseDASMListing(lstpath, lsttext, listings, errors, unresolved) { 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; if (lstlist && lstlist.lines) { lstlist.lines.push({ line: lstline, offset, insns, iscode: true }); } let lst = listings[filename]; if (lst) { var lines = lst.lines; 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, insns, iscode: restline[0] != "." }); } lastline = linenum; } else { let mac = macros[filename.toLowerCase()]; if (mac && linenum == 0) { lines.push({ line: lastline + 1, offset, 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, insns, iscode: true }); } } else { if (insns && linem[3] && lastline > 0) { lines.push({ line: lastline + 1, offset, insns: null }); } } } for (let key in unresolved) { let l = restline || line; let pos = l.indexOf(key); if (pos >= 0) { let cmt = l.indexOf(";"); if (cmt < 0 || cmt > pos) { 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) { load("dasm"); var re_usl = /(\w+)\s+0000\s+[?][?][?][?]/; var unresolved = {}; var errors = []; var errorMatcher = msvcErrorMatcher(errors); function match_fn(s2) { var matches = re_usl.exec(s2); if (matches) { var key = matches[1]; if (key != "NO_ILLEGAL_OPCODES") { unresolved[matches[1]] = 0; } } else if (s2.startsWith("Warning:")) { errors.push({ line: 0, msg: s2.substr(9) }); } else if (s2.startsWith("unable ")) { errors.push({ line: 0, msg: s2 }); } else if (s2.startsWith("segment: ")) { errors.push({ line: 0, msg: "Segment overflow: " + s2.substring(9) }); } else if (s2.toLowerCase().indexOf("error:") >= 0) { errors.push({ line: 0, msg: s2.trim() }); } else { errorMatcher(s2); } } var Module = 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" }); var listings = {}; for (let path of step.files) { listings[path] = { lines: [] }; } parseDASMListing(lstpath, alst, listings, errors, unresolved); if (errors.length) { return { errors }; } 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 }; } putWorkFile(binpath, aout); putWorkFile(lstpath, alst); putWorkFile(sympath, asym); if (!anyTargetChanged(step, [binpath])) 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); } } if (step["bblines"]) { let lst = listings[step.path]; if (lst) { lst.asmlines = lst.lines; lst.text = alst; lst.lines = []; } } return { output: aout, listings, errors, symbolmap }; } function preprocessBatariBasic(code) { load("bbpreprocess"); var bbout = ""; function addbbout_fn(s) { bbout += s; bbout += "\n"; } var BBPRE = emglobal.preprocess({ noInitialRun: 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) { load("bb2600basic"); var params = step.params; var asmout = ""; function addasmout_fn(s) { asmout += s; asmout += "\n"; } 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 = emglobal.bb2600basic({ noInitialRun: true, print: addasmout_fn, printErr: match_fn, noFSInit: true, TOTAL_MEMORY: 64 * 1024 * 1024 }); var FS = BB.FS; populateFiles(step, FS); 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 }; 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; } 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 }; } // src/worker/tools/sdcc.ts 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]; 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); } } } if (high_size > rom_size) { } return output; } function assembleSDASZ80(step) { 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])) { var match_asm_re1 = / in line (\d+) of (\S+)/; var match_asm_re2 = / <\w> (.+)/; var errline = 0; var errpath = step.path; var match_asm_fn = (s) => { 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 = emglobal.sdasz80({ instantiateWasm: moduleInstFn("sdasz80"), noInitialRun: 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 }; } 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] }; } function linkSDLDZ80(step) { loadNative("sdldz80"); var errors = []; gatherFiles(step); var binpath = "main.ihx"; if (staleFiles(step, [binpath])) { var match_aslink_re = /\?ASlink-(\w+)-(.+)/; var match_aslink_fn = (s2) => { var matches = match_aslink_re.exec(s2); if (matches) { errors.push({ line: 0, msg: matches[2] }); } }; var params = step.params; var LDZ80 = emglobal.sdldz80({ instantiateWasm: moduleInstFn("sdldz80"), noInitialRun: 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); if (step.platform.startsWith("coleco")) { FS.writeFile("crt0.rel", FS.readFile("/share/lib/coleco/crt0.rel", { encoding: "utf8" })); FS.writeFile("crt0.lst", "\n"); } var args = [ "-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" ]; if (params.extra_link_args) args.push.apply(args, params.extra_link_args); args.push.apply(args, step.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); if (!anyTargetChanged(step, ["main.ihx", "main.noi"])) return; var binout = parseIHX(hexout, params.rom_start !== void 0 ? params.rom_start : params.code_start, params.rom_size, errors); if (errors.length) { return { errors }; } var listings = {}; for (var fn of step.files) { if (fn.endsWith(".lst")) { var rstout = FS.readFile(fn.replace(".lst", ".rst"), { encoding: "utf8" }); 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); listings[fn] = { asmlines: srclines.length ? asmlines : null, lines: srclines.length ? srclines : asmlines, text: rstout }; } } 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); } } var seg_re = /^s__(\w+)$/; var segments = []; for (let ident in symbolmap) { let m = seg_re.exec(ident); if (m) { let seg = m[1]; let segstart = symbolmap[ident]; let segsize = symbolmap["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) segments.push({ name: seg, start: segstart, size: segsize, type }); } } } return { output: binout, listings, errors, symbolmap, segments }; } } function compileSDCC(step) { gatherFiles(step, { mainFilePath: "main.c" }); var outpath = step.prefix + ".asm"; if (staleFiles(step, [outpath])) { var errors = []; var params = step.params; loadNative("sdcc"); var SDCC = emglobal.sdcc({ instantiateWasm: moduleInstFn("sdcc"), noInitialRun: true, noFSInit: true, print: print_fn, printErr: msvcErrorMatcher(errors) }); var FS = SDCC.FS; populateFiles(step, FS); var code = getWorkFileAsString(step.path); var preproc = preprocessMCPP(step, "sdcc"); if (preproc.errors) { return { errors: preproc.errors }; } else code = preproc.code; setupStdin(FS, code); setupFS(FS, "sdcc"); var args = [ "--vc", "--std-sdcc99", "-mz80", "--c1mode", "--less-pedantic", "-o", outpath ]; 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); if (errors.length) { return { errors }; } 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] }; } // src/worker/assembler.ts var isError = (o) => o.error !== void 0; function hex2(v, nd) { try { if (!nd) nd = 2; if (nd == 8) { return hex2(v >> 16 & 65535, 4) + hex2(v & 65535, 4); } var s = v.toString(16).toUpperCase(); while (s.length < nd) s = "0" + s; return s; } catch (e) { return v + ""; } } function stringToData(s) { var data = []; for (var i = 0; i < s.length; i++) { data[i] = s.charCodeAt(i); } return data; } var Assembler = class { constructor(spec) { this.ip = 0; this.origin = 0; this.linenum = 0; this.symbols = {}; this.errors = []; this.outwords = []; this.asmlines = []; this.fixups = []; this.width = 8; this.codelen = 0; this.aborted = false; this.spec = spec; if (spec) { this.preprocessRules(); } } rule2regex(rule, vars) { var s = rule.fmt; if (!s || !(typeof s === "string")) throw Error('Each rule must have a "fmt" string field'); if (!rule.bits || !(rule.bits instanceof Array)) throw Error('Each rule must have a "bits" array field'); var varlist = []; rule.prefix = s.split(/\s+/)[0]; s = s.replace(/\+/g, "\\+"); s = s.replace(/\*/g, "\\*"); s = s.replace(/\s+/g, "\\s+"); s = s.replace(/\[/g, "\\["); s = s.replace(/\]/g, "\\]"); s = s.replace(/\(/g, "\\("); s = s.replace(/\)/g, "\\)"); s = s.replace(/\./g, "\\."); s = s.replace(/~\w+/g, (varname) => { varname = varname.substr(1); var v = vars[varname]; varlist.push(varname); if (!v) throw Error('Could not find variable definition for "~' + varname + '"'); else if (v.toks) return "(\\w+)"; else return "([0-9]+|[$][0-9a-f]+|\\w+)"; }); try { rule.re = new RegExp("^" + s + "$", "i"); } catch (e) { throw Error('Bad regex for rule "' + rule.fmt + '": /' + s + "/ -- " + e); } rule.varlist = varlist; return rule; } preprocessRules() { if (this.spec.width) { this.width = this.spec.width || 8; } for (var rule of this.spec.rules) { this.rule2regex(rule, this.spec.vars); } } warning(msg, line) { this.errors.push({ msg, line: line ? line : this.linenum }); } fatal(msg, line) { this.warning(msg, line); this.aborted = true; } fatalIf(msg, line) { if (msg) this.fatal(msg, line); } addBytes(result) { this.asmlines.push({ line: this.linenum, offset: this.ip, nbits: result.nbits }); var op = result.opcode; var nb = result.nbits / this.width; for (var i = 0; i < nb; i++) { if (this.width < 32) this.outwords[this.ip++ - this.origin] = op >> (nb - 1 - i) * this.width & (1 << this.width) - 1; else this.outwords[this.ip++ - this.origin] = op; } } addWords(data) { this.asmlines.push({ line: this.linenum, offset: this.ip, nbits: this.width * data.length }); for (var i = 0; i < data.length; i++) { if (this.width < 32) this.outwords[this.ip++ - this.origin] = data[i] & (1 << this.width) - 1; else this.outwords[this.ip++ - this.origin] = data[i]; } } parseData(toks) { var data = []; for (var i = 0; i < toks.length; i++) { data[i] = this.parseConst(toks[i]); } return data; } alignIP(align) { if (align < 1 || align > this.codelen) this.fatal("Invalid alignment value"); else this.ip = Math.floor((this.ip + align - 1) / align) * align; } parseConst(s, nbits) { if (s && s[0] == "$") return parseInt(s.substr(1), 16); else return parseInt(s); } swapEndian(x, nbits) { var y = 0; while (nbits > 0) { var n = Math.min(nbits, this.width); var mask = (1 << n) - 1; y <<= n; y |= x & mask; x >>>= n; nbits -= n; } return y; } buildInstruction(rule, m) { var opcode = 0; var oplen = 0; for (let b of rule.bits) { let n, x; if (typeof b === "string") { n = b.length; x = parseInt(b, 2); } else { var index = typeof b === "number" ? b : b.a; var id = m[index + 1]; var v = this.spec.vars[rule.varlist[index]]; if (!v) { return { error: `Could not find matching identifier for '${m[0]}' index ${index}` }; } n = v.bits; var shift = 0; if (typeof b !== "number") { n = b.n; shift = b.b; } if (v.toks) { x = v.toks.indexOf(id); if (x < 0) return { error: "Can't use '" + id + "' here, only one of: " + v.toks.join(", ") }; } else { x = this.parseConst(id, n); if (isNaN(x)) { this.fixups.push({ sym: id, ofs: this.ip, size: v.bits, line: this.linenum, dstlen: n, dstofs: oplen, srcofs: shift, endian: v.endian, iprel: !!v.iprel, ipofs: v.ipofs + 0, ipmul: v.ipmul || 1 }); x = 0; } else { var mask = (1 << v.bits) - 1; if ((x & mask) != x) return { error: "Value " + x + " does not fit in " + v.bits + " bits" }; } } if (v.endian == "little") x = this.swapEndian(x, v.bits); if (typeof b !== "number") { x = x >>> shift & (1 << b.n) - 1; } } opcode = opcode << n | x; oplen += n; } if (oplen == 0) this.warning("Opcode had zero length"); else if (oplen > 32) this.warning("Opcodes > 32 bits not supported"); else if (oplen % this.width != 0) this.warning("Opcode was not word-aligned (" + oplen + " bits)"); return { opcode, nbits: oplen }; } loadArch(arch) { if (this.loadJSON) { var json = this.loadJSON(arch + ".json"); if (json && json.vars && json.rules) { this.spec = json; this.preprocessRules(); } else { return "Could not load arch file '" + arch + ".json'"; } } } parseDirective(tokens) { var cmd = tokens[0].toLowerCase(); if (cmd == ".define") this.symbols[tokens[1].toLowerCase()] = { value: tokens[2] }; else if (cmd == ".org") this.ip = this.origin = parseInt(tokens[1]); else if (cmd == ".len") this.codelen = parseInt(tokens[1]); else if (cmd == ".width") this.width = parseInt(tokens[1]); else if (cmd == ".arch") this.fatalIf(this.loadArch(tokens[1])); else if (cmd == ".include") this.fatalIf(this.loadInclude(tokens[1])); else if (cmd == ".module") this.fatalIf(this.loadModule(tokens[1])); else if (cmd == ".data") this.addWords(this.parseData(tokens.slice(1))); else if (cmd == ".string") this.addWords(stringToData(tokens.slice(1).join(" "))); else if (cmd == ".align") this.alignIP(this.parseConst(tokens[1])); else this.warning("Unrecognized directive: " + tokens); } assemble(line) { this.linenum++; line = line.replace(/[;].*/g, "").trim(); if (line[0] == ".") { var tokens = line.split(/\s+/); this.parseDirective(tokens); return; } line = line.toLowerCase(); line = line.replace(/(\w+):/, (_label, label) => { this.symbols[label] = { value: this.ip }; return ""; }); line = line.trim(); if (line == "") return; if (!this.spec) { this.fatal("Need to load .arch first"); return; } var lastError; for (var rule of this.spec.rules) { var m = rule.re.exec(line); if (m) { var result = this.buildInstruction(rule, m); if (!isError(result)) { this.addBytes(result); return result; } else { lastError = result.error; } } } this.warning(lastError ? lastError : "Could not decode instruction: " + line); } applyFixup(fix, sym) { var ofs = fix.ofs + Math.floor(fix.dstofs / this.width); var mask = (1 << fix.size) - 1; var value = this.parseConst(sym.value + "", fix.dstlen); if (fix.iprel) value = (value - fix.ofs) * fix.ipmul - fix.ipofs; if (fix.srcofs == 0 && (value > mask || value < -mask)) this.warning("Symbol " + fix.sym + " (" + value + ") does not fit in " + fix.dstlen + " bits", fix.line); if (fix.srcofs > 0) value >>>= fix.srcofs; value &= (1 << fix.dstlen) - 1; if (this.width == 32) { var shift = 32 - fix.dstofs - fix.dstlen; value <<= shift; } if (fix.size <= this.width) { this.outwords[ofs - this.origin] ^= value; } else { if (fix.endian == "big") value = this.swapEndian(value, fix.size); while (value) { if (value & this.outwords[ofs - this.origin]) { this.warning("Instruction bits overlapped: " + hex2(this.outwords[ofs - this.origin], 8), hex2(value, 8)); } else { this.outwords[ofs - this.origin] ^= value & (1 << this.width) - 1; } value >>>= this.width; ofs++; } } } finish() { for (var i = 0; i < this.fixups.length; i++) { var fix = this.fixups[i]; var sym = this.symbols[fix.sym]; if (sym) { this.applyFixup(fix, sym); } else { this.warning("Symbol '" + fix.sym + "' not found"); } } for (var i = 0; i < this.asmlines.length; i++) { var al = this.asmlines[i]; al.insns = ""; for (var j = 0; j < al.nbits / this.width; j++) { var word = this.outwords[al.offset + j - this.origin]; if (j > 0) al.insns += " "; al.insns += hex2(word, this.width / 4); } } while (this.outwords.length < this.codelen) { this.outwords.push(0); } this.fixups = []; return this.state(); } assembleFile(text) { var lines = text.split(/\n/g); for (var i = 0; i < lines.length && !this.aborted; i++) { try { this.assemble(lines[i]); } catch (e) { console.log(e); this.fatal("Exception during assembly: " + e); } } return this.finish(); } state() { return { ip: this.ip, line: this.linenum, origin: this.origin, codelen: this.codelen, intermediate: {}, output: this.outwords, lines: this.asmlines, errors: this.errors, fixups: this.fixups }; } }; // src/common/hdl/hdltypes.ts function isVarDecl(arg) { return typeof arg.isParam !== "undefined"; } function isConstExpr(arg) { return typeof arg.cvalue === "number"; } // src/common/hdl/vxmlparser.ts var CompileError2 = class extends Error { constructor($loc, msg) { super(msg); this.$loc = $loc; Object.setPrototypeOf(this, CompileError2.prototype); } }; var VerilogXMLParser = class { constructor() { this.files = {}; this.dtypes = {}; this.modules = {}; this.hierarchies = {}; this.cur_deferred = []; this.dtypes["QData"] = { left: 63, right: 0, signed: false }; this.dtypes["IData"] = { left: 31, right: 0, signed: false }; this.dtypes["SData"] = { left: 15, right: 0, signed: false }; this.dtypes["CData"] = { left: 7, right: 0, signed: false }; this.dtypes["byte"] = { left: 7, right: 0, signed: true }; this.dtypes["shortint"] = { left: 15, right: 0, signed: true }; this.dtypes["int"] = { left: 31, right: 0, signed: true }; this.dtypes["integer"] = { left: 31, right: 0, signed: true }; this.dtypes["longint"] = { left: 63, right: 0, signed: true }; this.dtypes["time"] = { left: 63, right: 0, signed: false }; } defer(fn) { this.cur_deferred.unshift(fn); } defer2(fn) { this.cur_deferred.push(fn); } run_deferred() { this.cur_deferred.forEach((fn) => fn()); this.cur_deferred = []; } name2js(s) { if (s == null) throw new CompileError2(this.cur_loc, `no name`); return s.replace(/[^a-z0-9_]/gi, "$"); } findChildren(node, type, required) { var arr = node.children.filter((n) => n.type == type); if (arr.length == 0 && required) throw new CompileError2(this.cur_loc, `no child of type ${type}`); return arr; } parseSourceLocation(node) { var loc = node.attrs["loc"]; if (loc) { if (loc == this.cur_loc_str) { return this.cur_loc; } else { var [fileid, line, col, end_line, end_col] = loc.split(","); var $loc = { hdlfile: this.files[fileid], path: this.files[fileid].filename, line: parseInt(line), start: parseInt(col) - 1, end_line: parseInt(end_line), end: parseInt(end_col) - 1 }; this.cur_loc = $loc; this.cur_loc_str = loc; return $loc; } } else { return null; } } open_module(node) { var module = { $loc: this.parseSourceLocation(node), name: node.attrs["name"], origName: node.attrs["origName"], blocks: [], instances: [], vardefs: {} }; if (this.cur_module) throw new CompileError2(this.cur_loc, `nested modules not supported`); this.cur_module = module; return module; } deferDataType(node, def) { var dtype_id = node.attrs["dtype_id"]; if (dtype_id != null) { this.defer(() => { def.dtype = this.dtypes[dtype_id]; if (!def.dtype) { throw new CompileError2(this.cur_loc, `Unknown data type ${dtype_id} for ${node.type}`); } }); } } parseConstValue(s) { const re_const = /(\d+)'([s]?)h([0-9a-f]+)/i; var m = re_const.exec(s); if (m) { var numstr = m[3]; if (numstr.length <= 8) return parseInt(numstr, 16); else return BigInt("0x" + numstr); } else { throw new CompileError2(this.cur_loc, `could not parse constant "${s}"`); } } resolveVar(s, mod) { var def = mod.vardefs[s]; if (def == null) throw new CompileError2(this.cur_loc, `could not resolve variable "${s}"`); return def; } resolveModule(s) { var mod = this.modules[s]; if (mod == null) throw new CompileError2(this.cur_loc, `could not resolve module "${s}"`); return mod; } visit_verilator_xml(node) { } visit_package(node) { } visit_module(node) { this.findChildren(node, "var", false).forEach((n) => { if (isVarDecl(n.obj)) { this.cur_module.vardefs[n.obj.name] = n.obj; } }); this.modules[this.cur_module.name] = this.cur_module; this.cur_module = null; } visit_var(node) { var name = node.attrs["name"]; name = this.name2js(name); var vardef = { $loc: this.parseSourceLocation(node), name, origName: node.attrs["origName"], isInput: node.attrs["dir"] == "input", isOutput: node.attrs["dir"] == "output", isParam: node.attrs["param"] == "true", dtype: null }; this.deferDataType(node, vardef); var const_nodes = this.findChildren(node, "const", false); if (const_nodes.length) { vardef.constValue = const_nodes[0].obj; } var init_nodes = this.findChildren(node, "initarray", false); if (init_nodes.length) { vardef.initValue = init_nodes[0].obj; } return vardef; } visit_const(node) { var name = node.attrs["name"]; var cvalue = this.parseConstValue(name); var constdef = { $loc: this.parseSourceLocation(node), dtype: null, cvalue: typeof cvalue === "number" ? cvalue : null, bigvalue: typeof cvalue === "bigint" ? cvalue : null }; this.deferDataType(node, constdef); return constdef; } visit_varref(node) { var name = node.attrs["name"]; name = this.name2js(name); var varref = { $loc: this.parseSourceLocation(node), dtype: null, refname: name }; this.deferDataType(node, varref); var mod = this.cur_module; return varref; } visit_sentree(node) { } visit_always(node) { var sentree; var expr; if (node.children.length == 2) { sentree = node.children[0].obj; expr = node.children[1].obj; } else { sentree = null; expr = node.children[0].obj; } var always = { $loc: this.parseSourceLocation(node), blocktype: node.type, name: null, senlist: sentree, exprs: [expr] }; this.cur_module.blocks.push(always); return always; } visit_begin(node) { var exprs = []; node.children.forEach((n) => exprs.push(n.obj)); return { $loc: this.parseSourceLocation(node), blocktype: node.type, name: node.attrs["name"], exprs }; } visit_initarray(node) { return this.visit_begin(node); } visit_inititem(node) { this.expectChildren(node, 1, 1); return { index: parseInt(node.attrs["index"]), expr: node.children[0].obj }; } visit_cfunc(node) { if (this.cur_module == null) { return; } var block = this.visit_begin(node); block.exprs = []; node.children.forEach((n) => block.exprs.push(n.obj)); this.cur_module.blocks.push(block); return block; } visit_cuse(node) { } visit_instance(node) { var instance = { $loc: this.parseSourceLocation(node), name: node.attrs["name"], origName: node.attrs["origName"], ports: [], module: null }; node.children.forEach((child) => { instance.ports.push(child.obj); }); this.cur_module.instances.push(instance); this.defer(() => { instance.module = this.resolveModule(node.attrs["defName"]); }); return instance; } visit_iface(node) { throw new CompileError2(this.cur_loc, `interfaces not supported`); } visit_intfref(node) { throw new CompileError2(this.cur_loc, `interfaces not supported`); } visit_port(node) { this.expectChildren(node, 1, 1); var varref = { $loc: this.parseSourceLocation(node), name: node.attrs["name"], expr: node.children[0].obj }; return varref; } visit_netlist(node) { } visit_files(node) { } visit_module_files(node) { node.children.forEach((n) => { if (n.obj) { var file = this.files[n.obj.id]; if (file) file.isModule = true; } }); } visit_file(node) { return this.visit_file_or_module(node, false); } visit_scope(node) { } visit_topscope(node) { } visit_file_or_module(node, isModule) { var file = { id: node.attrs["id"], filename: node.attrs["filename"], isModule }; this.files[file.id] = file; return file; } visit_cells(node) { this.expectChildren(node, 1, 9999); var hier = node.children[0].obj; if (hier != null) { var hiername = hier.name; this.hierarchies[hiername] = hier; } } visit_cell(node) { var hier = { $loc: this.parseSourceLocation(node), name: node.attrs["name"], module: null, parent: null, children: node.children.map((n) => n.obj) }; if (node.children.length > 0) throw new CompileError2(this.cur_loc, `multiple non-flattened modules not yet supported`); node.children.forEach((n) => n.obj.parent = hier); this.defer(() => { hier.module = this.resolveModule(node.attrs["submodname"]); }); return hier; } visit_basicdtype(node) { let id = node.attrs["id"]; var dtype; var dtypename = node.attrs["name"]; switch (dtypename) { case "logic": case "integer": case "bit": let dlogic = { $loc: this.parseSourceLocation(node), left: parseInt(node.attrs["left"] || "0"), right: parseInt(node.attrs["right"] || "0"), signed: node.attrs["signed"] == "true" }; dtype = dlogic; break; case "string": let dstring = { $loc: this.parseSourceLocation(node), jstype: "string" }; dtype = dstring; break; default: dtype = this.dtypes[dtypename]; if (dtype == null) { throw new CompileError2(this.cur_loc, `unknown data type ${dtypename}`); } } this.dtypes[id] = dtype; return dtype; } visit_refdtype(node) { } visit_enumdtype(node) { } visit_enumitem(node) { } visit_packarraydtype(node) { return this.visit_unpackarraydtype(node); } visit_memberdtype(node) { throw new CompileError2(null, `structs not supported`); } visit_constdtype(node) { } visit_paramtypedtype(node) { } visit_unpackarraydtype(node) { let id = node.attrs["id"]; let sub_dtype_id = node.attrs["sub_dtype_id"]; let range = node.children[0].obj; if (isConstExpr(range.left) && isConstExpr(range.right)) { var dtype = { $loc: this.parseSourceLocation(node), subtype: null, low: range.left, high: range.right }; this.dtypes[id] = dtype; this.defer(() => { dtype.subtype = this.dtypes[sub_dtype_id]; if (!dtype.subtype) throw new CompileError2(this.cur_loc, `Unknown data type ${sub_dtype_id} for array`); }); return dtype; } else { throw new CompileError2(this.cur_loc, `could not parse constant exprs in array`); } } visit_senitem(node) { var edgeType = node.attrs["edgeType"]; if (edgeType != "POS" && edgeType != "NEG") throw new CompileError2(this.cur_loc, "POS/NEG required"); return { $loc: this.parseSourceLocation(node), edgeType, expr: node.obj }; } visit_text(node) { } visit_cstmt(node) { } visit_cfile(node) { } visit_typetable(node) { } visit_constpool(node) { } visit_comment(node) { } expectChildren(node, low, high) { if (node.children.length < low || node.children.length > high) throw new CompileError2(this.cur_loc, `expected between ${low} and ${high} children`); } __visit_unop(node) { this.expectChildren(node, 1, 1); var expr = { $loc: this.parseSourceLocation(node), op: node.type, dtype: null, left: node.children[0].obj }; this.deferDataType(node, expr); return expr; } visit_extend(node) { var unop = this.__visit_unop(node); unop.width = parseInt(node.attrs["width"]); unop.widthminv = parseInt(node.attrs["widthminv"]); if (unop.width != 32) throw new CompileError2(this.cur_loc, `extends width ${unop.width} != 32`); return unop; } visit_extends(node) { return this.visit_extend(node); } __visit_binop(node) { this.expectChildren(node, 2, 2); var expr = { $loc: this.parseSourceLocation(node), op: node.type, dtype: null, left: node.children[0].obj, right: node.children[1].obj }; this.deferDataType(node, expr); return expr; } visit_if(node) { this.expectChildren(node, 2, 3); var expr = { $loc: this.parseSourceLocation(node), op: "if", dtype: null, cond: node.children[0].obj, left: node.children[1].obj, right: node.children[2] && node.children[2].obj }; return expr; } visit_while(node) { this.expectChildren(node, 2, 4); var expr = { $loc: this.parseSourceLocation(node), op: "while", dtype: null, precond: node.children[0].obj, loopcond: node.children[1].obj, body: node.children[2] && node.children[2].obj, inc: node.children[3] && node.children[3].obj }; return expr; } __visit_triop(node) { this.expectChildren(node, 3, 3); var expr = { $loc: this.parseSourceLocation(node), op: node.type, dtype: null, cond: node.children[0].obj, left: node.children[1].obj, right: node.children[2].obj }; this.deferDataType(node, expr); return expr; } __visit_func(node) { var expr = { $loc: this.parseSourceLocation(node), dtype: null, funcname: node.attrs["func"] || "$" + node.type, args: node.children.map((n) => n.obj) }; this.deferDataType(node, expr); return expr; } visit_not(node) { return this.__visit_unop(node); } visit_negate(node) { return this.__visit_unop(node); } visit_redand(node) { return this.__visit_unop(node); } visit_redor(node) { return this.__visit_unop(node); } visit_redxor(node) { return this.__visit_unop(node); } visit_initial(node) { return this.__visit_unop(node); } visit_ccast(node) { return this.__visit_unop(node); } visit_creset(node) { return this.__visit_unop(node); } visit_creturn(node) { return this.__visit_unop(node); } visit_contassign(node) { return this.__visit_binop(node); } visit_assigndly(node) { return this.__visit_binop(node); } visit_assignpre(node) { return this.__visit_binop(node); } visit_assignpost(node) { return this.__visit_binop(node); } visit_assign(node) { return this.__visit_binop(node); } visit_arraysel(node) { return this.__visit_binop(node); } visit_wordsel(node) { return this.__visit_binop(node); } visit_eq(node) { return this.__visit_binop(node); } visit_neq(node) { return this.__visit_binop(node); } visit_lte(node) { return this.__visit_binop(node); } visit_gte(node) { return this.__visit_binop(node); } visit_lt(node) { return this.__visit_binop(node); } visit_gt(node) { return this.__visit_binop(node); } visit_and(node) { return this.__visit_binop(node); } visit_or(node) { return this.__visit_binop(node); } visit_xor(node) { return this.__visit_binop(node); } visit_add(node) { return this.__visit_binop(node); } visit_sub(node) { return this.__visit_binop(node); } visit_concat(node) { return this.__visit_binop(node); } visit_shiftl(node) { return this.__visit_binop(node); } visit_shiftr(node) { return this.__visit_binop(node); } visit_shiftrs(node) { return this.__visit_binop(node); } visit_mul(node) { return this.__visit_binop(node); } visit_div(node) { return this.__visit_binop(node); } visit_moddiv(node) { return this.__visit_binop(node); } visit_muls(node) { return this.__visit_binop(node); } visit_divs(node) { return this.__visit_binop(node); } visit_moddivs(node) { return this.__visit_binop(node); } visit_gts(node) { return this.__visit_binop(node); } visit_lts(node) { return this.__visit_binop(node); } visit_gtes(node) { return this.__visit_binop(node); } visit_ltes(node) { return this.__visit_binop(node); } visit_range(node) { return this.__visit_binop(node); } visit_cond(node) { return this.__visit_triop(node); } visit_condbound(node) { return this.__visit_triop(node); } visit_sel(node) { return this.__visit_triop(node); } visit_changedet(node) { if (node.children.length == 0) return null; else return this.__visit_binop(node); } visit_ccall(node) { return this.__visit_func(node); } visit_finish(node) { return this.__visit_func(node); } visit_stop(node) { return this.__visit_func(node); } visit_rand(node) { return this.__visit_func(node); } visit_time(node) { return this.__visit_func(node); } visit_display(node) { return null; } visit_sformatf(node) { return null; } visit_scopename(node) { return null; } visit_readmem(node) { return this.__visit_func(node); } xml_open(node) { this.cur_node = node; var method = this[`open_${node.type}`]; if (method) { return method.bind(this)(node); } } xml_close(node) { this.cur_node = node; var method = this[`visit_${node.type}`]; if (method) { return method.bind(this)(node); } else { throw new CompileError2(this.cur_loc, `no visitor for ${node.type}`); } } parse(xmls) { parseXMLPoorly(xmls, this.xml_open.bind(this), this.xml_close.bind(this)); this.cur_node = null; this.run_deferred(); } }; // src/worker/tools/verilog.ts function detectModuleName(code) { 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) { var topmod = detectModuleName(code) || "top"; var m = /^\s*module\s+(\w+?_top)/m.exec(code); if (m && m[1]) topmod = m[1]; return topmod; } var jsasm_module_top; var jsasm_module_output; var jsasm_module_key; function compileJSASM(asmcode, platform, options, is_inline) { var asm = new Assembler(null); var includes = []; asm.loadJSON = (filename) => { 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) => { 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]; var voutput = compileVerilator({ platform, files: includes, path: main_filename, tool: "verilator" }); if (voutput) jsasm_module_output = voutput; return null; }; var result = asm.assembleFile(asmcode); if (loaded_module && jsasm_module_output) { if (jsasm_module_output.errors && jsasm_module_output.errors.length) return jsasm_module_output; var asmout = result.output; result.output = jsasm_module_output.output; result.output.program_rom = asmout; result.output.program_rom_variable = jsasm_module_top + "$program_rom"; result.listings = {}; result.listings[options.path] = { lines: result.lines }; return result; } else { return result; } } function compileJSASMStep(step) { gatherFiles(step); var code = getWorkFileAsString(step.path); var platform = step.platform || "verilog"; return compileJSASM(code, platform, step, false); } function compileInlineASM(code, 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 s2 = ""; var out = asmout.output; for (var i = 0; i < out.length; i++) { if (i > 0) { s2 += ","; if ((i & 255) == 0) s2 += "\n"; } s2 += 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 s2; } }); return code; } function compileVerilator(step) { loadNative("verilator_bin"); var platform = step.platform || "verilog"; var errors = []; gatherFiles(step); if (staleFiles(step, [xmlPath])) { var match_fn = makeErrorMatcher(errors, /%(.+?): (.+?):(\d+)?[:]?\s*(.+)/i, 3, 4, step.path, 2); var verilator_mod = emglobal.verilator_bin({ instantiateWasm: moduleInstFn("verilator_bin"), noInitialRun: true, noExitRuntime: true, print: print_fn, printErr: match_fn, wasmMemory: getWASMMemory() }); var code = getWorkFileAsString(step.path); var topmod = detectTopModuleName(code); var FS = verilator_mod.FS; var listings = {}; populateFiles(step, FS, { mainFilePath: step.path, processFn: (path, code2) => { if (typeof code2 === "string") { let asmlines = []; code2 = compileInlineASM(code2, platform, step, errors, asmlines); if (asmlines.length) { listings[path] = { lines: asmlines }; } } return code2; } }); 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", "--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"); 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 }; } starttime(); var xmlParser = new VerilogXMLParser(); try { var xmlContent = FS.readFile(xmlPath, { encoding: "utf8" }); var xmlScrubbed = xmlContent.replace(/ fl=".+?" loc=".+?"/g, ""); putWorkFile(xmlPath, xmlScrubbed); if (!anyTargetChanged(step, [xmlPath])) return; xmlParser.parse(xmlContent); } catch (e) { console.log(e, e.stack); if (e.$loc != null) { let $loc = e.$loc; errors.push({ msg: "" + e, path: $loc.path, line: $loc.line }); } else { errors.push({ line: 0, msg: "" + e }); } return { errors, listings }; } finally { endtime("parse"); } return { output: xmlParser, errors, listings }; } } function compileYosys(step) { 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 = 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 }; } endtime("compile"); if (errors.length) return { errors }; try { var json_file = FS.readFile(topmod + ".json", { encoding: "utf8" }); var json = JSON.parse(json_file); console.log(json); return { output: json, errors }; } catch (e) { console.log(e); return { errors }; } } function compileSilice(step) { loadNative("silice"); var params = step.params; gatherFiles(step, { mainFilePath: "main.ice" }); var destpath = step.prefix + ".v"; var errors = []; var errfile; var errline; if (staleFiles(step, [destpath])) { var match_fn = (s) => { s = s.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 = 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 }; var vout = FS.readFile(destpath, { encoding: "utf8" }); putWorkFile(destpath, vout); } return { nexttool: "verilator", path: destpath, args: [destpath], files: [destpath] }; } // src/worker/tools/m6809.ts function assembleXASM6809(step) { 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, msg: lasterror }); lasterror = null; } else if (s.startsWith("***** ")) { lasterror = s.slice(6); } } var Module = emglobal.xasm6809({ noInitialRun: true, print: match_fn, printErr: print_fn }); var FS = Module.FS; populateFiles(step, FS, { mainFilePath: "main.asm" }); var binpath = step.prefix + ".bin"; var lstpath = step.prefix + ".lst"; execMain(step, Module, ["-c", "-l", "-s", "-y", "-o=" + binpath, step.path]); if (errors.length) return { errors }; var aout = FS.readFile(binpath, { encoding: "binary" }); if (aout.length == 0) { errors.push({ line: 0, msg: "Empty output file" }); return { errors }; } putWorkFile(binpath, aout); putWorkFile(lstpath, alst); var symbolmap = {}; var asmlines = parseListing(alst, /^\s*([0-9]+) .+ ([0-9A-F]+)\s+\[([0-9 ]+)\]\s+([0-9A-F]+) (.*)/i, 1, 2, 4, 3); var listings = {}; listings[step.prefix + ".lst"] = { lines: asmlines, text: alst }; return { output: aout, listings, errors, symbolmap }; } function compileCMOC(step) { loadNative("cmoc"); var params = step.params; var re_err1 = /^[/]*([^:]*):(\d+): (.+)$/; var errors = []; 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 = emglobal.cmoc({ instantiateWasm: moduleInstFn("cmoc"), noInitialRun: true, print: match_fn, printErr: match_fn }); var code = getWorkFileAsString(step.path); var preproc = preprocessMCPP(step, null); if (preproc.errors) { return { errors: preproc.errors }; } else code = preproc.code; var FS = CMOC.FS; 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 }; var asmout = FS.readFile(destpath, { encoding: "utf8" }); if (step.params.set_stack_end) asmout = asmout.replace("stack space in bytes", ` lds #${step.params.set_stack_end} `); putWorkFile(destpath, asmout); } return { nexttool: "lwasm", path: destpath, args: [destpath], files: [destpath] }; } function assembleLWASM(step) { loadNative("lwasm"); var errors = []; gatherFiles(step, { mainFilePath: "main.s" }); var objpath = step.prefix + ".o"; var lstpath = step.prefix + ".lst"; const isRaw = step.path.endsWith(".asm"); if (staleFiles(step, [objpath, lstpath])) { var objout, lstout; var args = ["-9", "-I/share/asminc", "-o" + objpath, "-l" + lstpath, step.path]; args.push(isRaw ? "-r" : "--obj"); var LWASM = emglobal.lwasm({ instantiateWasm: moduleInstFn("lwasm"), noInitialRun: true, print: print_fn, printErr: msvcErrorMatcher(errors) }); var FS = LWASM.FS; populateFiles(step, FS); fixParamsWithDefines(step.path, step.params); execMain(step, LWASM, args); if (errors.length) return { errors }; objout = FS.readFile(objpath, { encoding: "binary" }); lstout = FS.readFile(lstpath, { encoding: "utf8" }); putWorkFile(objpath, objout); putWorkFile(lstpath, lstout); if (isRaw) { return { output: objout }; } } return { linktool: "lwlink", files: [objpath, lstpath], args: [objpath] }; } function linkLWLINK(step) { loadNative("lwlink"); var params = step.params; gatherFiles(step); var binpath = "main"; if (staleFiles(step, [binpath])) { var errors = []; var LWLINK = emglobal.lwlink({ instantiateWasm: moduleInstFn("lwlink"), noInitialRun: true, print: print_fn, printErr: function(s2) { if (s2.startsWith("Warning:")) console.log(s2); else errors.push({ msg: s2, line: 0 }); } }); var FS = LWLINK.FS; 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 }; var aout = FS.readFile("main", { encoding: "binary" }); var mapout = FS.readFile("main.map", { encoding: "utf8" }); putWorkFile("main", aout); putWorkFile("main.map", mapout); if (!anyTargetChanged(step, ["main", "main.map"])) return; var symbolmap = {}; var segments = []; for (var s of mapout.split("\n")) { var toks = s.split(" "); if (toks[0] == "Symbol:") { let ident = toks[1]; let ofs = parseInt(toks[4], 16); if (ident && ofs >= 0 && !ident.startsWith("l_") && !ident.startsWith("funcsize_") && !ident.startsWith("funcend_")) { 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 }); } } const re_segment = /\s*SECTION\s+(\w+)/i; const re_function = /\s*([0-9a-f]+).+?(\w+)\s+EQU\s+[*]/i; var listings = {}; for (var fn of step.files) { if (fn.endsWith(".lst")) { 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, re_function, re_segment); for (let l of asmlines) { l.offset += symbolmap[l.func] || 0; } var srclines = parseSourceLines(lstout, /Line .+?:(\d+)/i, /^([0-9A-F]{4})/i, re_function, re_segment); for (let l of srclines) { l.offset += symbolmap[l.func] || 0; } putWorkFile(fn, lstout); lstout = lstout.split("\n").map((l) => l.substring(0, 15) + l.substring(56)).join("\n"); listings[fn] = { asmlines: srclines.length ? asmlines : null, lines: srclines.length ? srclines : asmlines, text: lstout }; } } return { output: aout, listings, errors, symbolmap, segments }; } } // src/worker/tools/m6502.ts function assembleNESASM(step) { 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 = []; var state = 0; var lineno = 0; var filename; function match_fn(s2) { var m2; switch (state) { case 0: m2 = re_filename.exec(s2); if (m2) { filename = m2[2]; } m2 = re_insn.exec(s2); if (m2) { lineno = parseInt(m2[1]); state = 1; } break; case 1: m2 = re_error.exec(s2); if (m2) { errors.push({ path: filename, line: lineno, msg: m2[1] }); state = 0; } break; } } var Module = 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"]); var listings = {}; try { var alst = FS.readFile(lstpath, { "encoding": "utf8" }); 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 }; } 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 }; } putWorkFile(binpath, aout); putWorkFile(sympath, asym); if (alst) putWorkFile(lstpath, alst); if (!anyTargetChanged(step, [binpath, sympath])) return; 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, errors, symbolmap }; } function assembleMerlin32(step) { 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 = emglobal.merlin32({ instantiateWasm: moduleInstFn("merlin32"), noInitialRun: true, print: (s) => { 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 }; 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 = {}; lstfiles.forEach((lstfn) => { var lst = FS.readFile(lstfn, { encoding: "utf8" }); 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 }; var lst2 = listings[path]; if (!lst2) listings[path] = lst2 = { lines: [] }; lst2.lines.push(lstline); } } }); }); return { output: objout, listings, errors, symbolmap, segments }; } } function compileFastBasic(step) { 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 = 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_link_files = [libfile, params.cfgfile]; var args = [step.path, destpath]; execMain(step, fastbasic, args); if (errors.length) return { errors }; var asmout = FS.readFile(destpath, { encoding: "utf8" }); putWorkFile(destpath, asmout); } return { nexttool: "ca65", path: destpath, args: [destpath], files: [destpath] }; } // src/worker/tools/z80.ts function assembleZMAC(step) { 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])) { var ZMAC = emglobal.zmac({ instantiateWasm: moduleInstFn("zmac"), noInitialRun: true, print: print_fn, printErr: makeErrorMatcher(errors, /([^( ]+)\s*[(](\d+)[)]\s*:\s*(.+)/, 2, 3, step.path) }); var FS = ZMAC.FS; populateFiles(step, FS); execMain(step, ZMAC, ["-z", "-c", "--oo", "lst,cim", step.path]); if (errors.length) { return { 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; var lines = parseListing(lstout, /\s*(\d+):\s*([0-9a-f]+)\s+([0-9a-f]+)\s+(.+)/i, 1, 2, 3); var listings = {}; listings[lstpath] = { lines }; 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, errors, symbolmap }; } } // src/worker/tools/x86.ts function compileSmallerC(step) { loadNative("smlrc"); var params = step.params; var re_err1 = /^Error in "[/]*(.+)" [(](\d+):(\d+)[)]/; var errors = []; 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", "-no-externs", step.path, destpath ]; var smlrc = emglobal.smlrc({ instantiateWasm: moduleInstFn("smlrc"), noInitialRun: true, print: match_fn, printErr: match_fn }); var code = getWorkFileAsString(step.path); var preproc = preprocessMCPP(step, null); if (preproc.errors) { return { errors: preproc.errors }; } else code = preproc.code; var FS = smlrc.FS; 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 }; var asmout = FS.readFile(destpath, { encoding: "utf8" }); putWorkFile(destpath, asmout); } return { nexttool: "yasm", path: destpath, args: [destpath], files: [destpath] }; } function assembleYASM(step) { 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", "-o", objpath, "-l", lstpath, "--mapfile=" + mappath, step.path ]; var YASM = emglobal.yasm({ instantiateWasm: moduleInstFn("yasm"), noInitialRun: true, print: print_fn, printErr: msvcErrorMatcher(errors) }); var FS = YASM.FS; populateFiles(step, FS); execMain(step, YASM, args); if (errors.length) return { 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); 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 = {}; listings[lstpath] = { lines, text: lstout }; return { output: objout, listings, errors, symbolmap, segments }; } } // src/worker/tools/arm.ts function assembleARMIPS(step) { loadNative("armips"); var errors = []; gatherFiles(step, { mainFilePath: "main.asm" }); var objpath = "main.bin"; var lstpath = step.prefix + ".lst"; var sympath = step.prefix + ".sym"; 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 = 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 }; var objout = FS.readFile(objpath, { encoding: "binary" }); putWorkFile(objpath, objout); if (!anyTargetChanged(step, [objpath])) return; var symbolmap = {}; var segments = []; var listings = {}; var lstout = FS.readFile(lstpath, { encoding: "utf8" }); var lines = lstout.split(re_crlf); 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"; var lst = listings[path2]; if (lst == null) { lst = listings[path2] = { lines: [] }; } var ofs = parseInt(m[1], 16); if (lastofs == ofs) { lst.lines.pop(); } 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, line: parseInt(m[4]), offset: ofs }); lastofs = ofs; } } var symout = FS.readFile(sympath, { encoding: "utf8" }); 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, listings, errors, symbolmap, segments }; } } function assembleVASMARM(step) { loadNative("vasmarm_std"); 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 = []; var undefsyms = []; function findUndefinedSymbols(line2) { undefsyms.forEach((sym) => { if (line2.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 m2 = re_undefsym.exec(matches[3]); if (m2) { undefsyms.push(m2[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 = 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 }; } 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" }); var symbolmap = {}; var segments = []; var 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 = {}; var lines = lstout.split(re_crlf); var lstlines = []; 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 { } 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 { } } listings[lstpath] = { lines: lstlines, text: lstout }; if (undefsyms.length && errors.length == 0) { errors.push({ line: 0, msg: "Undefined symbols: " + undefsyms.join(", ") }); } return { output: objout, listings, errors, symbolmap, segments }; } } // src/common/tokenizer.ts var CompileError3 = class extends Error { constructor(msg, loc) { super(msg); Object.setPrototypeOf(this, CompileError3.prototype); this.$loc = loc; } }; function mergeLocs2(a, b) { return { line: Math.min(a.line, b.line), start: Math.min(a.start, b.start), end: Math.max(a.end, b.end), label: a.label || b.label, path: a.path || b.path }; } var TokenType2; (function(TokenType3) { TokenType3["EOF"] = "eof"; TokenType3["EOL"] = "eol"; TokenType3["Ident"] = "ident"; TokenType3["Comment"] = "comment"; TokenType3["Ignore"] = "ignore"; TokenType3["CatchAll"] = "catch-all"; })(TokenType2 || (TokenType2 = {})); var CATCH_ALL_RULES = [ { type: TokenType2.CatchAll, regex: /.+?/ } ]; function re_escape(rule) { return `(${rule.regex.source})`; } var TokenizerRuleSet = class { constructor(rules) { this.rules = rules.concat(CATCH_ALL_RULES); var pattern = this.rules.map(re_escape).join("|"); this.regex = new RegExp(pattern, "gs"); } }; var Tokenizer = class { constructor() { this.errorOnCatchAll = false; this.deferred = []; this.errors = []; this.lineno = 0; this.lineindex = []; this.tokens = []; } setTokenRuleSet(ruleset) { this.ruleset = ruleset; } setTokenRules(rules) { this.setTokenRuleSet(new TokenizerRuleSet(rules)); } tokenizeFile(contents, path) { this.path = path; let m; let re = /\n|\r\n?/g; this.lineindex.push(0); while (m = re.exec(contents)) { this.lineindex.push(m.index); } this._tokenize(contents); this.eof = { type: TokenType2.EOF, str: "", eol: true, $loc: { path: this.path, line: this.lineno } }; this.pushToken(this.eof); } _tokenize(text) { let m; this.lineno = 0; while (m = this.ruleset.regex.exec(text)) { let found = false; while (m.index >= this.lineindex[this.lineno]) { this.lineno++; } let rules = this.ruleset.rules; for (let i = 0; i < rules.length; i++) { let s = m[i + 1]; if (s != null) { found = true; let col = m.index - (this.lineindex[this.lineno - 1] || -1) - 1; let loc = { path: this.path, line: this.lineno, start: col, end: col + s.length }; let rule = rules[i]; switch (rule.type) { case TokenType2.CatchAll: if (this.errorOnCatchAll) { this.compileError(`I didn't expect the character "${m[0]}" here.`, loc); } default: this.pushToken({ str: s, type: rule.type, $loc: loc, eol: false }); break; case TokenType2.EOL: if (this.tokens.length) this.tokens[this.tokens.length - 1].eol = true; case TokenType2.Comment: case TokenType2.Ignore: break; } break; } } if (!found) { this.compileError(`Could not parse token: <<${m[0]}>>`); } } } pushToken(token) { this.tokens.push(token); } addError(msg, loc) { let tok = this.lasttoken || this.peekToken(); if (!loc) loc = tok.$loc; this.errors.push({ path: loc.path, line: loc.line, label: this.curlabel, start: loc.start, end: loc.end, msg }); } internalError() { return this.compileError("Internal error."); } notImplementedError() { return this.compileError("Not yet implemented."); } compileError(msg, loc, loc2) { this.addError(msg, loc); let e = new CompileError3(msg, loc); throw e; return e; } peekToken(lookahead) { let tok = this.tokens[lookahead || 0]; return tok ? tok : this.eof; } consumeToken() { let tok = this.lasttoken = this.tokens.shift() || this.eof; return tok; } ifToken(match) { if (this.peekToken().str == match) return this.consumeToken(); } expectToken(str, msg) { let tok = this.consumeToken(); let tokstr = tok.str; if (str != tokstr) { this.compileError(msg || `There should be a "${str}" here.`); } return tok; } expectTokens(strlist, msg) { let tok = this.consumeToken(); let tokstr = tok.str; if (!strlist.includes(tokstr)) { this.compileError(msg || `These keywords are valid here: ${strlist.join(", ")}`); } return tok; } parseModifiers(modifiers) { let result = {}; do { var tok = this.peekToken(); if (modifiers.indexOf(tok.str) < 0) return result; this.consumeToken(); result[tok.str] = true; } while (tok != null); } expectIdent(msg) { let tok = this.consumeToken(); if (tok.type != TokenType2.Ident) this.compileError(msg || `There should be an identifier here.`); return tok; } pushbackToken(tok) { this.tokens.unshift(tok); } isEOF() { return this.tokens.length == 0 || this.peekToken().type == "eof"; } expectEOL(msg) { let tok = this.consumeToken(); if (tok.type != TokenType2.EOL) this.compileError(msg || `There's too much stuff on this line.`); } skipBlankLines() { this.skipTokenTypes(["eol"]); } skipTokenTypes(types) { while (types.includes(this.peekToken().type)) this.consumeToken(); } expectTokenTypes(types, msg) { let tok = this.consumeToken(); if (!types.includes(tok.type)) this.compileError(msg || `There should be a ${types.map((s) => `"${s}"`).join(" or ")} here. not a "${tok.type}".`); return tok; } parseList(parseFunc, delim) { var sep; var list = []; do { var el = parseFunc.bind(this)(); if (el != null) list.push(el); sep = this.consumeToken(); } while (sep.str == delim); this.pushbackToken(sep); return list; } runDeferred() { while (this.deferred.length) { this.deferred.shift()(); } } }; // src/common/ecs/binpack.ts var debug = false; var BoxPlacement; (function(BoxPlacement2) { BoxPlacement2[BoxPlacement2["TopLeft"] = 0] = "TopLeft"; BoxPlacement2[BoxPlacement2["TopRight"] = 1] = "TopRight"; BoxPlacement2[BoxPlacement2["BottomLeft"] = 2] = "BottomLeft"; BoxPlacement2[BoxPlacement2["BottomRight"] = 3] = "BottomRight"; })(BoxPlacement || (BoxPlacement = {})); function boxesIntersect(a, b) { return !(b.left >= a.right || b.right <= a.left || b.top >= a.bottom || b.bottom <= a.top); } function boxesContain(a, b) { return b.left >= a.left && b.top >= a.top && b.right <= a.right && b.bottom <= a.bottom; } var Bin = class { constructor(binbounds) { this.binbounds = binbounds; this.boxes = []; this.free = []; this.extents = { left: 0, top: 0, right: 0, bottom: 0 }; this.free.push(binbounds); } getBoxes(bounds, limit, boxes) { let result = []; if (!boxes) boxes = this.boxes; for (let box of boxes) { if (boxesIntersect(bounds, box)) { result.push(box); if (result.length >= limit) break; } } return result; } fits(b) { if (!boxesContain(this.binbounds, b)) { if (debug) console.log("out of bounds!", b.left, b.top, b.right, b.bottom); return false; } if (this.getBoxes(b, 1).length > 0) { if (debug) console.log("intersect!", b.left, b.top, b.right, b.bottom); return false; } return true; } bestFit(b) { let bestscore = 0; let best = null; for (let f of this.free) { if (b.left != null && b.left < f.left) continue; if (b.left != null && b.left + b.width > f.right) continue; if (b.top != null && b.top < f.top) continue; if (b.top != null && b.top + b.height > f.bottom) continue; let dx = f.right - f.left - b.width; let dy = f.bottom - f.top - b.height; if (dx >= 0 && dy >= 0) { let score = 1 / (1 + dx + dy + f.left * 1e-3); if (score > bestscore) { best = f; bestscore = score; if (score == 1) break; } } } return best; } anyFit(b) { let bestscore = 0; let best = null; for (let f of this.free) { let box = { left: b.left != null ? b.left : f.left, right: f.left + b.width, top: b.top != null ? b.top : f.top, bottom: f.top + b.height }; if (this.fits(box)) { let score = 1 / (1 + box.left + box.top); if (score > bestscore) { best = f; if (score == 1) break; } } } return best; } add(b) { if (debug) console.log("add", b.left, b.top, b.right, b.bottom); if (!this.fits(b)) { throw new Error(`bad fit ${b.left} ${b.top} ${b.right} ${b.bottom}`); } this.boxes.push(b); this.extents.right = Math.max(this.extents.right, b.right); this.extents.bottom = Math.max(this.extents.bottom, b.bottom); for (let p of b.parents) { let i = this.free.indexOf(p); if (i < 0) throw new Error("cannot find parent"); if (debug) console.log("removed", p.left, p.top, p.right, p.bottom); this.free.splice(i, 1); this.addFree(p.left, p.top, b.left, p.bottom); this.addFree(b.right, p.top, p.right, p.bottom); this.addFree(b.left, p.top, b.right, b.top); this.addFree(b.left, b.bottom, b.right, p.bottom); } } addFree(left, top, right, bottom) { if (bottom > top && right > left) { let b = { left, top, right, bottom }; if (debug) console.log("free", b.left, b.top, b.right, b.bottom); this.free.push(b); } } }; var Packer = class { constructor() { this.bins = []; this.boxes = []; this.defaultPlacement = 0; } pack() { for (let bc of this.boxes) { let box = this.bestPlacement(bc); if (!box) return false; box.bin.add(box); bc.box = box; } return true; } bestPlacement(b) { for (let bin of this.bins) { let parent = bin.bestFit(b); let approx = false; if (!parent) { parent = bin.anyFit(b); approx = true; if (debug) console.log("anyfit", parent == null ? void 0 : parent.left, parent == null ? void 0 : parent.top); } if (parent) { let place = this.defaultPlacement; let box = { left: parent.left, top: parent.top, right: parent.left + b.width, bottom: parent.top + b.height }; if (b.left != null) { box.left = b.left; box.right = b.left + b.width; } if (b.top != null) { box.top = b.top; box.bottom = b.top + b.height; } if (place == 2 || place == 3) { let h = box.bottom - box.top; box.top = parent.bottom - h; box.bottom = parent.bottom; } if (place == 1 || place == 3) { let w = box.right - box.left; box.left = parent.right - w; box.right = parent.right; } if (debug) console.log("place", b.label, box.left, box.top, box.right, box.bottom, parent == null ? void 0 : parent.left, parent == null ? void 0 : parent.top); let parents = [parent]; if (approx) parents = bin.getBoxes(box, 100, bin.free); return __spreadValues({ parents, place, bin }, box); } } if (debug) console.log("cannot place!", b.left, b.top, b.width, b.height); return null; } toSVG() { let s = ""; let r = { width: 100, height: 70 }; for (let bin of this.bins) { r.width = Math.max(r.width, bin.binbounds.right); r.height = Math.max(r.height, bin.binbounds.bottom); } s += ``; for (let bin of this.bins) { let be = bin.extents; s += ""; s += ``; let textx = be.right + 1; let texty = 0; for (let box of this.boxes) { let b = box.box; if (b) { if (b.bin == bin) s += ``; if (b.top == texty) textx += 10; else textx = be.right + 1; texty = b.top; if (box.label) s += `${box.label}`; } } s += ""; } s += ``; return s; } toSVGUrl() { return `data:image/svg+xml;base64,${btoa(this.toSVG())}`; } }; // src/common/ecs/ecs.ts var ECSError = class extends Error { constructor(msg, obj) { super(msg); this.$sources = []; Object.setPrototypeOf(this, ECSError.prototype); if (obj) this.$loc = obj.$loc || obj; } }; function mksymbol(c, fieldName) { return c.name + "_" + fieldName; } function mkscopesymbol(s, c, fieldName) { return s.name + "_" + c.name + "_" + fieldName; } var SystemStats = class { }; var SELECT_TYPE = ["once", "foreach", "join", "with", "if", "select", "unroll"]; function isLiteral2(arg) { return arg.value != null; } function isLiteralInt(arg) { return isLiteral2(arg) && arg.valtype.dtype == "int"; } function isBinOp2(arg) { return arg.op != null && arg.left != null && arg.right != null; } function isUnOp2(arg) { return arg.op != null && arg.expr != null; } function isBlockStmt(arg) { return arg.stmts != null; } function isInlineCode(arg) { return arg.code != null; } function isQueryExpr(arg) { return arg.query != null; } var Dialect_CA65 = class { constructor() { this.ASM_ITERATE_EACH_ASC = ` ldx #0 @__each: {{%code}} inx cpx #{{%ecount}} jne @__each @__exit: `; this.ASM_ITERATE_EACH_DESC = ` ldx #{{%ecount}}-1 @__each: {{%code}} dex jpl @__each @__exit: `; this.ASM_ITERATE_JOIN_ASC = ` ldy #0 @__each: ldx {{%joinfield}},y {{%code}} iny cpy #{{%ecount}} jne @__each @__exit: `; this.ASM_ITERATE_JOIN_DESC = ` ldy #{{%ecount}}-1 @__each: ldx {{%joinfield}},y {{%code}} dey jpl @__each @__exit: `; this.ASM_FILTER_RANGE_LO_X = ` cpx #{{%xofs}} jcc @__skipxlo {{%code}} @__skipxlo: `; this.ASM_FILTER_RANGE_HI_X = ` cpx #{{%xofs}}+{{%ecount}} jcs @__skipxhi {{%code}} @__skipxhi: `; this.ASM_LOOKUP_REF_X = ` ldx {{%reffield}} {{%code}} `; this.INIT_FROM_ARRAY = ` ldy #{{%nbytes}} : lda {{%src}}-1,y sta {{%dest}}-1,y dey bne :- `; } comment(s) { return ` ;;; ${s} `; } absolute(ident, offset) { return this.addOffset(ident, offset || 0); } addOffset(ident, offset) { if (offset > 0) return `${ident}+${offset}`; if (offset < 0) return `${ident}-${-offset}`; return ident; } indexed_x(ident, offset) { return this.addOffset(ident, offset) + ",x"; } indexed_y(ident, offset) { return this.addOffset(ident, offset) + ",y"; } fieldsymbol(component, field, bitofs) { return `${component.name}_${field.name}_b${bitofs}`; } datasymbol(component, field, eid, bitofs) { return `${component.name}_${field.name}_e${eid}_b${bitofs}`; } debug_file(path) { return `.dbg file, "${path}", 0, 0`; } debug_line(path, line) { return `.dbg line, "${path}", ${line}`; } startScope(name) { return `.scope ${name}`; } endScope(name) { return `.endscope ${this.scopeSymbol(name)} = ${name}::__Start`; } scopeSymbol(name) { return `${name}__Start`; } align(value) { return `.align ${value}`; } alignSegmentStart() { return this.label("__ALIGNORIGIN"); } warningIfPageCrossed(startlabel) { return ` .assert >(${startlabel}) = >(*), error, "${startlabel} crosses a page boundary!"`; } warningIfMoreThan(bytes, startlabel) { return ` .assert (* - ${startlabel}) <= ${bytes}, error, .sprintf("${startlabel} does not fit in ${bytes} bytes, it took %d!", (* - ${startlabel}))`; } alignIfLessThan(bytes) { return ` .if <(* - __ALIGNORIGIN) > 256-${bytes} .align $100 .endif`; } segment(segtype) { if (segtype == "bss") { return `.zeropage`; } else if (segtype == "rodata") { return ".rodata"; } else { return `.code`; } } label(sym) { return `${sym}:`; } byte(b) { if (b === void 0) { return `.res 1`; } else if (typeof b === "number") { if (b < 0 || b > 255) throw new ECSError(`out of range byte ${b}`); return `.byte ${b}`; } else { if (b.bitofs == 0) return `.byte <${b.symbol}`; else if (b.bitofs == 8) return `.byte >${b.symbol}`; else return `.byte ((${b.symbol} >> ${b.bitofs})&255)`; } } tempLabel(inst) { return `${inst.system.name}__${inst.id}__tmp`; } equate(symbol, value) { return `${symbol} = ${value}`; } define(symbol, value) { if (value) return `.define ${symbol} ${value}`; else return `.define ${symbol}`; } call(symbol) { return ` jsr ${symbol}`; } jump(symbol) { return ` jmp ${symbol}`; } return() { return " rts"; } }; var SourceFileExport = class { constructor() { this.lines = []; } line(s) { this.text(s); } text(s) { for (let l of s.split("\n")) this.lines.push(l); } toString() { return this.lines.join("\n"); } }; var CodeSegment = class { constructor() { this.codefrags = []; } addCodeFragment(code) { this.codefrags.push(code); } dump(file) { for (let code of this.codefrags) { file.text(code); } } }; var DataSegment = class { constructor() { this.symbols = {}; this.equates = {}; this.ofs2sym = new Map(); this.fieldranges = {}; this.size = 0; this.initdata = []; } allocateBytes(name, bytes) { let ofs = this.symbols[name]; if (ofs == null) { ofs = this.size; this.declareSymbol(name, ofs); this.size += bytes; } return ofs; } declareSymbol(name, ofs) { var _a; this.symbols[name] = ofs; if (!this.ofs2sym.has(ofs)) this.ofs2sym.set(ofs, []); (_a = this.ofs2sym.get(ofs)) == null ? void 0 : _a.push(name); } findExistingInitData(bytes) { for (let i = 0; i < this.size - bytes.length; i++) { for (var j = 0; j < bytes.length; j++) { if (this.initdata[i + j] !== bytes[j]) break; } if (j == bytes.length) return i; } return -1; } allocateInitData(name, bytes) { let ofs = this.findExistingInitData(bytes); if (ofs >= 0) { this.declareSymbol(name, ofs); } else { ofs = this.allocateBytes(name, bytes.length); for (let i = 0; i < bytes.length; i++) { this.initdata[ofs + i] = bytes[i]; } } } dump(file, dialect) { for (let i = 0; i < this.size; i++) { let syms = this.ofs2sym.get(i); if (syms) { for (let sym of syms) file.line(dialect.label(sym)); } file.line(dialect.byte(this.initdata[i])); } for (let [symbol, value] of Object.entries(this.equates)) { file.line(dialect.equate(symbol, value)); } } getFieldRange(component, fieldName) { return this.fieldranges[mksymbol(component, fieldName)]; } getByteOffset(range, access, entityID) { if (entityID < range.elo) throw new ECSError(`entity ID ${entityID} too low for ${access.symbol}`); if (entityID > range.ehi) throw new ECSError(`entity ID ${entityID} too high for ${access.symbol}`); let ofs = this.symbols[access.symbol]; if (ofs !== void 0) { return ofs + entityID - range.elo; } throw new ECSError(`cannot find field access for ${access.symbol}`); } getOriginSymbol() { let a = this.ofs2sym.get(0); if (!a) throw new ECSError("getOriginSymbol(): no symbol at offset 0"); return a[0]; } }; var UninitDataSegment = class extends DataSegment { }; var ConstDataSegment = class extends DataSegment { }; function getFieldBits(f) { let n = f.hi - f.lo + 1; return Math.ceil(Math.log2(n)); } function getFieldLength(f) { if (f.dtype == "int") { return f.hi - f.lo + 1; } else { return 1; } } function getPackedFieldSize(f, constValue) { if (f.dtype == "int") { return getFieldBits(f); } if (f.dtype == "array" && f.index) { return 0; } if (f.dtype == "array" && constValue != null && Array.isArray(constValue)) { return constValue.length * getPackedFieldSize(f.elem); } if (f.dtype == "ref") { return 8; } return 0; } var EntitySet = class { constructor(scope, query, e) { this.scope = scope; if (query) { if (query.entities) { this.entities = query.entities.slice(0); } else { this.atypes = scope.em.archetypesMatching(query); this.entities = scope.entitiesMatching(this.atypes); } if (query.limit) { this.entities = this.entities.slice(0, query.limit); } } else if (e) { this.entities = e; } else { throw new ECSError("invalid EntitySet constructor"); } if (!this.atypes) { let at = new Set(); for (let e2 of this.entities) at.add(e2.etype); this.atypes = Array.from(at.values()); } } contains(c, f, where) { return this.scope.em.singleComponentWithFieldName(this.atypes, f.name, where); } intersection(qr) { let ents = this.entities.filter((e) => qr.entities.includes(e)); return new EntitySet(this.scope, void 0, ents); } union(qr) { let ents = this.entities.concat(qr.entities); let atypes = this.atypes.concat(qr.atypes); return new EntitySet(this.scope, void 0, ents); } isContiguous() { if (this.entities.length == 0) return true; let id = this.entities[0].id; for (let i = 1; i < this.entities.length; i++) { if (this.entities[i].id != ++id) return false; } return true; } }; var IndexRegister = class { constructor(scope, eset) { this.scope = scope; this.elo = 0; this.ehi = scope.entities.length - 1; this.lo = null; this.hi = null; if (eset) { this.narrowInPlace(eset); } } entityCount() { return this.ehi - this.elo + 1; } clone() { return Object.assign(new IndexRegister(this.scope), this); } narrow(eset, action) { let i = this.clone(); return i.narrowInPlace(eset, action) ? i : null; } narrowInPlace(eset, action) { if (this.scope != eset.scope) throw new ECSError(`scope mismatch`, action); if (!eset.isContiguous()) throw new ECSError(`entities are not contiguous`, action); if (this.eset) { this.eset = this.eset.intersection(eset); } else { this.eset = eset; } if (this.eset.entities.length == 0) { return false; } let newelo = this.eset.entities[0].id; let newehi = this.eset.entities[this.eset.entities.length - 1].id; if (this.lo === null || this.hi === null) { this.lo = 0; this.hi = newehi - newelo; this.elo = newelo; this.ehi = newehi; } else { this.lo += newelo - this.elo; this.hi += newehi - this.ehi; } return true; } offset() { return this.lo || 0; } }; var ActionCPUState = class { constructor() { this.xreg = null; this.yreg = null; } }; var ActionEval = class { constructor(scope, instance, action, eventargs) { this.scope = scope; this.instance = instance; this.action = action; this.eventargs = eventargs; this.tmplabel = ""; this.em = scope.em; this.dialect = scope.em.dialect; this.tmplabel = this.dialect.tempLabel(this.instance); this.seq = this.em.seq++; this.label = `${this.instance.system.name}__${action.event}__${this.seq}`; } begin() { } end() { } codeToString() { let code = this.exprToCode(this.action.expr); return code; } replaceTags(code, action, props) { const tag_re = /\{\{(.+?)\}\}/g; code = code.replace(tag_re, (entire, group) => { let toks = group.split(/\s+/); if (toks.length == 0) throw new ECSError(`empty command`, action); let cmd = group.charAt(0); let arg0 = toks[0].substring(1).trim(); let args = [arg0].concat(toks.slice(1)); switch (cmd) { case "!": return this.__emit(args); case "$": return this.__local(args); case "^": return this.__use(args); case "#": return this.__arg(args); case "&": return this.__eid(args); case "<": return this.__get([arg0, "0"]); case ">": return this.__get([arg0, "8"]); default: let value = props[toks[0]]; if (value) return value; let fn = this["__" + toks[0]]; if (fn) return fn.bind(this)(toks.slice(1)); throw new ECSError(`unrecognized command {{${toks[0]}}}`, action); } }); return code; } replaceLabels(code) { const label_re = /@(\w+)\b/g; let seq = this.em.seq++; let label = `${this.instance.system.name}__${this.action.event}__${seq}`; code = code.replace(label_re, (s, a) => `${label}__${a}`); return code; } __get(args) { return this.getset(args, false); } __set(args) { return this.getset(args, true); } getset(args, canwrite) { let fieldName = args[0]; let bitofs = parseInt(args[1] || "0"); return this.generateCodeForField(fieldName, bitofs, canwrite); } parseFieldArgs(args) { let fieldName = args[0]; let bitofs = parseInt(args[1] || "0"); let component = this.em.singleComponentWithFieldName(this.scope.state.working.atypes, fieldName, this.action); let field = component.fields.find((f) => f.name == fieldName); if (field == null) throw new ECSError(`no field named "${fieldName}" in component`, this.action); return { component, field, bitofs }; } __base(args) { let { component, field, bitofs } = this.parseFieldArgs(args); return this.dialect.fieldsymbol(component, field, bitofs); } __data(args) { let { component, field, bitofs } = this.parseFieldArgs(args); let entities = this.scope.state.working.entities; if (entities.length != 1) throw new ECSError(`data operates on exactly one entity`, this.action); let eid = entities[0].id; return this.dialect.datasymbol(component, field, eid, bitofs); } __const(args) { let { component, field, bitofs } = this.parseFieldArgs(args); let entities = this.scope.state.working.entities; if (entities.length != 1) throw new ECSError(`const operates on exactly one entity`, this.action); let constVal = entities[0].consts[mksymbol(component, field.name)]; if (constVal === void 0) throw new ECSError(`field is not constant`, this.action); if (typeof constVal !== "number") throw new ECSError(`field is not numeric`, this.action); return constVal << bitofs; } __index(args) { let ident = args[0]; let index = parseInt(args[1] || "0"); let entities = this.scope.state.working.entities; if (entities.length == 1) { return this.dialect.absolute(ident); } else { return this.dialect.indexed_x(ident, index); } } __eid(args) { let e = this.scope.getEntityByName(args[0] || "?"); if (!e) throw new ECSError(`can't find entity named "${args[0]}"`, this.action); return e.id.toString(); } __use(args) { return this.scope.includeResource(args[0]); } __emit(args) { let event = args[0]; let eventargs = args.slice(1); try { return this.scope.generateCodeForEvent(event, eventargs); } catch (e) { if (e.$sources) e.$sources.push(this.action); throw e; } } __local(args) { let tempinc = parseInt(args[0]); let tempbytes = this.instance.system.tempbytes; if (isNaN(tempinc)) throw new ECSError(`bad temporary offset`, this.action); if (!tempbytes) throw new ECSError(`this system has no locals`, this.action); if (tempinc < 0 || tempinc >= tempbytes) throw new ECSError(`this system only has ${tempbytes} locals`, this.action); this.scope.updateTempLiveness(this.instance); return `${this.tmplabel}+${tempinc}`; } __arg(args) { let argindex = parseInt(args[0] || "0"); let argvalue = this.eventargs[argindex] || ""; return argvalue; } __start(args) { let startSymbol = this.dialect.scopeSymbol(args[0]); return this.dialect.jump(startSymbol); } generateCodeForField(fieldName, bitofs, canWrite) { var _a, _b, _c, _d; const action = this.action; const qr = this.scope.state.working; var component; var baseLookup = false; var entityLookup = false; let entities; if (fieldName.indexOf(".") > 0) { let [entname, fname] = fieldName.split("."); let ent = this.scope.getEntityByName(entname); if (ent == null) throw new ECSError(`no entity named "${entname}" in this scope`, action); component = this.em.singleComponentWithFieldName([ent.etype], fname, action); fieldName = fname; entities = [ent]; entityLookup = true; } else if (fieldName.indexOf(":") > 0) { let [cname, fname] = fieldName.split(":"); component = this.em.getComponentByName(cname); if (component == null) throw new ECSError(`no component named "${cname}"`, action); entities = this.scope.state.working.entities; fieldName = fname; baseLookup = true; } else { component = this.em.singleComponentWithFieldName(qr.atypes, fieldName, action); entities = this.scope.state.working.entities; } let field = component.fields.find((f) => f.name == fieldName); if (field == null) throw new ECSError(`no field named "${fieldName}" in component`, action); let ident = this.dialect.fieldsymbol(component, field, bitofs); let constValues = new Set(); let isConst = false; for (let e of entities) { let constVal = e.consts[mksymbol(component, fieldName)]; if (constVal !== void 0) isConst = true; constValues.add(constVal); } if (isConst && canWrite) throw new ECSError(`can't write to constant field ${fieldName}`, action); if (constValues.size == 1) { let value = constValues.values().next().value; if (typeof value === "number") { return `#${value >> bitofs & 255}`; } } let range = this.scope.getFieldRange(component, field.name); if (!range) throw new ECSError(`couldn't find field for ${component.name}:${fieldName}, maybe no entities?`); if (baseLookup) { return this.dialect.absolute(ident); } else if (entities.length == 1) { let eidofs = entities[0].id - range.elo; return this.dialect.absolute(ident, eidofs); } else { let ir; let int; let eidofs; let xreg = this.scope.state.xreg; let yreg = this.scope.state.yreg; if (xreg && (int = (_a = xreg.eset) == null ? void 0 : _a.intersection(qr))) { ir = xreg.eset; eidofs = xreg.elo - range.elo; } else if (yreg && (int = (_b = yreg.eset) == null ? void 0 : _b.intersection(qr))) { ir = yreg.eset; eidofs = yreg.elo - range.elo; } else { ir = null; eidofs = 0; } if (!ir) { throw new ECSError(`no intersection for index register`, action); } if (ir.entities.length == 0) throw new ECSError(`no common entities for index register`, action); if (!ir.isContiguous()) throw new ECSError(`entities in query are not contiguous`, action); if (ir == ((_c = this.scope.state.xreg) == null ? void 0 : _c.eset)) return this.dialect.indexed_x(ident, eidofs); if (ir == ((_d = this.scope.state.yreg) == null ? void 0 : _d.eset)) return this.dialect.indexed_y(ident, eidofs); throw new ECSError(`cannot find "${component.name}:${field.name}" in state`, action); } } getJoinField(action, atypes, jtypes) { let refs = Array.from(this.scope.iterateArchetypeFields(atypes, (c, f) => f.dtype == "ref")); if (refs.length == 0) throw new ECSError(`cannot find join fields`, action); if (refs.length > 1) throw new ECSError(`cannot join multiple fields (${refs.map((r) => r.f.name).join(" ")})`, action); return refs[0]; } isSubroutineSized(code) { if (code.length > 2e4) return false; if (code.split("\n ").length >= 4) return true; return false; } exprToCode(expr) { if (isQueryExpr(expr)) { return this.queryExprToCode(expr); } if (isBlockStmt(expr)) { return this.blockStmtToCode(expr); } if (isInlineCode(expr)) { return this.evalInlineCode(expr.code); } throw new ECSError(`cannot convert expression to code`, expr); } evalInlineCode(code) { let props = this.scope.state.props || {}; code = this.replaceLabels(code); code = this.replaceTags(code, this.action, props); return code; } blockStmtToCode(expr) { return expr.stmts.map((node) => this.exprToCode(node)).join("\n"); } queryExprToCode(qexpr) { let q = this.startQuery(qexpr); const allowEmpty = ["if", "foreach", "join"]; if (q.working.entities.length == 0 && allowEmpty.includes(qexpr.select)) { this.endQuery(q); return ""; } else { this.scope.state.working = q.working; this.scope.state.props = q.props; q.code = this.evalInlineCode(q.code); let body = this.blockStmtToCode(qexpr); this.endQuery(q); body = q.code.replace("%%CODE%%", body); return body; } } queryWorkingSet(qexpr) { const scope = this.scope; const instance = this.instance; let select = qexpr.select; let q = qexpr.query; let qr = new EntitySet(scope, q); if (!(qexpr.all || q.entities)) { let ir = qr.intersection(scope.state.working); if (ir.entities.length || select == "if") { qr = ir; } } if (instance.params.refEntity && instance.params.refField) { let rf = instance.params.refField; if (rf.f.dtype == "ref") { let rq = rf.f.query; qr = qr.intersection(new EntitySet(scope, rq)); } } else if (instance.params.query) { qr = qr.intersection(new EntitySet(scope, instance.params.query)); } return qr; } updateIndexRegisters(qr, jr, select) { const action = this.action; const scope = this.scope; const instance = this.instance; const state = this.scope.state; if (qr.entities.length > 1) { switch (select) { case "once": break; case "foreach": case "unroll": if (state.xreg && state.yreg) throw new ECSError("no more index registers", action); if (state.xreg) state.yreg = new IndexRegister(scope, qr); else state.xreg = new IndexRegister(scope, qr); break; case "join": if (state.xreg || state.yreg) throw new ECSError("no free index registers for join", action); if (jr) state.xreg = new IndexRegister(scope, jr); state.yreg = new IndexRegister(scope, qr); break; case "if": case "with": if (state.xreg && state.xreg.eset) { state.xreg = state.xreg.narrow(qr, action); } else if (select == "with") { if (instance.params.refEntity && instance.params.refField) { if (state.xreg) state.xreg.eset = qr; else state.xreg = new IndexRegister(scope, qr); } } break; } } } getCodeAndProps(qexpr, qr, jr, oldState) { const entities = qr.entities; const select = qexpr.select; let code = "%%CODE%%"; let props = {}; if (select == "join" && jr) { if (qr.entities.length) { let joinfield = this.getJoinField(this.action, qr.atypes, jr.atypes); code = this.wrapCodeInLoop(code, qexpr, qr.entities, joinfield); props["%joinfield"] = this.dialect.fieldsymbol(joinfield.c, joinfield.f, 0); } } let fullEntityCount = qr.entities.length; if (select == "with") { if (this.instance.params.refEntity && this.instance.params.refField) { let re = this.instance.params.refEntity; let rf = this.instance.params.refField; code = this.wrapCodeInRefLookup(code); let range = this.scope.getFieldRange(rf.c, rf.f.name); let eidofs = re.id - range.elo; props["%reffield"] = `${this.dialect.fieldsymbol(rf.c, rf.f, 0)}+${eidofs}`; } else { code = this.wrapCodeInFilter(code, qr, oldState, props); } } if (select == "if") { code = this.wrapCodeInFilter(code, qr, oldState, props); } if (select == "foreach" && entities.length > 1) { code = this.wrapCodeInLoop(code, qexpr, qr.entities); } if (select == "unroll" && entities.length > 1) { throw new ECSError("unroll is not yet implemented"); } if (entities.length) { props["%elo"] = entities[0].id.toString(); props["%ehi"] = entities[entities.length - 1].id.toString(); } props["%ecount"] = entities.length.toString(); props["%efullcount"] = fullEntityCount.toString(); return { code, props }; } startQuery(qexpr) { const scope = this.scope; const action = this.action; const select = qexpr.select; const oldState = this.scope.state; this.scope.state = Object.assign(new ActionCPUState(), oldState); const qr = this.queryWorkingSet(qexpr); const jr = qexpr.join && qr.entities.length ? new EntitySet(scope, qexpr.join) : null; this.updateIndexRegisters(qr, jr, select); const { code, props } = this.getCodeAndProps(qexpr, qr, jr, oldState); let working = jr ? qr.union(jr) : qr; return { working, oldState, props, code }; } endQuery(q) { this.scope.state = q.oldState; } wrapCodeInLoop(code, qexpr, ents, joinfield) { let dir = qexpr.direction; let s = dir == "desc" ? this.dialect.ASM_ITERATE_EACH_DESC : this.dialect.ASM_ITERATE_EACH_ASC; if (joinfield) s = dir == "desc" ? this.dialect.ASM_ITERATE_JOIN_DESC : this.dialect.ASM_ITERATE_JOIN_ASC; s = s.replace("{{%code}}", code); return s; } wrapCodeInFilter(code, qr, oldState, props) { var _a, _b; const ents = qr.entities; const ents2 = (_b = (_a = oldState.xreg) == null ? void 0 : _a.eset) == null ? void 0 : _b.entities; if (ents && ents.length && ents2) { let lo = ents[0].id; let hi = ents[ents.length - 1].id; let lo2 = ents2[0].id; let hi2 = ents2[ents2.length - 1].id; if (lo != lo2) { code = this.dialect.ASM_FILTER_RANGE_LO_X.replace("{{%code}}", code); props["%xofs"] = lo - lo2; } if (hi != hi2) { code = this.dialect.ASM_FILTER_RANGE_HI_X.replace("{{%code}}", code); } } return code; } wrapCodeInRefLookup(code) { code = this.dialect.ASM_LOOKUP_REF_X.replace("{{%code}}", code); return code; } }; var EventCodeStats = class { constructor(inst, action, eventcode) { this.inst = inst; this.action = action; this.eventcode = eventcode; this.labels = []; this.count = 0; } }; var EntityScope = class { constructor(em, dialect, name, parent) { this.em = em; this.dialect = dialect; this.name = name; this.parent = parent; this.childScopes = []; this.instances = []; this.entities = []; this.fieldtypes = {}; this.sysstats = new Map(); this.bss = new UninitDataSegment(); this.rodata = new ConstDataSegment(); this.code = new CodeSegment(); this.componentsInScope = new Set(); this.resources = new Set(); this.isDemo = false; this.filePath = ""; this.inCritical = 0; parent == null ? void 0 : parent.childScopes.push(this); this.state = new ActionCPUState(); this.state.working = new EntitySet(this, void 0, this.entities); } newEntity(etype, name) { if (name && this.getEntityByName(name)) throw new ECSError(`already an entity named "${name}"`); let id = this.entities.length; etype = this.em.addArchetype(etype); let entity = { id, etype, consts: {}, inits: {} }; for (let c of etype.components) { this.componentsInScope.add(c.name); } entity.name = name; this.entities.push(entity); return entity; } newSystemInstance(inst) { if (!inst) throw new Error(); inst.id = this.instances.length + 1; this.instances.push(inst); this.em.registerSystemEvents(inst.system); return inst; } newSystemInstanceWithDefaults(system) { return this.newSystemInstance({ system, params: {}, id: 0 }); } getSystemInstanceNamed(name) { return this.instances.find((sys) => sys.system.name == name); } getEntityByName(name) { return this.entities.find((e) => e.name == name); } *iterateEntityFields(entities) { for (let i = 0; i < entities.length; i++) { let e = entities[i]; for (let c of e.etype.components) { for (let f of c.fields) { yield { i, e, c, f, v: e.consts[mksymbol(c, f.name)] }; } } } } *iterateArchetypeFields(arch, filter) { for (let i = 0; i < arch.length; i++) { let a = arch[i]; for (let c of a.components) { for (let f of c.fields) { if (!filter || filter(c, f)) yield { i, c, f }; } } } } *iterateChildScopes() { for (let scope of this.childScopes) { yield scope; } } entitiesMatching(atypes) { let result = []; for (let e of this.entities) { for (let a of atypes) { if (e.etype === a) { result.push(e); break; } } } return result; } hasComponent(ctype) { return this.componentsInScope.has(ctype.name); } buildSegments() { let iter = this.iterateEntityFields(this.entities); for (var o = iter.next(); o.value; o = iter.next()) { let { i, e, c, f, v } = o.value; let cfname = mksymbol(c, f.name); let ftype = this.fieldtypes[cfname]; let isConst = ftype == "const"; let segment = isConst ? this.rodata : this.bss; if (v === void 0 && isConst) throw new ECSError(`no value for const field ${cfname}`, e); let array = segment.fieldranges[cfname]; if (!array) { array = segment.fieldranges[cfname] = { component: c, field: f, elo: i, ehi: i }; } else { array.ehi = i; if (array.ehi - array.elo + 1 >= 256) throw new ECSError(`too many entities have field ${cfname}, limit is 256`); } if (!isConst) { if (f.dtype == "int" && f.defvalue !== void 0) { let ecfname = mkscopesymbol(this, c, f.name); if (e.inits[ecfname] == null) { this.setInitValue(e, c, f, f.defvalue); } } } } } allocateSegment(segment, alloc, type) { let fields = Object.values(segment.fieldranges); for (let f of fields) { if (this.fieldtypes[mksymbol(f.component, f.field.name)] == type) { let rangelen = f.ehi - f.elo + 1; let bits = getPackedFieldSize(f.field); if (bits == 0) bits = 16; let bytesperelem = Math.ceil(bits / 8); let access = []; for (let i = 0; i < bits; i += 8) { let symbol = this.dialect.fieldsymbol(f.component, f.field, i); access.push({ symbol, bit: i, width: 8 }); if (alloc) { segment.allocateBytes(symbol, rangelen); } } f.access = access; } } } allocateROData(segment) { let iter = this.iterateEntityFields(this.entities); for (var o = iter.next(); o.value; o = iter.next()) { let { i, e, c, f, v } = o.value; let cfname = mksymbol(c, f.name); if (this.fieldtypes[cfname] == "const") { let range = segment.fieldranges[cfname]; let entcount = range ? range.ehi - range.elo + 1 : 0; if (v == null && f.dtype == "int") v = 0; if (v == null && f.dtype == "ref") v = 0; if (v == null && f.dtype == "array") throw new ECSError(`no default value for array ${cfname}`, e); if (v instanceof Uint8Array && f.dtype == "array") { let ptrlosym = this.dialect.fieldsymbol(c, f, 0); let ptrhisym = this.dialect.fieldsymbol(c, f, 8); let loofs = segment.allocateBytes(ptrlosym, entcount); let hiofs = segment.allocateBytes(ptrhisym, entcount); let datasym = this.dialect.datasymbol(c, f, e.id, 0); segment.allocateInitData(datasym, v); if (f.baseoffset) datasym = `(${datasym}+${f.baseoffset})`; segment.initdata[loofs + e.id - range.elo] = { symbol: datasym, bitofs: 0 }; segment.initdata[hiofs + e.id - range.elo] = { symbol: datasym, bitofs: 8 }; } else if (typeof v === "number") { { if (!range.access) throw new ECSError(`no access for field ${cfname}`); for (let a of range.access) { segment.allocateBytes(a.symbol, entcount); let ofs = segment.getByteOffset(range, a, e.id); if (e.id < range.elo) throw new ECSError("entity out of range " + c.name + " " + f.name, e); if (segment.initdata[ofs] !== void 0) throw new ECSError("initdata already set " + ofs), e; segment.initdata[ofs] = v >> a.bit & 255; } } } else if (v == null && f.dtype == "array" && f.index) { let datasym = this.dialect.datasymbol(c, f, e.id, 0); let databytes = getFieldLength(f.index); let offset = this.bss.allocateBytes(datasym, databytes); let ptrlosym = this.dialect.fieldsymbol(c, f, 0); let ptrhisym = this.dialect.fieldsymbol(c, f, 8); let loofs = segment.allocateBytes(ptrlosym, entcount); let hiofs = segment.allocateBytes(ptrhisym, entcount); if (f.baseoffset) datasym = `(${datasym}+${f.baseoffset})`; segment.initdata[loofs + e.id - range.elo] = { symbol: datasym, bitofs: 0 }; segment.initdata[hiofs + e.id - range.elo] = { symbol: datasym, bitofs: 8 }; } else { throw new ECSError(`unhandled constant ${e.id}:${cfname} -- ${typeof v}`); } } } } allocateInitData(segment) { if (segment.size == 0) return ""; let initbytes = new Uint8Array(segment.size); let iter = this.iterateEntityFields(this.entities); for (var o = iter.next(); o.value; o = iter.next()) { let { i, e, c, f, v } = o.value; let scfname = mkscopesymbol(this, c, f.name); let initvalue = e.inits[scfname]; if (initvalue !== void 0) { let range = segment.getFieldRange(c, f.name); if (!range) throw new ECSError(`no init range for ${scfname}`, e); if (!range.access) throw new ECSError(`no init range access for ${scfname}`, e); if (typeof initvalue === "number") { for (let a of range.access) { let offset = segment.getByteOffset(range, a, e.id); initbytes[offset] = initvalue >> a.bit & (1 << a.width) - 1; } } else if (initvalue instanceof Uint8Array) { let datasym = this.dialect.datasymbol(c, f, e.id, 0); let ofs = this.bss.symbols[datasym]; initbytes.set(initvalue, ofs); } else { throw new ECSError(`cannot initialize ${scfname} = ${initvalue}`); } } } let bufsym = this.name + "__INITDATA"; let bufofs = this.rodata.allocateInitData(bufsym, initbytes); let code = this.dialect.INIT_FROM_ARRAY; code = code.replace("{{%nbytes}}", initbytes.length.toString()); code = code.replace("{{%src}}", bufsym); code = code.replace("{{%dest}}", segment.getOriginSymbol()); return code; } getFieldRange(c, fn) { return this.bss.getFieldRange(c, fn) || this.rodata.getFieldRange(c, fn); } setConstValue(e, component, field, value) { this.setConstInitValue(e, component, field, value, "const"); } setInitValue(e, component, field, value) { this.setConstInitValue(e, component, field, value, "init"); } setConstInitValue(e, component, field, value, type) { this.checkFieldValue(field, value); let fieldName = field.name; let cfname = mksymbol(component, fieldName); let ecfname = mkscopesymbol(this, component, fieldName); if (e.consts[cfname] !== void 0) throw new ECSError(`"${fieldName}" is already defined as a constant`, e); if (e.inits[ecfname] !== void 0) throw new ECSError(`"${fieldName}" is already defined as a variable`, e); if (type == "const") e.consts[cfname] = value; if (type == "init") e.inits[ecfname] = value; this.fieldtypes[cfname] = type; } isConstOrInit(component, fieldName) { return this.fieldtypes[mksymbol(component, fieldName)]; } getConstValue(entity, fieldName) { let component = this.em.singleComponentWithFieldName([entity.etype], fieldName, entity); let cfname = mksymbol(component, fieldName); return entity.consts[cfname]; } checkFieldValue(field, value) { if (field.dtype == "array") { if (!(value instanceof Uint8Array)) throw new ECSError(`This "${field.name}" value should be an array.`); } else if (typeof value !== "number") { throw new ECSError(`This "${field.name}" ${field.dtype} value should be an number.`); } else { if (field.dtype == "int") { if (value < field.lo || value > field.hi) throw new ECSError(`This "${field.name}" value is out of range, should be between ${field.lo} and ${field.hi}.`); } else if (field.dtype == "ref") { let eset = new EntitySet(this, field.query); if (value < 0 || value >= eset.entities.length) throw new ECSError(`This "${field.name}" value is out of range for this ref type.`); } } } generateCodeForEvent(event, args, codelabel) { let systems = this.em.event2systems[event]; if (!systems || systems.length == 0) { console.log(`warning: no system responds to "${event}"`); return ""; } this.eventSeq++; let code = ""; if (codelabel) { code += this.dialect.label(codelabel) + "\n"; } if (event == "start") { code += this.allocateInitData(this.bss); } let eventCount = 0; let instances = this.instances.filter((inst) => systems.includes(inst.system)); for (let inst of instances) { let sys = inst.system; for (let action of sys.actions) { if (action.event == event) { eventCount++; let codeeval = new ActionEval(this, inst, action, args || []); codeeval.begin(); if (action.critical) this.inCritical++; let eventcode = codeeval.codeToString(); if (action.critical) this.inCritical--; if (!this.inCritical && codeeval.isSubroutineSized(eventcode)) { let normcode = this.normalizeCode(eventcode, action); let estats = this.eventCodeStats[normcode]; if (!estats) { estats = this.eventCodeStats[normcode] = new EventCodeStats(inst, action, eventcode); } estats.labels.push(codeeval.label); estats.count++; if (action.critical) estats.count++; } let s = ""; s += this.dialect.comment(`start action ${codeeval.label}`); s += eventcode; s += this.dialect.comment(`end action ${codeeval.label}`); code += s; codeeval.end(); } } } if (eventCount == 0) { console.log(`warning: event ${event} not handled`); } return code; } normalizeCode(code, action) { code = code.replace(/\b(\w+__\w+__)(\d+)__(\w+)\b/g, (z, a, b, c) => a + c); return code; } getSystemStats(inst) { let stats = this.sysstats.get(inst); if (!stats) { stats = new SystemStats(); this.sysstats.set(inst, stats); } return stats; } updateTempLiveness(inst) { let stats = this.getSystemStats(inst); let n = this.eventSeq; if (stats.tempstartseq && stats.tempendseq) { stats.tempstartseq = Math.min(stats.tempstartseq, n); stats.tempendseq = Math.max(stats.tempendseq, n); } else { stats.tempstartseq = stats.tempendseq = n; } } includeResource(symbol) { this.resources.add(symbol); return symbol; } allocateTempVars() { let pack = new Packer(); let maxTempBytes = 128 - this.bss.size; let bssbin = new Bin({ left: 0, top: 0, bottom: this.eventSeq + 1, right: maxTempBytes }); pack.bins.push(bssbin); for (let instance of this.instances) { let stats = this.getSystemStats(instance); if (instance.system.tempbytes && stats.tempstartseq && stats.tempendseq) { let v = { inst: instance, top: stats.tempstartseq, bottom: stats.tempendseq + 1, width: instance.system.tempbytes, height: stats.tempendseq - stats.tempstartseq + 1, label: instance.system.name }; pack.boxes.push(v); } } if (!pack.pack()) console.log("cannot pack temporary local vars"); if (bssbin.extents.right > 0) { let tempofs = this.bss.allocateBytes("TEMP", bssbin.extents.right); for (let b of pack.boxes) { let inst = b.inst; if (b.box) this.bss.declareSymbol(this.dialect.tempLabel(inst), tempofs + b.box.left); } } console.log(pack.toSVGUrl()); } analyzeEntities() { this.buildSegments(); this.allocateSegment(this.bss, true, "init"); this.allocateSegment(this.bss, true, void 0); this.allocateSegment(this.rodata, false, "const"); this.allocateROData(this.rodata); } generateCode() { this.eventSeq = 0; this.eventCodeStats = {}; let isMainScope = this.parent == null; let start; let initsys = this.em.getSystemByName("Init"); if (isMainScope && initsys) { this.newSystemInstanceWithDefaults(initsys); start = this.generateCodeForEvent("main_init"); } else { start = this.generateCodeForEvent("start"); } start = this.replaceSubroutines(start); this.code.addCodeFragment(start); for (let sub of Array.from(this.resources.values())) { if (!this.getSystemInstanceNamed(sub)) { let sys = this.em.getSystemByName(sub); if (!sys) throw new ECSError(`cannot find resource named "${sub}"`); this.newSystemInstanceWithDefaults(sys); } let code = this.generateCodeForEvent(sub, [], sub); this.code.addCodeFragment(code); } } replaceSubroutines(code) { let allsubs = []; for (let stats of Object.values(this.eventCodeStats)) { if (stats.count > 1) { if (allsubs.length == 0) { allsubs = [ this.dialect.segment("rodata"), this.dialect.alignSegmentStart() ]; } else if (stats.action.fitbytes) { allsubs.push(this.dialect.alignIfLessThan(stats.action.fitbytes)); } let subcall = this.dialect.call(stats.labels[0]); for (let label of stats.labels) { let startdelim = this.dialect.comment(`start action ${label}`).trim(); let enddelim = this.dialect.comment(`end action ${label}`).trim(); let istart = code.indexOf(startdelim); let iend = code.indexOf(enddelim, istart); if (istart >= 0 && iend > istart) { code = code.substring(0, istart) + subcall + code.substring(iend + enddelim.length); } } let substart = stats.labels[0]; let sublines = [ this.dialect.segment("rodata"), this.dialect.label(substart), stats.eventcode, this.dialect.return() ]; if (stats.action.critical) { sublines.push(this.dialect.warningIfPageCrossed(substart)); } if (stats.action.fitbytes) { sublines.push(this.dialect.warningIfMoreThan(stats.action.fitbytes, substart)); } allsubs = allsubs.concat(sublines); } } code += allsubs.join("\n"); return code; } showStats() { for (let inst of this.instances) { console.log(inst.system.name, this.getSystemStats(inst)); } } dumpCodeTo(file) { let dialect = this.dialect; file.line(dialect.startScope(this.name)); file.line(dialect.segment("bss")); this.bss.dump(file, dialect); file.line(dialect.segment("code")); this.rodata.dump(file, dialect); file.line(dialect.label("__Start")); this.code.dump(file); for (let subscope of this.childScopes) { subscope.dump(file); } file.line(dialect.endScope(this.name)); } dump(file) { this.analyzeEntities(); this.generateCode(); this.allocateTempVars(); this.dumpCodeTo(file); } }; var EntityManager = class { constructor(dialect) { this.dialect = dialect; this.archetypes = {}; this.components = {}; this.systems = {}; this.topScopes = {}; this.event2systems = {}; this.name2cfpairs = {}; this.mainPath = ""; this.imported = {}; this.seq = 1; } newScope(name, parent) { let existing = this.topScopes[name]; if (existing && !existing.isDemo) throw new ECSError(`scope ${name} already defined`, existing); let scope = new EntityScope(this, this.dialect, name, parent); if (!parent) this.topScopes[name] = scope; return scope; } deferComponent(name) { this.components[name] = { name, fields: [] }; } defineComponent(ctype) { let existing = this.components[ctype.name]; if (existing && existing.fields.length > 0) throw new ECSError(`component ${ctype.name} already defined`, existing); if (existing) { existing.fields = ctype.fields; ctype = existing; } for (let field of ctype.fields) { let list = this.name2cfpairs[field.name]; if (!list) list = this.name2cfpairs[field.name] = []; list.push({ c: ctype, f: field }); } this.components[ctype.name] = ctype; return ctype; } defineSystem(system) { let existing = this.systems[system.name]; if (existing) throw new ECSError(`system ${system.name} already defined`, existing); return this.systems[system.name] = system; } registerSystemEvents(system) { for (let a of system.actions) { let event = a.event; let list = this.event2systems[event]; if (list == null) list = this.event2systems[event] = []; if (!list.includes(system)) list.push(system); } } addArchetype(atype) { let key = atype.components.map((c) => c.name).join(","); if (this.archetypes[key]) return this.archetypes[key]; else return this.archetypes[key] = atype; } componentsMatching(q, etype) { var _a; let list = []; for (let c of etype.components) { if ((_a = q.exclude) == null ? void 0 : _a.includes(c)) { return []; } if (q.include.length == 0 || q.include.includes(c)) { list.push(c); } } return list.length == q.include.length ? list : []; } archetypesMatching(q) { let result = new Set(); for (let etype of Object.values(this.archetypes)) { let cmatch = this.componentsMatching(q, etype); if (cmatch.length > 0) { result.add(etype); } } return Array.from(result.values()); } componentsWithFieldName(atypes, fieldName) { let comps = new Set(); for (let at of atypes) { for (let c of at.components) { for (let f of c.fields) { if (f.name == fieldName) comps.add(c); } } } return Array.from(comps); } getComponentByName(name) { return this.components[name]; } getSystemByName(name) { return this.systems[name]; } singleComponentWithFieldName(atypes, fieldName, where) { let cfpairs = this.name2cfpairs[fieldName]; if (!cfpairs) throw new ECSError(`cannot find field named "${fieldName}"`, where); let filtered = cfpairs.filter((cf) => atypes.find((a) => a.components.includes(cf.c))); if (filtered.length == 0) { throw new ECSError(`cannot find component with field "${fieldName}" in this context`, where); } if (filtered.length > 1) { throw new ECSError(`ambiguous field name "${fieldName}"`, where); } return filtered[0].c; } toJSON() { return JSON.stringify({ components: this.components, systems: this.systems }); } exportToFile(file) { for (let event of Object.keys(this.event2systems)) { file.line(this.dialect.equate(`EVENT__${event}`, "1")); } for (let scope of Object.values(this.topScopes)) { if (!scope.isDemo || scope.filePath == this.mainPath) { scope.dump(file); } } } *iterateScopes() { for (let scope of Object.values(this.topScopes)) { yield scope; scope.iterateChildScopes(); } } getDebugTree() { let scopes = this.topScopes; let components = this.components; let fields = this.name2cfpairs; let systems = this.systems; let events = this.event2systems; let entities = {}; for (let scope of Array.from(this.iterateScopes())) { for (let e of scope.entities) entities[e.name || "#" + e.id.toString()] = e; } return { scopes, components, fields, systems, events, entities }; } evalExpr(expr, scope) { if (isLiteral2(expr)) return expr; if (isBinOp2(expr) || isUnOp2(expr)) { var fn = this["evalop__" + expr.op]; if (!fn) throw new ECSError(`no eval function for "${expr.op}"`); } if (isBinOp2(expr)) { expr.left = this.evalExpr(expr.left, scope); expr.right = this.evalExpr(expr.right, scope); let e = fn(expr.left, expr.right); return e || expr; } if (isUnOp2(expr)) { expr.expr = this.evalExpr(expr.expr, scope); let e = fn(expr.expr); return e || expr; } return expr; } evalop__neg(arg) { if (isLiteralInt(arg)) { let valtype = { dtype: "int", lo: -arg.valtype.hi, hi: arg.valtype.hi }; return { valtype, value: -arg.value }; } } evalop__add(left, right) { if (isLiteralInt(left) && isLiteralInt(right)) { let valtype = { dtype: "int", lo: left.valtype.lo + right.valtype.lo, hi: left.valtype.hi + right.valtype.hi }; return { valtype, value: left.value + right.value }; } } evalop__sub(left, right) { if (isLiteralInt(left) && isLiteralInt(right)) { let valtype = { dtype: "int", lo: left.valtype.lo - right.valtype.hi, hi: left.valtype.hi - right.valtype.lo }; return { valtype, value: left.value - right.value }; } } }; // src/common/ecs/decoder.ts var LineDecoder = class { constructor(text) { this.curline = 0; this.lines = text.split("\n").map((s) => s.trim()).filter((s) => !!s).map((s) => s.split(/\s+/)); } decodeBits(s, n, msbfirst) { if (s.length != n) throw new ECSError(`Expected ${n} characters`); let b = 0; for (let i = 0; i < n; i++) { let bit; let ch = s.charAt(i); if (ch == "x" || ch == "X" || ch == "1") bit = 1; else if (ch == "." || ch == "0") bit = 0; else throw new ECSError("need x or . (or 0 or 1)"); if (bit) { if (msbfirst) b |= 1 << n - 1 - i; else b |= 1 << i; } } return b; } assertTokens(toks, count) { if (toks.length != count) throw new ECSError(`Expected ${count} tokens on line.`); } hex(s) { let v = parseInt(s, 16); if (isNaN(v)) throw new ECSError(`Invalid hex value: ${s}`); return v; } getErrorLocation($loc) { $loc.line += this.curline + 1; return $loc; } }; var VCSSpriteDecoder = class extends LineDecoder { parse() { let height = this.lines.length; let bitmapdata = new Uint8Array(height); let colormapdata = new Uint8Array(height); for (let i = 0; i < height; i++) { this.curline = height - 1 - i; let toks = this.lines[this.curline]; this.assertTokens(toks, 2); bitmapdata[i] = this.decodeBits(toks[0], 8, true); colormapdata[i] = this.hex(toks[1]); } return { properties: { bitmapdata, colormapdata, height: height - 1 } }; } }; var VCSBitmapDecoder = class extends LineDecoder { parse() { let height = this.lines.length; let bitmapdata = new Uint8Array(height); for (let i = 0; i < height; i++) { this.curline = height - 1 - i; let toks = this.lines[this.curline]; this.assertTokens(toks, 1); bitmapdata[i] = this.decodeBits(toks[0], 8, true); } return { properties: { bitmapdata, height: height - 1 } }; } }; var VCSPlayfieldDecoder = class extends LineDecoder { parse() { let height = this.lines.length; let pf = new Uint32Array(height); for (let i = 0; i < height; i++) { this.curline = height - 1 - i; let toks = this.lines[this.curline]; this.assertTokens(toks, 1); let pf0 = this.decodeBits(toks[0].substring(0, 4), 4, false) << 4; let pf1 = this.decodeBits(toks[0].substring(4, 12), 8, true); let pf2 = this.decodeBits(toks[0].substring(12, 20), 8, false); pf[i] = pf0 << 0 | pf1 << 8 | pf2 << 16; } return { properties: { pf } }; } }; var VCSVersatilePlayfieldDecoder = class extends LineDecoder { parse() { let height = this.lines.length; let data = new Uint8Array(height * 2); data.fill(63); const regs = [13, 14, 15, 8, 9, 10, 63]; let prev = [0, 0, 0, 0, 0, 0, 0]; let cur = [0, 0, 0, 0, 0, 0, 0]; for (let i = 0; i < height; i++) { let dataofs = height * 2 - i * 2; this.curline = i; let toks = this.lines[this.curline]; if (toks.length == 2) { data[dataofs - 1] = this.hex(toks[0]); data[dataofs - 2] = this.hex(toks[1]); continue; } this.assertTokens(toks, 4); cur[0] = this.decodeBits(toks[0].substring(0, 4), 4, false) << 4; cur[1] = this.decodeBits(toks[0].substring(4, 12), 8, true); cur[2] = this.decodeBits(toks[0].substring(12, 20), 8, false); if (toks[1] != "..") cur[3] = this.hex(toks[1]); if (toks[2] != "..") cur[4] = this.hex(toks[2]); if (toks[3] != "..") cur[5] = this.hex(toks[3]); let changed = []; for (let j = 0; j < cur.length; j++) { if (cur[j] != prev[j]) changed.push(j); } if (changed.length > 1) { console.log(changed, cur, prev); throw new ECSError(`More than one register change in line ${i + 1}: [${changed}]`); } let chgidx = changed.length ? changed[0] : regs.length - 1; data[dataofs - 1] = regs[chgidx]; data[dataofs - 2] = cur[chgidx]; prev[chgidx] = cur[chgidx]; } return { properties: { data } }; } }; var VCSBitmap48Decoder = class extends LineDecoder { parse() { let height = this.lines.length; let bitmap0 = new Uint8Array(height); let bitmap1 = new Uint8Array(height); let bitmap2 = new Uint8Array(height); let bitmap3 = new Uint8Array(height); let bitmap4 = new Uint8Array(height); let bitmap5 = new Uint8Array(height); for (let i = 0; i < height; i++) { this.curline = height - 1 - i; let toks = this.lines[this.curline]; this.assertTokens(toks, 1); bitmap0[i] = this.decodeBits(toks[0].slice(0, 8), 8, true); bitmap1[i] = this.decodeBits(toks[0].slice(8, 16), 8, true); bitmap2[i] = this.decodeBits(toks[0].slice(16, 24), 8, true); bitmap3[i] = this.decodeBits(toks[0].slice(24, 32), 8, true); bitmap4[i] = this.decodeBits(toks[0].slice(32, 40), 8, true); bitmap5[i] = this.decodeBits(toks[0].slice(40, 48), 8, true); } return { properties: { bitmap0, bitmap1, bitmap2, bitmap3, bitmap4, bitmap5, height: height - 1 } }; } }; function newDecoder(name, text) { let cons = DECODERS[name]; if (cons) return new cons(text); } var DECODERS = { "vcs_sprite": VCSSpriteDecoder, "vcs_bitmap": VCSBitmapDecoder, "vcs_playfield": VCSPlayfieldDecoder, "vcs_versatile": VCSVersatilePlayfieldDecoder, "vcs_bitmap48": VCSBitmap48Decoder }; // src/common/ecs/compiler.ts var ECSTokenType; (function(ECSTokenType2) { ECSTokenType2["Ellipsis"] = "ellipsis"; ECSTokenType2["Operator"] = "operator"; ECSTokenType2["Relational"] = "relational"; ECSTokenType2["QuotedString"] = "quoted-string"; ECSTokenType2["Integer"] = "integer"; ECSTokenType2["CodeFragment"] = "code-fragment"; ECSTokenType2["Placeholder"] = "placeholder"; })(ECSTokenType || (ECSTokenType = {})); var OPERATORS2 = { "IMP": { f: "bimp", p: 4 }, "EQV": { f: "beqv", p: 5 }, "XOR": { f: "bxor", p: 6 }, "OR": { f: "bor", p: 7 }, "AND": { f: "band", p: 8 }, "||": { f: "lor", p: 17 }, "&&": { f: "land", p: 18 }, "=": { f: "eq", p: 50 }, "==": { f: "eq", p: 50 }, "<>": { f: "ne", p: 50 }, "><": { f: "ne", p: 50 }, "!=": { f: "ne", p: 50 }, "#": { f: "ne", p: 50 }, "<": { f: "lt", p: 50 }, ">": { f: "gt", p: 50 }, "<=": { f: "le", p: 50 }, ">=": { f: "ge", p: 50 }, "MIN": { f: "min", p: 75 }, "MAX": { f: "max", p: 75 }, "+": { f: "add", p: 100 }, "-": { f: "sub", p: 100 } }; function getOperator2(op) { return OPERATORS2[op]; } function getPrecedence2(tok) { switch (tok.type) { case ECSTokenType.Operator: case ECSTokenType.Relational: case TokenType2.Ident: let op = getOperator2(tok.str); if (op) return op.p; } return -1; } var ECSCompiler = class extends Tokenizer { constructor(em, isMainFile) { super(); this.em = em; this.isMainFile = isMainFile; this.currentScope = null; this.currentContext = null; this.includeDebugInfo = false; this.setTokenRules([ { type: ECSTokenType.Ellipsis, regex: /\.\./ }, { type: ECSTokenType.QuotedString, regex: /".*?"/ }, { type: ECSTokenType.CodeFragment, regex: /---.*?---/ }, { type: ECSTokenType.Integer, regex: /0[xX][A-Fa-f0-9]+/ }, { type: ECSTokenType.Integer, regex: /\$[A-Fa-f0-9]+/ }, { type: ECSTokenType.Integer, regex: /[%][01]+/ }, { type: ECSTokenType.Integer, regex: /\d+/ }, { type: ECSTokenType.Relational, regex: /[=<>][=<>]?/ }, { type: ECSTokenType.Operator, regex: /[.#,:(){}\[\]\-\+]/ }, { type: TokenType2.Ident, regex: /[A-Za-z_][A-Za-z0-9_]*/ }, { type: TokenType2.Ignore, regex: /\/\/.*?[\n\r]/ }, { type: TokenType2.Ignore, regex: /\/\*.*?\*\// }, { type: TokenType2.EOL, regex: /[\n\r]+/ }, { type: TokenType2.Ignore, regex: /\s+/ } ]); this.errorOnCatchAll = true; } annotate(fn) { let start = this.peekToken(); let obj = fn(); let end = this.lasttoken; let $loc = end ? mergeLocs2(start.$loc, end.$loc) : start.$loc; if (obj) obj.$loc = $loc; return obj; } parseFile(text, path) { this.tokenizeFile(text, path); while (!this.isEOF()) { let top = this.parseTopLevel(); if (top) { let t = top; this.annotate(() => t); } } this.runDeferred(); } importFile(path) { if (!this.em.imported[path]) { let text = this.getImportFile && this.getImportFile(path); if (!text) this.compileError(`I can't find the import file "${path}".`); this.em.imported[path] = true; let comp = new ECSCompiler(this.em, false); comp.includeDebugInfo = this.includeDebugInfo; try { comp.parseFile(text, path); } catch (e) { for (var err of comp.errors) this.errors.push(err); throw e; } } } parseTopLevel() { let tok = this.expectTokens(["component", "system", "scope", "resource", "import", "demo", "comment"]); if (tok.str == "component") { return this.em.defineComponent(this.parseComponentDefinition()); } if (tok.str == "system") { return this.em.defineSystem(this.parseSystem()); } if (tok.str == "scope") { return this.parseScope(); } if (tok.str == "resource") { return this.em.defineSystem(this.parseResource()); } if (tok.str == "import") { let tok2 = this.expectTokenTypes([ECSTokenType.QuotedString]); let path = tok2.str.substring(1, tok2.str.length - 1); return this.importFile(path); } if (tok.str == "demo") { if (this.isMainFile) { let scope = this.parseScope(); scope.isDemo = true; this.expectToken("demo"); return scope; } else { this.skipDemo(); return; } } if (tok.str == "comment") { this.expectTokenTypes([ECSTokenType.CodeFragment]); return; } this.compileError(`Unexpected top-level keyword: ${tok.str}`); } skipDemo() { var tok; while ((tok = this.consumeToken()) && !this.isEOF()) { if (tok.str == "end" && this.peekToken().str == "demo") { this.consumeToken(); return; } } throw new ECSError(`Expected "end demo" after a "demo" declaration.`); } parseComponentDefinition() { let name = this.expectIdent().str; let fields = []; this.em.deferComponent(name); while (this.peekToken().str != "end") { fields.push(this.parseComponentField()); } this.expectToken("end"); return { name, fields }; } parseComponentField() { let name = this.expectIdent(); this.expectToken(":", 'I expected either a ":" or "end" here.'); let type = this.parseDataType(); return __spreadValues({ name: name.str, $loc: name.$loc }, type); } parseDataType() { if (this.peekToken().type == "integer") { let lo = this.parseIntegerConstant(); this.expectToken(".."); let hi = this.parseIntegerConstant(); this.checkLowerLimit(lo, -2147483648, "lower int range"); this.checkUpperLimit(hi, 2147483647, "upper int range"); this.checkUpperLimit(hi - lo, 4294967295, "int range"); this.checkLowerLimit(hi, lo, "int range"); let defvalue; if (this.ifToken("default")) { defvalue = this.parseIntegerConstant(); } return { dtype: "int", lo, hi, defvalue }; } if (this.peekToken().str == "[") { return { dtype: "ref", query: this.parseQuery() }; } if (this.ifToken("array")) { let index = void 0; if (this.peekToken().type == ECSTokenType.Integer) { index = this.parseDataType(); } this.expectToken("of"); let elem = this.parseDataType(); let baseoffset; if (this.ifToken("baseoffset")) { baseoffset = this.parseIntegerConstant(); this.checkLowerLimit(baseoffset, -32768, "base offset"); this.checkUpperLimit(baseoffset, 32767, "base offset"); } return { dtype: "array", index, elem, baseoffset }; } if (this.ifToken("enum")) { this.expectToken("["); let enumtoks = this.parseList(this.parseEnumIdent, ","); this.expectToken("]"); if (enumtoks.length == 0) this.compileError(`must define at least one enum`); let lo = 0; let hi = enumtoks.length - 1; this.checkLowerLimit(hi, 0, "enum count"); this.checkUpperLimit(hi, 255, "enum count"); let enums = {}; for (let i = 0; i <= hi; i++) enums[enumtoks[i].str] = i; let defvalue; if (this.ifToken("default")) { defvalue = this.parseIntegerConstant(); } return { dtype: "int", lo, hi, defvalue, enums }; } throw this.compileError(`I expected a data type here.`); } parseEnumIdent() { let tok = this.expectTokenTypes([TokenType2.Ident]); return tok; } parseEnumValue(tok, field) { if (!field.enums) throw new ECSError(`field is not an enum`); let value = field.enums[tok.str]; if (value == null) throw new ECSError(`unknown enum "${tok.str}"`); return value; } parseDataValue(field) { var _a, _b; let tok = this.peekToken(); if (tok.type == TokenType2.Ident && field.dtype == "int") { return this.parseEnumValue(this.consumeToken(), field); } if (tok.type == TokenType2.Ident) { let entity = (_a = this.currentScope) == null ? void 0 : _a.getEntityByName(tok.str); if (!entity) this.compileError('no entity named "${tok.str}"'); else { this.consumeToken(); this.expectToken("."); let fieldName = this.expectIdent().str; let constValue = (_b = this.currentScope) == null ? void 0 : _b.getConstValue(entity, fieldName); if (constValue == null) throw new ECSError(`"${fieldName}" is not defined as a constant`, entity); else return constValue; } } if (tok.str == "[") { return new Uint8Array(this.parseDataArray()); } if (tok.str == "#") { this.consumeToken(); let reftype = field.dtype == "ref" ? field : void 0; return this.parseEntityForwardRef(reftype); } return this.parseIntegerConstant(); } parseEntityForwardRef(reftype) { let token = this.expectIdent(); return { reftype, token }; } parseDataArray() { this.expectToken("["); let arr = this.parseList(this.parseIntegerConstant, ","); this.expectToken("]"); return arr; } expectInteger() { let s = this.consumeToken().str; let i; if (s.startsWith("$")) i = parseInt(s.substring(1), 16); else if (s.startsWith("%")) i = parseInt(s.substring(1), 2); else i = parseInt(s); if (isNaN(i)) this.compileError("There should be an integer here."); return i; } parseSystem() { let name = this.expectIdent().str; let actions = []; let system = { name, actions }; let cmd; while ((cmd = this.expectTokens(["on", "locals", "end"]).str) != "end") { if (cmd == "on") { let action = this.annotate(() => this.parseAction(system)); actions.push(action); } else if (cmd == "locals") { system.tempbytes = this.parseIntegerConstant(); } else { this.compileError(`Unexpected system keyword: ${cmd}`); } } return system; } parseResource() { let name = this.expectIdent().str; let tempbytes; if (this.peekToken().str == "locals") { this.consumeToken(); tempbytes = this.parseIntegerConstant(); } let system = { name, tempbytes, actions: [] }; let expr = this.annotate(() => this.parseBlockStatement()); let action = { expr, event: name }; system.actions.push(action); return system; } parseAction(system) { const event = this.expectIdent().str; this.expectToken("do"); let fitbytes = void 0; let critical = void 0; if (this.ifToken("critical")) critical = true; if (this.ifToken("fit")) fitbytes = this.parseIntegerConstant(); let expr = this.annotate(() => this.parseBlockStatement()); let action = { expr, event, fitbytes, critical }; return action; } parseQuery() { let q = { include: [] }; let start = this.expectToken("["); this.parseList(() => this.parseQueryItem(q), ","); this.expectToken("]"); q.$loc = mergeLocs2(start.$loc, this.lasttoken.$loc); return q; } parseQueryItem(q) { let prefix = this.peekToken(); if (prefix.type != TokenType2.Ident) { this.consumeToken(); } if (prefix.type == TokenType2.Ident) { let cref = this.parseComponentRef(); q.include.push(cref); } else if (prefix.str == "-") { let cref = this.parseComponentRef(); if (!q.exclude) q.exclude = []; q.exclude.push(cref); } else if (prefix.str == "#") { const scope = this.currentScope; if (scope == null) { throw this.compileError("You can only reference specific entities inside of a scope."); } let eref = this.parseEntityForwardRef(); this.deferred.push(() => { let refvalue = this.resolveEntityRef(scope, eref); if (!q.entities) q.entities = []; q.entities.push(scope.entities[refvalue]); }); } else { this.compileError(`Query components may be preceded only by a '-'.`); } } parseEventName() { return this.expectIdent().str; } parseEventList() { return this.parseList(this.parseEventName, ","); } parseCode() { let tok = this.expectTokenTypes([ECSTokenType.CodeFragment]); let code = tok.str.substring(3, tok.str.length - 3); let lines = code.split("\n"); if (this.includeDebugInfo) this.addDebugInfo(lines, tok.$loc.line); code = lines.join("\n"); return code; } addDebugInfo(lines, startline) { const re = /^\s*(;|\/\/|$)/; for (let i = 0; i < lines.length; i++) { if (!lines[i].match(re)) lines[i] = this.em.dialect.debug_line(this.path, startline + i) + "\n" + lines[i]; } } parseScope() { let name = this.expectIdent().str; let scope = this.em.newScope(name, this.currentScope || void 0); scope.filePath = this.path; this.currentScope = scope; let cmd; while ((cmd = this.expectTokens(["end", "using", "entity", "scope", "comment", "system"]).str) != "end") { if (cmd == "using") { this.parseScopeUsing(); } if (cmd == "entity") { this.annotate(() => this.parseEntity()); } if (cmd == "scope") { this.annotate(() => this.parseScope()); } if (cmd == "comment") { this.expectTokenTypes([ECSTokenType.CodeFragment]); } if (cmd == "system") { let sys = this.annotate(() => this.parseSystem()); this.em.defineSystem(sys); this.currentScope.newSystemInstanceWithDefaults(sys); } } this.currentScope = scope.parent || null; return scope; } parseScopeUsing() { var _a; let instlist = this.parseList(this.parseSystemInstanceRef, ","); let params = {}; if (this.peekToken().str == "with") { this.consumeToken(); params = this.parseSystemInstanceParameters(); } for (let inst of instlist) { inst.params = params; (_a = this.currentScope) == null ? void 0 : _a.newSystemInstance(inst); } } parseEntity() { if (!this.currentScope) { throw this.internalError(); } const scope = this.currentScope; let entname = ""; if (this.peekToken().type == TokenType2.Ident) { entname = this.expectIdent().str; } let etype = this.parseEntityArchetype(); let entity = this.currentScope.newEntity(etype, entname); let cmd2; while ((cmd2 = this.expectTokens(["const", "init", "var", "decode", "end"]).str) != "end") { let cmd = cmd2; if (cmd == "var") cmd = "init"; if (cmd == "init" || cmd == "const") { this.parseInitConst(cmd, scope, entity); } else if (cmd == "decode") { this.parseDecode(scope, entity); } } return entity; } parseInitConst(cmd, scope, entity) { let name = this.expectIdent().str; let { c, f } = this.getEntityField(entity, name); let symtype = scope.isConstOrInit(c, name); if (symtype && symtype != cmd) this.compileError(`I can't mix const and init values for a given field in a scope.`); this.expectToken("="); let valueOrRef = this.parseDataValue(f); if (valueOrRef.token != null) { this.deferred.push(() => { this.lasttoken = valueOrRef.token; let refvalue = this.resolveEntityRef(scope, valueOrRef); if (cmd == "const") scope.setConstValue(entity, c, f, refvalue); if (cmd == "init") scope.setInitValue(entity, c, f, refvalue); }); } else { if (cmd == "const") scope.setConstValue(entity, c, f, valueOrRef); if (cmd == "init") scope.setInitValue(entity, c, f, valueOrRef); } } parseDecode(scope, entity) { let decoderid = this.expectIdent().str; let codetok = this.expectTokenTypes([ECSTokenType.CodeFragment]); let code = codetok.str; code = code.substring(3, code.length - 3); let decoder = newDecoder(decoderid, code); if (!decoder) { throw this.compileError(`I can't find a "${decoderid}" decoder.`); } let result; try { result = decoder.parse(); } catch (e) { throw new ECSError(e.message, decoder.getErrorLocation(codetok.$loc)); } for (let entry of Object.entries(result.properties)) { let { c, f } = this.getEntityField(entity, entry[0]); scope.setConstValue(entity, c, f, entry[1]); } } getEntityField(e, name) { if (!this.currentScope) { throw this.internalError(); } let comps = this.em.componentsWithFieldName([e.etype], name); if (comps.length == 0) this.compileError(`I couldn't find a field named "${name}" for this entity.`); if (comps.length > 1) this.compileError(`I found more than one field named "${name}" for this entity.`); let component = comps[0]; let field = component.fields.find((f) => f.name == name); if (!field) { throw this.internalError(); } return { c: component, f: field }; } parseEntityArchetype() { this.expectToken("["); let components = this.parseList(this.parseComponentRef, ","); this.expectToken("]"); return { components }; } parseComponentRef() { let name = this.expectIdent().str; let cref = this.em.getComponentByName(name); if (!cref) this.compileError(`I couldn't find a component named "${name}".`); return cref; } findEntityByName(scope, token) { let name = token.str; let eref = scope.entities.find((e) => e.name == name); if (!eref) { throw this.compileError(`I couldn't find an entity named "${name}" in this scope.`, token.$loc); } return eref; } resolveEntityRef(scope, ref) { let id = this.findEntityByName(scope, ref.token).id; if (ref.reftype) { let atypes = this.em.archetypesMatching(ref.reftype.query); let entities = scope.entitiesMatching(atypes); if (entities.length == 0) throw this.compileError(`This entity doesn't seem to fit the reference type.`, ref.token.$loc); id -= entities[0].id; } return id; } parseSystemInstanceRef() { let name = this.expectIdent().str; let system = this.em.getSystemByName(name); if (!system) throw this.compileError(`I couldn't find a system named "${name}".`, this.lasttoken.$loc); let params = {}; let inst = { system, params, id: 0 }; return inst; } parseSystemInstanceParameters() { let scope = this.currentScope; if (scope == null) throw this.internalError(); if (this.peekToken().str == "[") { return { query: this.parseQuery() }; } this.expectToken("#"); let entname = this.expectIdent(); this.expectToken("."); let fieldname = this.expectIdent(); let entity = this.findEntityByName(scope, entname); let cf = this.getEntityField(entity, fieldname.str); return { refEntity: entity, refField: cf }; } exportToFile(src) { this.em.exportToFile(src); } export() { let src = new SourceFileExport(); src.line(this.em.dialect.debug_file(this.path)); for (let path of Object.keys(this.em.imported)) src.line(this.em.dialect.debug_file(path)); this.exportToFile(src); return src.toString(); } checkUpperLimit(value, upper, what) { if (value > upper) this.compileError(`This ${what} is too high; must be ${upper} or less`); } checkLowerLimit(value, lower, what) { if (value < lower) this.compileError(`This ${what} is too low; must be ${lower} or more`); } parseConstant() { let expr = this.parseExpr(); expr = this.em.evalExpr(expr, this.currentScope); if (isLiteral2(expr)) return expr.value; throw this.compileError("This expression is not a constant."); } parseIntegerConstant() { let value = this.parseConstant(); if (typeof value === "number") return value; throw this.compileError("This expression is not an integer."); } parseExpr() { var startloc = this.peekToken().$loc; var expr = this.parseExpr1(this.parsePrimary(), 0); var endloc = this.lasttoken.$loc; expr.$loc = mergeLocs2(startloc, endloc); return expr; } parseExpr1(left, minPred) { let look = this.peekToken(); while (getPrecedence2(look) >= minPred) { let op = this.consumeToken(); let right = this.parsePrimary(); look = this.peekToken(); while (getPrecedence2(look) > getPrecedence2(op)) { right = this.parseExpr1(right, getPrecedence2(look)); look = this.peekToken(); } var opfn = getOperator2(op.str).f; if (op.str == "and") opfn = "land"; if (op.str == "or") opfn = "lor"; var valtype = this.exprTypeForOp(opfn, left, right, op); left = { valtype, op: opfn, left, right }; } return left; } parsePrimary() { let tok = this.consumeToken(); switch (tok.type) { case ECSTokenType.Integer: this.pushbackToken(tok); let value = this.expectInteger(); let valtype = { dtype: "int", lo: value, hi: value }; return { valtype, value }; case TokenType2.Ident: if (tok.str == "not") { let expr = this.parsePrimary(); let valtype2 = { dtype: "int", lo: 0, hi: 1 }; return { valtype: valtype2, op: "lnot", expr }; } else { this.pushbackToken(tok); return this.parseVarSubscriptOrFunc(); } case ECSTokenType.Operator: if (tok.str == "(") { let expr = this.parseExpr(); this.expectToken(")", `There should be another expression or a ")" here.`); return expr; } else if (tok.str == "-") { let expr = this.parsePrimary(); let valtype2 = expr.valtype; if ((valtype2 == null ? void 0 : valtype2.dtype) == "int") { let hi = Math.abs(valtype2.hi); let negtype = { dtype: "int", lo: -hi, hi }; return { valtype: negtype, op: "neg", expr }; } } else if (tok.str == "+") { return this.parsePrimary(); } default: throw this.compileError(`The expression is incomplete.`); } } parseVarSubscriptOrFunc() { var tok = this.consumeToken(); switch (tok.type) { case TokenType2.Ident: if (this.ifToken(":")) { let ftok = this.consumeToken(); let component = this.em.getComponentByName(tok.str); if (!component) throw this.compileError(`A component named "${tok.str}" has not been defined.`); let field = component.fields.find((f) => f.name == ftok.str); if (!field) throw this.compileError(`There is no "${ftok.str}" field in the ${tok.str} component.`); if (!this.currentScope) throw this.compileError(`This operation only works inside of a scope.`); let atypes = this.em.archetypesMatching({ include: [component] }); let entities = this.currentScope.entitiesMatching(atypes); return { entities, field }; } if (this.ifToken(".")) { let ftok = this.consumeToken(); if (!this.currentScope) throw this.compileError(`This operation only works inside of a scope.`); let entity = this.currentScope.getEntityByName(tok.str); if (!entity) throw this.compileError(`An entity named "${tok.str}" has not been defined.`); let component = this.em.singleComponentWithFieldName([entity.etype], ftok.str, ftok); let field = component.fields.find((f) => f.name == ftok.str); if (!field) throw this.compileError(`There is no "${ftok.str}" field in this entity.`); let entities = [entity]; return { entities, field }; } let args = []; if (this.ifToken("(")) { args = this.parseExprList(); this.expectToken(")", `There should be another expression or a ")" here.`); } var loc = mergeLocs2(tok.$loc, this.lasttoken.$loc); var valtype = this.exprTypeForSubscript(tok.str, args, loc); return { valtype, name: tok.str, args, $loc: loc }; default: throw this.compileError(`There should be a variable name here.`); } } parseLexpr() { var lexpr = this.parseVarSubscriptOrFunc(); return lexpr; } exprTypeForOp(fnname, left, right, optok) { return { dtype: "int", lo: 0, hi: 255 }; } exprTypeForSubscript(fnname, args, loc) { return { dtype: "int", lo: 0, hi: 255 }; } parseLexprList() { return this.parseList(this.parseLexpr, ","); } parseExprList() { return this.parseList(this.parseExpr, ","); } parseBlockStatement() { let valtype = { dtype: "int", lo: 0, hi: 0 }; if (this.peekToken().type == ECSTokenType.CodeFragment) { return { valtype, code: this.parseCode() }; } if (this.ifToken("begin")) { let stmts = []; while (this.peekToken().str != "end") { stmts.push(this.annotate(() => this.parseBlockStatement())); } this.expectToken("end"); return { valtype, stmts }; } let cmd = this.peekToken(); if (SELECT_TYPE.includes(cmd.str)) { return this.parseQueryStatement(); } throw this.compileError(`There should be a statement or "end" here.`, cmd.$loc); } parseQueryStatement() { const select = this.expectTokens(SELECT_TYPE).str; let all = this.ifToken("all") != null; let query = void 0; let join = void 0; if (select == "once") { if (this.peekToken().str == "[") this.compileError(`A "${select}" action can't include a query.`); } else { query = this.parseQuery(); } if (select == "join") { this.expectToken("with"); join = this.parseQuery(); } if (this.ifToken("limit")) { if (!query) { this.compileError(`A "${select}" query can't include a limit.`); } else query.limit = this.parseIntegerConstant(); } const all_modifiers = ["asc", "desc"]; const modifiers = this.parseModifiers(all_modifiers); let direction = void 0; if (modifiers["asc"]) direction = "asc"; else if (modifiers["desc"]) direction = "desc"; let body = this.annotate(() => this.parseBlockStatement()); return { select, query, join, direction, all, stmts: [body], loop: select == "foreach" }; } }; // src/worker/tools/ecs.ts function assembleECS(step) { let em = new EntityManager(new Dialect_CA65()); let compiler = new ECSCompiler(em, true); compiler.getImportFile = (path) => { return getWorkFileAsString(path); }; gatherFiles(step, { mainFilePath: "main.ecs" }); if (step.mainfile) em.mainPath = step.path; var destpath = step.prefix + ".ca65"; if (staleFiles(step, [destpath])) { let code = getWorkFileAsString(step.path); fixParamsWithDefines(step.path, step.params); try { compiler.includeDebugInfo = true; compiler.parseFile(code, step.path); let outtext = compiler.export().toString(); putWorkFile(destpath, outtext); var listings = {}; listings[destpath] = { lines: [], text: outtext }; var debuginfo = compiler.em.getDebugTree(); } catch (e) { if (e instanceof ECSError) { compiler.addError(e.message, e.$loc); for (let obj of e.$sources) { let name = obj.event; if (name == "start") break; compiler.addError(`... ${name}`, obj.$loc); } return { errors: compiler.errors }; } else if (e instanceof CompileError3) { return { errors: compiler.errors }; } else { throw e; } } return { nexttool: "ca65", path: destpath, args: [destpath], files: [destpath].concat(step.files), listings, debuginfo }; } } // src/worker/workermain.ts var ENVIRONMENT_IS_WEB = typeof window === "object"; var ENVIRONMENT_IS_WORKER = typeof importScripts === "function"; var emglobal = ENVIRONMENT_IS_WORKER ? self : ENVIRONMENT_IS_WEB ? window : global; if (!emglobal["require"]) { emglobal["require"] = (modpath) => { if (modpath.endsWith(".js")) modpath = modpath.slice(-3); var modname = modpath.split("/").slice(-1)[0]; var hasNamespace = emglobal[modname] != null; console.log("@@@ require", modname, modpath, hasNamespace); if (!hasNamespace) { exports = {}; importScripts(`${modpath}.js`); } if (emglobal[modname] == null) { emglobal[modname] = exports; } return emglobal[modname]; }; } var _WASM_module_cache = {}; var CACHE_WASM_MODULES = true; var wasmMemory; function getWASMMemory() { if (wasmMemory == null) { wasmMemory = new WebAssembly.Memory({ "initial": 1024, "maximum": 16384 }); } return wasmMemory; } function getWASMModule(module_id) { var module = _WASM_module_cache[module_id]; if (!module) { starttime(); module = new WebAssembly.Module(wasmBlob[module_id]); if (CACHE_WASM_MODULES) { _WASM_module_cache[module_id] = module; delete wasmBlob[module_id]; } endtime("module creation " + module_id); } return module; } function moduleInstFn(module_id) { return function(imports, ri) { var mod = getWASMModule(module_id); var inst = new WebAssembly.Instance(mod, imports); ri(inst); return inst.exports; }; } var PLATFORM_PARAMS = { "vcs": { arch: "6502", code_start: 4096, code_size: 61440, data_start: 128, data_size: 128, wiz_rom_ext: ".a26", wiz_inc_dir: "2600", extra_link_files: ["atari2600.cfg"], cfgfile: "atari2600.cfg" }, "mw8080bw": { arch: "z80", code_start: 0, rom_size: 8192, data_start: 8192, data_size: 1024, stack_end: 9216 }, "vicdual": { arch: "z80", code_start: 0, rom_size: 16416, data_start: 58368, data_size: 1024, stack_end: 59392 }, "galaxian": { arch: "z80", code_start: 0, rom_size: 16384, data_start: 16384, data_size: 1024, stack_end: 18432 }, "galaxian-scramble": { arch: "z80", code_start: 0, rom_size: 20512, data_start: 16384, data_size: 1024, stack_end: 18432 }, "williams": { arch: "6809", code_start: 0, rom_size: 49152, data_start: 38912, data_size: 10240, stack_end: 49152, set_stack_end: 49152, extra_link_files: ["williams.scr", "libcmoc-crt-vec.a", "libcmoc-std-vec.a"], extra_link_args: ["-swilliams.scr", "-lcmoc-crt-vec", "-lcmoc-std-vec"], extra_compile_files: ["assert.h", "cmoc.h", "stdarg.h", "stdlib.h"] }, "williams-defender": { arch: "6809", code_start: 0, rom_size: 49152, data_start: 38912, data_size: 10240, stack_end: 49152 }, "williams-z80": { arch: "z80", code_start: 0, rom_size: 38912, data_start: 38912, data_size: 10240, stack_end: 49152 }, "vector-z80color": { arch: "z80", code_start: 0, rom_size: 32768, data_start: 57344, data_size: 8192, stack_end: 0 }, "vector-ataricolor": { arch: "6502", define: ["__VECTOR__"], cfgfile: "vector-color.cfg", libargs: ["crt0.o", "none.lib"], extra_link_files: ["crt0.o", "vector-color.cfg"] }, "sound_williams-z80": { arch: "z80", code_start: 0, rom_size: 16384, data_start: 16384, data_size: 1024, stack_end: 32768 }, "base_z80": { arch: "z80", code_start: 0, rom_size: 32768, data_start: 32768, data_size: 32768, stack_end: 0 }, "coleco": { arch: "z80", rom_start: 32768, code_start: 33024, rom_size: 32768, data_start: 28672, data_size: 1024, stack_end: 32768, extra_preproc_args: ["-I", "/share/include/coleco", "-D", "CV_CV"], extra_link_args: ["-k", "/share/lib/coleco", "-l", "libcv", "-l", "libcvu", "crt0.rel"] }, "msx": { arch: "z80", rom_start: 16384, code_start: 16384, rom_size: 32768, data_start: 49152, data_size: 12288, stack_end: 65535, extra_link_args: ["crt0-msx.rel"], extra_link_files: ["crt0-msx.rel", "crt0-msx.lst"], wiz_sys_type: "z80", wiz_inc_dir: "msx" }, "msx-libcv": { arch: "z80", rom_start: 16384, code_start: 16384, rom_size: 32768, data_start: 49152, data_size: 12288, stack_end: 65535, extra_preproc_args: ["-I", ".", "-D", "CV_MSX"], extra_link_args: ["-k", ".", "-l", "libcv-msx", "-l", "libcvu-msx", "crt0-msx.rel"], extra_link_files: ["libcv-msx.lib", "libcvu-msx.lib", "crt0-msx.rel", "crt0-msx.lst"], extra_compile_files: ["cv.h", "cv_graphics.h", "cv_input.h", "cv_sound.h", "cv_support.h", "cvu.h", "cvu_c.h", "cvu_compression.h", "cvu_f.h", "cvu_graphics.h", "cvu_input.h", "cvu_sound.h"] }, "sms-sg1000-libcv": { arch: "z80", rom_start: 0, code_start: 256, rom_size: 49152, data_start: 49152, data_size: 1024, stack_end: 57344, extra_preproc_args: ["-I", ".", "-D", "CV_SMS"], extra_link_args: ["-k", ".", "-l", "libcv-sms", "-l", "libcvu-sms", "crt0-sms.rel"], extra_link_files: ["libcv-sms.lib", "libcvu-sms.lib", "crt0-sms.rel", "crt0-sms.lst"], extra_compile_files: ["cv.h", "cv_graphics.h", "cv_input.h", "cv_sound.h", "cv_support.h", "cvu.h", "cvu_c.h", "cvu_compression.h", "cvu_f.h", "cvu_graphics.h", "cvu_input.h", "cvu_sound.h"] }, "nes": { arch: "6502", define: ["__NES__"], cfgfile: "neslib2.cfg", libargs: [ "crt0.o", "nes.lib", "neslib2.lib", "-D", "NES_MAPPER=0", "-D", "NES_PRG_BANKS=2", "-D", "NES_CHR_BANKS=1", "-D", "NES_MIRRORING=0" ], extra_link_files: ["crt0.o", "neslib2.lib", "neslib2.cfg", "nesbanked.cfg"], wiz_rom_ext: ".nes" }, "apple2": { arch: "6502", define: ["__APPLE2__"], cfgfile: "apple2.cfg", libargs: ["--lib-path", "/share/target/apple2/drv", "-D", "__EXEHDR__=0", "apple2.lib"], __CODE_RUN__: 16384, code_start: 2051 }, "apple2-e": { arch: "6502", define: ["__APPLE2__"], cfgfile: "apple2.cfg", libargs: ["apple2.lib"] }, "atari8-800xl.disk": { arch: "6502", define: ["__ATARI__"], cfgfile: "atari.cfg", libargs: ["atari.lib"], fastbasic_cfgfile: "fastbasic-cart.cfg" }, "atari8-800xl": { arch: "6502", define: ["__ATARI__"], cfgfile: "atari-cart.cfg", libargs: ["atari.lib", "-D", "__CARTFLAGS__=4"], fastbasic_cfgfile: "fastbasic-cart.cfg" }, "atari8-800": { arch: "6502", define: ["__ATARI__"], cfgfile: "atari-cart.cfg", libargs: ["atari.lib", "-D", "__CARTFLAGS__=4"], fastbasic_cfgfile: "fastbasic-cart.cfg" }, "atari8-5200": { arch: "6502", define: ["__ATARI5200__"], cfgfile: "atari5200.cfg", libargs: ["atari5200.lib", "-D", "__CARTFLAGS__=255"], fastbasic_cfgfile: "fastbasic-cart.cfg" }, "verilog": { arch: "verilog", extra_compile_files: ["8bitworkshop.v"] }, "astrocade": { arch: "z80", code_start: 8192, rom_size: 8192, data_start: 19984, data_size: 496, stack_end: 20480 }, "astrocade-arcade": { arch: "z80", code_start: 0, rom_size: 16384, data_start: 32224, data_size: 544, stack_end: 32768 }, "astrocade-bios": { arch: "z80", code_start: 0, rom_size: 8192, data_start: 20430, data_size: 50, stack_end: 20430 }, "atari7800": { arch: "6502", define: ["__ATARI7800__"], cfgfile: "atari7800.cfg", libargs: ["crt0.o", "none.lib"], extra_link_files: ["crt0.o", "atari7800.cfg"] }, "c64": { arch: "6502", define: ["__CBM__", "__C64__"], cfgfile: "c64.cfg", libargs: ["c64.lib"] }, "vic20": { arch: "6502", define: ["__CBM__", "__VIC20__"], cfgfile: "vic20.cfg", libargs: ["vic20.lib"] }, "kim1": { arch: "6502" }, "vectrex": { arch: "6809", code_start: 0, rom_size: 32768, data_start: 51328, data_size: 896, stack_end: 52224, extra_compile_files: ["assert.h", "cmoc.h", "stdarg.h", "vectrex.h", "stdlib.h", "bios.h"], extra_link_files: ["vectrex.scr", "libcmoc-crt-vec.a", "libcmoc-std-vec.a"], extra_compile_args: ["--vectrex"], extra_link_args: ["-svectrex.scr", "-lcmoc-crt-vec", "-lcmoc-std-vec"] }, "x86": { arch: "x86" }, "zx": { arch: "z80", code_start: 23755, rom_size: 65368 - 23755, data_start: 61440, data_size: 65024 - 61440, stack_end: 65368, extra_link_args: ["crt0-zx.rel"], extra_link_files: ["crt0-zx.rel", "crt0-zx.lst"] }, "devel-6502": { arch: "6502", cfgfile: "devel-6502.cfg", libargs: ["crt0.o", "none.lib"], extra_link_files: ["crt0.o", "devel-6502.cfg"] }, "cpc.rslib": { arch: "z80", code_start: 16384, rom_size: 45312 - 16384, data_start: 45312, data_size: 45312 - 49152, stack_end: 49152, extra_compile_files: ["cpcrslib.h"], extra_link_args: ["crt0-cpc.rel", "cpcrslib.lib"], extra_link_files: ["crt0-cpc.rel", "crt0-cpc.lst", "cpcrslib.lib", "cpcrslib.lst"] }, "cpc": { arch: "z80", code_start: 16384, rom_size: 45312 - 16384, data_start: 45312, data_size: 45312 - 49152, stack_end: 49152, extra_compile_files: ["cpctelera.h"], extra_link_args: ["crt0-cpc.rel", "cpctelera.lib"], extra_link_files: ["crt0-cpc.rel", "crt0-cpc.lst", "cpctelera.lib", "cpctelera.lst"] } }; PLATFORM_PARAMS["sms-sms-libcv"] = PLATFORM_PARAMS["sms-sg1000-libcv"]; PLATFORM_PARAMS["sms-gg-libcv"] = PLATFORM_PARAMS["sms-sms-libcv"]; var _t1; function starttime() { _t1 = new Date(); } function endtime(msg) { var _t2 = new Date(); console.log(msg, _t2.getTime() - _t1.getTime(), "ms"); } var FileWorkingStore = class { constructor() { this.workfs = {}; this.workerseq = 0; this.reset(); } reset() { this.workfs = {}; this.newVersion(); } currentVersion() { return this.workerseq; } newVersion() { let ts = new Date().getTime(); if (ts <= this.workerseq) ts = ++this.workerseq; return ts; } putFile(path, data) { var encoding = typeof data === "string" ? "utf8" : "binary"; var entry = this.workfs[path]; if (!entry || !compareData(entry.data, data) || entry.encoding != encoding) { this.workfs[path] = entry = { path, data, encoding, ts: this.newVersion() }; console.log("+++", entry.path, entry.encoding, entry.data.length, entry.ts); } return entry; } hasFile(path) { return this.workfs[path] != null; } getFileData(path) { return this.workfs[path] && this.workfs[path].data; } getFileAsString(path) { let data = this.getFileData(path); if (data != null && typeof data !== "string") throw new Error(`${path}: expected string`); return data; } getFileEntry(path) { return this.workfs[path]; } setItem(key, value) { this.items[key] = value; } }; var store = new FileWorkingStore(); function errorResult(msg) { return { errors: [{ line: 0, msg }] }; } var Builder = class { constructor() { this.steps = []; this.startseq = 0; } wasChanged(entry) { return entry.ts > this.startseq; } async executeBuildSteps() { this.startseq = store.currentVersion(); var linkstep = null; while (this.steps.length) { var step = this.steps.shift(); var platform = step.platform; var toolfn = TOOLS[step.tool]; if (!toolfn) throw Error("no tool named " + step.tool); step.params = PLATFORM_PARAMS[getBasePlatform(platform)]; try { step.result = await toolfn(step); } catch (e) { console.log("EXCEPTION", e, e.stack); return errorResult(e + ""); } if (step.result) { step.result.params = step.params; if (step.debuginfo) { let r = step.result; if (!r.debuginfo) r.debuginfo = {}; Object.assign(r.debuginfo, step.debuginfo); } if ("errors" in step.result && step.result.errors.length) { applyDefaultErrorPath(step.result.errors, step.path); return step.result; } if ("output" in step.result && step.result.output) { return step.result; } if ("linktool" in step.result) { if (linkstep) { linkstep.files = linkstep.files.concat(step.result.files); linkstep.args = linkstep.args.concat(step.result.args); } else { linkstep = { tool: step.result.linktool, platform, files: step.result.files, args: step.result.args }; } linkstep.debuginfo = step.debuginfo; } if ("nexttool" in step.result) { var asmstep = __spreadValues({ tool: step.result.nexttool, platform }, step.result); this.steps.push(asmstep); } if (this.steps.length == 0 && linkstep) { this.steps.push(linkstep); linkstep = null; } } } } async handleMessage(data) { this.steps = []; if (data.updates) { data.updates.forEach((u) => store.putFile(u.path, u.data)); } if (data.setitems) { data.setitems.forEach((i) => store.setItem(i.key, i.value)); } if (data.buildsteps) { this.steps.push.apply(this.steps, data.buildsteps); } if (data.code) { this.steps.push(data); } if (this.steps.length) { var result = await this.executeBuildSteps(); return result ? result : { unchanged: true }; } console.log("Unknown message", data); } }; var builder = new Builder(); function applyDefaultErrorPath(errors, path) { if (!path) return; for (var i = 0; i < errors.length; i++) { var err = errors[i]; if (!err.path && err.line) err.path = path; } } function compareData(a, b) { if (a.length != b.length) return false; if (typeof a === "string" && typeof b === "string") { return a == b; } else { for (var i = 0; i < a.length; i++) { if (a[i] != b[i]) return false; } return true; } } function putWorkFile(path, data) { return store.putFile(path, data); } function getWorkFileAsString(path) { return store.getFileAsString(path); } function populateEntry(fs, path, entry, options) { var data = entry.data; if (options && options.processFn) { data = options.processFn(path, data); } var toks = path.split("/"); if (toks.length > 1) { for (var i = 0; i < toks.length - 1; i++) try { fs.mkdir(toks[i]); } catch (e) { } } fs.writeFile(path, data, { encoding: entry.encoding }); var time = new Date(entry.ts); fs.utime(path, time, time); console.log("<<<", path, entry.data.length); } function gatherFiles(step, options) { var maxts = 0; if (step.files) { for (var i = 0; i < step.files.length; i++) { var path = step.files[i]; var entry = store.workfs[path]; if (!entry) { throw new Error("No entry for path '" + path + "'"); } else { maxts = Math.max(maxts, entry.ts); } } } else if (step.code) { var path = step.path ? step.path : options.mainFilePath; if (!path) throw Error("need path or mainFilePath"); var code = step.code; var entry = putWorkFile(path, code); step.path = path; step.files = [path]; maxts = entry.ts; } else if (step.path) { var path = step.path; var entry = store.workfs[path]; maxts = entry.ts; step.files = [path]; } if (step.path && !step.prefix) { step.prefix = getPrefix(step.path); } step.maxts = maxts; return maxts; } function getPrefix(s) { var pos = s.lastIndexOf("."); return pos > 0 ? s.substring(0, pos) : s; } function populateFiles(step, fs, options) { gatherFiles(step, options); if (!step.files) throw Error("call gatherFiles() first"); for (var i = 0; i < step.files.length; i++) { var path = step.files[i]; populateEntry(fs, path, store.workfs[path], options); } } function populateExtraFiles(step, fs, extrafiles) { if (extrafiles) { for (var i = 0; i < extrafiles.length; i++) { var xfn = extrafiles[i]; if (store.workfs[xfn]) { fs.writeFile(xfn, store.workfs[xfn].data, { encoding: "binary" }); continue; } var xpath = "lib/" + getBasePlatform(step.platform) + "/" + xfn; var xhr = new XMLHttpRequest(); xhr.responseType = "arraybuffer"; xhr.open("GET", PWORKER + xpath, false); xhr.send(null); if (xhr.response && xhr.status == 200) { var data = new Uint8Array(xhr.response); fs.writeFile(xfn, data, { encoding: "binary" }); putWorkFile(xfn, data); console.log(":::", xfn, data.length); } else { throw Error("Could not load extra file " + xpath); } } } } function staleFiles(step, targets) { if (!step.maxts) throw Error("call populateFiles() first"); for (var i = 0; i < targets.length; i++) { var entry = store.workfs[targets[i]]; if (!entry || step.maxts > entry.ts) return true; } console.log("unchanged", step.maxts, targets); return false; } function anyTargetChanged(step, targets) { if (!step.maxts) throw Error("call populateFiles() first"); for (var i = 0; i < targets.length; i++) { var entry = store.workfs[targets[i]]; if (!entry || entry.ts > step.maxts) return true; } console.log("unchanged", step.maxts, targets); return false; } function execMain(step, mod, args) { starttime(); var run = mod.callMain || mod.run; run(args); endtime(step.tool); } var fsMeta = {}; var fsBlob = {}; var wasmBlob = {}; var PSRC = "../../src/"; var PWORKER = PSRC + "worker/"; function loadFilesystem(name) { var xhr = new XMLHttpRequest(); xhr.responseType = "blob"; xhr.open("GET", PWORKER + "fs/fs" + name + ".data", false); xhr.send(null); fsBlob[name] = xhr.response; xhr = new XMLHttpRequest(); xhr.responseType = "json"; xhr.open("GET", PWORKER + "fs/fs" + name + ".js.metadata", false); xhr.send(null); fsMeta[name] = xhr.response; console.log("Loaded " + name + " filesystem", fsMeta[name].files.length, "files", fsBlob[name].size, "bytes"); } var loaded = {}; function load(modulename, debug2) { if (!loaded[modulename]) { importScripts(PWORKER + "asmjs/" + modulename + (debug2 ? "." + debug2 + ".js" : ".js")); loaded[modulename] = 1; } } function loadWASM(modulename, debug2) { if (!loaded[modulename]) { importScripts(PWORKER + "wasm/" + modulename + (debug2 ? "." + debug2 + ".js" : ".js")); var xhr = new XMLHttpRequest(); xhr.responseType = "arraybuffer"; xhr.open("GET", PWORKER + "wasm/" + modulename + ".wasm", false); xhr.send(null); if (xhr.response) { wasmBlob[modulename] = new Uint8Array(xhr.response); console.log("Loaded " + modulename + ".wasm (" + wasmBlob[modulename].length + " bytes)"); loaded[modulename] = 1; } else { throw Error("Could not load WASM file " + modulename + ".wasm"); } } } function loadNative(modulename) { if (CACHE_WASM_MODULES && typeof WebAssembly === "object") { loadWASM(modulename); } else { load(modulename); } } function setupFS(FS, name) { var WORKERFS = FS.filesystems["WORKERFS"]; if (name === "65-vector") name = "65-none"; if (name === "65-atari7800") name = "65-none"; if (name === "65-devel") name = "65-none"; if (name === "65-vcs") name = "65-none"; if (!fsMeta[name]) throw Error("No filesystem for '" + name + "'"); FS.mkdir("/share"); FS.mount(WORKERFS, { packages: [{ metadata: fsMeta[name], blob: fsBlob[name] }] }, "/share"); var reader = WORKERFS.reader; var blobcache = {}; WORKERFS.stream_ops.read = function(stream, buffer, offset, length, position) { if (position >= stream.node.size) return 0; var contents = blobcache[stream.path]; if (!contents) { var ab = reader.readAsArrayBuffer(stream.node.contents); contents = blobcache[stream.path] = new Uint8Array(ab); } if (position + length > contents.length) length = contents.length - position; for (var i = 0; i < length; i++) { buffer[offset + i] = contents[position + i]; } return length; }; } var print_fn = function(s) { console.log(s); }; var re_msvc = /[/]*([^( ]+)\s*[(](\d+)[)]\s*:\s*(.+?):\s*(.*)/; var re_msvc2 = /\s*(at)\s+(\d+)\s*(:)\s*(.*)/; function msvcErrorMatcher(errors) { return function(s) { var matches = re_msvc.exec(s) || re_msvc2.exec(s); if (matches) { var errline = parseInt(matches[2]); errors.push({ line: errline, path: matches[1], msg: matches[4] }); } else { console.log(s); } }; } function makeErrorMatcher(errors, regex, iline, imsg, mainpath, ifilename) { return function(s) { var matches = regex.exec(s); if (matches) { errors.push({ line: parseInt(matches[iline]) || 1, msg: matches[imsg], path: ifilename ? matches[ifilename] : mainpath }); } else { console.log("??? " + s); } }; } function extractErrors(regex, strings, path, iline, imsg, ifilename) { var errors = []; var matcher = makeErrorMatcher(errors, regex, iline, imsg, path, ifilename); for (var i = 0; i < strings.length; i++) { matcher(strings[i]); } return errors; } var re_crlf = /\r?\n/; var re_lineoffset = /\s*(\d+)\s+[%]line\s+(\d+)\+(\d+)\s+(.+)/; function parseListing(code, lineMatch, iline, ioffset, iinsns, icycles, funcMatch, segMatch) { var lines = []; var lineofs = 0; var segment = ""; var func = ""; var funcbase = 0; code.split(re_crlf).forEach((line, lineindex) => { let segm = segMatch && segMatch.exec(line); if (segm) { segment = segm[1]; } let funcm = funcMatch && funcMatch.exec(line); if (funcm) { funcbase = parseInt(funcm[1], 16); func = funcm[2]; } var linem = lineMatch.exec(line); if (linem && linem[1]) { var linenum = iline < 0 ? lineindex : parseInt(linem[iline]); var offset = parseInt(linem[ioffset], 16); var insns = linem[iinsns]; var cycles = icycles ? parseInt(linem[icycles]) : null; var iscode = cycles > 0; if (insns) { lines.push({ line: linenum + lineofs, offset: offset - funcbase, insns, cycles, iscode, segment, func }); } } else { let m = re_lineoffset.exec(line); if (m) { lineofs = parseInt(m[2]) - parseInt(m[1]) - parseInt(m[3]); } } }); return lines; } function parseSourceLines(code, lineMatch, offsetMatch, funcMatch, segMatch) { var lines = []; var lastlinenum = 0; var segment = ""; var func = ""; var funcbase = 0; for (var line of code.split(re_crlf)) { let segm = segMatch && segMatch.exec(line); if (segm) { segment = segm[1]; } let funcm = funcMatch && funcMatch.exec(line); if (funcm) { funcbase = parseInt(funcm[1], 16); func = funcm[2]; } var linem = lineMatch.exec(line); if (linem && linem[1]) { lastlinenum = parseInt(linem[1]); } else if (lastlinenum) { var linem = offsetMatch.exec(line); if (linem && linem[1]) { var offset = parseInt(linem[1], 16); lines.push({ line: lastlinenum, offset: offset - funcbase, segment, func }); lastlinenum = 0; } } } return lines; } function setupStdin(fs, code) { var i = 0; fs.init(function() { return i < code.length ? code.charCodeAt(i++) : null; }); } function fixParamsWithDefines(path, params) { var libargs = params.libargs; if (path && libargs) { var code = getWorkFileAsString(path); if (code) { var oldcfgfile = params.cfgfile; var ident2index = {}; for (var i = 0; i < libargs.length; i++) { var toks = libargs[i].split("="); if (toks.length == 2) { ident2index[toks[0]] = i; } } var re = /^[;]?#define\s+(\w+)\s+(\S+)/gmi; var m; while (m = re.exec(code)) { var ident = m[1]; var value = m[2]; var index = ident2index[ident]; if (index >= 0) { libargs[index] = ident + "=" + value; console.log("Using libargs", index, libargs[index]); if (ident == "NES_MAPPER" && value == "4") { params.cfgfile = "nesbanked.cfg"; console.log("using config file", params.cfgfile); } } else if (ident == "CFGFILE" && value) { params.cfgfile = value; } else if (ident == "LIBARGS" && value) { params.libargs = value.split(",").filter((s) => { return s != ""; }); console.log("Using libargs", params.libargs); } else if (ident == "CC65_FLAGS" && value) { params.extra_compiler_args = value.split(",").filter((s) => { return s != ""; }); console.log("Using compiler flags", params.extra_compiler_args); } } } } } function makeCPPSafe(s) { return s.replace(/[^A-Za-z0-9_]/g, "_"); } function preprocessMCPP(step, filesys) { load("mcpp"); var platform = step.platform; var params = PLATFORM_PARAMS[getBasePlatform(platform)]; if (!params) throw Error("Platform not supported: " + platform); var errors = []; var match_fn = makeErrorMatcher(errors, /:(\d+): (.+)/, 1, 2, step.path); var MCPP = emglobal.mcpp({ noInitialRun: true, noFSInit: true, print: print_fn, printErr: match_fn }); var FS = MCPP.FS; if (filesys) setupFS(FS, filesys); populateFiles(step, FS); populateExtraFiles(step, FS, params.extra_compile_files); var args = [ "-D", "__8BITWORKSHOP__", "-D", "__SDCC_z80", "-D", makeCPPSafe(platform.toUpperCase()), "-I", "/share/include", "-Q", step.path, "main.i" ]; if (step.mainfile) { args.unshift.apply(args, ["-D", "__MAIN__"]); } let platform_def = platform.toUpperCase().replaceAll(/[^a-zA-Z0-9]/g, "_"); args.unshift.apply(args, ["-D", `__PLATFORM_${platform_def}__`]); if (params.extra_preproc_args) { args.push.apply(args, params.extra_preproc_args); } execMain(step, MCPP, args); if (errors.length) return { errors }; var iout = FS.readFile("main.i", { encoding: "utf8" }); iout = iout.replace(/^#line /gm, "\n# "); try { var errout = FS.readFile("mcpp.err", { encoding: "utf8" }); if (errout.length) { var errors = extractErrors(/([^:]+):(\d+): (.+)/, errout.split("\n"), step.path, 2, 3, 1); if (errors.length == 0) { errors = errorResult(errout).errors; } return { errors }; } } catch (e) { } return { code: iout }; } function setupRequireFunction() { var exports2 = {}; exports2["jsdom"] = { JSDOM: function(a, b) { this.window = {}; } }; emglobal["require"] = (modname) => { console.log("require", modname, exports2[modname] != null); return exports2[modname]; }; } var TOOLS = { "dasm": assembleDASM, "cc65": compileCC65, "ca65": assembleCA65, "ld65": linkLD65, "sdasz80": assembleSDASZ80, "sdldz80": linkSDLDZ80, "sdcc": compileSDCC, "xasm6809": assembleXASM6809, "cmoc": compileCMOC, "lwasm": assembleLWASM, "lwlink": linkLWLINK, "verilator": compileVerilator, "yosys": compileYosys, "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, "ecs": assembleECS }; var TOOL_PRELOADFS = { "cc65-apple2": "65-apple2", "ca65-apple2": "65-apple2", "cc65-c64": "65-c64", "ca65-c64": "65-c64", "cc65-vic20": "65-vic20", "ca65-vic20": "65-vic20", "cc65-nes": "65-nes", "ca65-nes": "65-nes", "cc65-atari8": "65-atari8", "ca65-atari8": "65-atari8", "cc65-vector": "65-none", "ca65-vector": "65-none", "cc65-atari7800": "65-none", "ca65-atari7800": "65-none", "cc65-devel": "65-none", "ca65-devel": "65-none", "ca65-vcs": "65-none", "sdasz80": "sdcc", "sdcc": "sdcc", "sccz80": "sccz80", "bataribasic": "2600basic", "inform6": "inform", "fastbasic": "65-atari8", "silice": "Silice", "wiz": "wiz", "ecs-vcs": "65-none", "ecs-nes": "65-nes", "ecs-c64": "65-c64" }; async function handleMessage(data) { if (data.preload) { var fs = TOOL_PRELOADFS[data.preload]; if (!fs && data.platform) fs = TOOL_PRELOADFS[data.preload + "-" + getBasePlatform(data.platform)]; if (!fs && data.platform) fs = TOOL_PRELOADFS[data.preload + "-" + getRootBasePlatform(data.platform)]; if (fs && !fsMeta[fs]) loadFilesystem(fs); return; } if (data.reset) { store.reset(); return; } return builder.handleMessage(data); } if (ENVIRONMENT_IS_WORKER) { lastpromise = null; onmessage = async function(e) { await lastpromise; lastpromise = handleMessage(e.data); var result = await lastpromise; lastpromise = null; if (result) { try { postMessage(result); } catch (e2) { console.log(e2); postMessage(errorResult(`${e2}`)); } } }; } var lastpromise; })(); //# sourceMappingURL=bundle.js.map