mirror of
https://github.com/bendtherules/completion-viz.git
synced 2026-08-18 13:42:51 +00:00
270 lines
7.3 KiB
JavaScript
270 lines
7.3 KiB
JavaScript
import { Value } from './value.mjs';
|
|
import {
|
|
surroundingAgent,
|
|
ExecutionContext,
|
|
HostEnqueueFinalizationRegistryCleanupJob,
|
|
ScriptEvaluation,
|
|
} from './engine.mjs';
|
|
import {
|
|
X,
|
|
ThrowCompletion,
|
|
AbruptCompletion,
|
|
EnsureCompletion,
|
|
} from './completion.mjs';
|
|
import {
|
|
Realm,
|
|
ClearKeptObjects,
|
|
CreateIntrinsics,
|
|
SetRealmGlobalObject,
|
|
SetDefaultGlobalBindings,
|
|
} from './abstract-ops/all.mjs';
|
|
import {
|
|
ParseScript,
|
|
ParseModule,
|
|
} from './parse.mjs';
|
|
import { SourceTextModuleRecord } from './modules.mjs';
|
|
|
|
export * from './value.mjs';
|
|
export * from './engine.mjs';
|
|
export * from './completion.mjs';
|
|
export * from './abstract-ops/all.mjs';
|
|
export * from './static-semantics/all.mjs';
|
|
export * from './runtime-semantics/all.mjs';
|
|
export * from './environment.mjs';
|
|
export * from './parse.mjs';
|
|
export * from './modules.mjs';
|
|
export * from './inspect.mjs';
|
|
|
|
export function Throw(...args) {
|
|
return surroundingAgent.Throw(...args);
|
|
}
|
|
|
|
export function gc() {
|
|
// #sec-weakref-execution
|
|
// At any time, if a set of objects S is not live, an ECMAScript implementation may perform the following steps atomically:
|
|
// 1. For each obj of S, do
|
|
// a. For each WeakRef ref such that ref.[[WeakRefTarget]] is obj,
|
|
// i. Set ref.[[WeakRefTarget]] to empty.
|
|
// b. For each FinalizationRegistry fg such that fg.[[Cells]] contains cell, and cell.[[WeakRefTarget]] is obj,
|
|
// i. Set cell.[[WeakRefTarget]] to empty.
|
|
// ii. Optionally, perform ! HostEnqueueFinalizationRegistryCleanupJob(fg).
|
|
// c. For each WeakMap map such that map.WeakMapData contains a record r such that r.Key is obj,
|
|
// i. Set r.[[Key]] to empty.
|
|
// ii. Set r.[[Value]] to empty.
|
|
// d. For each WeakSet set such that set.[[WeakSetData]] contains obj,
|
|
// i. Replace the element of set whose value is obj with an element whose value is empty.
|
|
|
|
const marked = new Set();
|
|
const weakrefs = new Set();
|
|
const fgs = new Set();
|
|
const weakmaps = new Set();
|
|
const weaksets = new Set();
|
|
const ephemeronQueue = [];
|
|
|
|
const markCb = (O) => {
|
|
if (typeof O !== 'object' || O === null) {
|
|
return;
|
|
}
|
|
|
|
if (marked.has(O)) {
|
|
return;
|
|
}
|
|
marked.add(O);
|
|
|
|
if ('WeakRefTarget' in O && !('HeldValue' in O)) {
|
|
weakrefs.add(O);
|
|
markCb(O.properties);
|
|
markCb(O.Prototype);
|
|
} else if ('Cells' in O) {
|
|
fgs.add(O);
|
|
markCb(O.properties);
|
|
markCb(O.Prototype);
|
|
O.Cells.forEach((cell) => {
|
|
markCb(cell.HeldValue);
|
|
});
|
|
} else if ('WeakMapData' in O) {
|
|
weakmaps.add(O);
|
|
markCb(O.properties);
|
|
markCb(O.Prototype);
|
|
O.WeakMapData.forEach((r) => {
|
|
ephemeronQueue.push(r);
|
|
});
|
|
} else if ('WeakSetData' in O) {
|
|
weaksets.add(O);
|
|
markCb(O.properties);
|
|
markCb(O.Prototype);
|
|
} else if (O.mark) {
|
|
O.mark(markCb);
|
|
}
|
|
};
|
|
|
|
markCb(surroundingAgent);
|
|
|
|
while (ephemeronQueue.length > 0) {
|
|
const item = ephemeronQueue.shift();
|
|
if (marked.has(item.Key)) {
|
|
markCb(item.Value);
|
|
}
|
|
}
|
|
|
|
weakrefs.forEach((ref) => {
|
|
if (!marked.has(ref.WeakRefTarget)) {
|
|
ref.WeakRefTarget = undefined;
|
|
}
|
|
});
|
|
|
|
fgs.forEach((fg) => {
|
|
let dirty = false;
|
|
fg.Cells.forEach((cell) => {
|
|
if (!marked.has(cell.WeakRefTarget)) {
|
|
cell.WeakRefTarget = undefined;
|
|
dirty = true;
|
|
}
|
|
});
|
|
if (dirty) {
|
|
X(HostEnqueueFinalizationRegistryCleanupJob(fg));
|
|
}
|
|
});
|
|
|
|
weakmaps.forEach((map) => {
|
|
map.WeakMapData.forEach((r) => {
|
|
if (!marked.has(r.Key)) {
|
|
r.Key = undefined;
|
|
r.Value = undefined;
|
|
}
|
|
});
|
|
});
|
|
|
|
weaksets.forEach((set) => {
|
|
set.WeakSetData.forEach((obj, i) => {
|
|
if (!marked.has(obj)) {
|
|
set.WeakSetData[i] = undefined;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// https://tc39.es/ecma262/#sec-jobs
|
|
export function runJobQueue() {
|
|
if (surroundingAgent.executionContextStack.some((e) => e.ScriptOrModule !== Value.null)) {
|
|
return;
|
|
}
|
|
|
|
// At some future point in time, when there is no running execution context
|
|
// and the execution context stack is empty, the implementation must:
|
|
while (surroundingAgent.jobQueue.length > 0) { // eslint-disable-line no-constant-condition
|
|
const {
|
|
job: abstractClosure,
|
|
callerRealm,
|
|
callerScriptOrModule,
|
|
} = surroundingAgent.jobQueue.shift();
|
|
|
|
// 1. Perform any implementation-defined preparation steps.
|
|
const newContext = new ExecutionContext();
|
|
surroundingAgent.executionContextStack.push(newContext);
|
|
newContext.Function = Value.null;
|
|
newContext.Realm = callerRealm;
|
|
newContext.ScriptOrModule = callerScriptOrModule;
|
|
// 2. Call the abstract closure.
|
|
X(abstractClosure());
|
|
// 3. Perform any host-defined cleanup steps, after which the execution context stack must be empty.
|
|
ClearKeptObjects();
|
|
gc();
|
|
surroundingAgent.executionContextStack.pop(newContext);
|
|
}
|
|
}
|
|
|
|
export function evaluateScript(sourceText, realm, hostDefined) {
|
|
const s = ParseScript(sourceText, realm, hostDefined);
|
|
if (Array.isArray(s)) {
|
|
return ThrowCompletion(s[0]);
|
|
}
|
|
|
|
return EnsureCompletion(ScriptEvaluation(s));
|
|
}
|
|
|
|
export class ManagedRealm extends Realm {
|
|
constructor(HostDefined = {}) {
|
|
super();
|
|
// CreateRealm()
|
|
CreateIntrinsics(this);
|
|
this.GlobalObject = Value.undefined;
|
|
this.GlobalEnv = Value.undefined;
|
|
this.TemplateMap = [];
|
|
|
|
// InitializeHostDefinedRealm()
|
|
const newContext = new ExecutionContext();
|
|
newContext.Function = Value.null;
|
|
newContext.Realm = this;
|
|
newContext.ScriptOrModule = Value.null;
|
|
surroundingAgent.executionContextStack.push(newContext);
|
|
SetRealmGlobalObject(this, Value.undefined, Value.undefined);
|
|
SetDefaultGlobalBindings(this);
|
|
|
|
// misc
|
|
surroundingAgent.executionContextStack.pop(newContext);
|
|
this.HostDefined = HostDefined;
|
|
this.topContext = newContext;
|
|
this.active = false;
|
|
}
|
|
|
|
scope(cb) {
|
|
if (this.active) {
|
|
return cb();
|
|
}
|
|
this.active = true;
|
|
surroundingAgent.executionContextStack.push(this.topContext);
|
|
const r = cb();
|
|
surroundingAgent.executionContextStack.pop(this.topContext);
|
|
this.active = false;
|
|
return r;
|
|
}
|
|
|
|
evaluateScript(sourceText, { specifier } = {}) {
|
|
if (typeof sourceText !== 'string') {
|
|
throw new TypeError('sourceText must be a string');
|
|
}
|
|
|
|
const res = this.scope(() => {
|
|
const realm = surroundingAgent.currentRealmRecord;
|
|
return evaluateScript(sourceText, realm, {
|
|
specifier,
|
|
public: { specifier },
|
|
});
|
|
});
|
|
|
|
if (!(res instanceof AbruptCompletion)) {
|
|
runJobQueue();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
createSourceTextModule(specifier, sourceText) {
|
|
if (typeof sourceText !== 'string') {
|
|
throw new TypeError('sourceText must be a string');
|
|
}
|
|
if (typeof specifier !== 'string') {
|
|
throw new TypeError('specifier must be a string');
|
|
}
|
|
const module = this.scope(() => ParseModule(sourceText, this, {
|
|
specifier,
|
|
SourceTextModuleRecord: ManagedSourceTextModuleRecord,
|
|
}));
|
|
if (Array.isArray(module)) {
|
|
return ThrowCompletion(module[0]);
|
|
}
|
|
return module;
|
|
}
|
|
}
|
|
|
|
class ManagedSourceTextModuleRecord extends SourceTextModuleRecord {
|
|
Evaluate() {
|
|
const r = super.Evaluate();
|
|
if (!(r instanceof AbruptCompletion)) {
|
|
runJobQueue();
|
|
}
|
|
return r;
|
|
}
|
|
}
|