silphius/app/util/script.js

149 lines
3.1 KiB
JavaScript
Raw Normal View History

import Sandbox from '@/astride/sandbox.js';
import TickingPromise from '@/util/ticking-promise.js';
import {LRUCache} from 'lru-cache';
const Populated = Symbol.for('sandbox.populated');
export const cache = new LRUCache({
max: 128,
});
export default class Script {
static swcInitialized = false;
constructor(sandbox) {
this.sandbox = sandbox;
this.promise = null;
}
get context() {
return this.sandbox.context;
}
static createContext(locals = {}) {
if (locals[Populated]) {
return locals;
}
return {
[Populated]: true,
...locals,
};
}
// async evaluate(callback) {
// this.sandbox.reset();
// let {done, value} = this.sandbox.next();
// if (value instanceof Promise) {
// await value;
// }
// while (!done) {
// ({done, value} = this.sandbox.next());
// if (value instanceof Promise) {
// // eslint-disable-next-line no-await-in-loop
// await value;
// }
// }
// if (value instanceof Promise) {
// value.then(callback);
// }
// else {
// callback(value);
// }
// }
static async fromCode(code, context = {}) {
let ast;
if (cache.has(code)) {
ast = cache.get(code);
}
else {
cache.set(code, ast = await this.parse(code));
}
return new this(
new Sandbox(ast, this.createContext(context)),
);
}
static async parse(code) {
const {default: initSwc, parse} = await import('@swc/wasm-web');
if (!this.swcInitialized) {
await initSwc();
this.swcInitialized = true;
}
return parse(
code,
{
allowReturnOutsideFunction: true,
syntax: 'ecmascript',
},
);
}
reset() {
this.promise = null;
this.sandbox.compile();
}
tick(elapsed, resolve, reject) {
if (this.promise) {
if (this.promise instanceof TickingPromise) {
this.promise.tick(elapsed);
}
return;
}
while (true) {
this.sandbox.context.elapsed = elapsed;
const {done, value} = this.sandbox.step();
if (value) {
const {value: result} = value;
if (result instanceof Promise) {
this.promise = result;
result
.catch(reject)
.then(() => {
if (done) {
resolve();
}
})
.finally(() => {
this.promise = null;
});
break;
}
}
if (done) {
resolve();
break;
}
}
}
tickingPromise() {
return new TickingPromise(
() => {},
(elapsed, resolve, reject) => {
this.tick(elapsed, resolve, reject);
},
);
}
static tickingPromise(code, context = {}) {
let tickingPromise;
return new TickingPromise(
(resolve) => {
this.fromCode(code, context)
.then((script) => {
tickingPromise = script.tickingPromise();
resolve(tickingPromise);
})
},
(elapsed) => {
tickingPromise?.tick?.(elapsed);
},
);
}
};