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)); }