mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
483 lines
20 KiB
TypeScript
483 lines
20 KiB
TypeScript
import { type GCMarker, surroundingAgent } from './host-defined/engine.mts';
|
|
import {
|
|
JSStringValue, Value, type Arguments,
|
|
} from './value.mts';
|
|
import {
|
|
callable,
|
|
kAsyncContext,
|
|
OutOfRange,
|
|
resume,
|
|
} from './helpers.mts';
|
|
import type { Evaluator, ValueEvaluator } from './evaluator.mts';
|
|
import {
|
|
Assert,
|
|
CreateBuiltinFunction,
|
|
PerformPromiseThen,
|
|
PromiseCapabilityRecord,
|
|
PromiseResolve,
|
|
type IteratorRecord,
|
|
} from '#self';
|
|
import { skipDebugger } from '#self';
|
|
|
|
let createNormalCompletion: <T>(init: NormalCompletionInit<T>) => NormalCompletionImpl<T>;
|
|
let createBreakCompletion: (init: BreakCompletionInit) => BreakCompletion;
|
|
let createContinueCompletion: (init: ContinueCompletionInit) => ContinueCompletion;
|
|
let createReturnCompletion: (init: ReturnCompletionInit) => ReturnCompletion;
|
|
let createThrowCompletion: (init: ThrowCompletionInit) => ThrowCompletion_;
|
|
|
|
type NormalCompletionInit<T> = Pick<NormalCompletion<T>, 'Type' | 'Value' | 'Target'>;
|
|
|
|
type BreakCompletionInit = Pick<BreakCompletion, 'Type' | 'Value' | 'Target'>;
|
|
|
|
type ContinueCompletionInit = Pick<ContinueCompletion, 'Type' | 'Value' | 'Target'>;
|
|
|
|
type ReturnCompletionInit = Pick<ReturnCompletion, 'Type' | 'Value' | 'Target'>;
|
|
|
|
type ThrowCompletionInit = Pick<ThrowCompletion, 'Type' | 'Value' | 'Target'>;
|
|
|
|
type AbruptCompletionInit =
|
|
| BreakCompletionInit
|
|
| ContinueCompletionInit
|
|
| ReturnCompletionInit
|
|
| ThrowCompletionInit;
|
|
|
|
type CompletionInit<T> =
|
|
| NormalCompletionInit<T>
|
|
| AbruptCompletionInit;
|
|
|
|
@callable((_target, _thisArg, [completionRecord]) => {
|
|
// 1. Assert: completionRecord is a Completion Record.
|
|
Assert(completionRecord instanceof Completion);
|
|
// 2. Return completionRecord as the Completion Record of this abstract operation.
|
|
return completionRecord;
|
|
})
|
|
class CompletionImpl<const T> {
|
|
declare readonly Type: 'normal' | 'break' | 'continue' | 'return' | 'throw';
|
|
|
|
readonly Value!: T | Value;
|
|
|
|
readonly Target!: JSStringValue | undefined;
|
|
|
|
constructor(init: CompletionInit<T>) {
|
|
if (new.target === CompletionImpl) {
|
|
switch (init.Type) {
|
|
case 'normal':
|
|
return createNormalCompletion(init);
|
|
case 'break':
|
|
return createBreakCompletion(init) as CompletionImpl<T>;
|
|
case 'continue':
|
|
return createContinueCompletion(init) as CompletionImpl<T>;
|
|
case 'return':
|
|
return createReturnCompletion(init) as CompletionImpl<T>;
|
|
case 'throw':
|
|
return createThrowCompletion(init) as CompletionImpl<T>;
|
|
default:
|
|
throw new OutOfRange('new Completion', init);
|
|
}
|
|
}
|
|
|
|
const { Type, Value, Target } = init;
|
|
Assert(new.target.prototype.Type === Type);
|
|
this.Value = Value as T;
|
|
this.Target = Target;
|
|
}
|
|
|
|
// NON-SPEC
|
|
mark(m: GCMarker) {
|
|
m(this.Value);
|
|
}
|
|
|
|
static {
|
|
Object.defineProperty(this, 'name', { value: 'Completion' });
|
|
}
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
export type Completion<T> =
|
|
| NormalCompletion<T>
|
|
| AbruptCompletion<T>;
|
|
|
|
/**
|
|
* A NON-SPEC shorthand to notate "returns either a normal completion containing an ECMAScript language value or a throw completion".
|
|
*/
|
|
// export type ValueEvaluator<T extends Value = Value> = T | NormalCompletion<T> | ThrowCompletion;
|
|
export type ValueCompletion<T extends Value = Value> = T | NormalCompletion<T> | ThrowCompletion;
|
|
export { type ValueEvaluator } from './evaluator.mts';
|
|
/**
|
|
* A NON-SPEC shorthand to notate "returns either a normal completion containing ... or a throw completion".
|
|
*
|
|
* If the T is an ECMAScript language value, use ExpressionCompletion<T>.
|
|
*/
|
|
export type PlainCompletion<T> = T | NormalCompletion<T> | ThrowCompletion;
|
|
export type YieldCompletion = NormalCompletion<Value> | ThrowCompletion | ReturnCompletion;
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-ao */
|
|
export const Completion = CompletionImpl as {
|
|
/** https://tc39.es/ecma262/#sec-completion-ao */
|
|
<T extends Completion<unknown>>(completionRecord: T): T;
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
new <const T>(completion: { Type: 'normal', Value: T, Target: undefined }): NormalCompletion<T>;
|
|
new(completion: { Type: 'break', Value: void, Target: JSStringValue | undefined }): BreakCompletion;
|
|
new(completion: { Type: 'continue', Value: void, Target: JSStringValue | undefined }): ContinueCompletion;
|
|
new(completion: { Type: 'return', Value: Value, Target: undefined }): ReturnCompletion;
|
|
new(completion: { Type: 'throw', Value: Value, Target: undefined }): ThrowCompletion;
|
|
readonly prototype: CompletionImpl<unknown>;
|
|
};
|
|
|
|
@callable((_target, _thisArg, [value]) => { // eslint-disable-line arrow-body-style -- Preserve algorithm steps comments
|
|
// 1. Return Completion { [[Type]]: normal, [[Value]]: value, [[Target]]: empty }.
|
|
return new Completion({ Type: 'normal', Value: value, Target: undefined });
|
|
})
|
|
class NormalCompletionImpl<const T> extends CompletionImpl<T> {
|
|
declare readonly Type: 'normal';
|
|
|
|
declare readonly Value: T;
|
|
|
|
declare readonly Target: undefined;
|
|
|
|
private constructor(init: NormalCompletionInit<T>) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor
|
|
super(init);
|
|
}
|
|
|
|
static {
|
|
Object.defineProperty(this, 'name', { value: 'NormalCompletion' });
|
|
Object.defineProperty(this.prototype, 'Type', { value: 'normal' });
|
|
createNormalCompletion = (init) => new NormalCompletionImpl(init);
|
|
}
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
export type NormalCompletion<T> = NormalCompletionImpl<T>;
|
|
|
|
/** https://tc39.es/ecma262/#sec-normalcompletion */
|
|
export const NormalCompletion = NormalCompletionImpl as typeof NormalCompletionImpl & {
|
|
/** https://tc39.es/ecma262/#sec-normalcompletion */
|
|
<const T>(value: T): NormalCompletion<T>;
|
|
};
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
export type AbruptCompletion<T = unknown> =
|
|
| ThrowCompletion
|
|
| ReturnCompletion
|
|
| BreakCompletion
|
|
| ContinueCompletion;
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
export const AbruptCompletion = (() => {
|
|
abstract class AbruptCompletion<const T> extends CompletionImpl<T | Value> {
|
|
declare readonly Type: 'break' | 'continue' | 'return' | 'throw';
|
|
|
|
declare readonly Value: T | Value;
|
|
|
|
declare readonly Target: JSStringValue | undefined;
|
|
|
|
constructor(init: AbruptCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor
|
|
super(init);
|
|
}
|
|
|
|
static {
|
|
Object.defineProperty(this, 'name', { value: 'AbruptCompletion' });
|
|
}
|
|
}
|
|
|
|
return AbruptCompletion;
|
|
})();
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
export class BreakCompletion extends AbruptCompletion<void> {
|
|
declare readonly Type: 'break';
|
|
|
|
declare readonly Value: void;
|
|
|
|
private constructor(init: BreakCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor
|
|
super(init);
|
|
}
|
|
|
|
static {
|
|
Object.defineProperty(this, 'name', { value: 'BreakCompletion' });
|
|
Object.defineProperty(this.prototype, 'Type', { value: 'break' });
|
|
createBreakCompletion = (init) => new BreakCompletion(init);
|
|
}
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
export class ContinueCompletion extends AbruptCompletion<void> {
|
|
declare readonly Type: 'continue';
|
|
|
|
declare readonly Value: void;
|
|
|
|
declare readonly Target: JSStringValue | undefined;
|
|
|
|
private constructor(init: ContinueCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor
|
|
super(init);
|
|
}
|
|
|
|
static {
|
|
Object.defineProperty(this, 'name', { value: 'ContinueCompletion' });
|
|
Object.defineProperty(this.prototype, 'Type', { value: 'continue' });
|
|
createContinueCompletion = (init) => new ContinueCompletion(init);
|
|
}
|
|
}
|
|
|
|
@callable((_target, _thisArg, [value]) => {
|
|
Assert(value instanceof Value);
|
|
// 1. Return Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
|
return new Completion({ Type: 'return', Value: value as Value, Target: undefined });
|
|
})
|
|
class ReturnCompletion_ extends AbruptCompletion<Value> {
|
|
declare readonly Type: 'return';
|
|
|
|
declare readonly Value: Value;
|
|
|
|
declare readonly Target: undefined;
|
|
|
|
private constructor(init: ReturnCompletionInit) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor
|
|
super(init);
|
|
}
|
|
|
|
static {
|
|
Object.defineProperty(this, 'name', { value: 'ReturnCompletion' });
|
|
Object.defineProperty(this.prototype, 'Type', { value: 'return' });
|
|
createReturnCompletion = (init) => new ReturnCompletion(init);
|
|
}
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
export type ReturnCompletion = ReturnCompletion_;
|
|
|
|
/** https://tc39.es/ecma262/#sec-throwcompletion */
|
|
export const ReturnCompletion = ReturnCompletion_ as typeof ReturnCompletion_ & {
|
|
/** https://tc39.es/ecma262/#sec-throwcompletion */
|
|
(value: Value): ThrowCompletion;
|
|
};
|
|
|
|
const debugging = false;
|
|
@callable((_target, _thisArg, [value]) => {
|
|
Assert(value instanceof Value);
|
|
// 1. Return Completion { [[Type]]: throw, [[Value]]: value, [[Target]]: empty }.
|
|
return new Completion({ Type: 'throw', Value: value as Value, Target: undefined });
|
|
})
|
|
class ThrowCompletion_ extends AbruptCompletion<Value> {
|
|
declare readonly Type: 'throw';
|
|
|
|
declare readonly Value: Value;
|
|
|
|
declare readonly Target: undefined;
|
|
|
|
readonly stack = debugging ? new Error() : undefined;
|
|
|
|
private constructor(init: Pick<ThrowCompletion_, 'Type' | 'Value' | 'Target'>) { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor
|
|
super(init);
|
|
if (debugging) {
|
|
Error.stackTraceLimit = Infinity;
|
|
}
|
|
}
|
|
|
|
static {
|
|
Object.defineProperty(this, 'name', { value: 'ThrowCompletion' });
|
|
Object.defineProperty(this.prototype, 'Type', { value: 'throw' });
|
|
createThrowCompletion = (init) => new ThrowCompletion_(init);
|
|
}
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-completion-record-specification-type */
|
|
export type ThrowCompletion = ThrowCompletion_;
|
|
|
|
/** https://tc39.es/ecma262/#sec-throwcompletion */
|
|
export const ThrowCompletion = ThrowCompletion_ as typeof ThrowCompletion_ & {
|
|
/** https://tc39.es/ecma262/#sec-throwcompletion */
|
|
(value: Value): ThrowCompletion;
|
|
};
|
|
|
|
/** https://tc39.es/ecma262/#sec-updateempty */
|
|
export type UpdateEmpty<T extends Completion<unknown>, U> =
|
|
T extends NormalCompletion<infer V> ? NormalCompletion<V extends undefined ? U : V> :
|
|
T extends BreakCompletion ? BreakCompletion :
|
|
T extends ContinueCompletion ? ContinueCompletion :
|
|
T extends AbruptCompletion ? T :
|
|
T extends ReturnCompletion ? T :
|
|
never;
|
|
|
|
/** https://tc39.es/ecma262/#sec-updateempty */
|
|
export function UpdateEmpty<C extends Completion<unknown>, const T>(completionRecord: C, value: T): UpdateEmpty<C, T>;
|
|
export function UpdateEmpty<C extends Completion<unknown>, const T>(completionRecord: C, value: T) {
|
|
// 1. Assert: If completionRecord.[[Type]] is either return or throw, then completionRecord.[[Value]] is not empty.
|
|
Assert(!(completionRecord.Type === 'return' || completionRecord.Type === 'throw') || completionRecord.Value !== undefined);
|
|
// 2. If completionRecord.[[Value]] is not empty, return Completion(completionRecord).
|
|
if (completionRecord.Value !== undefined) {
|
|
return Completion(completionRecord);
|
|
}
|
|
// 3. Return Completion { [[Type]]: completionRecord.[[Type]], [[Value]]: value, [[Target]]: completionRecord.[[Target]] }.
|
|
return new CompletionImpl({ Type: completionRecord.Type, Value: value, Target: completionRecord.Target } as unknown as CompletionInit<unknown>); // NOTE: unsound cast
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-returnifabrupt */
|
|
export type Q<T> =
|
|
T extends NormalCompletion<infer V> ? V :
|
|
T extends AbruptCompletion ? never :
|
|
T;
|
|
|
|
/**
|
|
* https://tc39.es/ecma262/#sec-returnifabrupt
|
|
* https://tc39.es/ecma262/#sec-returnifabrupt-shorthands ? OperationName()
|
|
*/
|
|
export function Q<const T>(_completion: T): Q<T> {
|
|
/* node:coverage ignore next */
|
|
throw new TypeError('Q requires build');
|
|
}
|
|
|
|
function Q_runtime<const T>(completion: T): Q<T> {
|
|
/* node:coverage ignore next 3 */
|
|
if (typeof completion === 'object' && completion && 'next' in completion) {
|
|
throw new TypeError('Forgot to yield* on the completion.');
|
|
}
|
|
const c = EnsureCompletion(completion);
|
|
if (c.Type === 'normal') {
|
|
return c.Value as Q<T>;
|
|
}
|
|
throw c;
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-returnifabrupt-shorthands ! OperationName() */
|
|
export function X<const T>(_completion: T | Evaluator<T>): Q<T> {
|
|
/* node:coverage ignore next */
|
|
throw new TypeError('X() requires build');
|
|
}
|
|
|
|
export function unwrapCompletion<const T>(completion: T | Evaluator<T>): Q<T> {
|
|
/* node:coverage ignore next 3 */
|
|
if (typeof completion === 'object' && completion && 'next' in completion) {
|
|
completion = skipDebugger(completion);
|
|
}
|
|
const c = EnsureCompletion(completion);
|
|
if (c instanceof NormalCompletion) {
|
|
return c.Value as Q<T>;
|
|
}
|
|
/* node:coverage ignore next */
|
|
throw new Assert.Error('Unexpected AbruptCompletion.', { cause: c });
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-ifabruptcloseiterator */
|
|
export function IfAbruptCloseIterator<T>(_value: T, _iteratorRecord: IteratorRecord): Q<T> {
|
|
/* node:coverage ignore next */
|
|
throw new TypeError('IfAbruptCloseIterator() requires build');
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-ifabruptcloseasynciterator */
|
|
export function IfAbruptCloseAsyncIterator<T>(_value: T, _iteratorRecord: IteratorRecord): Q<T> {
|
|
/* node:coverage ignore next */
|
|
throw new TypeError('IfAbruptCloseAsyncIterator() requires build');
|
|
}
|
|
|
|
/** https://tc39.es/ecma262/#sec-ifabruptrejectpromise */
|
|
export function IfAbruptRejectPromise<T>(_value: T, _capability: PromiseCapabilityRecord): Q<T> {
|
|
/* node:coverage ignore next */
|
|
throw new TypeError('IfAbruptRejectPromise requires build');
|
|
}
|
|
|
|
/**
|
|
* This is a util for code that cannot use Q() or X() marco to emulate this behaviour.
|
|
*
|
|
* @example
|
|
* import { evalQ } from '...'
|
|
* evalQ((Q) => {
|
|
* let val = Q(operation);
|
|
* });
|
|
*/
|
|
export function evalQ<T>(callback: (q: typeof Q, x: typeof X) => Promise<T>): Promise<NormalCompletion<T> | ThrowCompletion>
|
|
export function evalQ<T>(callback: (q: typeof Q, x: typeof X) => T): NormalCompletion<T> | ThrowCompletion
|
|
export function evalQ<T>(callback: (q: typeof Q, x: typeof X) => T | Promise<T>): Promise<NormalCompletion<T> | ThrowCompletion> | NormalCompletion<T> | ThrowCompletion {
|
|
try {
|
|
const result = callback(Q_runtime, unwrapCompletion);
|
|
if (result instanceof Promise) {
|
|
return result.then(EnsureCompletion, (error) => {
|
|
if (error instanceof ThrowCompletion) {
|
|
return error;
|
|
}
|
|
throw error;
|
|
});
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return EnsureCompletion(result) as any;
|
|
} catch (error) {
|
|
if (error instanceof ThrowCompletion) {
|
|
return error;
|
|
}
|
|
// a real error
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export type EnsureCompletion<T> = EnsureCompletionWorker<T, T>;
|
|
|
|
// Distribute over `T`s that are `Completion`s, but don't distribute over `T`s that aren't `Completion`s
|
|
type EnsureCompletionWorker<T, _T> = T extends Completion<unknown> ? T : NormalCompletion<Exclude<_T, PlainCompletion<unknown>>>;
|
|
|
|
/** https://tc39.es/ecma262/#sec-implicit-normal-completion */
|
|
export function EnsureCompletion(val: Value): NormalCompletion<Value>;
|
|
export function EnsureCompletion<const T>(val: T): EnsureCompletion<T>;
|
|
export function EnsureCompletion<const T>(val: T) {
|
|
if (val instanceof Completion) {
|
|
return val;
|
|
}
|
|
return NormalCompletion(val);
|
|
}
|
|
|
|
export function ValueOfNormalCompletion<T>(value: NormalCompletion<T> | T) {
|
|
return value instanceof NormalCompletion ? value.Value : value;
|
|
}
|
|
|
|
export function* Await(value: Value): ValueEvaluator {
|
|
// 1. Let asyncContext be the running execution context.
|
|
const asyncContext = surroundingAgent.runningExecutionContext;
|
|
// 2. Let promise be ? PromiseResolve(%Promise%, value).
|
|
const promise = Q(yield* PromiseResolve(surroundingAgent.intrinsic('%Promise%'), value));
|
|
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
|
|
const fulfilledClosure = function* fulfilledClosure([v = Value.undefined]: Arguments) {
|
|
// a. Let prevContext be the running execution context.
|
|
const prevContext = surroundingAgent.runningExecutionContext;
|
|
// b. Suspend prevContext.
|
|
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
|
|
surroundingAgent.executionContextStack.push(asyncContext);
|
|
// d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
|
|
yield* resume(asyncContext, { type: 'await-resume', value: NormalCompletion(v) });
|
|
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
|
|
Assert(surroundingAgent.runningExecutionContext === prevContext);
|
|
// f. Return undefined.
|
|
return Value.undefined;
|
|
};
|
|
// 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
|
|
const onFulfilled = CreateBuiltinFunction(fulfilledClosure, 1, Value(''), []);
|
|
// @ts-expect-error TODO(ts): CreateBuiltinFunction should return a specalized type FunctionObjectValue that has a kAsyncContext on it.
|
|
onFulfilled[kAsyncContext] = asyncContext;
|
|
// 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called:
|
|
const rejectedClosure = function* rejectedClosure([reason = Value.undefined]: Arguments) {
|
|
// a. Let prevContext be the running execution context.
|
|
const prevContext = surroundingAgent.runningExecutionContext;
|
|
// b. Suspend prevContext.
|
|
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
|
|
surroundingAgent.executionContextStack.push(asyncContext);
|
|
// d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
|
|
yield* resume(asyncContext, { type: 'await-resume', value: ThrowCompletion(reason) });
|
|
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
|
|
Assert(surroundingAgent.runningExecutionContext === prevContext);
|
|
// f. Return undefined.
|
|
return Value.undefined;
|
|
};
|
|
// 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
|
|
const onRejected = CreateBuiltinFunction(rejectedClosure, 1, Value(''), []);
|
|
// @ts-expect-error TODO(ts): CreateBuiltinFunction should return a specalized type FunctionObjectValue that has a kAsyncContext on it.
|
|
onRejected[kAsyncContext] = asyncContext;
|
|
// 7. Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
|
|
X(PerformPromiseThen(promise, onFulfilled, onRejected));
|
|
// 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
|
|
surroundingAgent.executionContextStack.pop(asyncContext);
|
|
// 9. Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion completion, the following steps of the algorithm that invoked Await will be performed, with completion available.
|
|
const completion = yield { type: 'await' };
|
|
Assert(completion.type === 'await-resume');
|
|
// 10. Return.
|
|
return completion.value;
|
|
// 11. NOTE: This returns to the evaluation of the operation that had most previously resumed evaluation of asyncContext.
|
|
}
|