mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-18 03:05:35 +00:00
9827 lines
289 KiB
JavaScript
9827 lines
289 KiB
JavaScript
(() => {
|
|
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+ ;<stdin>:(\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 += `<svg viewBox="0 0 ${r.width} ${r.height}" xmlns="http://www.w3.org/2000/svg"><style><![CDATA[text {font: 1px sans-serif;}]]></style>`;
|
|
for (let bin of this.bins) {
|
|
let be = bin.extents;
|
|
s += "<g>";
|
|
s += `<rect width="${be.right - be.left}" height="${be.bottom - be.top}" stroke="black" stroke-width="0.5" fill="none"/>`;
|
|
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 += `<rect width="${b.right - b.left}" height="${b.bottom - b.top}" x="${b.left}" y="${b.top}" stroke="black" stroke-width="0.25" fill="#ccc"/>`;
|
|
if (b.top == texty)
|
|
textx += 10;
|
|
else
|
|
textx = be.right + 1;
|
|
texty = b.top;
|
|
if (box.label)
|
|
s += `<text x="${textx}" y="${texty}" height="1">${box.label}</text>`;
|
|
}
|
|
}
|
|
s += "</g>";
|
|
}
|
|
s += `</svg>`;
|
|
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, /<stdin>:(\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
|