1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-10 21:29:33 +00:00
8bitworkshop/gen/common/script/env.js

304 lines
12 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Environment = exports.PROP_CONSTRUCTOR_NAME = void 0;
const ErrorStackParser = require("error-stack-parser");
const yufka_1 = __importDefault(require("yufka"));
const bitmap = __importStar(require("./lib/bitmap"));
const io = __importStar(require("./lib/io"));
const output = __importStar(require("./lib/output"));
const color = __importStar(require("./lib/color"));
const scriptui = __importStar(require("./lib/scriptui"));
exports.PROP_CONSTRUCTOR_NAME = "$$consname";
const IMPORTS = {
'bitmap': bitmap,
'io': io,
'output': output,
'color': color,
'ui': scriptui,
};
const LINE_NUMBER_OFFSET = 3; // TODO: shouldnt need?
const GLOBAL_BADLIST = [
'eval'
];
const GLOBAL_GOODLIST = [
'eval',
'Math', 'JSON',
'parseFloat', 'parseInt', 'isFinite', 'isNaN',
'String', 'Symbol', 'Number', 'Object', 'Boolean', 'NaN', 'Infinity', 'Date', 'BigInt',
'Set', 'Map', 'RegExp', 'Array', 'ArrayBuffer', 'DataView',
'Float32Array', 'Float64Array',
'Int8Array', 'Int16Array', 'Int32Array',
'Uint8Array', 'Uint16Array', 'Uint32Array', 'Uint8ClampedArray',
];
class RuntimeError extends Error {
constructor(loc, msg) {
super(msg);
this.loc = loc;
}
}
function setConstructorName(o) {
var _a, _b;
let name = (_b = (_a = Object.getPrototypeOf(o)) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name;
if (name != null && name != 'Object') {
o[exports.PROP_CONSTRUCTOR_NAME] = name;
}
}
class Environment {
constructor(globalenv, path) {
this.globalenv = globalenv;
this.path = path;
var badlst = Object.getOwnPropertyNames(this.globalenv).filter(name => GLOBAL_GOODLIST.indexOf(name) < 0);
this.builtins = Object.assign({ print: (...args) => this.print(args) }, IMPORTS);
this.preamble = `'use strict';var ${badlst.join(',')};`;
for (var impname in this.builtins) {
this.preamble += `var ${impname}=$$.${impname};`;
}
this.preamble += '{\n';
this.postamble = '\n}';
}
error(varname, msg) {
let obj = this.declvars && this.declvars[varname];
console.log('ERROR', varname, obj, this);
throw new RuntimeError(obj && obj.loc, msg);
}
print(args) {
if (args && args.length > 0 && args[0] != null) {
this.obj[`$print__${this.seq++}`] = args.length == 1 ? args[0] : args;
}
}
preprocess(code) {
this.declvars = {};
this.seq = 0;
let options = {
// https://www.npmjs.com/package/magic-string#sgeneratemap-options-
sourceMap: {
file: this.path,
source: this.path,
hires: false,
includeContent: false
},
// https://github.com/acornjs/acorn/blob/master/acorn/README.md
acorn: {
ecmaVersion: 6,
locations: true,
allowAwaitOutsideFunction: true,
allowReturnOutsideFunction: true,
allowReserved: true,
}
};
const result = (0, yufka_1.default)(code, options, (node, { update, source, parent }) => {
const isTopLevel = () => {
return parent() && parent().type === 'ExpressionStatement' && parent(2) && parent(2).type === 'Program';
};
const convertTopToPrint = () => {
if (isTopLevel()) {
let printkey = `$print__${this.seq++}`;
update(`this.${printkey} = io.data.load(${source()}, ${JSON.stringify(printkey)})`);
//update(`print(${source()});`)
}
};
const left = node['left'];
switch (node.type) {
// add preamble, postamble
case 'Program':
update(`${this.preamble}${source()}${this.postamble}`);
break;
// error on forbidden keywords
case 'Identifier':
if (GLOBAL_BADLIST.indexOf(source()) >= 0) {
update(`__FORBIDDEN__KEYWORD__${source()}__`); // TODO? how to preserve line number?
}
else {
convertTopToPrint();
}
break;
// x = expr --> var x = expr (first use)
case 'AssignmentExpression':
if (isTopLevel()) {
if (left && left.type === 'Identifier') {
if (!this.declvars[left.name]) {
update(`var ${left.name}=io.data.load(this.${source()}, ${JSON.stringify(left.name)})`);
this.declvars[left.name] = left;
}
else {
update(`${left.name}=this.${source()}`);
}
}
}
break;
// convert lone expressions to print()
case 'UnaryExpression':
case 'BinaryExpression':
case 'CallExpression':
case 'MemberExpression':
convertTopToPrint();
break;
// literal comments
case 'Literal':
if (typeof node['value'] === 'string' && isTopLevel()) {
update(`this.$doc__${this.seq++} = { literaltext: ${source()} };`);
}
else {
convertTopToPrint();
}
break;
}
});
return result.toString();
}
async run(code) {
// TODO: split into cells based on "--" linebreaks?
code = this.preprocess(code);
this.obj = {};
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
const fn = new AsyncFunction('$$', code).bind(this.obj, this.builtins);
await fn.call(this);
this.checkResult(this.obj, new Set(), []);
}
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
// TODO: return initial location of thingie
checkResult(o, checked, fullkey) {
if (o == null)
return;
if (checked.has(o))
return;
if (typeof o === 'object') {
setConstructorName(o);
delete o.$$callback; // clear callbacks (TODO? put somewhere else?)
if (o.length > 100)
return; // big array, don't bother
if (o.BYTES_PER_ELEMENT > 0)
return; // typed array, don't bother
checked.add(o); // so we don't recurse if cycle
function prkey() { return fullkey.join('.'); }
// go through all object properties recursively
for (var [key, value] of Object.entries(o)) {
if (value == null && fullkey.length == 0 && !key.startsWith("$")) {
this.error(key, `"${key}" has no value.`);
}
fullkey.push(key);
if (typeof value === 'function') {
if (fullkey.length == 1)
this.error(fullkey[0], `"${prkey()}" is a function. Did you forget to pass parameters?`); // TODO? did you mean (needs to see entire expr)
else
this.error(fullkey[0], `This expression may be incomplete, or it contains a function object: ${prkey()}`); // TODO? did you mean (needs to see entire expr)
}
if (typeof value === 'symbol') {
this.error(fullkey[0], `"${prkey()}" is a Symbol, and can't be used.`); // TODO?
}
if (value instanceof Promise) {
this.error(fullkey[0], `"${prkey()}" is unresolved. Use "await" before expression.`); // TODO?
}
this.checkResult(value, checked, fullkey);
fullkey.pop();
}
}
}
render() {
var cells = [];
for (var [key, value] of Object.entries(this.obj)) {
if (typeof value === 'function') {
// TODO: find other values, functions embedded in objects?
}
else {
var cell = { id: key, object: value };
cells.push(cell);
}
}
return cells;
}
extractErrors(e) {
let loc = e['loc'];
if (loc && loc.start && loc.end) {
return [{
path: this.path,
msg: e.message,
line: loc.start.line,
start: loc.start.column,
end: loc.end.line,
}];
}
if (loc && loc.line != null) {
return [{
path: this.path,
msg: e.message,
line: loc.line,
start: loc.column,
}];
}
// TODO: Cannot parse given Error object?
let frames = ErrorStackParser.parse(e);
let frame = frames.findIndex(f => f.functionName === 'anonymous');
let errors = [];
// if ErrorStackParser fails, resort to regex
if (frame < 0 && e.stack != null) {
let m = /.anonymous.:(\d+):(\d+)/g.exec(e.stack);
if (m != null) {
errors.push({
path: this.path,
msg: e.message,
line: parseInt(m[1]) - LINE_NUMBER_OFFSET,
});
}
}
// otherwise iterate thru all the frames
while (frame >= 0) {
console.log(frames[frame]);
if (frames[frame].fileName.endsWith('Function')) {
// TODO: use source map
errors.push({
path: this.path,
msg: e.message,
line: frames[frame].lineNumber - LINE_NUMBER_OFFSET,
//start: frames[frame].columnNumber,
});
}
--frame;
}
// if no stack frames parsed, last resort error msg
if (errors.length == 0) {
errors.push({
path: this.path,
msg: e.message,
line: 0
});
}
return errors;
}
commitLoadableState() {
// TODO: visit children?
for (let [key, value] of Object.entries(this.obj)) {
let loadable = value;
io.data.save(loadable, key);
}
return io.$$getData();
}
}
exports.Environment = Environment;
//# sourceMappingURL=env.js.map