mirror of
https://github.com/bendtherules/completion-viz.git
synced 2026-08-19 00:31:34 +00:00
405 lines
13 KiB
JavaScript
405 lines
13 KiB
JavaScript
import { Value } from './value.mjs';
|
|
import {
|
|
AbruptCompletion,
|
|
EnsureCompletion,
|
|
NormalCompletion,
|
|
ThrowCompletion,
|
|
Q, X,
|
|
} from './completion.mjs';
|
|
import {
|
|
IsCallable,
|
|
Call, Construct, Assert, GetModuleNamespace,
|
|
PerformPromiseThen, CreateBuiltinFunction,
|
|
GetActiveScriptOrModule,
|
|
CleanupFinalizationRegistry,
|
|
CreateArrayFromList,
|
|
} from './abstract-ops/all.mjs';
|
|
import { GlobalDeclarationInstantiation } from './runtime-semantics/all.mjs';
|
|
import { Evaluate } from './evaluator.mjs';
|
|
import { CyclicModuleRecord } from './modules.mjs';
|
|
import { CallSite, unwind } from './helpers.mjs';
|
|
import * as messages from './messages.mjs';
|
|
|
|
export const FEATURES = Object.freeze([
|
|
{
|
|
name: 'Top-Level Await',
|
|
flag: 'top-level-await',
|
|
url: 'https://github.com/tc39/proposal-top-level-await',
|
|
},
|
|
{
|
|
name: 'Hashbang Grammar',
|
|
flag: 'hashbang',
|
|
url: 'https://github.com/tc39/proposal-hashbang',
|
|
},
|
|
{
|
|
name: 'Numeric Separators',
|
|
flag: 'numeric-separators',
|
|
url: 'https://github.com/tc39/proposal-numeric-separator',
|
|
},
|
|
{
|
|
name: 'RegExp Match Indices',
|
|
flag: 'regexp-match-indices',
|
|
url: 'https://github.com/tc39/proposal-regexp-match-indices',
|
|
},
|
|
{
|
|
name: 'FinalizationRegistry.prototype.cleanupSome',
|
|
flag: 'cleanup-some',
|
|
url: 'https://github.com/tc39/proposal-cleanup-some',
|
|
},
|
|
{
|
|
name: 'Arbitrary Module Namespace Names',
|
|
flag: 'arbitrary-module-namespace-names',
|
|
url: 'https://github.com/tc39/ecma262/pull/2154',
|
|
},
|
|
].map(Object.freeze));
|
|
|
|
let agentSignifier = 0;
|
|
// #sec-agents
|
|
export class Agent {
|
|
constructor(options = {}) {
|
|
// #table-agent-record
|
|
const Signifier = agentSignifier;
|
|
agentSignifier += 1;
|
|
this.AgentRecord = {
|
|
LittleEndian: Value.true,
|
|
CanBlock: Value.true,
|
|
Signifier,
|
|
IsLockFree1: Value.true,
|
|
IsLockFree2: Value.true,
|
|
CandidateExecution: undefined,
|
|
KeptAlive: new Set(),
|
|
};
|
|
|
|
// #execution-context-stack
|
|
this.executionContextStack = [];
|
|
const stackPop = this.executionContextStack.pop;
|
|
this.executionContextStack.pop = function pop(ctx) {
|
|
if (!ctx.poppedForTailCall) {
|
|
const popped = stackPop.call(this);
|
|
Assert(popped === ctx);
|
|
}
|
|
};
|
|
|
|
// NON-SPEC
|
|
this.jobQueue = [];
|
|
this.hostDefinedOptions = {
|
|
...options,
|
|
features: FEATURES.reduce((acc, { flag }) => {
|
|
if (options.features) {
|
|
acc[flag] = options.features.includes(flag);
|
|
} else {
|
|
acc[flag] = false;
|
|
}
|
|
return acc;
|
|
}, {}),
|
|
};
|
|
}
|
|
|
|
// #running-execution-context
|
|
get runningExecutionContext() {
|
|
return this.executionContextStack[this.executionContextStack.length - 1];
|
|
}
|
|
|
|
// #current-realm
|
|
get currentRealmRecord() {
|
|
return this.runningExecutionContext.Realm;
|
|
}
|
|
|
|
// #active-function-object
|
|
get activeFunctionObject() {
|
|
return this.runningExecutionContext.Function;
|
|
}
|
|
|
|
// Get an intrinsic by name for the current realm
|
|
intrinsic(name) {
|
|
return this.currentRealmRecord.Intrinsics[name];
|
|
}
|
|
|
|
// Generate a throw completion using message templates
|
|
Throw(type, template, ...templateArgs) {
|
|
if (type instanceof Value) {
|
|
return ThrowCompletion(type);
|
|
}
|
|
const message = messages[template](...templateArgs);
|
|
const cons = this.currentRealmRecord.Intrinsics[`%${type}%`];
|
|
let error;
|
|
if (type === 'AggregateError') {
|
|
error = X(Construct(cons, [
|
|
X(CreateArrayFromList([])),
|
|
new Value(message),
|
|
]));
|
|
} else {
|
|
error = X(Construct(cons, [new Value(message)]));
|
|
}
|
|
return ThrowCompletion(error);
|
|
}
|
|
|
|
queueJob(queueName, job) {
|
|
const callerContext = this.runningExecutionContext;
|
|
const callerRealm = callerContext.Realm;
|
|
const callerScriptOrModule = GetActiveScriptOrModule();
|
|
const pending = {
|
|
queueName,
|
|
job,
|
|
callerRealm,
|
|
callerScriptOrModule,
|
|
};
|
|
this.jobQueue.push(pending);
|
|
}
|
|
|
|
// NON-SPEC: Check if a feature is enabled in this agent.
|
|
feature(name) {
|
|
return this.hostDefinedOptions.features[name];
|
|
}
|
|
|
|
// NON-SPEC
|
|
mark(m) {
|
|
this.AgentRecord.KeptAlive.forEach((v) => {
|
|
m(v);
|
|
});
|
|
this.executionContextStack.forEach((e) => {
|
|
m(e);
|
|
});
|
|
this.jobQueue.forEach((j) => {
|
|
m(j.callerRealm);
|
|
m(j.callerScriptOrModule);
|
|
});
|
|
}
|
|
}
|
|
|
|
export let surroundingAgent;
|
|
export function setSurroundingAgent(a) {
|
|
surroundingAgent = a;
|
|
}
|
|
|
|
// #sec-execution-contexts
|
|
export class ExecutionContext {
|
|
constructor() {
|
|
this.codeEvaluationState = undefined;
|
|
this.Function = undefined;
|
|
this.Realm = undefined;
|
|
this.ScriptOrModule = undefined;
|
|
this.VariableEnvironment = undefined;
|
|
this.LexicalEnvironment = undefined;
|
|
|
|
// NON-SPEC
|
|
this.callSite = new CallSite(this);
|
|
this.promiseCapability = undefined;
|
|
this.poppedForTailCall = false;
|
|
}
|
|
|
|
copy() {
|
|
const e = new ExecutionContext();
|
|
e.codeEvaluationState = this.codeEvaluationState;
|
|
e.Function = this.Function;
|
|
e.Realm = this.Realm;
|
|
e.ScriptOrModule = this.ScriptOrModule;
|
|
e.VariableEnvironment = this.VariableEnvironment;
|
|
e.LexicalEnvironment = this.LexicalEnvironment;
|
|
|
|
e.callSite = this.callSite.clone(e);
|
|
e.promiseCapability = this.promiseCapability;
|
|
return e;
|
|
}
|
|
|
|
// NON-SPEC
|
|
mark(m) {
|
|
m(this.Function);
|
|
m(this.Realm);
|
|
m(this.ScriptOrModule);
|
|
m(this.VariableEnvironment);
|
|
m(this.LexicalEnvironment);
|
|
m(this.promiseCapability);
|
|
}
|
|
}
|
|
|
|
// 15.1.10 #sec-runtime-semantics-scriptevaluation
|
|
export function ScriptEvaluation(scriptRecord) {
|
|
if (surroundingAgent.hostDefinedOptions.boost) {
|
|
return surroundingAgent.hostDefinedOptions.boost.evaluateScript(scriptRecord);
|
|
}
|
|
|
|
const globalEnv = scriptRecord.Realm.GlobalEnv;
|
|
const scriptContext = new ExecutionContext();
|
|
scriptContext.Function = Value.null;
|
|
scriptContext.Realm = scriptRecord.Realm;
|
|
scriptContext.ScriptOrModule = scriptRecord;
|
|
scriptContext.VariableEnvironment = globalEnv;
|
|
scriptContext.LexicalEnvironment = globalEnv;
|
|
scriptContext.HostDefined = scriptRecord.HostDefined;
|
|
// Suspend runningExecutionContext
|
|
surroundingAgent.executionContextStack.push(scriptContext);
|
|
const scriptBody = scriptRecord.ECMAScriptCode;
|
|
let result = EnsureCompletion(GlobalDeclarationInstantiation(scriptBody, globalEnv));
|
|
|
|
if (result.Type === 'normal') {
|
|
result = EnsureCompletion(unwind(Evaluate(scriptBody)));
|
|
}
|
|
|
|
if (result.Type === 'normal' && !result.Value) {
|
|
result = NormalCompletion(Value.undefined);
|
|
}
|
|
|
|
// Suspend scriptCtx
|
|
surroundingAgent.executionContextStack.pop(scriptContext);
|
|
// Resume(surroundingAgent.runningExecutionContext);
|
|
|
|
return result;
|
|
}
|
|
|
|
// #sec-hostenqueuepromisejob
|
|
export function HostEnqueuePromiseJob(job, _realm) {
|
|
surroundingAgent.queueJob('PromiseJobs', job);
|
|
}
|
|
|
|
// #sec-agentsignifier
|
|
export function AgentSignifier() {
|
|
// 1. Let AR be the Agent Record of the surrounding agent.
|
|
const AR = surroundingAgent.AgentRecord;
|
|
// 2. Return AR.[[Signifier]].
|
|
return AR.Signifier;
|
|
}
|
|
|
|
export function HostEnsureCanCompileStrings(callerRealm, calleeRealm) {
|
|
if (surroundingAgent.hostDefinedOptions.ensureCanCompileStrings !== undefined) {
|
|
Q(surroundingAgent.hostDefinedOptions.ensureCanCompileStrings(callerRealm, calleeRealm));
|
|
}
|
|
return NormalCompletion(undefined);
|
|
}
|
|
|
|
export function HostPromiseRejectionTracker(promise, operation) {
|
|
const realm = surroundingAgent.currentRealmRecord;
|
|
if (realm && realm.HostDefined.promiseRejectionTracker) {
|
|
X(realm.HostDefined.promiseRejectionTracker(promise, operation));
|
|
}
|
|
}
|
|
|
|
export function HostHasSourceTextAvailable(func) {
|
|
if (surroundingAgent.hostDefinedOptions.hasSourceTextAvailable) {
|
|
return X(surroundingAgent.hostDefinedOptions.hasSourceTextAvailable(func));
|
|
}
|
|
return Value.true;
|
|
}
|
|
|
|
export function HostResolveImportedModule(referencingScriptOrModule, specifier) {
|
|
const realm = referencingScriptOrModule.Realm || surroundingAgent.currentRealmRecord;
|
|
if (realm.HostDefined.resolveImportedModule) {
|
|
specifier = specifier.stringValue();
|
|
if (referencingScriptOrModule !== Value.null) {
|
|
if (!referencingScriptOrModule.HostDefined.moduleMap) {
|
|
referencingScriptOrModule.HostDefined.moduleMap = new Map();
|
|
}
|
|
if (referencingScriptOrModule.HostDefined.moduleMap.has(specifier)) {
|
|
return referencingScriptOrModule.HostDefined.moduleMap.get(specifier);
|
|
}
|
|
}
|
|
const resolved = Q(realm.HostDefined.resolveImportedModule(referencingScriptOrModule, specifier));
|
|
if (referencingScriptOrModule !== Value.null) {
|
|
referencingScriptOrModule.HostDefined.moduleMap.set(specifier, resolved);
|
|
}
|
|
return resolved;
|
|
}
|
|
return surroundingAgent.Throw('Error', 'CouldNotResolveModule', specifier);
|
|
}
|
|
|
|
function FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, completion) {
|
|
// 1. If completion is an abrupt completion, then perform ! Call(promiseCapability.[[Reject]], undefined, « completion.[[Value]] »).
|
|
if (completion instanceof AbruptCompletion) {
|
|
X(Call(promiseCapability.Reject, Value.undefined, [completion.Value]));
|
|
} else { // 2. Else,
|
|
// a. Assert: completion is a normal completion and completion.[[Value]] is undefined.
|
|
Assert(completion instanceof NormalCompletion);
|
|
// b. Let moduleRecord be ! HostResolveImportedModule(referencingScriptOrModule, specifier).
|
|
const moduleRecord = X(HostResolveImportedModule(referencingScriptOrModule, specifier));
|
|
// c. Assert: Evaluate has already been invoked on moduleRecord and successfully completed.
|
|
// d. Let namespace be GetModuleNamespace(moduleRecord).
|
|
const namespace = EnsureCompletion(GetModuleNamespace(moduleRecord));
|
|
// e. If namespace is an abrupt completion, perform ! Call(promiseCapability.[[Reject]], undefined, « namespace.[[Value]] »).
|
|
if (namespace instanceof AbruptCompletion) {
|
|
X(Call(promiseCapability.Reject, Value.undefined, [namespace.Value]));
|
|
} else {
|
|
// f. Else, perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace.[[Value]] »).
|
|
X(Call(promiseCapability.Resolve, Value.undefined, [namespace.Value]));
|
|
}
|
|
}
|
|
}
|
|
|
|
export function HostImportModuleDynamically(referencingScriptOrModule, specifier, promiseCapability) {
|
|
surroundingAgent.queueJob('ImportModuleDynamicallyJobs', () => {
|
|
const finish = (c) => FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, c);
|
|
const c = (() => {
|
|
const module = Q(HostResolveImportedModule(referencingScriptOrModule, specifier));
|
|
Q(module.Link());
|
|
const maybePromise = Q(module.Evaluate());
|
|
if (module instanceof CyclicModuleRecord) {
|
|
const onFulfilled = CreateBuiltinFunction(([v = Value.undefined]) => {
|
|
finish(NormalCompletion(v));
|
|
return Value.undefined;
|
|
}, []);
|
|
const onRejected = CreateBuiltinFunction(([r = Value.undefined]) => {
|
|
finish(ThrowCompletion(r));
|
|
return Value.undefined;
|
|
}, []);
|
|
PerformPromiseThen(maybePromise, onFulfilled, onRejected);
|
|
} else {
|
|
finish(NormalCompletion(undefined));
|
|
}
|
|
})();
|
|
if (c instanceof AbruptCompletion) {
|
|
finish(c);
|
|
}
|
|
});
|
|
return NormalCompletion(Value.undefined);
|
|
}
|
|
|
|
// #sec-hostgetimportmetaproperties
|
|
export function HostGetImportMetaProperties(moduleRecord) {
|
|
const realm = surroundingAgent.currentRealmRecord;
|
|
if (realm.HostDefined.getImportMetaProperties) {
|
|
return X(realm.HostDefined.getImportMetaProperties(moduleRecord.HostDefined.public));
|
|
}
|
|
return [];
|
|
}
|
|
|
|
// #sec-hostfinalizeimportmeta
|
|
export function HostFinalizeImportMeta(importMeta, moduleRecord) {
|
|
const realm = surroundingAgent.currentRealmRecord;
|
|
if (realm.HostDefined.finalizeImportMeta) {
|
|
return X(realm.HostDefined.finalizeImportMeta(importMeta, moduleRecord.HostDefined.public));
|
|
}
|
|
return Value.undefined;
|
|
}
|
|
|
|
// #sec-host-cleanup-finalization-registry
|
|
const scheduledForCleanup = new Set();
|
|
export function HostEnqueueFinalizationRegistryCleanupJob(fg) {
|
|
if (surroundingAgent.hostDefinedOptions.cleanupFinalizationRegistry !== undefined) {
|
|
Q(surroundingAgent.hostDefinedOptions.cleanupFinalizationRegistry(fg));
|
|
} else {
|
|
if (!scheduledForCleanup.has(fg)) {
|
|
scheduledForCleanup.add(fg);
|
|
surroundingAgent.queueJob('FinalizationCleanup', () => {
|
|
scheduledForCleanup.delete(fg);
|
|
CleanupFinalizationRegistry(fg);
|
|
});
|
|
}
|
|
}
|
|
return NormalCompletion(undefined);
|
|
}
|
|
|
|
// #sec-hostmakejobcallback
|
|
export function HostMakeJobCallback(callback) {
|
|
// 1. Assert: IsCallable(callback) is true.
|
|
Assert(IsCallable(callback) === Value.true);
|
|
// 2. Return the JobCallback Record { [[Callback]]: callback, [[HostDefined]]: empty }.
|
|
return { Callback: callback, HostDefined: undefined };
|
|
}
|
|
|
|
// #sec-hostcalljobcallback
|
|
export function HostCallJobCallback(jobCallback, V, argumentsList) {
|
|
// 1. Assert: IsCallable(jobCallback.[[Callback]]) is true.
|
|
Assert(IsCallable(jobCallback.Callback) === Value.true);
|
|
// 1. Return ? Call(jobCallback.[[Callback]], V, argumentsList).
|
|
return Q(Call(jobCallback.Callback, V, argumentsList));
|
|
}
|