mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-19 00:31:06 +00:00
Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
Value, Descriptor, type Arguments, type FunctionCallContext,
|
||||
UndefinedValue,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { captureStack, callSiteToErrorString } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import type { ErrorObject } from './Error.mts';
|
||||
import {
|
||||
ToString,
|
||||
IteratorToList,
|
||||
OrdinaryCreateFromConstructor,
|
||||
DefinePropertyOrThrow,
|
||||
InstallErrorCause,
|
||||
CreateArrayFromList,
|
||||
type FunctionObject,
|
||||
CreateNonEnumerableDataPropertyOrThrow,
|
||||
GetIterator,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-aggregate-error-constructor */
|
||||
function* AggregateErrorConstructor([errors = Value.undefined, message = Value.undefined, options = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
|
||||
let newTarget;
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
newTarget = surroundingAgent.activeFunctionObject as FunctionObject;
|
||||
} else {
|
||||
newTarget = NewTarget;
|
||||
}
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%AggregateError.prototype%", « [[ErrorData]] »).
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(newTarget, '%AggregateError.prototype%', [
|
||||
'ErrorData',
|
||||
'HostDefinedErrorStack',
|
||||
])) as ErrorObject;
|
||||
// 3. If message is not undefined, then
|
||||
if (message !== Value.undefined) {
|
||||
// a. Let msg be ? ToString(message).
|
||||
const msg = Q(yield* ToString(message));
|
||||
// b. Perform ! CreateMethodProperty(O, "message", msg).
|
||||
X(CreateNonEnumerableDataPropertyOrThrow(O, Value('message'), msg));
|
||||
}
|
||||
Q(yield* InstallErrorCause(O, options));
|
||||
// 4. Let errorsList be ? IterableToList(errors).
|
||||
const errorsList = Q(yield* IteratorToList(Q(yield* GetIterator(errors, 'sync'))));
|
||||
// 5. Perform ! DefinePropertyOrThrow(O, "errors", Property Descriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: ! CreateArrayFromList(errorsList) }).
|
||||
X(DefinePropertyOrThrow(O, Value('errors'), Descriptor({
|
||||
Configurable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Writable: Value.true,
|
||||
Value: CreateArrayFromList(errorsList),
|
||||
})));
|
||||
|
||||
// NON-SPEC
|
||||
const S = captureStack();
|
||||
O.HostDefinedErrorStack = S.stack;
|
||||
O.ErrorData = X(callSiteToErrorString(O, S.stack, S.nativeStack));
|
||||
|
||||
// 7. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
export function bootstrapAggregateError(realmRec: Realm) {
|
||||
const c = bootstrapConstructor(realmRec, AggregateErrorConstructor, 'AggregateError', 2, realmRec.Intrinsics['%AggregateError.prototype%'], []);
|
||||
c.Prototype = realmRec.Intrinsics['%Error%'];
|
||||
realmRec.Intrinsics['%AggregateError%'] = c;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Value } from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
export function bootstrapAggregateErrorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['name', Value('AggregateError')],
|
||||
['message', Value('')],
|
||||
], realmRec.Intrinsics['%Error.prototype%'], 'AggregateError');
|
||||
|
||||
realmRec.Intrinsics['%AggregateError.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import {
|
||||
Await,
|
||||
IfAbruptCloseIterator,
|
||||
Q,
|
||||
ThrowCompletion, X,
|
||||
type ValueCompletion,
|
||||
type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import {
|
||||
NumberValue,
|
||||
ObjectValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { __ts_cast__, OutOfRange } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
ArrayCreate,
|
||||
Assert,
|
||||
Call,
|
||||
Construct,
|
||||
CreateDataProperty,
|
||||
CreateDataPropertyOrThrow,
|
||||
Get,
|
||||
GetMethod,
|
||||
GetPrototypeFromConstructor,
|
||||
IsArray,
|
||||
IsCallable,
|
||||
IsConstructor,
|
||||
IteratorClose,
|
||||
Set,
|
||||
LengthOfArrayLike,
|
||||
ToObject,
|
||||
ToString,
|
||||
ToUint32,
|
||||
F, R,
|
||||
type FunctionObject,
|
||||
IteratorStepValue,
|
||||
GetIteratorFromMethod,
|
||||
type IteratorRecord,
|
||||
CreateAsyncFromSyncIterator,
|
||||
AsyncIteratorClose,
|
||||
IteratorComplete,
|
||||
} from '#self';
|
||||
import {
|
||||
Realm,
|
||||
IfAbruptCloseAsyncIterator, IteratorValue, Throw,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array-constructor */
|
||||
function* ArrayConstructor(argumentsList: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
const numberOfArgs = argumentsList.length;
|
||||
if (numberOfArgs === 0) {
|
||||
/** https://tc39.es/ecma262/#sec-array-constructor-array */
|
||||
Assert(numberOfArgs === 0);
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
NewTarget = surroundingAgent.activeFunctionObject as FunctionObject;
|
||||
}
|
||||
const proto = X(GetPrototypeFromConstructor(NewTarget, '%Array.prototype%'));
|
||||
return ArrayCreate(0, proto);
|
||||
} else if (numberOfArgs === 1) {
|
||||
/** https://tc39.es/ecma262/#sec-array-len */
|
||||
const [len] = argumentsList;
|
||||
Assert(numberOfArgs === 1);
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
NewTarget = surroundingAgent.activeFunctionObject as FunctionObject;
|
||||
}
|
||||
const proto = X(GetPrototypeFromConstructor(NewTarget, '%Array.prototype%'));
|
||||
const array = X(ArrayCreate(0, proto));
|
||||
let intLen;
|
||||
if (!(len instanceof NumberValue)) {
|
||||
const defineStatus = X(CreateDataProperty(array, Value('0'), len!));
|
||||
Assert(defineStatus === Value.true);
|
||||
intLen = F(1);
|
||||
} else {
|
||||
intLen = X(ToUint32(len));
|
||||
if (R(intLen) !== R(len)) {
|
||||
return surroundingAgent.Throw('RangeError', 'InvalidArrayLength', len);
|
||||
}
|
||||
}
|
||||
yield* Set(array, Value('length'), intLen, Value.true);
|
||||
return array;
|
||||
} else if (numberOfArgs >= 2) {
|
||||
/** https://tc39.es/ecma262/#sec-array-items */
|
||||
const items = argumentsList;
|
||||
Assert(numberOfArgs >= 2);
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
NewTarget = surroundingAgent.activeFunctionObject as FunctionObject;
|
||||
}
|
||||
const proto = Q(yield* GetPrototypeFromConstructor(NewTarget, '%Array.prototype%'));
|
||||
const array = X(ArrayCreate(0, proto));
|
||||
let k = 0;
|
||||
while (k < numberOfArgs) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const itemK = items[k]!;
|
||||
const defineStatus = X(CreateDataProperty(array, Pk, itemK));
|
||||
Assert(defineStatus === Value.true);
|
||||
k += 1;
|
||||
}
|
||||
Assert(R(X(Get(array, Value('length'))) as NumberValue) === numberOfArgs);
|
||||
return array;
|
||||
}
|
||||
|
||||
throw new OutOfRange('ArrayConstructor', numberOfArgs);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.from */
|
||||
function* Array_from([items = Value.undefined, mapper = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
const C = thisValue;
|
||||
let mapping;
|
||||
let A;
|
||||
if (mapper === Value.undefined) {
|
||||
mapping = false;
|
||||
} else {
|
||||
if (!IsCallable(mapper)) {
|
||||
return Throw.TypeError('$1 is not a function', mapper);
|
||||
}
|
||||
mapping = true;
|
||||
}
|
||||
const usingIterator = Q(yield* GetMethod(items, wellKnownSymbols.iterator));
|
||||
if (!(usingIterator instanceof UndefinedValue)) {
|
||||
if (IsConstructor(C)) {
|
||||
A = Q(yield* Construct(C));
|
||||
} else {
|
||||
A = X(ArrayCreate(0));
|
||||
}
|
||||
const iteratorRecord = Q(yield* GetIteratorFromMethod(items, usingIterator));
|
||||
let k = 0;
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
if (k >= (2 ** 53) - 1) {
|
||||
const error = ThrowCompletion(surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength').Value);
|
||||
return Q(yield* IteratorClose(iteratorRecord, error));
|
||||
}
|
||||
const Pk = X(ToString(F(k)));
|
||||
const next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
if (next === 'done') {
|
||||
Q(yield* Set(A, Value('length'), F(k), Value.true));
|
||||
return A;
|
||||
}
|
||||
let mappedValue;
|
||||
if (mapping) {
|
||||
mappedValue = yield* Call(mapper, thisArg, [next, F(k)]);
|
||||
IfAbruptCloseIterator(mappedValue, iteratorRecord);
|
||||
__ts_cast__<Value>(mappedValue);
|
||||
} else {
|
||||
mappedValue = next;
|
||||
}
|
||||
const defineStatus = yield* CreateDataPropertyOrThrow(A, Pk, mappedValue);
|
||||
IfAbruptCloseIterator(defineStatus, iteratorRecord);
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
const arrayLike = X(ToObject(items));
|
||||
const len = Q(yield* LengthOfArrayLike(arrayLike));
|
||||
if (IsConstructor(C)) {
|
||||
A = Q(yield* Construct(C, [F(len)]));
|
||||
} else {
|
||||
A = Q(ArrayCreate(len));
|
||||
}
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = Q(yield* Get(arrayLike, Pk));
|
||||
let mappedValue;
|
||||
if (mapping === true) {
|
||||
mappedValue = Q(yield* Call(mapper, thisArg, [kValue, F(k)]));
|
||||
} else {
|
||||
mappedValue = kValue;
|
||||
}
|
||||
Q(yield* CreateDataPropertyOrThrow(A, Pk, mappedValue));
|
||||
k += 1;
|
||||
}
|
||||
Q(yield* Set(A, Value('length'), F(len), Value.true));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.fromasync */
|
||||
function* Array_fromAsync([items = Value.undefined, mapper = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
const C = thisValue;
|
||||
let mapping = false;
|
||||
if (mapper !== Value.undefined) {
|
||||
if (!IsCallable(mapper)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', mapper);
|
||||
}
|
||||
mapping = true;
|
||||
}
|
||||
let iteratorRecord: IteratorRecord | undefined;
|
||||
const usingAsyncIterator = Q(yield* GetMethod(items, wellKnownSymbols.asyncIterator));
|
||||
let usingSyncIterator: UndefinedValue | FunctionObject = Value.undefined;
|
||||
if (usingAsyncIterator instanceof UndefinedValue) {
|
||||
usingSyncIterator = Q(yield* GetMethod(items, wellKnownSymbols.iterator));
|
||||
if (!(usingSyncIterator instanceof UndefinedValue)) {
|
||||
iteratorRecord = CreateAsyncFromSyncIterator(Q(yield* GetIteratorFromMethod(items, usingSyncIterator)));
|
||||
}
|
||||
} else {
|
||||
iteratorRecord = Q(yield* GetIteratorFromMethod(items, usingAsyncIterator));
|
||||
}
|
||||
|
||||
if (iteratorRecord) {
|
||||
const MAX_SAFE_INTEGER = (2 ** 53) - 1;
|
||||
let A: ObjectValue;
|
||||
if (IsConstructor(C)) {
|
||||
A = Q(yield* Construct(C));
|
||||
} else {
|
||||
A = X(ArrayCreate(0));
|
||||
}
|
||||
|
||||
let k = 0;
|
||||
while (true) {
|
||||
if (k > MAX_SAFE_INTEGER) {
|
||||
const error = surroundingAgent.Throw('TypeError', 'OutOfRange', k);
|
||||
return Q(yield* AsyncIteratorClose(iteratorRecord, error));
|
||||
}
|
||||
|
||||
const Pk = X(ToString(F(k)));
|
||||
let nextResult: Value = Q(yield* Call(iteratorRecord.NextMethod, iteratorRecord.Iterator));
|
||||
nextResult = Q(yield* Await(nextResult));
|
||||
if (!(nextResult instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', nextResult);
|
||||
}
|
||||
const done = Q(yield* IteratorComplete(nextResult));
|
||||
if (done === Value.true) {
|
||||
Q(yield* Set(A, Value('length'), F(k), Value.true));
|
||||
return A;
|
||||
}
|
||||
|
||||
const nextValue: ValueCompletion<Value> = Q(yield* IteratorValue(nextResult));
|
||||
let mappedValue;
|
||||
if (mapping) {
|
||||
mappedValue = (yield* Call(mapper, thisArg, [nextValue, F(k)]));
|
||||
IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord);
|
||||
__ts_cast__<Value>(mappedValue);
|
||||
mappedValue = yield* Await(mappedValue);
|
||||
IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord);
|
||||
__ts_cast__<Value>(mappedValue);
|
||||
} else {
|
||||
mappedValue = nextValue;
|
||||
}
|
||||
|
||||
const defineStatus = yield* CreateDataPropertyOrThrow(A, Pk, mappedValue);
|
||||
IfAbruptCloseAsyncIterator(defineStatus, iteratorRecord);
|
||||
k += 1;
|
||||
}
|
||||
} else {
|
||||
const arrayLike = X(ToObject(items));
|
||||
const len = Q(yield* LengthOfArrayLike(arrayLike));
|
||||
|
||||
let A: ObjectValue;
|
||||
if (IsConstructor(C)) {
|
||||
A = Q(yield* Construct(C, [F(len)]));
|
||||
} else {
|
||||
A = Q(ArrayCreate(len));
|
||||
}
|
||||
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let kValue = Q(yield* Get(arrayLike, Pk));
|
||||
kValue = Q(yield* Await(kValue));
|
||||
let mappedValue: Value;
|
||||
if (mapping) {
|
||||
mappedValue = Q(yield* Call(mapper, thisArg, [kValue, F(k)]));
|
||||
mappedValue = Q(yield* Await(mappedValue));
|
||||
} else {
|
||||
mappedValue = kValue;
|
||||
}
|
||||
Q(yield* CreateDataPropertyOrThrow(A, Pk, mappedValue));
|
||||
k += 1;
|
||||
}
|
||||
|
||||
Q(yield* Set(A, Value('length'), F(len), Value.true));
|
||||
return A;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.isarray */
|
||||
function Array_isArray([arg = Value.undefined]: Arguments): ValueCompletion {
|
||||
return Q(IsArray(arg));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.of */
|
||||
function* Array_of(items: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const len = items.length;
|
||||
// Let items be the List of arguments passed to this function.
|
||||
const C = thisValue;
|
||||
let A;
|
||||
if (IsConstructor(C)) {
|
||||
A = Q(yield* Construct(C, [F(len)]));
|
||||
} else {
|
||||
A = Q(ArrayCreate(len));
|
||||
}
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const kValue = items[k]!;
|
||||
const Pk = X(ToString(F(k)));
|
||||
Q(yield* CreateDataPropertyOrThrow(A, Pk, kValue));
|
||||
k += 1;
|
||||
}
|
||||
Q(yield* Set(A, Value('length'), F(len), Value.true));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-array-@@species */
|
||||
function Array_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) {
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
export function bootstrapArray(realmRec: Realm) {
|
||||
const proto = realmRec.Intrinsics['%Array.prototype%'];
|
||||
|
||||
const cons = bootstrapConstructor(realmRec, ArrayConstructor, 'Array', 1, proto, [
|
||||
['from', Array_from, 1],
|
||||
['fromAsync', Array_fromAsync, 1, undefined, true],
|
||||
['isArray', Array_isArray, 1],
|
||||
['of', Array_of, 0],
|
||||
[wellKnownSymbols.species, [Array_speciesGetter]],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Array%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue, UndefinedValue, Value, wellKnownSymbols, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
ToIndex, AllocateArrayBuffer, type FunctionObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-arraybuffer-length */
|
||||
function* ArrayBufferConstructor(this: FunctionObject, [length = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. Let byteLength be ? ToIndex(length).
|
||||
const byteLength = Q(yield* ToIndex(length));
|
||||
// 3. Return ? AllocateArrayBuffer(NewTarget, byteLength).
|
||||
return Q(yield* AllocateArrayBuffer(NewTarget, byteLength));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-arraybuffer.isview */
|
||||
function ArrayBuffer_isView([arg = Value.undefined]: Arguments) {
|
||||
// 1. If Type(arg) is not Object, return false.
|
||||
if (!(arg instanceof ObjectValue)) {
|
||||
return Value.false;
|
||||
}
|
||||
// 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true.
|
||||
if ('ViewedArrayBuffer' in arg) {
|
||||
return Value.true;
|
||||
}
|
||||
// 3. Return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-arraybuffer-@@species */
|
||||
function ArrayBuffer_species(_: Arguments, { thisValue }: FunctionCallContext) {
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
export function bootstrapArrayBuffer(realmRec: Realm) {
|
||||
const c = bootstrapConstructor(realmRec, ArrayBufferConstructor, 'ArrayBuffer', 1, realmRec.Intrinsics['%ArrayBuffer.prototype%'], [
|
||||
['isView', ArrayBuffer_isView, 1],
|
||||
[wellKnownSymbols.species, [ArrayBuffer_species]],
|
||||
]);
|
||||
realmRec.Intrinsics['%ArrayBuffer%'] = c;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
DataBlock, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
RequireInternalSlot, IsDetachedBuffer, IsSharedArrayBuffer,
|
||||
SpeciesConstructor, Construct, ToIntegerOrInfinity, SameValue, CopyDataBlockBytes,
|
||||
F,
|
||||
type ArrayBufferObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.bytelength */
|
||||
function ArrayBufferProto_byteLength(_args: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let O be this value.
|
||||
const O = thisValue as ArrayBufferObject;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
Q(RequireInternalSlot(O, 'ArrayBufferData'));
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
if (IsSharedArrayBuffer(O)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferShared');
|
||||
}
|
||||
// 4. If IsDetachedBuffer(O) is true, return +0𝔽.
|
||||
if (IsDetachedBuffer(O)) {
|
||||
return F(+0);
|
||||
}
|
||||
// 5. Let length be O.[[ArrayBufferByteLength]].
|
||||
const length = O.ArrayBufferByteLength;
|
||||
// 6. Return length.
|
||||
return F(length);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice */
|
||||
function* ArrayBufferProto_slice([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as ArrayBufferObject;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
|
||||
Q(RequireInternalSlot(O, 'ArrayBufferData'));
|
||||
// 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
|
||||
if (IsSharedArrayBuffer(O)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferShared');
|
||||
}
|
||||
// 4. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if (IsDetachedBuffer(O)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached');
|
||||
}
|
||||
// 5. Let len be O.[[ArrayBufferByteLength]].
|
||||
const len = O.ArrayBufferByteLength;
|
||||
// 6. Let relativeStart be ? ToIntegerOrInfinity(start).
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let first;
|
||||
// 7. If relativeStart < 0, let first be max((len + relativeStart), 0); else let first be min(relativeStart, len).
|
||||
if (relativeStart < 0) {
|
||||
first = Math.max(len + relativeStart, 0);
|
||||
} else {
|
||||
first = Math.min(relativeStart, len);
|
||||
}
|
||||
let relativeEnd;
|
||||
// 8. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
|
||||
if (end === Value.undefined) {
|
||||
relativeEnd = len;
|
||||
} else {
|
||||
relativeEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let final;
|
||||
// 9. If relativeEnd < 0, let final be max((len + relativeEnd), 0); else let final be min(relativeEnd, len).
|
||||
if (relativeEnd < 0) {
|
||||
final = Math.max(len + relativeEnd, 0);
|
||||
} else {
|
||||
final = Math.min(relativeEnd, len);
|
||||
}
|
||||
// 10. Let newLen be max(final - first, 0).
|
||||
const newLen = Math.max(final - first, 0);
|
||||
// 11. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%).
|
||||
const ctor = Q(yield* SpeciesConstructor(O, surroundingAgent.intrinsic('%ArrayBuffer%')));
|
||||
// 12. Let new be ? Construct(ctor, « newLen »).
|
||||
const newO = Q(yield* Construct(ctor, [F(newLen)])) as ArrayBufferObject;
|
||||
// 13. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]).
|
||||
Q(RequireInternalSlot(newO, 'ArrayBufferData'));
|
||||
// 14. If IsSharedArrayBuffer(new) is true, throw a TypeError exception.
|
||||
if (IsSharedArrayBuffer(newO)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferShared');
|
||||
}
|
||||
// 15. If IsDetachedBuffer(new) is true, throw a TypeError exception.
|
||||
if (IsDetachedBuffer(newO)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached');
|
||||
}
|
||||
// 16. If SameValue(new, O) is true, throw a TypeError exception.
|
||||
if (SameValue(newO, O) === Value.true) {
|
||||
return surroundingAgent.Throw('TypeError', 'SubclassSameValue', newO);
|
||||
}
|
||||
// 17. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception.
|
||||
if (newO.ArrayBufferByteLength < newLen) {
|
||||
return surroundingAgent.Throw('TypeError', 'SubclassLengthTooSmall', newO);
|
||||
}
|
||||
// 18. NOTE: Side-effects of the above steps may have detached O.
|
||||
// 19. If IsDetachedBuffer(O) is true, throw a TypeError exception.
|
||||
if (IsDetachedBuffer(O)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached');
|
||||
}
|
||||
// 20. Let fromBuf be O.[[ArrayBufferData]].
|
||||
const fromBuf = O.ArrayBufferData as DataBlock;
|
||||
// 21. Let toBuf be new.[[ArrayBufferData]].
|
||||
const toBuf = newO.ArrayBufferData as DataBlock;
|
||||
// 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen).
|
||||
CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen);
|
||||
// 23. Return new.
|
||||
return newO;
|
||||
}
|
||||
|
||||
export function bootstrapArrayBufferPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['byteLength', [ArrayBufferProto_byteLength]],
|
||||
['slice', ArrayBufferProto_slice, 2],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'ArrayBuffer');
|
||||
|
||||
realmRec.Intrinsics['%ArrayBuffer.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Q, type ValueEvaluator } from '../completion.mts';
|
||||
import {
|
||||
Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
GeneratorResume,
|
||||
} from '#self';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next */
|
||||
function* ArrayIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Return ? GeneratorResume(this value, empty, "%ArrayIteratorPrototype%").
|
||||
return Q(yield* GeneratorResume(thisValue, undefined, Value('%ArrayIteratorPrototype%')));
|
||||
}
|
||||
|
||||
export function bootstrapArrayIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', ArrayIteratorPrototype_next, 0],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%'], 'Array Iterator');
|
||||
|
||||
realmRec.Intrinsics['%ArrayIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,755 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
BooleanValue,
|
||||
Descriptor,
|
||||
JSStringValue,
|
||||
ObjectValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import type { PlainEvaluator } from '../evaluator.mts';
|
||||
import { assignProps } from './bootstrap.mts';
|
||||
import { ArrayProto_sortBody, bootstrapArrayPrototypeShared, SortIndexedProperties } from './ArrayPrototypeShared.mts';
|
||||
import {
|
||||
ArrayCreate,
|
||||
ArraySpeciesCreate,
|
||||
Assert,
|
||||
Call,
|
||||
CreateArrayIterator,
|
||||
CreateDataProperty,
|
||||
CreateDataPropertyOrThrow,
|
||||
DeletePropertyOrThrow,
|
||||
Get,
|
||||
HasProperty,
|
||||
IsArray,
|
||||
IsCallable,
|
||||
IsConcatSpreadable,
|
||||
Set,
|
||||
CompareArrayElements,
|
||||
LengthOfArrayLike,
|
||||
OrdinaryObjectCreate,
|
||||
ToBoolean,
|
||||
ToIntegerOrInfinity,
|
||||
ToObject,
|
||||
ToString,
|
||||
F,
|
||||
type FunctionObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.concat */
|
||||
function* ArrayProto_concat(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const A = Q(yield* ArraySpeciesCreate(O, 0));
|
||||
let n = 0;
|
||||
const items = [O, ...args];
|
||||
while (items.length > 0) {
|
||||
const E = items.shift()!;
|
||||
const spreadable = Q(yield* IsConcatSpreadable(E));
|
||||
__ts_cast__<ObjectValue>(E);
|
||||
if (spreadable === Value.true) {
|
||||
let k = 0;
|
||||
const len = Q(yield* LengthOfArrayLike(E));
|
||||
if (n + len > (2 ** 53) - 1) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength');
|
||||
}
|
||||
while (k < len) {
|
||||
const P = X(ToString(F(k)));
|
||||
const exists = Q(yield* HasProperty(E, P));
|
||||
if (exists === Value.true) {
|
||||
const subElement = Q(yield* Get(E, P));
|
||||
const nStr = X(ToString(F(n)));
|
||||
Q(yield* CreateDataPropertyOrThrow(A, nStr, subElement));
|
||||
}
|
||||
n += 1;
|
||||
k += 1;
|
||||
}
|
||||
} else {
|
||||
if (n >= (2 ** 53) - 1) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength');
|
||||
}
|
||||
const nStr = X(ToString(F(n)));
|
||||
Q(yield* CreateDataPropertyOrThrow(A, nStr, E));
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
Q(yield* Set(A, Value('length'), F(n), Value.true));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.copywithin */
|
||||
function* ArrayProto_copyWithin([target = Value.undefined, start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const relativeTarget = Q(yield* ToIntegerOrInfinity(target));
|
||||
let to;
|
||||
if (relativeTarget < 0) {
|
||||
to = Math.max(len + relativeTarget, 0);
|
||||
} else {
|
||||
to = Math.min(relativeTarget, len);
|
||||
}
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let from;
|
||||
if (relativeStart < 0) {
|
||||
from = Math.max(len + relativeStart, 0);
|
||||
} else {
|
||||
from = Math.min(relativeStart, len);
|
||||
}
|
||||
let relativeEnd;
|
||||
if (end === Value.undefined) {
|
||||
relativeEnd = len;
|
||||
} else {
|
||||
relativeEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let final;
|
||||
if (relativeEnd < 0) {
|
||||
final = Math.max(len + relativeEnd, 0);
|
||||
} else {
|
||||
final = Math.min(relativeEnd, len);
|
||||
}
|
||||
let count = Math.min(final - from, len - to);
|
||||
let direction;
|
||||
if (from < to && to < from + count) {
|
||||
direction = -1;
|
||||
from += count - 1;
|
||||
to += count - 1;
|
||||
} else {
|
||||
direction = 1;
|
||||
}
|
||||
while (count > 0) {
|
||||
const fromKey: JSStringValue = X(ToString(F(from)));
|
||||
const toKey: JSStringValue = X(ToString(F(to)));
|
||||
const fromPresent = Q(yield* HasProperty(O, fromKey));
|
||||
if (fromPresent === Value.true) {
|
||||
const fromVal = Q(yield* Get(O, fromKey));
|
||||
Q(yield* Set(O, toKey, fromVal, Value.true));
|
||||
} else {
|
||||
Q(yield* DeletePropertyOrThrow(O, toKey));
|
||||
}
|
||||
from += direction;
|
||||
to += direction;
|
||||
count -= 1;
|
||||
}
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.entries */
|
||||
function ArrayProto_entries(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const O = Q(ToObject(thisValue));
|
||||
return CreateArrayIterator(O, 'key+value');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.fill */
|
||||
function* ArrayProto_fill([value = Value.undefined, start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let k;
|
||||
if (relativeStart < 0) {
|
||||
k = Math.max(len + relativeStart, 0);
|
||||
} else {
|
||||
k = Math.min(relativeStart, len);
|
||||
}
|
||||
let relativeEnd;
|
||||
if (end instanceof UndefinedValue) {
|
||||
relativeEnd = len;
|
||||
} else {
|
||||
relativeEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let final;
|
||||
if (relativeEnd < 0) {
|
||||
final = Math.max(len + relativeEnd, 0);
|
||||
} else {
|
||||
final = Math.min(relativeEnd, len);
|
||||
}
|
||||
while (k < final) {
|
||||
const Pk: JSStringValue = X(ToString(F(k)));
|
||||
Q(yield* Set(O, Pk, value, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.filter */
|
||||
function* ArrayProto_filter([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
const A = Q(yield* ArraySpeciesCreate(O, 0));
|
||||
let k = 0;
|
||||
let to = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kPresent = Q(yield* HasProperty(O, Pk));
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
const selected = ToBoolean(Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O])));
|
||||
if (selected === Value.true) {
|
||||
Q(yield* CreateDataPropertyOrThrow(A, X(ToString(F(to))), kValue));
|
||||
to += 1;
|
||||
}
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-flattenintoarray */
|
||||
function* FlattenIntoArray(target: ObjectValue, source: ObjectValue, sourceLen: number, start: number, depth: number, mapperFunction?: FunctionObject, thisArg?: Value): PlainEvaluator<number> {
|
||||
Assert(target instanceof ObjectValue);
|
||||
Assert(source instanceof ObjectValue);
|
||||
Assert(sourceLen >= 0);
|
||||
Assert(start >= 0);
|
||||
// Assert: _depth_ is an integer Number, *+∞*, or *-∞*.
|
||||
// Assert(mapperFunction === undefined || (X(IsCallable(mapperFunction)) === Value.true && thisArg !== undefined && depth === 1));
|
||||
let targetIndex = start;
|
||||
let sourceIndex = 0;
|
||||
while (sourceIndex < sourceLen) {
|
||||
const P = X(ToString(F(sourceIndex)));
|
||||
const exists = Q(yield* HasProperty(source, P));
|
||||
if (exists === Value.true) {
|
||||
let element = Q(yield* Get(source, P));
|
||||
if (mapperFunction) {
|
||||
Assert(!!thisArg);
|
||||
element = Q(yield* Call(mapperFunction, thisArg, [element, F(sourceIndex), source]));
|
||||
}
|
||||
let shouldFlatten: BooleanValue = Value.false;
|
||||
if (depth > 0) {
|
||||
shouldFlatten = Q(IsArray(element));
|
||||
}
|
||||
if (shouldFlatten === Value.true) {
|
||||
const elementLen = Q(yield* LengthOfArrayLike(element as ObjectValue));
|
||||
targetIndex = Q(yield* FlattenIntoArray(target, element as ObjectValue, elementLen, targetIndex, depth - 1));
|
||||
} else {
|
||||
if (targetIndex >= (2 ** 53) - 1) {
|
||||
return surroundingAgent.Throw('TypeError', 'OutOfRange', targetIndex);
|
||||
}
|
||||
Q(yield* CreateDataPropertyOrThrow(target, X(ToString(F(targetIndex))), element));
|
||||
targetIndex += 1;
|
||||
}
|
||||
}
|
||||
sourceIndex += 1;
|
||||
}
|
||||
return targetIndex;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.flat */
|
||||
function* ArrayProto_flat([depth = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const sourceLen = Q(yield* LengthOfArrayLike(O));
|
||||
let depthNum = 1;
|
||||
if (depth !== Value.undefined) {
|
||||
depthNum = Q(yield* ToIntegerOrInfinity(depth));
|
||||
}
|
||||
const A = Q(yield* ArraySpeciesCreate(O, 0));
|
||||
Q(yield* FlattenIntoArray(A, O, sourceLen, 0, depthNum));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.flatmap */
|
||||
function* ArrayProto_flatMap([mapperFunction = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const sourceLen = Q(yield* LengthOfArrayLike(O));
|
||||
if (!IsCallable(mapperFunction)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', mapperFunction);
|
||||
}
|
||||
const A = Q(yield* ArraySpeciesCreate(O, 0));
|
||||
Q(yield* FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.keys */
|
||||
function ArrayProto_keys(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const O = Q(ToObject(thisValue));
|
||||
return CreateArrayIterator(O, 'key');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.map */
|
||||
function* ArrayProto_map([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
const A = Q(yield* ArraySpeciesCreate(O, len));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kPresent = Q(yield* HasProperty(O, Pk));
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
const mappedValue = Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O]));
|
||||
Q(yield* CreateDataPropertyOrThrow(A, Pk, mappedValue));
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.pop */
|
||||
function* ArrayProto_pop(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
if (len === 0) {
|
||||
Q(yield* Set(O, Value('length'), F(+0), Value.true));
|
||||
return Value.undefined;
|
||||
} else {
|
||||
const newLen = len - 1;
|
||||
const index = Q(yield* ToString(F(newLen)));
|
||||
const element = Q(yield* Get(O, index));
|
||||
Q(yield* DeletePropertyOrThrow(O, index));
|
||||
Q(yield* Set(O, Value('length'), F(newLen), Value.true));
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.push */
|
||||
function* ArrayProto_push(_items: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const items = [..._items];
|
||||
const O = Q(ToObject(thisValue));
|
||||
let len = Q(yield* LengthOfArrayLike(O));
|
||||
const argCount = items.length;
|
||||
if (len + argCount > (2 ** 53) - 1) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength');
|
||||
}
|
||||
while (items.length > 0) {
|
||||
const E = items.shift()!;
|
||||
Q(yield* Set(O, X(ToString(F(len))), E, Value.true));
|
||||
len += 1;
|
||||
}
|
||||
Q(yield* Set(O, Value('length'), F(len), Value.true));
|
||||
return F(len);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.shift */
|
||||
function* ArrayProto_shift(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
if (len === 0) {
|
||||
Q(yield* Set(O, Value('length'), F(+0), Value.true));
|
||||
return Value.undefined;
|
||||
}
|
||||
const first = Q(yield* Get(O, Value('0')));
|
||||
let k = 1;
|
||||
while (k < len) {
|
||||
const from = X(ToString(F(k)));
|
||||
const to = X(ToString(F(k - 1)));
|
||||
const fromPresent = Q(yield* HasProperty(O, from));
|
||||
if (fromPresent === Value.true) {
|
||||
const fromVal = Q(yield* Get(O, from));
|
||||
Q(yield* Set(O, to, fromVal, Value.true));
|
||||
} else {
|
||||
Q(yield* DeletePropertyOrThrow(O, to));
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
Q(yield* DeletePropertyOrThrow(O, X(ToString(F(len - 1)))));
|
||||
Q(yield* Set(O, Value('length'), F(len - 1), Value.true));
|
||||
return first;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.slice */
|
||||
function* ArrayProto_slice([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let k;
|
||||
if (relativeStart < 0) {
|
||||
k = Math.max(len + relativeStart, 0);
|
||||
} else {
|
||||
k = Math.min(relativeStart, len);
|
||||
}
|
||||
let relativeEnd;
|
||||
if (end instanceof UndefinedValue) {
|
||||
relativeEnd = len;
|
||||
} else {
|
||||
relativeEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let final;
|
||||
if (relativeEnd < 0) {
|
||||
final = Math.max(len + relativeEnd, 0);
|
||||
} else {
|
||||
final = Math.min(relativeEnd, len);
|
||||
}
|
||||
const count = Math.max(final - k, 0);
|
||||
const A = Q(yield* ArraySpeciesCreate(O, count));
|
||||
let n = 0;
|
||||
while (k < final) {
|
||||
const Pk: JSStringValue = X(ToString(F(k)));
|
||||
const kPresent = Q(yield* HasProperty(O, Pk));
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
const nStr = X(ToString(F(n)));
|
||||
Q(yield* CreateDataPropertyOrThrow(A, nStr, kValue));
|
||||
}
|
||||
k += 1;
|
||||
n += 1;
|
||||
}
|
||||
Q(yield* Set(A, Value('length'), F(n), Value.true));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.sort */
|
||||
function* ArrayProto_sort([comparefn = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
if (comparefn !== Value.undefined && !IsCallable(comparefn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', comparefn);
|
||||
}
|
||||
const obj = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(obj));
|
||||
|
||||
return yield* ArrayProto_sortBody(obj, len, (x, y) => CompareArrayElements(x, y, comparefn));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.tosorted */
|
||||
function* ArrayProto_toSorted([comparator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
if (comparator !== Value.undefined && !IsCallable(comparator)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', comparator);
|
||||
}
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const A = Q(ArrayCreate(len));
|
||||
const SortCompare = function* SortCompare(x: Value, y: Value) {
|
||||
return yield* CompareArrayElements(x, y, comparator);
|
||||
};
|
||||
const sortedList = Q(yield* SortIndexedProperties(O, len, SortCompare, 'read-through-holes'));
|
||||
let j = 0;
|
||||
while (j < len) {
|
||||
X(CreateDataPropertyOrThrow(A, X(ToString(F(j))), sortedList[j]));
|
||||
j += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.splice */
|
||||
function* ArrayProto_splice(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const [start = Value.undefined, deleteCount = Value.undefined, ...items] = args;
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let actualStart;
|
||||
if (relativeStart < 0) {
|
||||
actualStart = Math.max(len + relativeStart, 0);
|
||||
} else {
|
||||
actualStart = Math.min(relativeStart, len);
|
||||
}
|
||||
let insertCount;
|
||||
let actualDeleteCount;
|
||||
if (args.length === 0) {
|
||||
insertCount = 0;
|
||||
actualDeleteCount = 0;
|
||||
} else if (args.length === 1) {
|
||||
insertCount = 0;
|
||||
actualDeleteCount = len - actualStart;
|
||||
} else {
|
||||
insertCount = args.length - 2;
|
||||
const dc = Q(yield* ToIntegerOrInfinity(deleteCount));
|
||||
actualDeleteCount = Math.min(Math.max(dc, 0), len - actualStart);
|
||||
}
|
||||
if (len + insertCount - actualDeleteCount > (2 ** 53) - 1) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength');
|
||||
}
|
||||
const A = Q(yield* ArraySpeciesCreate(O, actualDeleteCount));
|
||||
let k = 0;
|
||||
while (k < actualDeleteCount) {
|
||||
const from = X(ToString(F(actualStart + k)));
|
||||
const fromPresent = Q(yield* HasProperty(O, from));
|
||||
if (fromPresent === Value.true) {
|
||||
const fromValue = Q(yield* Get(O, from));
|
||||
Q(yield* CreateDataPropertyOrThrow(A, X(ToString(F(k))), fromValue));
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
Q(yield* Set(A, Value('length'), F(actualDeleteCount), Value.true));
|
||||
const itemCount = items.length;
|
||||
if (itemCount < actualDeleteCount) {
|
||||
k = actualStart;
|
||||
while (k < len - actualDeleteCount) {
|
||||
const from: JSStringValue = X(ToString(F(k + actualDeleteCount)));
|
||||
const to = X(ToString(F(k + itemCount)));
|
||||
const fromPresent = Q(yield* HasProperty(O, from));
|
||||
if (fromPresent === Value.true) {
|
||||
const fromValue = Q(yield* Get(O, from));
|
||||
Q(yield* Set(O, to, fromValue, Value.true));
|
||||
} else {
|
||||
Q(yield* DeletePropertyOrThrow(O, to));
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
k = len;
|
||||
while (k > len - actualDeleteCount + itemCount) {
|
||||
Q(yield* DeletePropertyOrThrow(O, X(ToString(F(k - 1)))));
|
||||
k -= 1;
|
||||
}
|
||||
} else if (itemCount > actualDeleteCount) {
|
||||
k = len - actualDeleteCount;
|
||||
while (k > actualStart) {
|
||||
const from: JSStringValue = X(ToString(F(k + actualDeleteCount - 1)));
|
||||
const to = X(ToString(F(k + itemCount - 1)));
|
||||
const fromPresent = Q(yield* HasProperty(O, from));
|
||||
if (fromPresent === Value.true) {
|
||||
const fromValue = Q(yield* Get(O, from));
|
||||
Q(yield* Set(O, to, fromValue, Value.true));
|
||||
} else {
|
||||
Q(yield* DeletePropertyOrThrow(O, to));
|
||||
}
|
||||
k -= 1;
|
||||
}
|
||||
}
|
||||
k = actualStart;
|
||||
while (items.length > 0) {
|
||||
const E = items.shift()!;
|
||||
Q(yield* Set(O, X(ToString(F(k))), E, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
Q(yield* Set(O, Value('length'), F(len - actualDeleteCount + itemCount), Value.true));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.tospliced */
|
||||
function* ArrayProto_toSpliced(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const [start = Value.undefined, skipCount = Value.undefined, ...items] = args as Value[];
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let actualStart;
|
||||
if (relativeStart === -Infinity) {
|
||||
actualStart = 0;
|
||||
} else if (relativeStart < 0) {
|
||||
actualStart = Math.max(len + relativeStart, 0);
|
||||
} else {
|
||||
actualStart = Math.min(relativeStart, len);
|
||||
}
|
||||
const insertCount = items.length;
|
||||
let actualSkipCount;
|
||||
if (args[0] === undefined) {
|
||||
actualSkipCount = 0;
|
||||
} else if (args[1] === undefined) {
|
||||
actualSkipCount = len - actualStart;
|
||||
} else {
|
||||
const sc = Q(yield* ToIntegerOrInfinity(skipCount));
|
||||
actualSkipCount = Math.min(Math.max(sc, 0), len - actualStart);
|
||||
}
|
||||
const newLen = len - actualSkipCount + insertCount;
|
||||
if (newLen > (2 ** 53) - 1) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength');
|
||||
}
|
||||
const A = Q(ArrayCreate(newLen));
|
||||
let i = 0;
|
||||
let r = actualStart + actualSkipCount;
|
||||
while (i < actualStart) {
|
||||
const Pi = X(ToString(F(i)));
|
||||
const iValue = Q(yield* Get(O, Pi));
|
||||
X(CreateDataPropertyOrThrow(A, Pi, iValue));
|
||||
i += 1;
|
||||
}
|
||||
for (const E of items) {
|
||||
const Pi = X(ToString(F(i)));
|
||||
X(CreateDataPropertyOrThrow(A, Pi, E));
|
||||
i += 1;
|
||||
}
|
||||
while (i < newLen) {
|
||||
const Pi = X(ToString(F(i)));
|
||||
const from = X(ToString(F(r)));
|
||||
const fromValue = Q(yield* Get(O, from));
|
||||
X(CreateDataPropertyOrThrow(A, Pi, fromValue));
|
||||
i += 1;
|
||||
r += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.with */
|
||||
function* ArrayProto_with([index = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const relativeIndex = Q(yield* ToIntegerOrInfinity(index));
|
||||
let actualIndex;
|
||||
if (relativeIndex >= 0) {
|
||||
actualIndex = relativeIndex;
|
||||
} else {
|
||||
actualIndex = len + relativeIndex;
|
||||
}
|
||||
if (actualIndex >= len || actualIndex < 0) {
|
||||
return surroundingAgent.Throw('RangeError', 'OutOfRange', index);
|
||||
}
|
||||
const A = Q(ArrayCreate(len));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let fromValue;
|
||||
if (k === actualIndex) {
|
||||
fromValue = value;
|
||||
} else {
|
||||
fromValue = Q(yield* Get(O, Pk));
|
||||
}
|
||||
X(CreateDataPropertyOrThrow(A, Pk, fromValue));
|
||||
k += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.tostring */
|
||||
function* ArrayProto_toString(_a: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const array = Q(ToObject(thisValue));
|
||||
let func = Q(yield* Get(array, Value('join')));
|
||||
if (!IsCallable(func)) {
|
||||
func = surroundingAgent.intrinsic('%Object.prototype.toString%');
|
||||
}
|
||||
return Q(yield* Call(func, array));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.unshift */
|
||||
function* ArrayProto_unshift(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const argCount = args.length;
|
||||
if (argCount > 0) {
|
||||
if (len + argCount > (2 ** 53) - 1) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayPastSafeLength');
|
||||
}
|
||||
let k = len;
|
||||
while (k > 0) {
|
||||
const from = X(ToString(F(k - 1)));
|
||||
const to = X(ToString(F(k + argCount - 1)));
|
||||
const fromPresent = Q(yield* HasProperty(O, from));
|
||||
if (fromPresent === Value.true) {
|
||||
const fromValue = Q(yield* Get(O, from));
|
||||
Q(yield* Set(O, to, fromValue, Value.true));
|
||||
} else {
|
||||
Q(yield* DeletePropertyOrThrow(O, to));
|
||||
}
|
||||
k -= 1;
|
||||
}
|
||||
let j = 0;
|
||||
const items = [...args];
|
||||
while (items.length !== 0) {
|
||||
const E = items.shift()!;
|
||||
const jStr = X(ToString(F(j)));
|
||||
Q(yield* Set(O, jStr, E, Value.true));
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
Q(yield* Set(O, Value('length'), F(len + argCount), Value.true));
|
||||
return F(len + argCount);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.values */
|
||||
function ArrayProto_values(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const O = Q(ToObject(thisValue));
|
||||
return CreateArrayIterator(O, 'value');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.at */
|
||||
function* ArrayProto_at([index = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 2. Let len be ? LengthOfArrayLike(O).
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
// 3. Let relativeIndex be ? ToIntegerOrInfinity(index).
|
||||
const relativeIndex = Q(yield* ToIntegerOrInfinity(index));
|
||||
let k;
|
||||
// 4. If relativeIndex ≥ 0, then
|
||||
if (relativeIndex >= 0) {
|
||||
// a. Let k be relativeIndex.
|
||||
k = relativeIndex;
|
||||
} else { // 5. Else,
|
||||
// a. Let k be len + relativeIndex.
|
||||
k = len + relativeIndex;
|
||||
}
|
||||
// 6. If k < 0 or k ≥ len, then return undefined.
|
||||
if (k < 0 || k >= len) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 7. Return ? Get(O, ! ToString(k)).
|
||||
return Q(yield* Get(O, X(ToString(F(k)))));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.toreversed */
|
||||
function* ArrayProto_toReversed(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* LengthOfArrayLike(O));
|
||||
const A = Q(ArrayCreate(len));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const from = X(ToString(F(len - 1 - k)));
|
||||
const Pk = X(ToString(F(k)));
|
||||
const fromValue = Q(yield* Get(O, from));
|
||||
X(CreateDataPropertyOrThrow(A, Pk, fromValue));
|
||||
k += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
export function bootstrapArrayPrototype(realmRec: Realm) {
|
||||
const proto = X(ArrayCreate(0, realmRec.Intrinsics['%Object.prototype%']));
|
||||
|
||||
assignProps(realmRec, proto, [
|
||||
['concat', ArrayProto_concat, 1],
|
||||
['copyWithin', ArrayProto_copyWithin, 2],
|
||||
['entries', ArrayProto_entries, 0],
|
||||
['fill', ArrayProto_fill, 1],
|
||||
['filter', ArrayProto_filter, 1],
|
||||
['flat', ArrayProto_flat, 0],
|
||||
['flatMap', ArrayProto_flatMap, 1],
|
||||
['at', ArrayProto_at, 1],
|
||||
['keys', ArrayProto_keys, 0],
|
||||
['map', ArrayProto_map, 1],
|
||||
['pop', ArrayProto_pop, 0],
|
||||
['push', ArrayProto_push, 1],
|
||||
['shift', ArrayProto_shift, 0],
|
||||
['slice', ArrayProto_slice, 2],
|
||||
['sort', ArrayProto_sort, 1],
|
||||
['toSorted', ArrayProto_toSorted, 1],
|
||||
['splice', ArrayProto_splice, 2],
|
||||
['toSpliced', ArrayProto_toSpliced, 2],
|
||||
['toString', ArrayProto_toString, 0],
|
||||
['unshift', ArrayProto_unshift, 1],
|
||||
['values', ArrayProto_values, 0],
|
||||
['with', ArrayProto_with, 2],
|
||||
['toReversed', ArrayProto_toReversed, 0],
|
||||
]);
|
||||
|
||||
bootstrapArrayPrototypeShared(realmRec, proto, 'Array');
|
||||
|
||||
X(proto.DefineOwnProperty(wellKnownSymbols.iterator, X(proto.GetOwnProperty(Value('values'))) as Descriptor));
|
||||
|
||||
{
|
||||
const unscopableList = OrdinaryObjectCreate(Value.null);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('at'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('copyWithin'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('entries'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('fill'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('find'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('findIndex'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('findLast'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('findLastIndex'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('flat'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('flatMap'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('includes'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('keys'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('toReversed'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('toSorted'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('toSpliced'), Value.true)) === Value.true);
|
||||
Assert(X(CreateDataProperty(unscopableList, Value('values'), Value.true)) === Value.true);
|
||||
X(proto.DefineOwnProperty(wellKnownSymbols.unscopables, Descriptor({
|
||||
Value: unscopableList,
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
}
|
||||
|
||||
// Used in `arguments` objects.
|
||||
realmRec.Intrinsics['%Array.prototype.values%'] = X(Get(proto, Value('values'))) as FunctionObject;
|
||||
|
||||
realmRec.Intrinsics['%Array.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,691 @@
|
||||
import {
|
||||
NormalCompletion,
|
||||
Q, ThrowCompletion, X, type ValueEvaluator,
|
||||
type ValueCompletion,
|
||||
} from '../completion.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import type { PlainEvaluator } from '../evaluator.mts';
|
||||
import {
|
||||
NullValue, NumberValue, ObjectValue, UndefinedValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { assignProps } from './bootstrap.mts';
|
||||
import { ValidateTypedArray } from './TypedArray.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
DeletePropertyOrThrow,
|
||||
Get,
|
||||
HasProperty,
|
||||
Invoke,
|
||||
IsCallable,
|
||||
SameValueZero,
|
||||
Set,
|
||||
IsStrictlyEqual,
|
||||
ToBoolean,
|
||||
ToIntegerOrInfinity,
|
||||
ToObject,
|
||||
ToString,
|
||||
F, R,
|
||||
Realm,
|
||||
LengthOfArrayLike, skipDebugger, TypedArrayLength,
|
||||
} from '#self';
|
||||
|
||||
// Algorithms and methods shared between %Array.prototype% and
|
||||
// %TypedArray.prototype%.
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.sort */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort */
|
||||
//
|
||||
// If internalMethodsRestricted is true, then Asserts are used to ensure that
|
||||
// "The only internal methods of the this object that the algorithm may call
|
||||
// are [[Get]] and [[Set]]," a requirement of %TypedArray%.prototype.sort.
|
||||
export function* ArrayProto_sortBody(obj: ObjectValue, len: number, SortCompare: (x: Value, y: Value) => ValueEvaluator<NumberValue>, internalMethodsRestricted = false): ValueEvaluator {
|
||||
const items = [];
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
if (internalMethodsRestricted) {
|
||||
items.push(Q(yield* Get(obj, Pk)));
|
||||
} else {
|
||||
const kPresent = Q(yield* HasProperty(obj, Pk));
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(obj, Pk));
|
||||
items.push(kValue);
|
||||
}
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
const itemCount = items.length;
|
||||
|
||||
// Mergesort.
|
||||
const lBuffer = [];
|
||||
const rBuffer = [];
|
||||
for (let step = 1; step < items.length; step *= 2) {
|
||||
for (let start = 0; start < items.length - 1; start += 2 * step) {
|
||||
const sizeLeft = step;
|
||||
const mid = start + sizeLeft;
|
||||
const sizeRight = Math.min(step, items.length - mid);
|
||||
if (sizeRight < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Merge.
|
||||
for (let l = 0; l < sizeLeft; l += 1) {
|
||||
lBuffer[l] = items[start + l];
|
||||
}
|
||||
for (let r = 0; r < sizeRight; r += 1) {
|
||||
rBuffer[r] = items[mid + r];
|
||||
}
|
||||
|
||||
{
|
||||
let l = 0;
|
||||
let r = 0;
|
||||
let o = start;
|
||||
while (l < sizeLeft && r < sizeRight) {
|
||||
const cmp = R(Q(yield* SortCompare(lBuffer[l], rBuffer[r])));
|
||||
if (cmp <= 0) {
|
||||
items[o] = lBuffer[l];
|
||||
o += 1;
|
||||
l += 1;
|
||||
} else {
|
||||
items[o] = rBuffer[r];
|
||||
o += 1;
|
||||
r += 1;
|
||||
}
|
||||
}
|
||||
while (l < sizeLeft) {
|
||||
items[o] = lBuffer[l];
|
||||
o += 1;
|
||||
l += 1;
|
||||
}
|
||||
while (r < sizeRight) {
|
||||
items[o] = rBuffer[r];
|
||||
o += 1;
|
||||
r += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let j = 0;
|
||||
while (j < itemCount) {
|
||||
Q(yield* Set(obj, X(ToString(F(j))), items[j], Value.true));
|
||||
j += 1;
|
||||
}
|
||||
while (j < len) {
|
||||
Q(yield* DeletePropertyOrThrow(obj, X(ToString(F(j)))));
|
||||
j += 1;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-sortindexedproperties */
|
||||
export function* SortIndexedProperties(obj: ObjectValue, len: number, SortCompare: (x: Value, y: Value) => ValueEvaluator<NumberValue>, holes: 'skip-holes' | 'read-through-holes'): PlainEvaluator<Value[]> {
|
||||
const items = [];
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let kRead;
|
||||
if (holes === 'skip-holes') {
|
||||
kRead = Q(yield* HasProperty(obj, Pk));
|
||||
} else {
|
||||
Assert(holes === 'read-through-holes');
|
||||
kRead = Value.true;
|
||||
}
|
||||
if (kRead === Value.true) {
|
||||
const kValue = Q(yield* Get(obj, Pk));
|
||||
items.push(kValue);
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
let completion: ValueCompletion<NumberValue> = NormalCompletion(Value(0));
|
||||
items.sort((a, b) => {
|
||||
if (completion instanceof ThrowCompletion) {
|
||||
return 0;
|
||||
}
|
||||
// TODO: remove skipDebugger
|
||||
completion = skipDebugger(SortCompare(a, b));
|
||||
if (completion instanceof ThrowCompletion) {
|
||||
return 0;
|
||||
}
|
||||
const cmp = R(X(completion));
|
||||
return cmp;
|
||||
});
|
||||
if (completion instanceof ThrowCompletion) {
|
||||
return completion;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
export function bootstrapArrayPrototypeShared(realmRec: Realm, proto: ObjectValue, kind: 'Array' | 'TypedArray') {
|
||||
const Validate = kind === 'Array' ? undefined : (thisValue: Value) => ValidateTypedArray(thisValue, 'seq-cst');
|
||||
const ToLength: (O: ObjectValue) => PlainEvaluator<number> = kind === 'Array'
|
||||
? function* ArrayToLength(O) {
|
||||
return yield* LengthOfArrayLike(O);
|
||||
}
|
||||
: function* TypedArrayToLength(O): PlainEvaluator<number> {
|
||||
const rec = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
return TypedArrayLength(rec);
|
||||
};
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.every */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.every */
|
||||
function* ArrayProto_every([callbackFn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (!IsCallable(callbackFn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackFn);
|
||||
}
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let kPresent;
|
||||
if (kind === 'Array') {
|
||||
kPresent = Q(yield* HasProperty(O, Pk));
|
||||
} else {
|
||||
kPresent = Value.true;
|
||||
}
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
const testResult = ToBoolean(Q(yield* Call(callbackFn, thisArg, [kValue, F(k), O])));
|
||||
if (testResult === Value.false) {
|
||||
return Value.false;
|
||||
}
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return Value.true;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.find */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.find */
|
||||
function* ArrayProto_find([predicate = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (!IsCallable(predicate)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate);
|
||||
}
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
const testResult = ToBoolean(Q(yield* Call(predicate, thisArg, [kValue, F(k), O])));
|
||||
if (testResult === Value.true) {
|
||||
return kValue;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.findindex */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.findindex */
|
||||
function* ArrayProto_findIndex([predicate = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (!IsCallable(predicate)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate);
|
||||
}
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
const testResult = ToBoolean(Q(yield* Call(predicate, thisArg, [kValue, F(k), O])));
|
||||
if (testResult === Value.true) {
|
||||
return F(k);
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return F(-1);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.findlast */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlast */
|
||||
function* ArrayProto_findLast([predicate = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
// Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 2. Let len be ? LengthOfArrayLike(O).
|
||||
const len = Q(yield* ToLength(O));
|
||||
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
|
||||
if (!IsCallable(predicate)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate);
|
||||
}
|
||||
// 4. Let k be len - 1.
|
||||
let k = len - 1;
|
||||
// 5. Repeat, while k ≥ 0,
|
||||
while (k >= 0) {
|
||||
// a. Let Pk be ! ToString(𝔽(k)).
|
||||
const Pk = X(ToString(F(k)));
|
||||
// b. Let kValue be ? Get(O, Pk).
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
// c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
|
||||
const testResult = ToBoolean(Q(yield* Call(predicate, thisArg, [kValue, F(k), O])));
|
||||
// d. If testResult is true, return kValue.
|
||||
if (testResult === Value.true) {
|
||||
return kValue;
|
||||
}
|
||||
// e. Set k to k - 1.
|
||||
k -= 1;
|
||||
}
|
||||
// 6. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.findlastindex */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.findlastindex */
|
||||
function* ArrayProto_findLastIndex([predicate = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
// Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 2. Let len be ? LengthOfArrayLike(O).
|
||||
const len = Q(yield* ToLength(O));
|
||||
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
|
||||
if (!IsCallable(predicate)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', predicate);
|
||||
}
|
||||
// 4. Let k be len - 1.
|
||||
let k = len - 1;
|
||||
// 5. Repeat, while k ≥ 0,
|
||||
while (k >= 0) {
|
||||
// a. Let Pk be ! ToString(𝔽(k)).
|
||||
const Pk = X(ToString(F(k)));
|
||||
// b. Let kValue be ? Get(O, Pk).
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
// c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
|
||||
const testResult = ToBoolean(Q(yield* Call(predicate, thisArg, [kValue, F(k), O])));
|
||||
// d. If testResult is true, return 𝔽(k).
|
||||
if (testResult === Value.true) {
|
||||
return F(k);
|
||||
}
|
||||
// e. Set k to k - 1.
|
||||
k -= 1;
|
||||
}
|
||||
// 6. Return Return -1𝔽.
|
||||
return F(-1);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.foreach */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach */
|
||||
function* ArrayProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let kPresent;
|
||||
if (kind === 'Array') {
|
||||
kPresent = Q(yield* HasProperty(O, Pk));
|
||||
} else {
|
||||
kPresent = Value.true;
|
||||
}
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O]));
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.includes */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.includes */
|
||||
function* ArrayProto_includes([searchElement = Value.undefined, fromIndex = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (len === 0) {
|
||||
return Value.false;
|
||||
}
|
||||
const n = Q(yield* ToIntegerOrInfinity(fromIndex));
|
||||
if (fromIndex === Value.undefined) {
|
||||
Assert(n === 0);
|
||||
}
|
||||
let k;
|
||||
if (n >= 0) {
|
||||
k = n;
|
||||
} else {
|
||||
k = len + n;
|
||||
if (k < 0) {
|
||||
k = 0;
|
||||
}
|
||||
}
|
||||
while (k < len) {
|
||||
const kStr = X(ToString(F(k)));
|
||||
const elementK = Q(yield* Get(O, kStr));
|
||||
if (SameValueZero(searchElement, elementK) === Value.true) {
|
||||
return Value.true;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.indexof */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.indexof */
|
||||
function* ArrayProto_indexOf([searchElement = Value.undefined, fromIndex = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (len === 0) {
|
||||
return F(-1);
|
||||
}
|
||||
const n = Q(yield* ToIntegerOrInfinity(fromIndex));
|
||||
if (fromIndex === Value.undefined) {
|
||||
Assert(n === 0);
|
||||
}
|
||||
if (n >= len) {
|
||||
return F(-1);
|
||||
}
|
||||
let k;
|
||||
if (n >= 0) {
|
||||
k = n;
|
||||
} else {
|
||||
k = len + n;
|
||||
if (k < 0) {
|
||||
k = 0;
|
||||
}
|
||||
}
|
||||
while (k < len) {
|
||||
const kStr = X(ToString(F(k)));
|
||||
const kPresent = Q(yield* HasProperty(O, kStr));
|
||||
if (kPresent === Value.true) {
|
||||
const elementK = Q(yield* Get(O, kStr));
|
||||
const same = IsStrictlyEqual(searchElement, elementK);
|
||||
if (same === Value.true) {
|
||||
return F(k);
|
||||
}
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return F(-1);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.join */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.join */
|
||||
function* ArrayProto_join([separator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
let sep;
|
||||
if (separator instanceof UndefinedValue) {
|
||||
sep = ',';
|
||||
} else {
|
||||
sep = Q(yield* ToString(separator)).stringValue();
|
||||
}
|
||||
let R = '';
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
if (k > 0) {
|
||||
R = `${R}${sep}`;
|
||||
}
|
||||
const kStr = X(ToString(F(k)));
|
||||
const element = Q(yield* Get(O, kStr));
|
||||
let next;
|
||||
if (element instanceof UndefinedValue || element instanceof NullValue) {
|
||||
next = '';
|
||||
} else {
|
||||
next = Q(yield* ToString(element)).stringValue();
|
||||
}
|
||||
R = `${R}${next}`;
|
||||
k += 1;
|
||||
}
|
||||
return Value(R);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.lastindexof */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.lastindexof */
|
||||
function* ArrayProto_lastIndexOf([searchElement = Value.undefined, fromIndex]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (len === 0) {
|
||||
return F(-1);
|
||||
}
|
||||
let n;
|
||||
if (fromIndex !== undefined) {
|
||||
n = Q(yield* ToIntegerOrInfinity(fromIndex));
|
||||
} else {
|
||||
n = len - 1;
|
||||
}
|
||||
let k;
|
||||
if (n >= 0) {
|
||||
k = Math.min(n, len - 1);
|
||||
} else {
|
||||
k = len + n;
|
||||
}
|
||||
while (k >= 0) {
|
||||
const kStr = X(ToString(F(k)));
|
||||
const kPresent = Q(yield* HasProperty(O, kStr));
|
||||
if (kPresent === Value.true) {
|
||||
const elementK = Q(yield* Get(O, kStr));
|
||||
const same = IsStrictlyEqual(searchElement, elementK);
|
||||
if (same === Value.true) {
|
||||
return F(k);
|
||||
}
|
||||
}
|
||||
k -= 1;
|
||||
}
|
||||
return F(-1);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.reduce */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduce */
|
||||
function* ArrayProto_reduce([callbackfn = Value.undefined, initialValue]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
if (len === 0 && initialValue === undefined) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce');
|
||||
}
|
||||
let k = 0;
|
||||
let accumulator: Value = Value.undefined;
|
||||
if (initialValue !== undefined) {
|
||||
accumulator = initialValue;
|
||||
} else {
|
||||
let kPresent = false;
|
||||
while (kPresent === false && k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
if (kind === 'Array') {
|
||||
kPresent = Q(yield* HasProperty(O, Pk)) === Value.true;
|
||||
} else {
|
||||
kPresent = true;
|
||||
}
|
||||
if (kPresent === true) {
|
||||
accumulator = Q(yield* Get(O, Pk));
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
if (kPresent === false) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce');
|
||||
}
|
||||
}
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let kPresent;
|
||||
if (kind === 'Array') {
|
||||
kPresent = Q(yield* HasProperty(O, Pk));
|
||||
} else {
|
||||
kPresent = Value.true;
|
||||
}
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
accumulator = Q(yield* Call(callbackfn, Value.undefined, [accumulator, kValue, F(k), O]));
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.reduceright */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.reduceright */
|
||||
function* ArrayProto_reduceRight([callbackfn = Value.undefined, initialValue]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
if (len === 0 && initialValue === undefined) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce');
|
||||
}
|
||||
let k = len - 1;
|
||||
let accumulator: Value = Value.undefined;
|
||||
if (initialValue !== undefined) {
|
||||
accumulator = initialValue;
|
||||
} else {
|
||||
let kPresent = false;
|
||||
while (kPresent === false && k >= 0) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
if (kind === 'Array') {
|
||||
kPresent = Q(yield* HasProperty(O, Pk)) === Value.true;
|
||||
} else {
|
||||
kPresent = true;
|
||||
}
|
||||
if (kPresent === true) {
|
||||
accumulator = Q(yield* Get(O, Pk));
|
||||
}
|
||||
k -= 1;
|
||||
}
|
||||
if (kPresent === false) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayEmptyReduce');
|
||||
}
|
||||
}
|
||||
while (k >= 0) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let kPresent;
|
||||
if (kind === 'Array') {
|
||||
kPresent = Q(yield* HasProperty(O, Pk));
|
||||
} else {
|
||||
kPresent = Value.true;
|
||||
}
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
accumulator = Q(yield* Call(callbackfn, Value.undefined, [accumulator, kValue, F(k), O]));
|
||||
}
|
||||
k -= 1;
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.reverse */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.reverse */
|
||||
function* ArrayProto_reverse(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
const middle = Math.floor(len / 2);
|
||||
let lower = 0;
|
||||
while (lower !== middle) {
|
||||
const upper = len - lower - 1;
|
||||
const upperP = X(ToString(F(upper)));
|
||||
const lowerP = X(ToString(F(lower)));
|
||||
const lowerExists = Q(yield* HasProperty(O, lowerP));
|
||||
let lowerValue;
|
||||
let upperValue;
|
||||
if (lowerExists === Value.true) {
|
||||
lowerValue = Q(yield* Get(O, lowerP));
|
||||
}
|
||||
const upperExists = Q(yield* HasProperty(O, upperP));
|
||||
if (upperExists === Value.true) {
|
||||
upperValue = Q(yield* Get(O, upperP));
|
||||
}
|
||||
if (lowerExists === Value.true && upperExists === Value.true) {
|
||||
Q(yield* Set(O, lowerP, upperValue as Value, Value.true));
|
||||
Q(yield* Set(O, upperP, lowerValue as Value, Value.true));
|
||||
} else if (lowerExists === Value.false && upperExists === Value.true) {
|
||||
Q(yield* Set(O, lowerP, upperValue as Value, Value.true));
|
||||
Q(yield* DeletePropertyOrThrow(O, upperP));
|
||||
} else if (lowerExists === Value.true && upperExists === Value.false) {
|
||||
Q(yield* DeletePropertyOrThrow(O, lowerP));
|
||||
Q(yield* Set(O, upperP, lowerValue as Value, Value.true));
|
||||
} else {
|
||||
// no further action is required
|
||||
}
|
||||
lower += 1;
|
||||
}
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.some */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.some */
|
||||
function* ArrayProto_some([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const O = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(O));
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let kPresent;
|
||||
if (kind === 'Array') {
|
||||
kPresent = Q(yield* HasProperty(O, Pk));
|
||||
} else {
|
||||
kPresent = Value.true;
|
||||
}
|
||||
if (kPresent === Value.true) {
|
||||
const kValue = Q(yield* Get(O, Pk));
|
||||
const testResult = ToBoolean(Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O])));
|
||||
if (testResult === Value.true) {
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-array.prototype.tolocalestring */
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.tolocalestring */
|
||||
function* ArrayProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(Validate?.(thisValue));
|
||||
const array = Q(ToObject(thisValue));
|
||||
const len = Q(yield* ToLength(array));
|
||||
const separator = ', ';
|
||||
let R = '';
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
if (k > 0) {
|
||||
R = `${R}${separator}`;
|
||||
}
|
||||
const kStr = X(ToString(F(k)));
|
||||
const nextElement = Q(yield* Get(array, kStr));
|
||||
if (nextElement !== Value.undefined && nextElement !== Value.null) {
|
||||
const S = Q(yield* ToString(Q(yield* Invoke(nextElement, Value('toLocaleString'))))).stringValue();
|
||||
R = `${R}${S}`;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return Value(R);
|
||||
}
|
||||
|
||||
assignProps(realmRec, proto, [
|
||||
['every', ArrayProto_every, 1],
|
||||
['find', ArrayProto_find, 1],
|
||||
['findIndex', ArrayProto_findIndex, 1],
|
||||
['findLast', ArrayProto_findLast, 1],
|
||||
['findLastIndex', ArrayProto_findLastIndex, 1],
|
||||
['forEach', ArrayProto_forEach, 1],
|
||||
['includes', ArrayProto_includes, 1],
|
||||
['indexOf', ArrayProto_indexOf, 1],
|
||||
['join', ArrayProto_join, 1],
|
||||
['lastIndexOf', ArrayProto_lastIndexOf, 1],
|
||||
['reduce', ArrayProto_reduce, 1],
|
||||
['reduceRight', ArrayProto_reduceRight, 1],
|
||||
['reverse', ArrayProto_reverse, 0],
|
||||
['some', ArrayProto_some, 1],
|
||||
['toLocaleString', ArrayProto_toLocaleString, 0],
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue, UndefinedValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
IfAbruptRejectPromise, NormalCompletion, X,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
AsyncFromSyncIteratorContinuation,
|
||||
Call,
|
||||
CreateIteratorResultObject,
|
||||
GetMethod,
|
||||
IteratorNext,
|
||||
NewPromiseCapability,
|
||||
Assert,
|
||||
type OrdinaryObject,
|
||||
type IteratorRecord,
|
||||
IteratorClose,
|
||||
type FunctionObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface AsyncFromSyncIteratorObject extends OrdinaryObject {
|
||||
readonly SyncIteratorRecord: IteratorRecord;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.next */
|
||||
function* AsyncFromSyncIteratorPrototype_next([value]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as AsyncFromSyncIteratorObject;
|
||||
// 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot.
|
||||
Assert(O instanceof ObjectValue && 'SyncIteratorRecord' in O);
|
||||
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%')));
|
||||
// 4. Let syncIteratorRecord be O.[[SyncIteratorRecord]].
|
||||
const syncIteratorRecord = O.SyncIteratorRecord;
|
||||
// 5. If value is present, then
|
||||
let result;
|
||||
if (value !== undefined) {
|
||||
// a. Let result be IteratorNext(syncIteratorRecord, value).
|
||||
result = yield* IteratorNext(syncIteratorRecord, value);
|
||||
} else { // 6. Else,
|
||||
// a. Let result be IteratorNext(syncIteratorRecord).
|
||||
result = yield* IteratorNext(syncIteratorRecord);
|
||||
}
|
||||
// 7. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
__ts_cast__<ObjectValue>(result);
|
||||
// 8. Return ! AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, true).
|
||||
return X(AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, Value.true));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.return */
|
||||
function* AsyncFromSyncIteratorPrototype_return([value]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as AsyncFromSyncIteratorObject;
|
||||
// 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot.
|
||||
Assert(O instanceof ObjectValue && 'SyncIteratorRecord' in O);
|
||||
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%')));
|
||||
// 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]].
|
||||
const syncIteratorRecord = O.SyncIteratorRecord;
|
||||
const syncIterator = syncIteratorRecord.Iterator;
|
||||
// 5. Let return be GetMethod(syncIterator, "return").
|
||||
const ret = yield* GetMethod(syncIterator, Value('return'));
|
||||
// 6. IfAbruptRejectPromise(return, promiseCapability).
|
||||
IfAbruptRejectPromise(ret, promiseCapability);
|
||||
__ts_cast__<UndefinedValue | FunctionObject>(ret);
|
||||
// 7. If return is undefined, then
|
||||
if (ret === Value.undefined) {
|
||||
// a. Let iteratorResult be CreateIteratorResultObject(value, true).
|
||||
const iteratorResult = CreateIteratorResultObject(value || Value.undefined, Value.true);
|
||||
// b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
|
||||
X(Call(promiseCapability.Resolve, Value.undefined, [iteratorResult]));
|
||||
// c. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
// 8. If value is present, then
|
||||
let result;
|
||||
if (value !== undefined) {
|
||||
// a. Let result be Call(return, syncIterator, « value »).
|
||||
result = yield* Call(ret, syncIterator, [value]);
|
||||
} else { // 9. Else,
|
||||
// a. Let result be Call(return, syncIterator).
|
||||
result = yield* Call(ret, syncIterator);
|
||||
}
|
||||
// 10. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
__ts_cast__<Value>(result);
|
||||
// 11. If result is not an Object, then
|
||||
if (!(result instanceof ObjectValue)) {
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
|
||||
X(Call(promiseCapability.Reject, Value.undefined, [
|
||||
surroundingAgent.Throw('TypeError', 'NotAnObject', result).Value,
|
||||
]));
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
// 12. Return ! AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, false).
|
||||
return X(AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, Value.false));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%asyncfromsynciteratorprototype%.throw */
|
||||
function* AsyncFromSyncIteratorPrototype_throw([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let O be this value.
|
||||
const O = thisValue as AsyncFromSyncIteratorObject;
|
||||
// 2. Assert: Type(O) is Object and O has a [[SyncIteratorRecord]] internal slot.
|
||||
Assert(O instanceof ObjectValue && 'SyncIteratorRecord' in O);
|
||||
// 3. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%')));
|
||||
// 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]].
|
||||
const syncIteratorRecord = O.SyncIteratorRecord;
|
||||
const syncIterator = syncIteratorRecord.Iterator;
|
||||
// 5. Let throw be GetMethod(syncIterator, "throw").
|
||||
const thr = yield* GetMethod(syncIterator, Value('throw'));
|
||||
// 6. IfAbruptRejectPromise(throw, promiseCapability).
|
||||
IfAbruptRejectPromise(thr, promiseCapability);
|
||||
__ts_cast__<UndefinedValue | FunctionObject>(thr);
|
||||
// 7. If throw is undefined, then
|
||||
if (thr === Value.undefined) {
|
||||
const closeCompletion = NormalCompletion(undefined);
|
||||
const result = yield* IteratorClose(syncIteratorRecord, closeCompletion);
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
X(Call(promiseCapability.Reject, Value.undefined, [
|
||||
// TODO: error message should be no throw method
|
||||
surroundingAgent.Throw('TypeError', 'NotAnObject', value).Value,
|
||||
]));
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
// 8. If value is present, then
|
||||
let result;
|
||||
if (value !== undefined) {
|
||||
// a. Let result be Call(throw, syncIterator, « value »).
|
||||
result = yield* Call(thr, syncIterator, [value]);
|
||||
} else { // 9. Else,
|
||||
// a. Let result be Call(throw, syncIterator).
|
||||
result = yield* Call(thr, syncIterator);
|
||||
}
|
||||
// 10. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
__ts_cast__<Value>(result);
|
||||
// 11. If Type(result) is not Object, then
|
||||
if (!(result instanceof ObjectValue)) {
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
|
||||
X(Call(promiseCapability.Reject, Value.undefined, [
|
||||
surroundingAgent.Throw('TypeError', 'NotAnObject', result).Value,
|
||||
]));
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
// 12. Return ! AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, true).
|
||||
return X(AsyncFromSyncIteratorContinuation(result, promiseCapability, syncIteratorRecord, Value.true));
|
||||
}
|
||||
|
||||
export function bootstrapAsyncFromSyncIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', AsyncFromSyncIteratorPrototype_next, 0],
|
||||
['return', AsyncFromSyncIteratorPrototype_return, 0],
|
||||
['throw', AsyncFromSyncIteratorPrototype_throw, 0],
|
||||
], realmRec.Intrinsics['%AsyncIteratorPrototype%']);
|
||||
|
||||
realmRec.Intrinsics['%AsyncFromSyncIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { Q, X } from '../completion.mts';
|
||||
import { CreateDynamicFunction } from '../runtime-semantics/all.mts';
|
||||
import { Descriptor, Value } from '../value.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import type {
|
||||
Arguments, ValueEvaluator, FunctionCallContext, FunctionObject, Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-async-function-constructor-arguments */
|
||||
function* AsyncFunctionConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
const bodyArg = args[args.length - 1] || Value('');
|
||||
args = args.slice(0, -1) as Arguments;
|
||||
// 1. Let C be the active function object.
|
||||
const C = surroundingAgent.activeFunctionObject as FunctionObject;
|
||||
// 2. Let args be the argumentsList that was passed to this function by [[Call]] or [[Construct]].
|
||||
// 3. Return CreateDynamicFunction(C, NewTarget, async, args).
|
||||
return Q(yield* CreateDynamicFunction(C, NewTarget, 'async', args, bodyArg));
|
||||
}
|
||||
|
||||
export function bootstrapAsyncFunction(realmRec: Realm) {
|
||||
const cons = bootstrapConstructor(realmRec, AsyncFunctionConstructor, 'AsyncFunction', 1, realmRec.Intrinsics['%AsyncFunction.prototype%'], []);
|
||||
|
||||
X(cons.DefineOwnProperty(Value('prototype'), Descriptor({
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.false,
|
||||
})));
|
||||
|
||||
cons.Prototype = realmRec.Intrinsics['%Function%'];
|
||||
|
||||
realmRec.Intrinsics['%AsyncFunction%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
export function bootstrapAsyncFunctionPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [], realmRec.Intrinsics['%Function.prototype%'], 'AsyncFunction');
|
||||
|
||||
realmRec.Intrinsics['%AsyncFunction.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { Q, X } from '../completion.mts';
|
||||
import { CreateDynamicFunction } from '../runtime-semantics/all.mts';
|
||||
import { Descriptor, Value } from '../value.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import type {
|
||||
Arguments, ValueEvaluator, FunctionCallContext, FunctionObject, Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-asyncgeneratorfunction */
|
||||
function* AsyncGeneratorFunctionConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
const bodyArg = args[args.length - 1] || Value('');
|
||||
args = args.slice(0, -1) as Arguments;
|
||||
// 1. Let C be the active function object.
|
||||
const C = surroundingAgent.activeFunctionObject as FunctionObject;
|
||||
// 2. Let args be the argumentsList that was passed to this function by [[Call]] or [[Construct]].
|
||||
// 3. Return ? CreateDynamicFunction(C, NewTarget, asyncGenerator, args).
|
||||
return Q(yield* CreateDynamicFunction(C, NewTarget, 'asyncGenerator', args, bodyArg));
|
||||
}
|
||||
|
||||
export function bootstrapAsyncGeneratorFunction(realmRec: Realm) {
|
||||
const cons = bootstrapConstructor(realmRec, AsyncGeneratorFunctionConstructor, 'AsyncGeneratorFunction', 1, realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%'], []);
|
||||
|
||||
X(cons.DefineOwnProperty(Value('prototype'), Descriptor({
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.false,
|
||||
})));
|
||||
|
||||
X((realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%']).DefineOwnProperty(Value('constructor'), Descriptor({
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
|
||||
cons.Prototype = realmRec.Intrinsics['%Function%'];
|
||||
|
||||
realmRec.Intrinsics['%AsyncGeneratorFunction%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { X } from '../completion.mts';
|
||||
import { Descriptor, Value } from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
export function bootstrapAsyncGeneratorFunctionPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['prototype', realmRec.Intrinsics['%AsyncGeneratorFunction.prototype.prototype%'], undefined, { Writable: Value.false }],
|
||||
], realmRec.Intrinsics['%Function.prototype%'], 'AsyncGeneratorFunction');
|
||||
|
||||
X((realmRec.Intrinsics['%AsyncGeneratorFunction.prototype.prototype%']).DefineOwnProperty(Value('constructor'), Descriptor({
|
||||
Value: proto,
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
|
||||
realmRec.Intrinsics['%AsyncGeneratorFunction.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
X,
|
||||
NormalCompletion,
|
||||
ThrowCompletion,
|
||||
IfAbruptRejectPromise,
|
||||
ReturnCompletion,
|
||||
} from '../completion.mts';
|
||||
import { Value, type Arguments, type FunctionCallContext } from '../value.mts';
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
NewPromiseCapability,
|
||||
AsyncGeneratorValidate,
|
||||
AsyncGeneratorEnqueue,
|
||||
AsyncGeneratorResume,
|
||||
AsyncGeneratorAwaitReturn,
|
||||
CreateIteratorResultObject,
|
||||
type AsyncGeneratorObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next */
|
||||
function* AsyncGeneratorPrototype_next([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let generator be the this value.
|
||||
const generator = thisValue;
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%')));
|
||||
// 3. Let result be AsyncGeneratorValidate(generator, empty).
|
||||
const result = AsyncGeneratorValidate(generator, undefined);
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
__ts_cast__<AsyncGeneratorObject>(generator);
|
||||
// 5. Let state be generator.[[AsyncGeneratorState]].
|
||||
const state = generator.AsyncGeneratorState;
|
||||
// 6. If state is completed, then
|
||||
if (state === 'completed') {
|
||||
// a. Let iteratorResult be CreateIteratorResultObject(undefined, true).
|
||||
const iteratorResult = CreateIteratorResultObject(Value.undefined, Value.true);
|
||||
// b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
|
||||
X(Call(promiseCapability.Resolve, Value.undefined, [iteratorResult]));
|
||||
// c. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
// 7. Let completion be NormalCompletion(value).
|
||||
const completion = NormalCompletion(value);
|
||||
// 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
AsyncGeneratorEnqueue(generator, completion, promiseCapability);
|
||||
// 9. If state is either suspendedStart or suspendedYield, then
|
||||
if (state === 'suspendedStart' || state === 'suspendedYield') {
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
yield* AsyncGeneratorResume(generator, completion);
|
||||
} else { // 10. Else,
|
||||
// a. Assert: state is either executing or draining-queue.
|
||||
Assert(state === 'executing' || state === 'draining-queue');
|
||||
}
|
||||
// 11. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return */
|
||||
function* AsyncGeneratorPrototype_return([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let generator be the this value.
|
||||
const generator = thisValue;
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%')));
|
||||
// 3. Let result be AsyncGeneratorValidate(generator, empty).
|
||||
const result = AsyncGeneratorValidate(generator, undefined);
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
__ts_cast__<AsyncGeneratorObject>(generator);
|
||||
// 5. Let completion be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
||||
const completion = ReturnCompletion(value);
|
||||
// 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
AsyncGeneratorEnqueue(generator, completion, promiseCapability);
|
||||
// 7. Let state be generator.[[AsyncGeneratorState]].
|
||||
const state = generator.AsyncGeneratorState;
|
||||
// 8. If state is either suspendedStart or completed, then
|
||||
if (state === 'suspendedStart' || state === 'completed') {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to draining-queue.
|
||||
generator.AsyncGeneratorState = 'draining-queue';
|
||||
// b. Perform AsyncGeneratorAwaitReturn(generator).
|
||||
yield* AsyncGeneratorAwaitReturn(generator);
|
||||
} else if (state === 'suspendedYield') { // 9. Else if state is suspendedYield, then
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
yield* AsyncGeneratorResume(generator, completion);
|
||||
} else { // 10. Else,
|
||||
// a. Assert: state is either executing or draining-queue.
|
||||
Assert(state === 'executing' || state === 'draining-queue');
|
||||
}
|
||||
// 11. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw */
|
||||
function* AsyncGeneratorPrototype_throw([exception = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let generator be the this value.
|
||||
const generator = thisValue;
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
const promiseCapability = X(NewPromiseCapability(surroundingAgent.intrinsic('%Promise%')));
|
||||
// 3. Let result be AsyncGeneratorValidate(generator, empty).
|
||||
const result = AsyncGeneratorValidate(generator, undefined);
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
__ts_cast__<AsyncGeneratorObject>(generator);
|
||||
// 5. Let state be generator.[[AsyncGeneratorState]].
|
||||
let state = generator.AsyncGeneratorState;
|
||||
// 6. If state is suspendedStart, then
|
||||
if (state === 'suspendedStart') {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
generator.AsyncGeneratorState = 'completed';
|
||||
// b. Set state to completed.
|
||||
state = 'completed';
|
||||
}
|
||||
// 7. If state is completed, then
|
||||
if (state === 'completed') {
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
|
||||
X(Call(promiseCapability.Reject, Value.undefined, [exception]));
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
// 8. Let completion be ThrowCompletion(exception).
|
||||
const completion = ThrowCompletion(exception);
|
||||
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
AsyncGeneratorEnqueue(generator, completion, promiseCapability);
|
||||
// 10. If state is suspendedYield, then
|
||||
if (state === 'suspendedYield') {
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
yield* AsyncGeneratorResume(generator, completion);
|
||||
} else { // 11. Else,
|
||||
// a. Assert: state is either executing or draining-queue.
|
||||
Assert(state === 'executing' || state === 'draining-queue');
|
||||
}
|
||||
// 12. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
|
||||
export function bootstrapAsyncGeneratorFunctionPrototypePrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', AsyncGeneratorPrototype_next, 1],
|
||||
['return', AsyncGeneratorPrototype_return, 1],
|
||||
['throw', AsyncGeneratorPrototype_throw, 1],
|
||||
], realmRec.Intrinsics['%AsyncIteratorPrototype%'], 'AsyncGenerator');
|
||||
|
||||
realmRec.Intrinsics['%AsyncGeneratorFunction.prototype.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { wellKnownSymbols } from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { Arguments, FunctionCallContext, Realm } from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-asynciteratorprototype-asynciterator */
|
||||
function AsyncIteratorPrototype_asyncIterator(_args: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Return the this value.
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
export function bootstrapAsyncIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
[wellKnownSymbols.asyncIterator, AsyncIteratorPrototype_asyncIterator, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%']);
|
||||
|
||||
realmRec.Intrinsics['%AsyncIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
BigIntValue, NumberValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { NumberToBigInt } from '../runtime-semantics/all.mts';
|
||||
import { Q, type ValueEvaluator } from '../completion.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
ToBigInt,
|
||||
ToIndex,
|
||||
ToPrimitive,
|
||||
Z, R,
|
||||
type OrdinaryObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface BigIntObject extends OrdinaryObject {
|
||||
readonly BigIntData: BigIntValue;
|
||||
}
|
||||
export function isBigIntObject(o: Value): o is BigIntObject {
|
||||
return 'BigIntData' in o;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-bigint-constructor */
|
||||
function* BigIntConstructor([value = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If NewTarget is not undefined, throw a TypeError exception.
|
||||
if (NewTarget !== Value.undefined) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAConstructor', 'BigInt');
|
||||
}
|
||||
// 2. Let prim be ? ToPrimitive(value, number).
|
||||
const prim = Q(yield* ToPrimitive(value, 'number'));
|
||||
// 3. If Type(prim) is Number, return ? NumberToBigInt(prim).
|
||||
// 4. Otherwise, return ? ToBigInt(prim).
|
||||
if (prim instanceof NumberValue) {
|
||||
return Q(NumberToBigInt(prim));
|
||||
} else {
|
||||
return Q(yield* ToBigInt(prim));
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-bigint.asintn */
|
||||
function* BigInt_asIntN([_bits = Value.undefined, _bigint = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Set bits to ? ToIndex(bits).
|
||||
const bits = Q(yield* ToIndex(_bits));
|
||||
// 2. Set bigint to ? ToBigInt(bigint).
|
||||
const bigint = Q(yield* ToBigInt(_bigint));
|
||||
// 3. Let mod be the BigInt value that represents bigint modulo 2bits.
|
||||
// 4. If mod ≥ 2^bits - 1, return mod - 2^bits; otherwise, return mod.
|
||||
return Z(BigInt.asIntN(bits, R(bigint)));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-bigint.asuintn */
|
||||
function* BigInt_asUintN([_bits = Value.undefined, _bigint = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Set bits to ? ToIndex(bits).
|
||||
const bits = Q(yield* ToIndex(_bits));
|
||||
// 2. Set bigint to ? ToBigInt(bigint).
|
||||
const bigint = Q(yield* ToBigInt(_bigint));
|
||||
// 3. Let mod be ℝ(bigint) modulo 2 ** bits.
|
||||
// 4. If mod ≥ 2 ** (bits - 1), return Z(mod - 2 ** bits); otherwise, return Z(mod).
|
||||
return Z(BigInt.asUintN(bits, R(bigint)));
|
||||
}
|
||||
|
||||
export function bootstrapBigInt(realmRec: Realm) {
|
||||
const bigintConstructor = bootstrapConstructor(realmRec, BigIntConstructor, 'BigInt', 1, realmRec.Intrinsics['%BigInt.prototype%'], [
|
||||
['asIntN', BigInt_asIntN, 2],
|
||||
['asUintN', BigInt_asUintN, 2],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%BigInt%'] = bigintConstructor;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue, BigIntValue, Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
Assert, ToIntegerOrInfinity, ToString, R,
|
||||
} from '#self';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-thisbigintvalue */
|
||||
function thisBigIntValue(value: Value) {
|
||||
// 1. If Type(value) is BigInt, return value.
|
||||
if (value instanceof BigIntValue) {
|
||||
return value;
|
||||
}
|
||||
// 2. If Type(value) is Object and value has a [[BigIntData]] internal slot, then
|
||||
if (value instanceof ObjectValue && 'BigIntData' in value) {
|
||||
// a. Assert: Type(value.[[BigIntData]]) is BigInt.
|
||||
Assert(value.BigIntData instanceof BigIntValue);
|
||||
// b. Return value.[[BigIntData]].
|
||||
return value.BigIntData;
|
||||
}
|
||||
// 3. Throw a TypeError exception.
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'BigInt', value);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-bigint.prototype.tolocalestring */
|
||||
function BigIntProto_toLocaleString(args: Arguments, context: FunctionCallContext): ValueEvaluator {
|
||||
return BigIntProto_toString(args, context);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-bigint.prototype.tostring */
|
||||
function* BigIntProto_toString([radix]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let x be ? thisBigIntValue(this value).
|
||||
const x = Q(thisBigIntValue(thisValue));
|
||||
// 2. If radix is not present, let radixNumber be 10.
|
||||
let radixNumber;
|
||||
if (radix === undefined) {
|
||||
radixNumber = 10;
|
||||
} else if (radix === Value.undefined) {
|
||||
// 3. Else if radix is undefined, let radixNumber be 10.
|
||||
radixNumber = 10;
|
||||
} else {
|
||||
// 4. Else, let radixNumber be ? ToIntegerOrInfinity(radix).
|
||||
radixNumber = Q(yield* ToIntegerOrInfinity(radix));
|
||||
}
|
||||
// 5. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
|
||||
if (radixNumber < 2 || radixNumber > 36) {
|
||||
return surroundingAgent.Throw('RangeError', 'InvalidRadix');
|
||||
}
|
||||
// 6. If radixNumber = 10, return ! ToString(x).
|
||||
if (radixNumber === 10) {
|
||||
return X(ToString(x));
|
||||
}
|
||||
// 7. Return the String representation of this Number value using the radix specified by
|
||||
// radixNumber. Letters a-z are used for digits with values 10 through 35. The precise
|
||||
// algorithm is implementation-dependent, however the algorithm should be a
|
||||
// generalization of that specified in 6.1.6.2.23.
|
||||
// TODO: Implementation stringification
|
||||
return Value(R(x).toString(radixNumber));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-bigint.prototype.tostring */
|
||||
function BigIntProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// Return ? thisBigIntValue(this value).
|
||||
return Q(thisBigIntValue(thisValue));
|
||||
}
|
||||
|
||||
export function bootstrapBigIntPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['toLocaleString', BigIntProto_toLocaleString, 0],
|
||||
['toString', BigIntProto_toString, 0],
|
||||
['valueOf', BigIntProto_valueOf, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'BigInt');
|
||||
|
||||
realmRec.Intrinsics['%BigInt.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
BooleanValue, UndefinedValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
OrdinaryCreateFromConstructor,
|
||||
ToBoolean,
|
||||
type OrdinaryObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface BooleanObject extends OrdinaryObject {
|
||||
readonly BooleanData: BooleanValue;
|
||||
}
|
||||
export function isBooleanObject(o: Value): o is BooleanObject {
|
||||
return 'BooleanData' in o;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-boolean-constructor-boolean-value */
|
||||
function* BooleanConstructor([value = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let b be ! ToBoolean(value).
|
||||
const b = X(ToBoolean(value));
|
||||
// 2. If NewTarget is undefined, return b.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return b;
|
||||
}
|
||||
// 3. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%Boolean.prototype%", « [[BooleanData]] »).
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Boolean.prototype%', ['BooleanData'])) as Mutable<BooleanObject>;
|
||||
// 4. Set O.[[BooleanData]] to b.
|
||||
O.BooleanData = b;
|
||||
// 5. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
export function bootstrapBoolean(realmRec: Realm) {
|
||||
const cons = bootstrapConstructor(
|
||||
realmRec,
|
||||
BooleanConstructor,
|
||||
'Boolean',
|
||||
1,
|
||||
realmRec.Intrinsics['%Boolean.prototype%'],
|
||||
[],
|
||||
);
|
||||
|
||||
realmRec.Intrinsics['%Boolean%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
ObjectValue,
|
||||
BooleanValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import { Q, type ValueCompletion } from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { BooleanObject } from './Boolean.mts';
|
||||
import { Assert } from '#self';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
|
||||
function thisBooleanValue(value: Value) {
|
||||
if (value instanceof BooleanValue) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value instanceof ObjectValue && 'BooleanData' in value) {
|
||||
const b = value.BooleanData;
|
||||
Assert(b instanceof BooleanValue);
|
||||
return b;
|
||||
}
|
||||
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Boolean', value);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-boolean.prototype.tostring */
|
||||
function BooleanProto_toString(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let b be ? thisBooleanValue(this value).
|
||||
const b = Q(thisBooleanValue(thisValue));
|
||||
// 2. If b is true, return "true"; else return "false".
|
||||
if (b === Value.true) {
|
||||
return Value('true');
|
||||
}
|
||||
return Value('false');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-boolean.prototype.valueof */
|
||||
function BooleanProto_valueOf(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Return ? thisBooleanValue(this value).
|
||||
return Q(thisBooleanValue(thisValue));
|
||||
}
|
||||
|
||||
export function bootstrapBooleanPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['toString', BooleanProto_toString, 0],
|
||||
['valueOf', BooleanProto_valueOf, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%']);
|
||||
|
||||
(proto as Mutable<BooleanObject>).BooleanData = Value.false;
|
||||
|
||||
realmRec.Intrinsics['%Boolean.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
UndefinedValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import { __ts_cast__, type Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
IsDetachedBuffer,
|
||||
OrdinaryCreateFromConstructor,
|
||||
ToIndex,
|
||||
RequireInternalSlot,
|
||||
type OrdinaryObject,
|
||||
type FunctionObject,
|
||||
type ArrayBufferObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface DataViewObject extends OrdinaryObject {
|
||||
readonly DataView: string;
|
||||
readonly ViewedArrayBuffer: Value;
|
||||
readonly ByteLength: number;
|
||||
readonly ByteOffset: number;
|
||||
}
|
||||
export function isDataViewObject(V: Value): V is DataViewObject {
|
||||
return 'DataView' in V;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-dataview-constructor */
|
||||
function* DataViewConstructor(this: FunctionObject, [buffer = Value.undefined, byteOffset = Value.undefined, byteLength = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]).
|
||||
Q(RequireInternalSlot(buffer, 'ArrayBufferData'));
|
||||
// 3. Let offset be ? ToIndex(byteOffset).
|
||||
const offset = Q(yield* ToIndex(byteOffset));
|
||||
// 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
__ts_cast__<ArrayBufferObject>(buffer);
|
||||
if (IsDetachedBuffer(buffer)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached');
|
||||
}
|
||||
// 5. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
|
||||
const bufferByteLength = (buffer).ArrayBufferByteLength;
|
||||
// 6. If offset > bufferByteLength, throw a RangeError exception.
|
||||
if (offset > bufferByteLength) {
|
||||
return surroundingAgent.Throw('RangeError', 'DataViewOOB');
|
||||
}
|
||||
let viewByteLength;
|
||||
// 7. If byteLength is undefined, then
|
||||
if (byteLength === Value.undefined) {
|
||||
// a. Let viewByteLength be bufferByteLength - offset.
|
||||
viewByteLength = bufferByteLength - offset;
|
||||
} else {
|
||||
// a. Let viewByteLength be ? ToIndex(byteLength).
|
||||
viewByteLength = Q(yield* ToIndex(byteLength));
|
||||
// b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
|
||||
if (offset + viewByteLength > bufferByteLength) {
|
||||
return surroundingAgent.Throw('RangeError', 'DataViewOOB');
|
||||
}
|
||||
}
|
||||
// 9. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%DataView.prototype%', ['DataView', 'ViewedArrayBuffer', 'ByteLength', 'ByteOffset'])) as Mutable<DataViewObject>;
|
||||
// 10. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (IsDetachedBuffer(buffer)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached');
|
||||
}
|
||||
// 11. Set O.[[ViewedArrayBuffer]] to buffer.
|
||||
O.ViewedArrayBuffer = buffer;
|
||||
// 12. Set O.[[ByteLength]] to viewByteLength.
|
||||
O.ByteLength = viewByteLength;
|
||||
// 13. Set O.[[ByteOffset]] to offset.
|
||||
O.ByteOffset = offset;
|
||||
// 14. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
export function bootstrapDataView(realmRec: Realm) {
|
||||
const dvConstructor = bootstrapConstructor(realmRec, DataViewConstructor, 'DataView', 1, realmRec.Intrinsics['%DataView.prototype%'], []);
|
||||
|
||||
realmRec.Intrinsics['%DataView%'] = dvConstructor;
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
import { Q, type ValueCompletion, type ValueEvaluator } from '../completion.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { Value, type Arguments, type FunctionCallContext } from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { DataViewObject } from './DataView.mts';
|
||||
import {
|
||||
Assert,
|
||||
GetViewValue,
|
||||
SetViewValue,
|
||||
IsDetachedBuffer,
|
||||
RequireInternalSlot,
|
||||
F,
|
||||
type ArrayBufferObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-dataview.prototype.buffer */
|
||||
function DataViewProto_buffer(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as DataViewObject;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[DataView]]).
|
||||
Q(RequireInternalSlot(O, 'DataView'));
|
||||
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
// 4. Let buffer be O.[[ViewedArrayBuffer]].
|
||||
const buffer = O.ViewedArrayBuffer;
|
||||
// 5. Return buffer.
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-dataview.prototype.bytelength */
|
||||
function* DataViewProto_byteLength(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as DataViewObject;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[DataView]]).
|
||||
Q(RequireInternalSlot(O, 'DataView'));
|
||||
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
// 4. Let buffer be O.[[ViewedArrayBuffer]].
|
||||
const buffer = O.ViewedArrayBuffer as ArrayBufferObject;
|
||||
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (IsDetachedBuffer(buffer)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached');
|
||||
}
|
||||
// 6. Let size be O.[[ByteLength]].
|
||||
const size = O.ByteLength;
|
||||
// 7. Return 𝔽(size).
|
||||
return F(size);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-dataview.prototype.byteoffset */
|
||||
function* DataViewProto_byteOffset(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as DataViewObject;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[DataView]]).
|
||||
Q(RequireInternalSlot(O, 'DataView'));
|
||||
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
// 4. Let buffer be O.[[ViewedArrayBuffer]].
|
||||
const buffer = O.ViewedArrayBuffer as ArrayBufferObject;
|
||||
// 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (IsDetachedBuffer(buffer)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached');
|
||||
}
|
||||
// 6. Let offset be O.[[ByteOffset]].
|
||||
const offset = O.ByteOffset;
|
||||
// 7. Return 𝔽(offset).
|
||||
return F(offset);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getbigint64 */
|
||||
function* DataViewProto_getBigInt64([byteOffset = Value.undefined, littleEndian = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let v be the this value.
|
||||
const v = thisValue;
|
||||
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigInt64).
|
||||
return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'BigInt64'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getbiguint64 */
|
||||
function* DataViewProto_getBigUint64([byteOffset = Value.undefined, littleEndian = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let v be the this value.
|
||||
const v = thisValue;
|
||||
// 2. Return ? GetViewValue(v, byteOffset, littleEndian, BigUint64).
|
||||
return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'BigUint64'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getfloat32 */
|
||||
function* DataViewProto_getFloat32([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Float32'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getfloat64 */
|
||||
function* DataViewProto_getFloat64([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Float64'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getint8 */
|
||||
function* DataViewProto_getInt8([byteOffset = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
return Q(yield* GetViewValue(v, byteOffset, Value.true, 'Int8'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getint16 */
|
||||
function* DataViewProto_getInt16([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Int16'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getint32 */
|
||||
function* DataViewProto_getInt32([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Int32'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getuint8 */
|
||||
function* DataViewProto_getUint8([byteOffset = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
return Q(yield* GetViewValue(v, byteOffset, Value.true, 'Uint8'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getuint16 */
|
||||
function* DataViewProto_getUint16([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Uint16'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.getuint32 */
|
||||
function* DataViewProto_getUint32([byteOffset = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* GetViewValue(v, byteOffset, littleEndian, 'Uint32'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setbigint64 */
|
||||
function* DataViewProto_setBigInt64([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let v be the this value.
|
||||
const v = thisValue;
|
||||
// 2. If littleEndian is not present, set littleEndian to undefined.
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.undefined;
|
||||
}
|
||||
// 3. Return ? SetViewValue(v, byteOffset, littleEndian, BigInt64, value).
|
||||
return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'BigInt64', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setbiguint64 */
|
||||
function* DataViewProto_setBigUint64([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let v be the this value.
|
||||
const v = thisValue;
|
||||
// 2. If littleEndian is not present, set littleEndian to undefined.
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.undefined;
|
||||
}
|
||||
// 3. Return ? SetViewValue(v, byteOffset, littleEndian, BigUint64, value).
|
||||
return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'BigUint64', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setfloat32 */
|
||||
function* DataViewProto_setFloat32([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Float32', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setfloat64 */
|
||||
function* DataViewProto_setFloat64([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Float64', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setint8 */
|
||||
function* DataViewProto_setInt8([byteOffset = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
return Q(yield* SetViewValue(v, byteOffset, Value.true, 'Int8', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setint16 */
|
||||
function* DataViewProto_setInt16([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Int16', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setint32 */
|
||||
function* DataViewProto_setInt32([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Int32', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setuint8 */
|
||||
function* DataViewProto_setUint8([byteOffset = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
return Q(yield* SetViewValue(v, byteOffset, Value.true, 'Uint8', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setuint16 */
|
||||
function* DataViewProto_setUint16([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Uint16', value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-dataview.prototype.setuint32 */
|
||||
function* DataViewProto_setUint32([byteOffset = Value.undefined, value = Value.undefined, littleEndian]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const v = thisValue;
|
||||
if (littleEndian === undefined) {
|
||||
littleEndian = Value.false;
|
||||
}
|
||||
return Q(yield* SetViewValue(v, byteOffset, littleEndian, 'Uint32', value));
|
||||
}
|
||||
|
||||
export function bootstrapDataViewPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['buffer', [DataViewProto_buffer]],
|
||||
['byteLength', [DataViewProto_byteLength]],
|
||||
['byteOffset', [DataViewProto_byteOffset]],
|
||||
['getBigInt64', DataViewProto_getBigInt64, 1],
|
||||
['getBigUint64', DataViewProto_getBigUint64, 1],
|
||||
['getFloat32', DataViewProto_getFloat32, 1],
|
||||
['getFloat64', DataViewProto_getFloat64, 1],
|
||||
['getInt8', DataViewProto_getInt8, 1],
|
||||
['getInt16', DataViewProto_getInt16, 1],
|
||||
['getInt32', DataViewProto_getInt32, 1],
|
||||
['getUint8', DataViewProto_getUint8, 1],
|
||||
['getUint16', DataViewProto_getUint16, 1],
|
||||
['getUint32', DataViewProto_getUint32, 1],
|
||||
['setBigInt64', DataViewProto_setBigInt64, 2],
|
||||
['setBigUint64', DataViewProto_setBigUint64, 2],
|
||||
['setFloat32', DataViewProto_setFloat32, 2],
|
||||
['setFloat64', DataViewProto_setFloat64, 2],
|
||||
['setInt8', DataViewProto_setInt8, 2],
|
||||
['setInt16', DataViewProto_setInt16, 2],
|
||||
['setInt32', DataViewProto_setInt32, 2],
|
||||
['setUint8', DataViewProto_setUint8, 2],
|
||||
['setUint16', DataViewProto_setUint16, 2],
|
||||
['setUint32', DataViewProto_setUint32, 2],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'DataView');
|
||||
|
||||
realmRec.Intrinsics['%DataView.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
import {
|
||||
Value, JSStringValue, ObjectValue, type Arguments, type FunctionCallContext, NumberValue,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
AbruptCompletion,
|
||||
Q, ValueOfNormalCompletion, X,
|
||||
type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import { ToDateString, thisTimeValue } from './DatePrototype.mts';
|
||||
import {
|
||||
Assert,
|
||||
OrdinaryCreateFromConstructor,
|
||||
ToPrimitive,
|
||||
ToNumber,
|
||||
ToIntegerOrInfinity,
|
||||
ToString,
|
||||
MakeDate,
|
||||
MakeDay,
|
||||
MakeTime,
|
||||
UTC,
|
||||
TimeClip,
|
||||
F,
|
||||
type OrdinaryObject,
|
||||
type FunctionObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface DateObject extends OrdinaryObject {
|
||||
DateValue: NumberValue;
|
||||
}
|
||||
export function isDateObject(value: Value): value is DateObject {
|
||||
return 'DateValue' in value;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-date-constructor */
|
||||
function* DateConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
const numberOfArgs = args.length;
|
||||
if (numberOfArgs >= 2) {
|
||||
/** https://tc39.es/ecma262/#sec-date-year-month-date-hours-minutes-seconds-ms */
|
||||
const [year, month, date, hours, minutes, seconds, ms] = args;
|
||||
Assert(numberOfArgs >= 2);
|
||||
if (NewTarget === Value.undefined) {
|
||||
const now = Date.now();
|
||||
return ToDateString(F(now));
|
||||
} else {
|
||||
const y = Q(yield* ToNumber(year!));
|
||||
const m = Q(yield* ToNumber(month!));
|
||||
let dt;
|
||||
if (date !== undefined) {
|
||||
dt = Q(yield* ToNumber(date));
|
||||
} else {
|
||||
dt = F(1);
|
||||
}
|
||||
let h;
|
||||
if (hours !== undefined) {
|
||||
h = Q(yield* ToNumber(hours));
|
||||
} else {
|
||||
h = F(+0);
|
||||
}
|
||||
let min;
|
||||
if (minutes !== undefined) {
|
||||
min = Q(yield* ToNumber(minutes));
|
||||
} else {
|
||||
min = F(+0);
|
||||
}
|
||||
let s;
|
||||
if (seconds !== undefined) {
|
||||
s = Q(yield* ToNumber(seconds));
|
||||
} else {
|
||||
s = F(+0);
|
||||
}
|
||||
let milli;
|
||||
if (ms !== undefined) {
|
||||
milli = Q(yield* ToNumber(ms));
|
||||
} else {
|
||||
milli = F(+0);
|
||||
}
|
||||
let yr;
|
||||
if (y.isNaN()) {
|
||||
yr = F(NaN);
|
||||
} else {
|
||||
const yi = X(ToIntegerOrInfinity(y));
|
||||
if (yi >= 0 && yi <= 99) {
|
||||
yr = F(1900 + yi);
|
||||
} else {
|
||||
yr = y;
|
||||
}
|
||||
}
|
||||
const finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget as FunctionObject, '%Date.prototype%', ['DateValue'])) as Mutable<DateObject>;
|
||||
O.DateValue = TimeClip(UTC(finalDate));
|
||||
return O;
|
||||
}
|
||||
} else if (numberOfArgs === 1) {
|
||||
const [value] = args;
|
||||
/** https://tc39.es/ecma262/#sec-date-value */
|
||||
Assert(numberOfArgs === 1);
|
||||
if (NewTarget === Value.undefined) {
|
||||
const now = Date.now();
|
||||
return ToDateString(F(now));
|
||||
} else {
|
||||
let tv;
|
||||
if (value instanceof ObjectValue && 'DateValue' in value) {
|
||||
tv = X(thisTimeValue(value));
|
||||
} else {
|
||||
const v = Q(yield* ToPrimitive(value!));
|
||||
if (v instanceof JSStringValue) {
|
||||
// Assert: The next step never returns an abrupt completion because Type(v) is String.
|
||||
tv = parseDate(v);
|
||||
} else {
|
||||
tv = Q(yield* ToNumber(v));
|
||||
}
|
||||
}
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget as FunctionObject, '%Date.prototype%', ['DateValue'])) as Mutable<DateObject>;
|
||||
O.DateValue = TimeClip(tv);
|
||||
return O;
|
||||
}
|
||||
} else {
|
||||
/** https://tc39.es/ecma262/#sec-date-constructor-date */
|
||||
Assert(numberOfArgs === 0);
|
||||
if (NewTarget === Value.undefined) {
|
||||
const now = Date.now();
|
||||
return ToDateString(F(now));
|
||||
} else {
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget as FunctionObject, '%Date.prototype%', ['DateValue'])) as Mutable<DateObject>;
|
||||
O.DateValue = F(Date.now());
|
||||
return O;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.now */
|
||||
function Date_now() {
|
||||
const now = Date.now();
|
||||
return F(now);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.parse */
|
||||
function* Date_parse([string = Value.undefined]: Arguments): ValueEvaluator<NumberValue> {
|
||||
const str = yield* ToString(string);
|
||||
if (str instanceof AbruptCompletion) {
|
||||
return str;
|
||||
}
|
||||
return parseDate(ValueOfNormalCompletion(str));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.utc */
|
||||
function* Date_UTC([year = Value.undefined, month, date, hours, minutes, seconds, ms]: Arguments): ValueEvaluator {
|
||||
const y = Q(yield* ToNumber(year));
|
||||
let m;
|
||||
if (month !== undefined) {
|
||||
m = Q(yield* ToNumber(month));
|
||||
} else {
|
||||
m = F(+0);
|
||||
}
|
||||
let dt;
|
||||
if (date !== undefined) {
|
||||
dt = Q(yield* ToNumber(date));
|
||||
} else {
|
||||
dt = F(1);
|
||||
}
|
||||
let h;
|
||||
if (hours !== undefined) {
|
||||
h = Q(yield* ToNumber(hours));
|
||||
} else {
|
||||
h = F(+0);
|
||||
}
|
||||
let min;
|
||||
if (minutes !== undefined) {
|
||||
min = Q(yield* ToNumber(minutes));
|
||||
} else {
|
||||
min = F(+0);
|
||||
}
|
||||
let s;
|
||||
if (seconds !== undefined) {
|
||||
s = Q(yield* ToNumber(seconds));
|
||||
} else {
|
||||
s = F(+0);
|
||||
}
|
||||
let milli;
|
||||
if (ms !== undefined) {
|
||||
milli = Q(yield* ToNumber(ms));
|
||||
} else {
|
||||
milli = F(+0);
|
||||
}
|
||||
|
||||
let yr;
|
||||
if (y.isNaN()) {
|
||||
yr = F(NaN);
|
||||
} else {
|
||||
const yi = X(ToIntegerOrInfinity(y));
|
||||
if (yi >= 0 && yi <= 99) {
|
||||
yr = F(1900 + yi);
|
||||
} else {
|
||||
yr = y;
|
||||
}
|
||||
}
|
||||
|
||||
return TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
|
||||
}
|
||||
|
||||
function parseDate(dateTimeString: JSStringValue) {
|
||||
/** https://tc39.es/ecma262/#sec-date-time-string-format */
|
||||
// TODO: implement parsing without the host.
|
||||
const parsed = Date.parse(dateTimeString.stringValue());
|
||||
return F(parsed);
|
||||
}
|
||||
|
||||
export function bootstrapDate(realmRec: Realm) {
|
||||
const cons = bootstrapConstructor(realmRec, DateConstructor, 'Date', 7, realmRec.Intrinsics['%Date.prototype%'], [
|
||||
['now', Date_now, 0],
|
||||
['parse', Date_parse, 1],
|
||||
['UTC', Date_UTC, 7],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Date%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,788 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
JSStringValue,
|
||||
NumberValue,
|
||||
ObjectValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { NumberToBigInt, StringPad } from '../runtime-semantics/all.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { DateObject } from './Date.mts';
|
||||
import {
|
||||
Assert,
|
||||
DateFromTime,
|
||||
Day,
|
||||
HourFromTime,
|
||||
Invoke,
|
||||
LocalTime,
|
||||
LocalTZA,
|
||||
MakeDate,
|
||||
MakeDay,
|
||||
MakeTime,
|
||||
MinFromTime,
|
||||
MonthFromTime,
|
||||
msFromTime,
|
||||
msPerMinute,
|
||||
OrdinaryToPrimitive,
|
||||
SecFromTime,
|
||||
TimeClip,
|
||||
TimeWithinDay,
|
||||
ToNumber,
|
||||
ToPrimitive,
|
||||
ToObject,
|
||||
UTC,
|
||||
WeekDay,
|
||||
YearFromTime,
|
||||
F, R,
|
||||
CreateTemporalInstant,
|
||||
} from '#self';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
|
||||
export function thisTimeValue(value: Value): ValueCompletion<NumberValue> {
|
||||
if (value instanceof ObjectValue && 'DateValue' in value) {
|
||||
return (value as DateObject).DateValue;
|
||||
}
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', value);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getdate */
|
||||
function DateProto_getDate(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return DateFromTime(LocalTime(t));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getday */
|
||||
function DateProto_getDay(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return WeekDay(LocalTime(t));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getfullyear */
|
||||
function DateProto_getFullYear(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return YearFromTime(LocalTime(t));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.gethours */
|
||||
function DateProto_getHours(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return HourFromTime(LocalTime(t));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getmilliseconds */
|
||||
function DateProto_getMilliseconds(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return msFromTime(LocalTime(t));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getminutes */
|
||||
function DateProto_getMinutes(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return MinFromTime(LocalTime(t));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getmonth */
|
||||
function DateProto_getMonth(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return MonthFromTime(LocalTime(t));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getseconds */
|
||||
function DateProto_getSeconds(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return SecFromTime(LocalTime(t));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.gettime */
|
||||
function DateProto_getTime(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
return Q(thisTimeValue(thisValue));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.gettimezoneoffset */
|
||||
function DateProto_getTimezoneOffset(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return F((R(t) - R(LocalTime(t))) / msPerMinute);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getutcdate */
|
||||
function DateProto_getUTCDate(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return DateFromTime(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getutcday */
|
||||
function DateProto_getUTCDay(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return WeekDay(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getutcfullyear */
|
||||
function DateProto_getUTCFullYear(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return YearFromTime(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getutchours */
|
||||
function DateProto_getUTCHours(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return HourFromTime(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getutcmilliseconds */
|
||||
function DateProto_getUTCMilliseconds(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return msFromTime(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getutcminutes */
|
||||
function DateProto_getUTCMinutes(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return MinFromTime(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getutcmonth */
|
||||
function DateProto_getUTCMonth(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return MonthFromTime(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.getutcseconds */
|
||||
function DateProto_getUTCSeconds(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
return F(NaN);
|
||||
}
|
||||
return SecFromTime(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setdate */
|
||||
function* DateProto_setDate([date = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
let t = Q(thisTimeValue(thisValue));
|
||||
const dt = Q(yield* ToNumber(date));
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
t = LocalTime(t);
|
||||
const newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t));
|
||||
const u = TimeClip(UTC(newDate));
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = u;
|
||||
return u;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setfullyear */
|
||||
function* DateProto_setFullYear([year = Value.undefined, month, date]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
let t = Q(thisTimeValue(thisValue));
|
||||
const y = Q(yield* ToNumber(year));
|
||||
t = t.isNaN() ? F(+0) : LocalTime(t);
|
||||
let m;
|
||||
if (month !== undefined) {
|
||||
m = Q(yield* ToNumber(month));
|
||||
} else {
|
||||
m = MonthFromTime(t);
|
||||
}
|
||||
let dt;
|
||||
if (date !== undefined) {
|
||||
dt = Q(yield* ToNumber(date));
|
||||
} else {
|
||||
dt = DateFromTime(t);
|
||||
}
|
||||
const newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t));
|
||||
const u = TimeClip(UTC(newDate));
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = u;
|
||||
return u;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.sethours */
|
||||
function* DateProto_setHours([hour = Value.undefined, min, sec, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
let t = Q(thisTimeValue(thisValue));
|
||||
const h = Q(yield* ToNumber(hour));
|
||||
let m;
|
||||
if (min) {
|
||||
m = Q(yield* ToNumber(min));
|
||||
}
|
||||
let s;
|
||||
if (sec) {
|
||||
s = Q(yield* ToNumber(sec));
|
||||
}
|
||||
let milli;
|
||||
if (ms !== undefined) {
|
||||
milli = Q(yield* ToNumber(ms));
|
||||
}
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
t = LocalTime(t);
|
||||
if (!m) {
|
||||
m = MinFromTime(t);
|
||||
}
|
||||
if (!s) {
|
||||
s = SecFromTime(t);
|
||||
}
|
||||
if (!milli) {
|
||||
milli = msFromTime(t);
|
||||
}
|
||||
const date = MakeDate(Day(t), MakeTime(h, m, s, milli));
|
||||
const u = TimeClip(UTC(date));
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = u;
|
||||
return u;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setmilliseconds */
|
||||
function* DateProto_setMilliseconds([ms = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
let t = Q(thisTimeValue(thisValue));
|
||||
ms = Q(yield* ToNumber(ms));
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
t = LocalTime(t);
|
||||
const time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
|
||||
const u = TimeClip(UTC(MakeDate(Day(t), time)));
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = u;
|
||||
return u;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setminutes */
|
||||
function* DateProto_setMinutes([min = Value.undefined, sec, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let t be LocalTime(? thisTimeValue(this value)).
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
// 2. Let m be ? ToNumber(min).
|
||||
const m = Q(yield* ToNumber(min));
|
||||
let s;
|
||||
if (sec) {
|
||||
s = Q(yield* ToNumber(sec));
|
||||
}
|
||||
let milli;
|
||||
if (ms) {
|
||||
milli = Q(yield* ToNumber(ms));
|
||||
}
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
if (!s) {
|
||||
s = SecFromTime(t);
|
||||
}
|
||||
if (!milli) {
|
||||
milli = msFromTime(t);
|
||||
}
|
||||
// 5. Let date be MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)).
|
||||
const date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
|
||||
// 6. Let u be TimeClip(UTC(date)).
|
||||
const u = TimeClip(UTC(date));
|
||||
// 7. Set the [[DateValue]] internal slot of this Date object to u.
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = u;
|
||||
// 8. Return u.
|
||||
return u;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setmonth */
|
||||
function* DateProto_setMonth([month = Value.undefined, date]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
let t = Q(thisTimeValue(thisValue));
|
||||
const m = Q(yield* ToNumber(month));
|
||||
let dt;
|
||||
if (date) {
|
||||
dt = Q(yield* ToNumber(date));
|
||||
}
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
t = LocalTime(t);
|
||||
if (!dt) {
|
||||
dt = DateFromTime(t);
|
||||
}
|
||||
const newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t));
|
||||
const u = TimeClip(UTC(newDate));
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = u;
|
||||
return u;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setseconds */
|
||||
function* DateProto_setSeconds([sec = Value.undefined, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
let t = Q(thisTimeValue(thisValue));
|
||||
const s = Q(yield* ToNumber(sec));
|
||||
let milli;
|
||||
if (ms) {
|
||||
milli = Q(yield* ToNumber(ms));
|
||||
}
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
t = LocalTime(t);
|
||||
if (!milli) {
|
||||
milli = msFromTime(t);
|
||||
}
|
||||
const date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
|
||||
const u = TimeClip(UTC(date));
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = u;
|
||||
return u;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.settime */
|
||||
function* DateProto_setTime([time = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(thisTimeValue(thisValue));
|
||||
const t = Q(yield* ToNumber(time));
|
||||
const v = TimeClip(t);
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setutcdate */
|
||||
function* DateProto_setUTCDate([date = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
const dt = Q(yield* ToNumber(date));
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
const newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), dt), TimeWithinDay(t));
|
||||
const v = TimeClip(newDate);
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setutcfullyear */
|
||||
function* DateProto_setUTCFullYear([year = Value.undefined, month, date]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
let t = Q(thisTimeValue(thisValue));
|
||||
if (t.isNaN()) {
|
||||
t = F(+0);
|
||||
}
|
||||
const y = Q(yield* ToNumber(year));
|
||||
let m;
|
||||
if (month !== undefined) {
|
||||
m = Q(yield* ToNumber(month));
|
||||
} else {
|
||||
m = MonthFromTime(t);
|
||||
}
|
||||
let dt;
|
||||
if (date !== undefined) {
|
||||
dt = Q(yield* ToNumber(date));
|
||||
} else {
|
||||
dt = DateFromTime(t);
|
||||
}
|
||||
const newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(t));
|
||||
const v = TimeClip(newDate);
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setutchours */
|
||||
function* DateProto_setUTCHours([hour = Value.undefined, min, sec, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
const h = Q(yield* ToNumber(hour));
|
||||
let m;
|
||||
if (min) {
|
||||
m = Q(yield* ToNumber(min));
|
||||
}
|
||||
let s;
|
||||
if (sec) {
|
||||
s = Q(yield* ToNumber(sec));
|
||||
}
|
||||
let milli;
|
||||
if (ms) {
|
||||
milli = Q(yield* ToNumber(ms));
|
||||
}
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
if (!m) {
|
||||
m = MinFromTime(t);
|
||||
}
|
||||
if (!s) {
|
||||
s = SecFromTime(t);
|
||||
}
|
||||
if (!milli) {
|
||||
milli = msFromTime(t);
|
||||
}
|
||||
const date = MakeDate(Day(t), MakeTime(h, m, s, milli));
|
||||
const v = TimeClip(date);
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setutcmilliseconds */
|
||||
function* DateProto_setUTCMilliseconds([ms = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
ms = Q(yield* ToNumber(ms));
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
const time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
|
||||
const v = TimeClip(MakeDate(Day(t), time));
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setutcminutes */
|
||||
function* DateProto_setUTCMinutes([min = Value.undefined, sec, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
const m = Q(yield* ToNumber(min));
|
||||
let s;
|
||||
if (sec) {
|
||||
s = Q(yield* ToNumber(sec));
|
||||
}
|
||||
let milli;
|
||||
if (ms) {
|
||||
milli = Q(yield* ToNumber(ms));
|
||||
}
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
if (!s) {
|
||||
s = SecFromTime(t);
|
||||
}
|
||||
if (!milli) {
|
||||
milli = msFromTime(t);
|
||||
}
|
||||
const date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
|
||||
const v = TimeClip(date);
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setutcmonth */
|
||||
function* DateProto_setUTCMonth([month = Value.undefined, date]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
const m = Q(yield* ToNumber(month));
|
||||
let dt;
|
||||
if (date) {
|
||||
dt = Q(yield* ToNumber(date));
|
||||
}
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
if (!dt) {
|
||||
dt = DateFromTime(t);
|
||||
}
|
||||
const newDate = MakeDate(MakeDay(YearFromTime(t), m, dt), TimeWithinDay(t));
|
||||
const v = TimeClip(newDate);
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.setutcseconds */
|
||||
function* DateProto_setUTCSeconds([sec = Value.undefined, ms]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
const s = Q(yield* ToNumber(sec));
|
||||
let milli;
|
||||
if (ms) {
|
||||
milli = Q(yield* ToNumber(ms));
|
||||
}
|
||||
if (t.isNaN()) {
|
||||
return t;
|
||||
}
|
||||
if (!milli) {
|
||||
milli = msFromTime(t);
|
||||
}
|
||||
const date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
|
||||
const v = TimeClip(date);
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(thisValue as DateObject));
|
||||
(thisValue as DateObject).DateValue = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.todatestring */
|
||||
function* DateProto_toDateString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O);
|
||||
}
|
||||
const tv = Q(thisTimeValue(O));
|
||||
if (tv.isNaN()) {
|
||||
return Value('Invalid Date');
|
||||
}
|
||||
const t = LocalTime(tv);
|
||||
return DateString(t);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.toisostring */
|
||||
export function DateProto_toISOString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion<JSStringValue> {
|
||||
const t = Q(thisTimeValue(thisValue));
|
||||
if (!Number.isFinite(R(t))) {
|
||||
return surroundingAgent.Throw('RangeError', 'DateInvalidTime');
|
||||
}
|
||||
const year = R(YearFromTime(t));
|
||||
const month = R(MonthFromTime(t)) + 1;
|
||||
const date = R(DateFromTime(t));
|
||||
const hour = R(HourFromTime(t));
|
||||
const min = R(MinFromTime(t));
|
||||
const sec = R(SecFromTime(t));
|
||||
const ms = R(msFromTime(t));
|
||||
|
||||
// TODO: figure out if there can be invalid years.
|
||||
let YYYY = String(year);
|
||||
if (year < 0 || year > 9999) {
|
||||
YYYY = year < 0 ? `${String(year).padStart(6, '0')}` : `+${String(year).padStart(6, '0')}`;
|
||||
}
|
||||
const MM = String(month).padStart(2, '0');
|
||||
const DD = String(date).padStart(2, '0');
|
||||
const HH = String(hour).padStart(2, '0');
|
||||
const mm = String(min).padStart(2, '0');
|
||||
const ss = String(sec).padStart(2, '0');
|
||||
const sss = String(ms).padStart(3, '0');
|
||||
const format = `${YYYY}-${MM}-${DD}T${HH}:${mm}:${ss}.${sss}Z`;
|
||||
return Value(format);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.tojson */
|
||||
function* DateProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = Q(ToObject(thisValue));
|
||||
const tv = Q(yield* ToPrimitive(O, 'number'));
|
||||
if (tv instanceof NumberValue && !Number.isFinite(R(tv))) {
|
||||
return Value.null;
|
||||
}
|
||||
return Q(yield* Invoke(O, Value('toISOString')));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.tolocaledatestring */
|
||||
function DateProto_toLocaleDateString(_args: Arguments, context: FunctionCallContext) {
|
||||
return DateProto_toString([], context);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.tolocalestring */
|
||||
function DateProto_toLocaleString(_args: Arguments, context: FunctionCallContext) {
|
||||
return DateProto_toString([], context);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.tolocaletimestring */
|
||||
function DateProto_toLocaleTimeString(_args: Arguments, context: FunctionCallContext) {
|
||||
return DateProto_toString([], context);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.tostring */
|
||||
function DateProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const tv = Q(thisTimeValue(thisValue));
|
||||
return ToDateString(tv);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-timestring */
|
||||
function TimeString(tv: NumberValue) {
|
||||
Assert(tv instanceof NumberValue);
|
||||
Assert(!tv.isNaN());
|
||||
const hour = String(R(HourFromTime(tv))).padStart(2, '0');
|
||||
const minute = String(R(MinFromTime(tv))).padStart(2, '0');
|
||||
const second = String(R(SecFromTime(tv))).padStart(2, '0');
|
||||
return Value(`${hour}:${minute}:${second} GMT`);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-todatestring-day-names */
|
||||
const daysOfTheWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
/** https://tc39.es/ecma262/#sec-todatestring-month-names */
|
||||
const monthsOfTheYear = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-datestring */
|
||||
function DateString(tv: NumberValue) {
|
||||
Assert(tv instanceof NumberValue);
|
||||
Assert(!tv.isNaN());
|
||||
const weekday = daysOfTheWeek[R(WeekDay(tv))];
|
||||
const month = monthsOfTheYear[R(MonthFromTime(tv))];
|
||||
const day = String(R(DateFromTime(tv))).padStart(2, '0');
|
||||
const yv = R(YearFromTime(tv));
|
||||
const yearSign = yv >= 0 ? '' : '-';
|
||||
const year = Value(String(Math.abs(yv)));
|
||||
const paddedYear = X(StringPad(year, F(4), Value('0'), 'start')).stringValue();
|
||||
return Value(`${weekday} ${month} ${day} ${yearSign}${paddedYear}`);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-timezoneestring */
|
||||
export function TimeZoneString(tv: NumberValue) {
|
||||
Assert(tv instanceof NumberValue);
|
||||
Assert(!tv.isNaN());
|
||||
const offset = LocalTZA(tv, true);
|
||||
const offsetSign = offset >= 0 ? '+' : '-';
|
||||
const offsetMin = String(R(MinFromTime(F(Math.abs(offset))))).padStart(2, '0');
|
||||
const offsetHour = String(R(HourFromTime(F(Math.abs(offset))))).padStart(2, '0');
|
||||
const tzName = '';
|
||||
return Value(`${offsetSign}${offsetHour}${offsetMin}${tzName}`);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-todatestring */
|
||||
export function ToDateString(tv: NumberValue) {
|
||||
Assert(tv instanceof NumberValue);
|
||||
if (tv.isNaN()) {
|
||||
return Value('Invalid Date');
|
||||
}
|
||||
const t = LocalTime(tv);
|
||||
return Value(`${DateString(t).stringValue()} ${TimeString(t).stringValue()}${TimeZoneString(t).stringValue()}`);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.totimestring */
|
||||
function DateProto_toTimeString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const O = thisValue;
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O);
|
||||
}
|
||||
const tv = Q(thisTimeValue(O));
|
||||
if (tv.isNaN()) {
|
||||
return Value('Invalid Date');
|
||||
}
|
||||
const t = LocalTime(tv);
|
||||
return Value(`${TimeString(t).stringValue()}${TimeZoneString(tv).stringValue()}`);
|
||||
}
|
||||
|
||||
function DateProto_toTemporalInstant(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const dateObject = thisValue;
|
||||
const t = Q(thisTimeValue(dateObject));
|
||||
const ns = R(Q(NumberToBigInt(t))) * BigInt(1e6);
|
||||
return CreateTemporalInstant(ns);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.toutcstring */
|
||||
function DateProto_toUTCString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const O = thisValue;
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O);
|
||||
}
|
||||
const tv = Q(thisTimeValue(O));
|
||||
if (tv.isNaN()) {
|
||||
return Value('Invalid Date');
|
||||
}
|
||||
const weekday = daysOfTheWeek[R(WeekDay(tv))];
|
||||
const month = monthsOfTheYear[R(MonthFromTime(tv))];
|
||||
const day = String(R(DateFromTime(tv))).padStart(2, '0');
|
||||
const yv = R(YearFromTime(tv));
|
||||
const yearSign = yv >= 0 ? '' : '-';
|
||||
const year = Value(String(Math.abs(yv)));
|
||||
const paddedYear = X(StringPad(year, F(4), Value('0'), 'start')).stringValue();
|
||||
return Value(`${weekday}, ${day} ${month} ${yearSign}${paddedYear} ${TimeString(tv).stringValue()}`);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype.valueof */
|
||||
function DateProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
return Q(thisTimeValue(thisValue));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-date.prototype-@@toprimitive */
|
||||
function* DateProto_toPrimitive([hint = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Date', O);
|
||||
}
|
||||
let tryFirst: 'string' | 'number';
|
||||
if (hint instanceof JSStringValue && (hint.stringValue() === 'string' || hint.stringValue() === 'default')) {
|
||||
tryFirst = 'string';
|
||||
} else if (hint instanceof JSStringValue && hint.stringValue() === 'number') {
|
||||
tryFirst = 'number';
|
||||
} else {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidHint', hint);
|
||||
}
|
||||
return Q(yield* OrdinaryToPrimitive(O, tryFirst));
|
||||
}
|
||||
|
||||
export function bootstrapDatePrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['getDate', DateProto_getDate, 0],
|
||||
['getDay', DateProto_getDay, 0],
|
||||
['getFullYear', DateProto_getFullYear, 0],
|
||||
['getHours', DateProto_getHours, 0],
|
||||
['getMilliseconds', DateProto_getMilliseconds, 0],
|
||||
['getMinutes', DateProto_getMinutes, 0],
|
||||
['getMonth', DateProto_getMonth, 0],
|
||||
['getSeconds', DateProto_getSeconds, 0],
|
||||
['getTime', DateProto_getTime, 0],
|
||||
['getTimezoneOffset', DateProto_getTimezoneOffset, 0],
|
||||
['getUTCDate', DateProto_getUTCDate, 0],
|
||||
['getUTCDay', DateProto_getUTCDay, 0],
|
||||
['getUTCFullYear', DateProto_getUTCFullYear, 0],
|
||||
['getUTCHours', DateProto_getUTCHours, 0],
|
||||
['getUTCMilliseconds', DateProto_getUTCMilliseconds, 0],
|
||||
['getUTCMinutes', DateProto_getUTCMinutes, 0],
|
||||
['getUTCMonth', DateProto_getUTCMonth, 0],
|
||||
['getUTCSeconds', DateProto_getUTCSeconds, 0],
|
||||
['setDate', DateProto_setDate, 1],
|
||||
['setFullYear', DateProto_setFullYear, 3],
|
||||
['setHours', DateProto_setHours, 4],
|
||||
['setMilliseconds', DateProto_setMilliseconds, 1],
|
||||
['setMinutes', DateProto_setMinutes, 3],
|
||||
['setMonth', DateProto_setMonth, 2],
|
||||
['setSeconds', DateProto_setSeconds, 2],
|
||||
['setTime', DateProto_setTime, 1],
|
||||
['setUTCDate', DateProto_setUTCDate, 1],
|
||||
['setUTCFullYear', DateProto_setUTCFullYear, 3],
|
||||
['setUTCHours', DateProto_setUTCHours, 4],
|
||||
['setUTCMilliseconds', DateProto_setUTCMilliseconds, 1],
|
||||
['setUTCMinutes', DateProto_setUTCMinutes, 3],
|
||||
['setUTCMonth', DateProto_setUTCMonth, 2],
|
||||
['setUTCSeconds', DateProto_setUTCSeconds, 2],
|
||||
['toDateString', DateProto_toDateString, 0],
|
||||
['toISOString', DateProto_toISOString, 0],
|
||||
['toJSON', DateProto_toJSON, 1],
|
||||
['toLocaleDateString', DateProto_toLocaleDateString, 0],
|
||||
['toLocaleString', DateProto_toLocaleString, 0],
|
||||
['toLocaleTimeString', DateProto_toLocaleTimeString, 0],
|
||||
['toString', DateProto_toString, 0],
|
||||
['toTimeString', DateProto_toTimeString, 0],
|
||||
surroundingAgent.feature('temporal') ? ['toTemporalInstant', DateProto_toTemporalInstant, 0] : undefined,
|
||||
['toUTCString', DateProto_toUTCString, 0],
|
||||
['valueOf', DateProto_valueOf, 0],
|
||||
[wellKnownSymbols.toPrimitive, DateProto_toPrimitive, 1, { Writable: Value.false, Enumerable: Value.false, Configurable: Value.true }],
|
||||
], realmRec.Intrinsics['%Object.prototype%']);
|
||||
|
||||
realmRec.Intrinsics['%Date.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
Descriptor,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type JSStringValue,
|
||||
type ObjectValue,
|
||||
type UndefinedValue,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
captureStack, callSiteToErrorString, type CallSite, CallFrame,
|
||||
} from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
DefinePropertyOrThrow,
|
||||
OrdinaryCreateFromConstructor,
|
||||
InstallErrorCause,
|
||||
ToString,
|
||||
type FunctionObject,
|
||||
IsError,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface ErrorObject extends ObjectValue {
|
||||
ErrorData: JSStringValue;
|
||||
HostDefinedErrorStack?: (CallSite | CallFrame)[] | UndefinedValue;
|
||||
}
|
||||
|
||||
export { IsError as isErrorObject } from '../abstract-ops/error-objects.mts';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-error-constructor */
|
||||
function* ErrorConstructor([message = Value.undefined, options = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
let newTarget;
|
||||
if (NewTarget === Value.undefined) {
|
||||
newTarget = surroundingAgent.activeFunctionObject;
|
||||
} else {
|
||||
newTarget = NewTarget;
|
||||
}
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%Error.prototype%", « [[ErrorData]] »).
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(newTarget as FunctionObject, '%Error.prototype%', [
|
||||
'ErrorData',
|
||||
'HostDefinedErrorStack',
|
||||
])) as ErrorObject;
|
||||
// 3. If message is not undefined, then
|
||||
if (message !== Value.undefined) {
|
||||
// a. Let msg be ? ToString(message).
|
||||
const msg = Q(yield* ToString(message));
|
||||
// b. Let msgDesc be the PropertyDescriptor { [[Value]]: msg, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }.
|
||||
const msgDesc = Descriptor({
|
||||
Value: msg,
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
});
|
||||
// c. Perform ! DefinePropertyOrThrow(O, "message", msgDesc).
|
||||
X(DefinePropertyOrThrow(O, Value('message'), msgDesc));
|
||||
}
|
||||
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Q(yield* InstallErrorCause(O, options));
|
||||
|
||||
// NON-SPEC
|
||||
const S = captureStack();
|
||||
O.HostDefinedErrorStack = S.stack;
|
||||
O.ErrorData = X(callSiteToErrorString(O, S.stack, S.nativeStack));
|
||||
|
||||
// 5. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-is-error/#sec-error.iserror */
|
||||
function Error_isError([value = Value.undefined]: Arguments) {
|
||||
return Value(IsError(value));
|
||||
}
|
||||
|
||||
export function bootstrapError(realmRec: Realm) {
|
||||
const error = bootstrapConstructor(realmRec, ErrorConstructor, 'Error', 1, realmRec.Intrinsics['%Error.prototype%'], [
|
||||
['isError', Error_isError, 1],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Error%'] = error;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import {
|
||||
JSStringValue,
|
||||
ObjectValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type UndefinedValue,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { isErrorObject } from './Error.mts';
|
||||
import {
|
||||
Assert,
|
||||
Get,
|
||||
SetterThatIgnoresPrototypeProperties,
|
||||
ToString,
|
||||
type BuiltinFunctionObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-error.prototype.tostring */
|
||||
function* ErrorProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator<JSStringValue> {
|
||||
// 1. Let O be this value.
|
||||
const O = thisValue;
|
||||
// 2. If Type(O) is not Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let name be ? Get(O, "name").
|
||||
let name = Q(yield* Get(O, Value('name')));
|
||||
// 4. If name is undefined, set name to "Error"; otherwise set name to ? ToString(name).
|
||||
if (name === Value.undefined) {
|
||||
name = Value('Error');
|
||||
} else {
|
||||
name = Q(yield* ToString(name));
|
||||
}
|
||||
// 5. Let msg be ? Get(O, "message").
|
||||
let msg = Q(yield* Get(O, Value('message')));
|
||||
// 6. If msg is undefined, set msg to the empty String; otherwise set msg to ? ToString(msg).
|
||||
if (msg === Value.undefined) {
|
||||
msg = Value('');
|
||||
} else {
|
||||
msg = Q(yield* ToString(msg));
|
||||
}
|
||||
// 7. If name is the empty String, return msg.
|
||||
if (name.stringValue() === '') {
|
||||
return msg;
|
||||
}
|
||||
// 8. If msg is the empty String, return name.
|
||||
if (msg.stringValue() === '') {
|
||||
return name;
|
||||
}
|
||||
// 9. Return the string-concatenation of name, the code unit 0x003A (COLON), the code unit 0x0020 (SPACE), and msg.
|
||||
return Value(`${name.stringValue()}: ${msg.stringValue()}`);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-error-stack-accessor/#sec-get-error.prototype.stack */
|
||||
function* ErrorProto_getStack(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator<JSStringValue | UndefinedValue> {
|
||||
// 1. Let E be the this value.
|
||||
const E = thisValue;
|
||||
// 2. If E is not an Object, throw a TypeError exception.
|
||||
if (!(E instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', E);
|
||||
}
|
||||
// 3. If E does not have an [[ErrorData]] internal slot, return undefined.
|
||||
if (!isErrorObject(E)) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 4. Return an implementation-defined string that represents the stack trace of E.
|
||||
Assert(E.ErrorData instanceof JSStringValue);
|
||||
return E.ErrorData;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-error-stack-accessor/#sec-set-error.prototype.stack */
|
||||
function* ErrorProto_setStack(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator<UndefinedValue> {
|
||||
const [v = Value.undefined] = args;
|
||||
|
||||
// 1. Let E be the this value.
|
||||
const E = thisValue;
|
||||
// 2. If E is not an Object, throw a TypeError exception.
|
||||
if (!(E instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', E);
|
||||
}
|
||||
// 3. Let numberOfArgs be the number of arguments passed to this function call.
|
||||
const numberOfArgs = args.length;
|
||||
// 4. If numberOfArgs is 0, throw a TypeError exception.
|
||||
if (numberOfArgs === 0) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotEnoughArguments', numberOfArgs, 1);
|
||||
}
|
||||
// 5. If E does not have an [[ErrorData]] internal slot, return undefined.
|
||||
if (!isErrorObject(E)) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 6. Perform ? SetterThatIgnoresPrototypeProperties(this value, %Error.prototype%, "stack", v).
|
||||
Q(yield* SetterThatIgnoresPrototypeProperties(thisValue, surroundingAgent.intrinsic('%Error.prototype%'), Value('stack'), v));
|
||||
// 7. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
export function bootstrapErrorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['toString', ErrorProto_toString, 0],
|
||||
['message', Value('')],
|
||||
['name', Value('Error')],
|
||||
['stack', [ErrorProto_getStack, ErrorProto_setStack]],
|
||||
], realmRec.Intrinsics['%Object.prototype%']);
|
||||
|
||||
realmRec.Intrinsics['%Error.prototype%'] = proto;
|
||||
realmRec.Intrinsics['%Error.prototype.toString%'] = X(Get(proto, Value('toString'))) as BuiltinFunctionObject;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { HostMakeJobCallback } from '../execution-context/Job.mts';
|
||||
import { type JobCallbackRecord } from '../execution-context/Job.mts';
|
||||
import {
|
||||
UndefinedValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
IsCallable, OrdinaryCreateFromConstructor,
|
||||
type BuiltinFunctionObject, type FunctionObject, type OrdinaryObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface FinalizationRegistryCell {
|
||||
WeakRefTarget: Value | undefined;
|
||||
readonly HeldValue: Value;
|
||||
readonly UnregisterToken: Value | undefined;
|
||||
}
|
||||
export interface FinalizationRegistryObject extends OrdinaryObject {
|
||||
readonly Realm: Realm;
|
||||
readonly CleanupCallback: JobCallbackRecord;
|
||||
Cells: FinalizationRegistryCell[];
|
||||
}
|
||||
export function isFinalizationRegistryObject(object: object): object is FinalizationRegistryObject {
|
||||
return 'Cells' in object;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-finalization-registry-cleanup-callback */
|
||||
function* FinalizationRegistryConstructor(this: BuiltinFunctionObject, [cleanupCallback = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. If IsCallable(cleanupCallback) is false, throw a TypeError exception.
|
||||
if (!IsCallable(cleanupCallback)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', cleanupCallback);
|
||||
}
|
||||
// 3. Let finalizationGroup be ? OrdinaryCreateFromConstructor(NewTarget, "%FinalizationRegistryPrototype%", « [[Realm]], [[CleanupCallback]], [[Cells]] »).
|
||||
const finalizationGroup = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%FinalizationRegistry.prototype%', [
|
||||
'Realm',
|
||||
'CleanupCallback',
|
||||
'Cells',
|
||||
])) as Mutable<FinalizationRegistryObject>;
|
||||
// 4. Let fn be the active function object.
|
||||
const fn = surroundingAgent.activeFunctionObject;
|
||||
// 5. Set finalizationGroup.[[Realm]] to fn.[[Realm]].
|
||||
finalizationGroup.Realm = (fn as FunctionObject).Realm;
|
||||
// 6. Set finalizationGroup.[[CleanupCallback]] to HostMakeJobCallback(cleanupCallback).
|
||||
finalizationGroup.CleanupCallback = HostMakeJobCallback(cleanupCallback);
|
||||
// 7. Set finalizationGroup.[[Cells]] to be an empty List.
|
||||
finalizationGroup.Cells = [];
|
||||
// 8. Return finalizationGroup.
|
||||
return finalizationGroup as FinalizationRegistryObject;
|
||||
}
|
||||
|
||||
export function bootstrapFinalizationRegistry(realmRec: Realm) {
|
||||
const cons = bootstrapConstructor(
|
||||
realmRec,
|
||||
FinalizationRegistryConstructor,
|
||||
'FinalizationRegistry',
|
||||
1,
|
||||
realmRec.Intrinsics['%FinalizationRegistry.prototype%'],
|
||||
[],
|
||||
);
|
||||
|
||||
realmRec.Intrinsics['%FinalizationRegistry%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
Value, type Arguments, type FunctionCallContext, BooleanValue,
|
||||
} from '../value.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { FinalizationRegistryCell, FinalizationRegistryObject } from './FinalizationRegistry.mts';
|
||||
import {
|
||||
CanBeHeldWeakly,
|
||||
CleanupFinalizationRegistry,
|
||||
IsCallable,
|
||||
RequireInternalSlot,
|
||||
SameValue,
|
||||
type FunctionObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-finalization-registry.prototype.cleanupSome */
|
||||
function* FinalizationRegistryProto_cleanupSome([callback = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let finalizationRegistry be the this value.
|
||||
const finalizationRegistry = thisValue;
|
||||
// 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
|
||||
Q(RequireInternalSlot(finalizationRegistry, 'Cells'));
|
||||
// 3. If callback is present and IsCallable(callback) is false, throw a TypeError exception.
|
||||
if (callback !== Value.undefined && !IsCallable(callback)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callback);
|
||||
}
|
||||
// 4. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
|
||||
Q(yield* CleanupFinalizationRegistry(finalizationRegistry as FinalizationRegistryObject, { Callback: callback as FunctionObject, HostDefined: undefined }));
|
||||
// 5. Return *undefined*.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-finalization-registry.prototype.register */
|
||||
function FinalizationRegistryProto_register([target = Value.undefined, heldValue = Value.undefined, unregisterToken = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let finalizationRegistry be the this value.
|
||||
const finalizationRegistry = thisValue as FinalizationRegistryObject;
|
||||
// 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
|
||||
Q(RequireInternalSlot(finalizationRegistry, 'Cells'));
|
||||
// 3. If CanBeHeldWeakly(target) is false, throw a TypeError exception.
|
||||
if (!CanBeHeldWeakly(target)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAWeakKey', target);
|
||||
}
|
||||
// 4. If SameValue(target, heldValue), throw a TypeError exception.
|
||||
if (SameValue(target, heldValue) === Value.true) {
|
||||
return surroundingAgent.Throw('TypeError', 'TargetMatchesHeldValue', heldValue);
|
||||
}
|
||||
// 5. If CanBeHeldWeakly(unregisterToken) is false, then
|
||||
if (!CanBeHeldWeakly(unregisterToken)) {
|
||||
// a. If unregisterToken is not undefined, throw a TypeError exception.
|
||||
if (unregisterToken !== Value.undefined) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAWeakKey', unregisterToken);
|
||||
}
|
||||
// b. Set unregisterToken to empty.
|
||||
unregisterToken = undefined!;
|
||||
}
|
||||
// 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]: heldValue, [[UnregisterToken]]: unregisterToken }.
|
||||
const cell: FinalizationRegistryCell = {
|
||||
WeakRefTarget: target,
|
||||
HeldValue: heldValue,
|
||||
UnregisterToken: unregisterToken,
|
||||
};
|
||||
// 7. Append cell to finalizationRegistry.[[Cells]].
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(finalizationRegistry));
|
||||
finalizationRegistry.Cells.push(cell);
|
||||
// 8. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-finalization-registry.prototype.unregister */
|
||||
function FinalizationRegistryProto_unregister([unregisterToken = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let finalizationRegistry be the this value.
|
||||
const finalizationRegistry = thisValue as FinalizationRegistryObject;
|
||||
// 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
|
||||
Q(RequireInternalSlot(finalizationRegistry, 'Cells'));
|
||||
// 3. If CanBeHeldWeakly(unregisterToken) is false, throw a TypeError exception.
|
||||
if (!CanBeHeldWeakly(unregisterToken)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAWeakKey', unregisterToken);
|
||||
}
|
||||
// 4. Let removed be false.
|
||||
let removed: BooleanValue = Value.false;
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(finalizationRegistry));
|
||||
// 5. For each Record { [[WeakRefTarget]], [[HeldValue]], [[UnregisterToken]] } cell that is an element of finalizationRegistry.[[Cells]], do
|
||||
finalizationRegistry.Cells = finalizationRegistry.Cells.filter((cell) => {
|
||||
let r = true;
|
||||
// a. If cell.[[UnregisterToken]] is not empty and SameValue(cell.[[UnregisterToken]], unregisterToken) is true, then
|
||||
if (cell.UnregisterToken !== undefined && SameValue(cell.UnregisterToken, unregisterToken) === Value.true) {
|
||||
// i. Remove cell from finalizationRegistry.Cells.
|
||||
r = false;
|
||||
// ii. Set removed to true.
|
||||
removed = Value.true;
|
||||
}
|
||||
return r;
|
||||
});
|
||||
// 6. Return removed.
|
||||
return removed;
|
||||
}
|
||||
|
||||
export function bootstrapFinalizationRegistryPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
surroundingAgent.feature('cleanup-some')
|
||||
? ['cleanupSome', FinalizationRegistryProto_cleanupSome, 0]
|
||||
: undefined,
|
||||
['register', FinalizationRegistryProto_register, 2],
|
||||
['unregister', FinalizationRegistryProto_unregister, 1],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'FinalizationRegistry');
|
||||
|
||||
realmRec.Intrinsics['%FinalizationRegistry.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import {
|
||||
Value, JSStringValue, ObjectValue, type Arguments,
|
||||
type FunctionCallContext,
|
||||
UndefinedValue,
|
||||
NullValue,
|
||||
} from '../value.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { Q, type ValueEvaluator } from '../completion.mts';
|
||||
import { __ts_cast__, type Mutable } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
SameValue,
|
||||
OrdinaryObjectCreate,
|
||||
CreateIteratorResultObject,
|
||||
type OrdinaryObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface ForInIteratorInstance extends OrdinaryObject {
|
||||
Object: ObjectValue | NullValue;
|
||||
ObjectWasVisited: Value;
|
||||
readonly VisitedKeys: JSStringValue[];
|
||||
readonly RemainingKeys: JSStringValue[];
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-createforiniterator */
|
||||
export function CreateForInIterator(object: ObjectValue) {
|
||||
// 1. Assert: Type(object) is Object.
|
||||
Assert(object instanceof ObjectValue);
|
||||
// 2. Let iterator be ObjectCreate(%ForInIteratorPrototype%, « [[Object]], [[ObjectWasVisited]], [[VisitedKeys]], [[RemainingKeys]] »).
|
||||
const iterator = OrdinaryObjectCreate(surroundingAgent.intrinsic('%ForInIteratorPrototype%'), [
|
||||
'Object',
|
||||
'ObjectWasVisited',
|
||||
'VisitedKeys',
|
||||
'RemainingKeys',
|
||||
]) as Mutable<ForInIteratorInstance>;
|
||||
// 3. Set iterator.[[Object]] to object.
|
||||
iterator.Object = object;
|
||||
// 4. Set iterator.[[ObjectWasVisited]] to false.
|
||||
iterator.ObjectWasVisited = Value.false;
|
||||
// 5. Set iterator.[[VisitedKeys]] to a new empty List.
|
||||
iterator.VisitedKeys = [];
|
||||
// 6. Set iterator.[[RemainingKeys]] to a new empty List.
|
||||
iterator.RemainingKeys = [];
|
||||
// 7. Return iterator.
|
||||
return iterator;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next */
|
||||
function* ForInIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be this value.
|
||||
const O = thisValue;
|
||||
// 2. Assert: Type(O) is Object.
|
||||
Assert(O instanceof ObjectValue);
|
||||
// 3. Assert: O has all the internal slot sof a For-In Iterator Instance.
|
||||
Assert('Object' in O && 'ObjectWasVisited' in O && 'VisitedKeys' in O && 'RemainingKeys' in O);
|
||||
__ts_cast__<ForInIteratorInstance>(O);
|
||||
// 4. Let object be O.[[Object]].
|
||||
let object: ObjectValue | NullValue = O.Object;
|
||||
// 5. Let visited be O.[[VisitedKeys]].
|
||||
const visited = O.VisitedKeys;
|
||||
// 6. Let remaining be O.[[RemainingKeys]].
|
||||
const remaining = O.RemainingKeys;
|
||||
// 7. Repeat,
|
||||
while (true) {
|
||||
__ts_cast__<ObjectValue>(object);
|
||||
// a. If O.[[ObjectWasVisited]] is false, then
|
||||
if (O.ObjectWasVisited === Value.false) {
|
||||
// i. Let keys be ? object.[[OwnPropertyKeys]]().
|
||||
const keys = Q(yield* object.OwnPropertyKeys());
|
||||
// ii. for each key of keys in List order, do
|
||||
for (const key of keys) {
|
||||
// 1. If Type(key) is String, then
|
||||
if (key instanceof JSStringValue) {
|
||||
// a. Append key to remaining.
|
||||
remaining.push(key);
|
||||
}
|
||||
}
|
||||
// iii. Set O.ObjectWasVisited to true.
|
||||
O.ObjectWasVisited = Value.true;
|
||||
}
|
||||
// b. Repeat, while remaining is not empty,
|
||||
while (remaining.length > 0) {
|
||||
// i. Remove the first element from remaining and let r be the value of the element.
|
||||
const r = remaining.shift()!;
|
||||
// ii. If there does not exist an element v of visisted such that SameValue(r, v) is true, then
|
||||
if (!visited.find((v) => SameValue(r, v) === Value.true)) {
|
||||
// 1. Let desc be ? object.[[GetOwnProperty]](r).
|
||||
const desc = Q(yield* object.GetOwnProperty(r));
|
||||
// 2. If desc is not undefined, then,
|
||||
if (!(desc instanceof UndefinedValue)) {
|
||||
// a. Append r to visited.
|
||||
visited.push(r);
|
||||
// b. If desc.[[Enumerable]] is true, return CreateIteratorResultObject(r, false).
|
||||
if (desc.Enumerable === Value.true) {
|
||||
return CreateIteratorResultObject(r, Value.false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// c. Set object to ? object.[[GetPrototypeOf]]().
|
||||
object = Q(yield* object.GetPrototypeOf());
|
||||
// d. Set O.Object to object.
|
||||
O.Object = object;
|
||||
// e. Set O.ObjectWasVisited to false.
|
||||
O.ObjectWasVisited = Value.false;
|
||||
// f. If object is null, return CreateIteratorResultObject(undefined, true).
|
||||
if (object === Value.null) {
|
||||
return CreateIteratorResultObject(Value.undefined, Value.true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function bootstrapForInIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', ForInIteratorPrototype_next, 0],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%']);
|
||||
|
||||
realmRec.Intrinsics['%ForInIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import { CreateDynamicFunction } from '../runtime-semantics/all.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
type Arguments, type ValueEvaluator, type FunctionCallContext, type FunctionObject, type Realm,
|
||||
Value,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-function-p1-p2-pn-body */
|
||||
function* FunctionConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
const bodyArg = args[args.length - 1] || Value('');
|
||||
args = args.slice(0, -1) as Arguments;
|
||||
// 1. Let C be the active function object.
|
||||
const C = surroundingAgent.activeFunctionObject as FunctionObject;
|
||||
// 2. Let args be the argumentsList that was passed to this function by [[Call]] or [[Construct]].
|
||||
// 3. Return ? CreateDynamicFunction(C, NewTarget, normal, args).
|
||||
return Q(yield* CreateDynamicFunction(C, NewTarget, 'normal', args, bodyArg));
|
||||
}
|
||||
|
||||
export function bootstrapFunction(realmRec: Realm) {
|
||||
const cons = bootstrapConstructor(realmRec, FunctionConstructor, 'Function', 1, realmRec.Intrinsics['%Function.prototype%'], []);
|
||||
realmRec.Intrinsics['%Function%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import {
|
||||
surroundingAgent,
|
||||
HostHasSourceTextAvailable,
|
||||
} from '../host-defined/engine.mts';
|
||||
import {
|
||||
JSStringValue,
|
||||
ObjectValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__, type Mutable } from '../helpers.mts';
|
||||
import { assignProps } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
Construct,
|
||||
CreateListFromArrayLike,
|
||||
IsCallable,
|
||||
IsConstructor,
|
||||
OrdinaryHasInstance,
|
||||
PrepareForTailCall,
|
||||
SameValue,
|
||||
CreateBuiltinFunction,
|
||||
MakeBasicObject,
|
||||
type ExoticObject,
|
||||
type FunctionObject,
|
||||
isBuiltinFunctionObject,
|
||||
type BuiltinFunctionObject,
|
||||
hasSourceTextInternalSlot,
|
||||
CopyNameAndLength,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
export interface BoundFunctionObject extends ExoticObject, BuiltinFunctionObject {
|
||||
readonly BoundTargetFunction: FunctionObject;
|
||||
readonly BoundThis: Value;
|
||||
readonly BoundArguments: Arguments;
|
||||
}
|
||||
|
||||
export function isBoundFunctionObject(object: object): object is BoundFunctionObject {
|
||||
return 'BoundTargetFunction' in object;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-properties-of-the-function-prototype-object */
|
||||
function FunctionProto() {
|
||||
// * accepts any arguments and returns undefined when invoked.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-function.prototype.apply */
|
||||
function* FunctionProto_apply([thisArg = Value.undefined, argArray = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let func be the this value.
|
||||
const func = thisValue;
|
||||
// 2. If IsCallable(func) is false, throw a TypeError exception.
|
||||
if (!IsCallable(func)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ThisNotAFunction', func);
|
||||
}
|
||||
// 3. If argArray is undefined or null, then
|
||||
if (argArray === Value.undefined || argArray === Value.null) {
|
||||
// a. Perform PrepareForTailCall().
|
||||
PrepareForTailCall();
|
||||
// b. Return ? Call(func, thisArg).
|
||||
return Q(yield* Call(func, thisArg));
|
||||
}
|
||||
// 4. Let argList be ? CreateListFromArrayLike(argArray).
|
||||
const argList = Q(yield* CreateListFromArrayLike(argArray));
|
||||
// 5. Perform PrepareForTailCall().
|
||||
PrepareForTailCall();
|
||||
// 6. Return ? Call(func, thisArg, argList).
|
||||
return Q(yield* Call(func, thisArg, argList));
|
||||
}
|
||||
|
||||
function* BoundFunctionExoticObjectCall(this: BoundFunctionObject, _thisArgument: ObjectValue, argumentsList: Arguments): ValueEvaluator {
|
||||
const F = this;
|
||||
|
||||
const target = F.BoundTargetFunction;
|
||||
const boundThis = F.BoundThis;
|
||||
const boundArgs = F.BoundArguments;
|
||||
const args = [...boundArgs.values(), ...argumentsList.values()];
|
||||
return Q(yield* Call(target, boundThis, args));
|
||||
}
|
||||
|
||||
function* BoundFunctionExoticObjectConstruct(this: BoundFunctionObject, argumentsList: Arguments, newTarget: FunctionObject | UndefinedValue): ValueEvaluator<ObjectValue> {
|
||||
const F = this;
|
||||
|
||||
const target = F.BoundTargetFunction;
|
||||
Assert(IsConstructor(target));
|
||||
const boundArgs = F.BoundArguments;
|
||||
const args = [...boundArgs.values(), ...argumentsList.values()];
|
||||
if (SameValue(F, newTarget) === Value.true) {
|
||||
newTarget = target;
|
||||
}
|
||||
return Q(yield* Construct(target, args, newTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-boundfunctioncreate */
|
||||
function* BoundFunctionCreate(targetFunction: ObjectValue, boundThis: Value, boundArgs: Arguments): ValueEvaluator<BoundFunctionObject> {
|
||||
// 1. Assert: Type(targetFunction) is Object.
|
||||
Assert(targetFunction instanceof ObjectValue);
|
||||
// 2. Let proto be ? targetFunction.[[GetPrototypeOf]]().
|
||||
const proto = Q(yield* targetFunction.GetPrototypeOf());
|
||||
// 3. Let internalSlotsList be the internal slots listed in Table 30, plus [[Prototype]] and [[Extensible]].
|
||||
const internalSlotsList = [
|
||||
'BoundTargetFunction',
|
||||
'BoundThis',
|
||||
'BoundArguments',
|
||||
'Prototype',
|
||||
'Extensible',
|
||||
];
|
||||
// 4. Let obj be ! MakeBasicObject(internalSlotsList).
|
||||
const obj = X(MakeBasicObject(internalSlotsList)) as Mutable<BoundFunctionObject>;
|
||||
// 5. Set obj.[[Prototype]] to proto.
|
||||
obj.Prototype = proto;
|
||||
// 6. Set obj.[[Call]] as described in 9.4.1.1.
|
||||
obj.Call = BoundFunctionExoticObjectCall;
|
||||
// 7. If IsConstructor(targetFunction) is true, then
|
||||
if (IsConstructor(targetFunction)) {
|
||||
// a. Set obj.[[Construct]] as described in 9.4.1.2.
|
||||
obj.Construct = BoundFunctionExoticObjectConstruct;
|
||||
}
|
||||
// 8. Set obj.[[BoundTargetFunction]] to targetFunction.
|
||||
obj.BoundTargetFunction = targetFunction as FunctionObject;
|
||||
// 9. Set obj.[[BoundThis]] to boundThis.
|
||||
obj.BoundThis = boundThis;
|
||||
// 10. Set obj.[[BoundArguments]] to boundArguments.
|
||||
obj.BoundArguments = boundArgs;
|
||||
// 11. Return obj.
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-function.prototype.bind */
|
||||
function* FunctionProto_bind([thisArg = Value.undefined, ...args]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let Target be the this value.
|
||||
const Target = thisValue;
|
||||
// 2. If IsCallable(Target) is false, throw a TypeError exception.
|
||||
if (!IsCallable(Target)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ThisNotAFunction', Target);
|
||||
}
|
||||
__ts_cast__<ObjectValue>(Target);
|
||||
// 3. Let F be ? BoundFunctionCreate(Target, thisArg, args).
|
||||
const F = Q(yield* BoundFunctionCreate(Target, thisArg, args as Arguments));
|
||||
Q(yield* CopyNameAndLength(F, Target, 'bound', args.length));
|
||||
return F;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-function.prototype.call */
|
||||
function* FunctionProto_call([thisArg = Value.undefined, ...args]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let func be the this value.
|
||||
const func = thisValue;
|
||||
// 2. If IsCallable(func) is false, throw a TypeError exception.
|
||||
if (!IsCallable(func)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ThisNotAFunction', func);
|
||||
}
|
||||
// 3. Let argList be a new empty List.
|
||||
const argList: Value[] = [];
|
||||
// 4. If this method was called with more than one argument, then in left to right order, starting with the second argument, append each argument as the last element of argList.
|
||||
for (const arg of args) {
|
||||
argList.push(arg!);
|
||||
}
|
||||
// 5. Perform PrepareForTailCall().
|
||||
PrepareForTailCall();
|
||||
// 6. Return ? Call(func, thisArg, argList).
|
||||
return Q(yield* Call(func, thisArg, argList));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-function.prototype.tostring */
|
||||
export function FunctionProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion<JSStringValue> {
|
||||
// 1. Let func be the this value.
|
||||
const func = thisValue;
|
||||
// 2. If Type(func) is Object and func has a [[SourceText]] internal slot and func.[[SourceText]]
|
||||
// is a sequence of Unicode code points and ! HostHasSourceTextAvailable(func) is true, then
|
||||
if (hasSourceTextInternalSlot(func)
|
||||
&& X(HostHasSourceTextAvailable(func)) === Value.true) {
|
||||
// Return ! UTF16Encode(func.[[SourceText]]).
|
||||
return Value(func.SourceText);
|
||||
}
|
||||
// 3. If func is a built-in function object, then return an implementation-defined
|
||||
// String source code representation of func. The representation must have the
|
||||
// syntax of a NativeFunction. Additionally, if func has an [[InitialName]] internal
|
||||
// slot and func.[[InitialName]] is a String, the portion of the returned String
|
||||
// that would be matched by `NativeFunctionAccessor? PropertyName` must be the
|
||||
// value of func.[[InitialName]].
|
||||
if (isBuiltinFunctionObject(func)) {
|
||||
if (func.InitialName instanceof JSStringValue) {
|
||||
return Value(`function ${func.InitialName.stringValue()}() { [native code] }`);
|
||||
}
|
||||
return Value('function() { [native code] }');
|
||||
}
|
||||
// 4. If Type(func) is Object and IsCallable(func) is true, then return an implementation
|
||||
// dependent String source code representation of func. The representation must have
|
||||
// the syntax of a NativeFunction.
|
||||
if (func instanceof ObjectValue && IsCallable(func)) {
|
||||
return Value('function() { [native code] }');
|
||||
}
|
||||
// 5. Throw a TypeError exception.
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', func);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-function.prototype-@@hasinstance */
|
||||
function* FunctionProto_hasInstance([V = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let F be this value.
|
||||
const F = thisValue;
|
||||
// 2. Return ? OrdinaryHasInstance(F, V).
|
||||
return Q(yield* OrdinaryHasInstance(F, V));
|
||||
}
|
||||
|
||||
export function bootstrapFunctionPrototype(realmRec: Realm) {
|
||||
const proto = CreateBuiltinFunction(
|
||||
FunctionProto,
|
||||
0,
|
||||
Value(''),
|
||||
[],
|
||||
realmRec,
|
||||
realmRec.Intrinsics['%Object.prototype%'],
|
||||
);
|
||||
realmRec.Intrinsics['%Function.prototype%'] = proto;
|
||||
|
||||
const readonly = { Writable: Value.false, Configurable: Value.false };
|
||||
assignProps(realmRec, proto, [
|
||||
['apply', FunctionProto_apply, 2],
|
||||
['bind', FunctionProto_bind, 1],
|
||||
['call', FunctionProto_call, 1],
|
||||
['toString', FunctionProto_toString, 0],
|
||||
[wellKnownSymbols.hasInstance, FunctionProto_hasInstance, 1, readonly],
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
Descriptor, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { CreateDynamicFunction } from '../runtime-semantics/all.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import { DefinePropertyOrThrow, type FunctionObject, Realm } from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-generatorfunction */
|
||||
function* GeneratorFunctionConstructor(args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
const bodyArg = args[args.length - 1] || Value('');
|
||||
args = args.slice(0, -1) as Arguments;
|
||||
// 1. Let C be the active function object.
|
||||
const C = surroundingAgent.activeFunctionObject as FunctionObject;
|
||||
// 2. Let args be the argumentsList that was passed to this function by [[Call]] or [[Construct]].
|
||||
// 3. Return ? CreateDynamicFunction(C, NewTarget, generator, args).
|
||||
return Q(yield* CreateDynamicFunction(C, NewTarget, 'generator', args, bodyArg));
|
||||
}
|
||||
|
||||
export function bootstrapGeneratorFunction(realmRec: Realm) {
|
||||
const generator = realmRec.Intrinsics['%GeneratorFunction.prototype%'];
|
||||
|
||||
const cons = bootstrapConstructor(realmRec, GeneratorFunctionConstructor, 'GeneratorFunction', 1, generator, []);
|
||||
X(DefinePropertyOrThrow(cons, Value('prototype'), Descriptor({
|
||||
Writable: Value.false,
|
||||
Configurable: Value.false,
|
||||
})));
|
||||
X(DefinePropertyOrThrow(generator, Value('constructor'), Descriptor({
|
||||
Writable: Value.false,
|
||||
})));
|
||||
|
||||
realmRec.Intrinsics['%GeneratorFunction%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Descriptor, Value } from '../value.mts';
|
||||
import { X } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { DefinePropertyOrThrow } from '#self';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
export function bootstrapGeneratorFunctionPrototype(realmRec: Realm) {
|
||||
const generatorPrototype = realmRec.Intrinsics['%GeneratorFunction.prototype.prototype%'];
|
||||
|
||||
const generator = bootstrapPrototype(realmRec, [
|
||||
['prototype', generatorPrototype, undefined, { Writable: Value.false }],
|
||||
], realmRec.Intrinsics['%Function.prototype%'], 'GeneratorFunction');
|
||||
|
||||
X(DefinePropertyOrThrow(generatorPrototype, Value('constructor'), Descriptor({
|
||||
Value: generator,
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
|
||||
realmRec.Intrinsics['%GeneratorFunction.prototype%'] = generator;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
Completion,
|
||||
ThrowCompletion,
|
||||
Q,
|
||||
X,
|
||||
type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import {
|
||||
Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
GeneratorResume,
|
||||
GeneratorResumeAbrupt,
|
||||
type FunctionObject,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-generator.prototype.next */
|
||||
function* GeneratorProto_next([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let g be the this value.
|
||||
const g = thisValue;
|
||||
// 2. Return ? GeneratorResume(g, value, empty).
|
||||
return Q(yield* GeneratorResume(g, value, undefined));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-generator.prototype.return */
|
||||
function* GeneratorProto_return([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let g be the this value.
|
||||
const g = thisValue;
|
||||
// 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
||||
const C = new Completion({ Type: 'return', Value: value, Target: undefined });
|
||||
// 3. Return ? GeneratorResumeAbrupt(g, C, empty).
|
||||
return Q(yield* GeneratorResumeAbrupt(g, C, undefined));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-generator.prototype.throw */
|
||||
function* GeneratorProto_throw([exception = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let g be the this value.
|
||||
const g = thisValue;
|
||||
// 2. Let C be ThrowCompletion(exception).
|
||||
const C = ThrowCompletion(exception);
|
||||
// 3. Return ? GeneratorResumeAbrupt(g, C, empty).
|
||||
return Q(yield* GeneratorResumeAbrupt(g, C, undefined));
|
||||
}
|
||||
|
||||
export function bootstrapGeneratorFunctionPrototypePrototype(realmRec: Realm) {
|
||||
const generatorPrototype = bootstrapPrototype(realmRec, [
|
||||
['next', GeneratorProto_next, 1],
|
||||
['return', GeneratorProto_return, 1],
|
||||
['throw', GeneratorProto_throw, 1],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%'], 'Generator');
|
||||
|
||||
realmRec.Intrinsics['%GeneratorFunction.prototype.prototype%'] = generatorPrototype;
|
||||
realmRec.Intrinsics['%GeneratorPrototype%'] = realmRec.Intrinsics['%GeneratorFunction.prototype.prototype%'];
|
||||
|
||||
// Used by `CreateListIteratorRecord`:
|
||||
realmRec.Intrinsics['%GeneratorFunction.prototype.prototype.next%'] = X(generatorPrototype.Get(Value('next'), generatorPrototype)) as FunctionObject;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { AbruptCompletion, Q, type ValueEvaluator } from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue,
|
||||
type BooleanValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
wellKnownSymbols,
|
||||
} from '../value.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Call,
|
||||
CreateIteratorFromClosure,
|
||||
GetIteratorDirect,
|
||||
GetIteratorFlattenable,
|
||||
GetMethod,
|
||||
IteratorClose,
|
||||
IteratorStepValue,
|
||||
OrdinaryCreateFromConstructor,
|
||||
OrdinaryHasInstance,
|
||||
OrdinaryObjectCreate,
|
||||
Yield,
|
||||
type BuiltinFunctionObject,
|
||||
type FunctionObject,
|
||||
type IteratorObject,
|
||||
type Realm,
|
||||
type YieldEvaluator,
|
||||
} from '#self';
|
||||
|
||||
|
||||
/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-iterator-constructor */
|
||||
function* IteratorConstructor(
|
||||
this: BuiltinFunctionObject,
|
||||
_args: Arguments,
|
||||
{ NewTarget }: FunctionCallContext,
|
||||
): ValueEvaluator<ObjectValue> {
|
||||
// 1. If NewTarget is either undefined or the active function object, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
if (NewTarget === surroundingAgent.activeFunctionObject) {
|
||||
return surroundingAgent.Throw('TypeError', 'CannotConstructAbstractFunction', NewTarget);
|
||||
}
|
||||
|
||||
// 2. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Iterator.prototype%").
|
||||
return Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Iterator.prototype%'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.from */
|
||||
function* Iterator_from([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let iteratorRecord be ? GetIteratorFlattenable(O, iterate-string-primitives).
|
||||
const iteratorRecord = Q(yield* GetIteratorFlattenable(O, 'iterate-string-primitives'));
|
||||
|
||||
// 2. Let hasInstance be ? OrdinaryHasInstance(%Iterator%, iteratorRecord.[[Iterator]]).
|
||||
const hasInstance: BooleanValue = Q(yield* OrdinaryHasInstance(surroundingAgent.intrinsic('%Iterator%'), iteratorRecord.Iterator));
|
||||
// 3. If hasInstance is true, then
|
||||
if (hasInstance === Value.true) {
|
||||
// a. Return iteratorRecord.[[Iterator]].
|
||||
return iteratorRecord.Iterator;
|
||||
}
|
||||
|
||||
// 4. Let wrapper be OrdinaryObjectCreate(%WrapForValidIteratorPrototype%, « [[Iterated]] »).
|
||||
const wrapper = OrdinaryObjectCreate(
|
||||
surroundingAgent.intrinsic('%WrapForValidIteratorPrototype%'),
|
||||
['Iterated'],
|
||||
) as Mutable<IteratorObject>;
|
||||
// 5. Set wrapper.[[Iterated]] to iteratorRecord.
|
||||
wrapper.Iterated = iteratorRecord;
|
||||
// 6. Return wrapper.
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.concat */
|
||||
function* Iterator_concat(items: Arguments): ValueEvaluator {
|
||||
const iterables: { OpenMethod: FunctionObject, Iterable: ObjectValue }[] = [];
|
||||
for (const item of items.values()) {
|
||||
if (!(item instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', item);
|
||||
}
|
||||
const method = Q(yield* GetMethod(item, wellKnownSymbols.iterator));
|
||||
if (method instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotIterable', item);
|
||||
}
|
||||
iterables.push({ OpenMethod: method, Iterable: item });
|
||||
}
|
||||
const gen = CreateIteratorFromClosure(function* Iterator_concat(): YieldEvaluator {
|
||||
for (const iterable of iterables) {
|
||||
const iter = Q(yield* Call(iterable.OpenMethod, iterable.Iterable));
|
||||
if (!(iter instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotIterable', iter);
|
||||
}
|
||||
const iteratorRecord = Q(yield* GetIteratorDirect(iter));
|
||||
let innerAlive = true;
|
||||
while (innerAlive) {
|
||||
const innerValue = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
if (innerValue === 'done') {
|
||||
innerAlive = false;
|
||||
} else {
|
||||
const completion = yield* Yield(innerValue);
|
||||
if (completion instanceof AbruptCompletion) {
|
||||
return Q(yield* IteratorClose(iteratorRecord, completion));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Value.undefined;
|
||||
}, Value('Iterator Helper'), surroundingAgent.intrinsic('%IteratorHelperPrototype%'), ['UnderlyingIterators']);
|
||||
gen.UnderlyingIterators = [];
|
||||
return gen;
|
||||
}
|
||||
|
||||
export function bootstrapIterator(realmRec: Realm) {
|
||||
const cons = bootstrapConstructor(realmRec, IteratorConstructor, 'Iterator', 0, realmRec.Intrinsics['%Iterator.prototype%'], [
|
||||
['from', Iterator_from, 1],
|
||||
['concat', Iterator_concat, 0],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Iterator%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import {
|
||||
NormalCompletion, Q, ReturnCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import {
|
||||
Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateIteratorResultObject,
|
||||
GeneratorResume,
|
||||
GeneratorResumeAbrupt,
|
||||
IteratorCloseAll,
|
||||
Realm,
|
||||
RequireInternalSlot,
|
||||
type GeneratorObject,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%iteratorhelperprototype%.next */
|
||||
function* IteratorHelperPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Return ? GeneratorResume(this value, undefined, "Iterator Helper").
|
||||
return Q(yield* GeneratorResume(thisValue, Value.undefined, Value('Iterator Helper')));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%iteratorhelperprototype%.return */
|
||||
function* IteratorHelperPrototype_return(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be this value.
|
||||
const O = thisValue;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[UnderlyingIterators]]).
|
||||
Q(RequireInternalSlot(O, 'UnderlyingIterators'));
|
||||
// 3. Assert: O has a [[GeneratorState]] internal slot.
|
||||
Assert('GeneratorState' in O);
|
||||
|
||||
// 4. If O.[[GeneratorState]] is suspended-start, then
|
||||
if (O.GeneratorState === 'suspendedStart') {
|
||||
// a. Set O.[[GeneratorState]] to completed.
|
||||
O.GeneratorState = 'completed';
|
||||
|
||||
// b. NOTE: Once a generator enters the completed state it never leaves it and its associated execution context is never resumed.
|
||||
// Any execution state associated with O can be discarded at this point.
|
||||
// c. Perform ? IteratorCloseAll(O.[[UnderlyingIterators]], NormalCompletion(unused)).
|
||||
Q(yield* IteratorCloseAll((O as GeneratorObject).UnderlyingIterators!, NormalCompletion(undefined)));
|
||||
|
||||
// d. Return CreateIteratorResultObject(undefined, true).
|
||||
return CreateIteratorResultObject(Value.undefined, Value.true);
|
||||
}
|
||||
|
||||
// 5. Let C be ReturnCompletion(undefined).
|
||||
const C = ReturnCompletion(Value.undefined);
|
||||
// 6. Return ? GeneratorResumeAbrupt(O, C, "Iterator Helper").
|
||||
return Q(yield* GeneratorResumeAbrupt(O, C, Value('Iterator Helper')));
|
||||
}
|
||||
|
||||
export function bootstrapIteratorHelperPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', IteratorHelperPrototype_next, 0],
|
||||
['return', IteratorHelperPrototype_return, 0],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%'], 'Iterator Helper');
|
||||
|
||||
realmRec.Intrinsics['%IteratorHelperPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,751 @@
|
||||
import {
|
||||
AbruptCompletion,
|
||||
EnsureCompletion,
|
||||
IfAbruptCloseIterator,
|
||||
NormalCompletion,
|
||||
Q,
|
||||
ReturnCompletion,
|
||||
X,
|
||||
type PlainCompletion,
|
||||
type ValueCompletion,
|
||||
type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__, type Mutable } from '../helpers.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
BooleanValue,
|
||||
NumberValue,
|
||||
ObjectValue,
|
||||
UndefinedValue,
|
||||
Value, wellKnownSymbols, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
Call,
|
||||
CreateArrayFromList,
|
||||
CreateIteratorFromClosure,
|
||||
GetIteratorDirect,
|
||||
GetIteratorFlattenable,
|
||||
IsCallable,
|
||||
IteratorClose,
|
||||
IteratorStep,
|
||||
IteratorStepValue,
|
||||
SetterThatIgnoresPrototypeProperties,
|
||||
ToBoolean,
|
||||
ToIntegerOrInfinity,
|
||||
ToNumber,
|
||||
ToString,
|
||||
Yield,
|
||||
type GeneratorObject,
|
||||
type IteratorRecord,
|
||||
type Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-get-iterator.prototype.constructor */
|
||||
function IteratorProto_constructorGetter() {
|
||||
// 1. Return %Iterator%.
|
||||
return surroundingAgent.intrinsic('%Iterator%');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-set-iterator.prototype.constructor */
|
||||
function* IteratorProto_constructorSetter([v = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator<UndefinedValue> {
|
||||
// 1. Perform ? SetterThatIgnoresPrototypeProperties(this value, %Iterator.prototype%, "constructor", v).
|
||||
Q(yield* SetterThatIgnoresPrototypeProperties(
|
||||
thisValue,
|
||||
surroundingAgent.intrinsic('%Iterator.prototype%'),
|
||||
Value('constructor'),
|
||||
v,
|
||||
));
|
||||
// 2. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.drop */
|
||||
function* IteratorPrototype_drop([limit = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. Let numLimit be Completion(ToNumber(limit)).
|
||||
const numLimit: ValueCompletion<NumberValue> = EnsureCompletion(yield* ToNumber(limit));
|
||||
// 5. IfAbruptCloseIterator(numLimit, iterated).
|
||||
IfAbruptCloseIterator(numLimit, iterated);
|
||||
__ts_cast__<NumberValue>(numLimit);
|
||||
// 6. If numLimit is NaN, then
|
||||
if (numLimit.isNaN()) {
|
||||
// a. Let error be ThrowCompletion(a newly created RangeError object).
|
||||
const error = surroundingAgent.Throw('RangeError', 'OutOfRange', numLimit);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 7. Let integerLimit be ! ToIntegerOrInfinity(numLimit).
|
||||
const integerLimit: number = X(yield* ToIntegerOrInfinity(numLimit instanceof NormalCompletion ? numLimit.Value : numLimit));
|
||||
// 8. If integerLimit < 0, then
|
||||
if (integerLimit < 0) {
|
||||
// a. Let error be ThrowCompletion(a newly created RangeError object).
|
||||
const error = surroundingAgent.Throw('RangeError', 'OutOfRange', numLimit);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 9. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 10. Let closure be a new Abstract Closure with no parameters that captures iterated and integerLimit and performs the following steps when called:
|
||||
const closure = function* closure() {
|
||||
// a. Let remaining be integerLimit.
|
||||
let remaining: number = integerLimit;
|
||||
// b. Repeat, while remaining > 0,
|
||||
while (remaining > 0) {
|
||||
// i. If remaining ≠ +∞, then
|
||||
if (remaining !== +Infinity) {
|
||||
// 1. Set remaining to remaining - 1.
|
||||
remaining -= 1;
|
||||
}
|
||||
// ii. Let next be ? IteratorStep(iterated).
|
||||
const next = Q(yield* IteratorStep(iterated));
|
||||
// iii. If next is done, return ReturnCompletion(undefined).
|
||||
if (next === 'done') {
|
||||
return ReturnCompletion(Value.undefined);
|
||||
}
|
||||
}
|
||||
// c. Repeat,
|
||||
while (true) {
|
||||
// i. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// ii. If value is done, return ReturnCompletion(undefined).
|
||||
if (value === 'done') {
|
||||
return ReturnCompletion(Value.undefined);
|
||||
}
|
||||
// iii. Let completion be Completion(Yield(value)).
|
||||
const completion = EnsureCompletion(yield* Yield(value));
|
||||
// iv. IfAbruptCloseIterator(completion, iterated).
|
||||
IfAbruptCloseIterator(completion, iterated);
|
||||
}
|
||||
};
|
||||
// 11. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
|
||||
const result: Mutable<GeneratorObject> = CreateIteratorFromClosure(
|
||||
closure,
|
||||
Value('Iterator Helper'),
|
||||
surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'],
|
||||
['UnderlyingIterators'],
|
||||
);
|
||||
// 12. Set result.[[UnderlyingIterators]] to iterated.
|
||||
result.UnderlyingIterators = [iterated];
|
||||
// 13. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.every */
|
||||
function* IteratorPrototype_every([predicate = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. If IsCallable(predicate) is false, then
|
||||
if (IsCallable(predicate) === false) {
|
||||
// a. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAFunction', predicate);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 5. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 6. Let counter be 0.
|
||||
let counter = 0;
|
||||
// 7. Repeat,
|
||||
while (true) {
|
||||
// a. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// b. If value is done, return true.
|
||||
if (value === 'done') {
|
||||
return Value.true;
|
||||
}
|
||||
// c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)).
|
||||
const result: ValueCompletion = yield* Call(predicate, Value.undefined, [value, Value(counter)]);
|
||||
// d. IfAbruptCloseIterator(result, iterated).
|
||||
IfAbruptCloseIterator(result, iterated);
|
||||
__ts_cast__<BooleanValue>(result);
|
||||
// e. If ToBoolean(result) is false, return ? IteratorClose(iterated, NormalCompletion(false)).
|
||||
if (ToBoolean(result) === Value.false) {
|
||||
return Q(yield* IteratorClose(iterated, EnsureCompletion(Value.false)));
|
||||
}
|
||||
// f. Set counter to counter + 1.
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.filter */
|
||||
function* IteratorPrototype_filter([predicate = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. If IsCallable(predicate) is false, then
|
||||
if (IsCallable(predicate) === false) {
|
||||
// a. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAFunction', predicate);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 5. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 6. Let closure be a new Abstract Closure with no parameters that captures iterated and predicate and performs the following steps when called:
|
||||
const closure = function* closure() {
|
||||
// a. Let counter be 0.
|
||||
let counter = 0;
|
||||
// b. Repeat,
|
||||
while (true) {
|
||||
// i. Let value be ? IteratorStepValue(iterated).
|
||||
const value = Q(yield* IteratorStepValue(iterated));
|
||||
// ii. If value is done, return ReturnCompletion(undefined).
|
||||
if (value === 'done') {
|
||||
return ReturnCompletion(Value.undefined);
|
||||
}
|
||||
// iii. Let selected be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)).
|
||||
const selected: ValueCompletion = yield* Call(predicate, Value.undefined, [value, Value(counter)]);
|
||||
// iv. IfAbruptCloseIterator(selected, iterated).
|
||||
IfAbruptCloseIterator(selected, iterated);
|
||||
// v. If ToBoolean(selected) is true, then
|
||||
__ts_cast__<BooleanValue>(selected);
|
||||
if (ToBoolean(selected) === Value.true) {
|
||||
// 1. Let completion be Completion(Yield(value)).
|
||||
const completion = EnsureCompletion(yield* Yield(value));
|
||||
// 2. IfAbruptCloseIterator(completion, iterated).
|
||||
IfAbruptCloseIterator(completion, iterated);
|
||||
}
|
||||
// vi. Set counter to counter + 1.
|
||||
counter += 1;
|
||||
}
|
||||
};
|
||||
// 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
|
||||
const result = CreateIteratorFromClosure(
|
||||
closure,
|
||||
Value('Iterator Helper'),
|
||||
surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'],
|
||||
['UnderlyingIterators'],
|
||||
);
|
||||
// 8. Set result.[[UnderlyingIterators]] to iterated.
|
||||
result.UnderlyingIterators = [iterated];
|
||||
// 9. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.find */
|
||||
function* IteratorPrototype_find([predicate = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. If IsCallable(predicate) is false, then
|
||||
if (IsCallable(predicate) === false) {
|
||||
// a. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAFunction', predicate);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 5. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 6. Let counter be 0.
|
||||
let counter = 0;
|
||||
// 7. Repeat,
|
||||
while (true) {
|
||||
// a. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// b. If value is done, return undefined.
|
||||
if (value === 'done') {
|
||||
return Value.undefined;
|
||||
}
|
||||
// c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)).
|
||||
const result: ValueCompletion = yield* Call(predicate, Value.undefined, [value, Value(counter)]);
|
||||
// d. IfAbruptCloseIterator(result, iterated).
|
||||
IfAbruptCloseIterator(result, iterated);
|
||||
// e. If ToBoolean(result) is true, return ? IteratorClose(iterated, NormalCompletion(value)).
|
||||
__ts_cast__<BooleanValue>(result);
|
||||
if (ToBoolean(result) === Value.true) {
|
||||
return Q(yield* IteratorClose(iterated, EnsureCompletion(value)));
|
||||
}
|
||||
// f. Set counter to counter + 1.
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.flatmap */
|
||||
function* IteratorPrototype_flatMap([mapper = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. If IsCallable(mapper) is false, then
|
||||
if (IsCallable(mapper) === false) {
|
||||
// a. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAFunction', mapper);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 5. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 6. Let closure be a new Abstract Closure with no parameters that captures iterated and mapper and performs the following steps when called:
|
||||
const closure = function* closure() {
|
||||
// a. Let counter be 0.
|
||||
let counter = 0;
|
||||
// b. Repeat,
|
||||
while (true) {
|
||||
// i. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// ii. If value is done, return ReturnCompletion(undefined).
|
||||
if (value === 'done') {
|
||||
return ReturnCompletion(Value.undefined);
|
||||
}
|
||||
// iii. Let mapped be Completion(Call(mapper, undefined, « value, 𝔽(counter) »)).
|
||||
const mapped: ValueCompletion = EnsureCompletion(yield* Call(mapper, Value.undefined, [value, Value(counter)]));
|
||||
// iv. IfAbruptCloseIterator(mapped, iterated).
|
||||
IfAbruptCloseIterator(mapped, iterated);
|
||||
__ts_cast__<Value>(mapped);
|
||||
// v. Let innerIterator be Completion(GetIteratorFlattenable(mapped, reject-primitives)).
|
||||
const innerIterator: PlainCompletion<IteratorRecord> = EnsureCompletion(yield* GetIteratorFlattenable(mapped, 'reject-primitives'));
|
||||
// vi. IfAbruptCloseIterator(innerIterator, iterated).
|
||||
IfAbruptCloseIterator(innerIterator, iterated);
|
||||
__ts_cast__<IteratorRecord>(innerIterator);
|
||||
// vii. Let innerAlive be true.
|
||||
let innerAlive = true;
|
||||
// viii. Repeat, while innerAlive is true,
|
||||
while (innerAlive) {
|
||||
// 1. Let innerValue be Completion(IteratorStepValue(innerIterator)).
|
||||
const innerValue: PlainCompletion<Value | 'done'> = yield* IteratorStepValue(innerIterator);
|
||||
// 2. IfAbruptCloseIterator(innerValue, iterated).
|
||||
IfAbruptCloseIterator(innerValue, iterated);
|
||||
__ts_cast__<Value | 'done'>(innerValue);
|
||||
// 3. If innerValue is done, then
|
||||
if (innerValue === 'done') {
|
||||
// a. Set innerAlive to false.
|
||||
innerAlive = false;
|
||||
// 4. Else,
|
||||
} else {
|
||||
// a. Let completion be Completion(Yield(innerValue)).
|
||||
const completion = EnsureCompletion(yield* Yield(innerValue));
|
||||
// b. If completion is an abrupt completion, then
|
||||
if (completion instanceof AbruptCompletion) {
|
||||
// i. Let backupCompletion be Completion(IteratorClose(innerIterator, completion)).
|
||||
const backupCompletion = EnsureCompletion(yield* IteratorClose(innerIterator, completion));
|
||||
// ii. IfAbruptCloseIterator(backupCompletion, iterated).
|
||||
IfAbruptCloseIterator(backupCompletion, iterated);
|
||||
// iii. Return ? IteratorClose(iterated, completion).
|
||||
return Q(yield* IteratorClose(iterated, completion));
|
||||
}
|
||||
}
|
||||
}
|
||||
// ix. Set counter to counter + 1.
|
||||
counter += 1;
|
||||
}
|
||||
};
|
||||
|
||||
// 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
|
||||
const result = CreateIteratorFromClosure(
|
||||
closure,
|
||||
Value('Iterator Helper'),
|
||||
surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'],
|
||||
['UnderlyingIterators'],
|
||||
);
|
||||
// 8. Set result.[[UnderlyingIterators]] to iterated.
|
||||
result.UnderlyingIterators = [iterated];
|
||||
// 9. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.foreach */
|
||||
function* IteratorPrototype_forEach([procedure = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. If IsCallable(procedure) is false, then
|
||||
if (IsCallable(procedure) === false) {
|
||||
// a. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAFunction', procedure);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 5. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 6. Let counter be 0.
|
||||
let counter = 0;
|
||||
// 7. Repeat,
|
||||
while (true) {
|
||||
// a. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// b. If value is done, return undefined.
|
||||
if (value === 'done') {
|
||||
return Value.undefined;
|
||||
}
|
||||
// c. Let result be Completion(Call(procedure, undefined, « value, 𝔽(counter) »)).
|
||||
const result: ValueCompletion = yield* Call(procedure, Value.undefined, [value, Value(counter)]);
|
||||
// d. IfAbruptCloseIterator(result, iterated).
|
||||
IfAbruptCloseIterator(result, iterated);
|
||||
// e. Set counter to counter + 1.
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-iterator.prototype-%symbol.iterator% */
|
||||
function IteratorPrototype_iterator(_args: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Return the this value.
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.map */
|
||||
function* IteratorPrototype_map([mapper = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. If IsCallable(mapper) is false, then
|
||||
if (IsCallable(mapper) === false) {
|
||||
// a. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAFunction', mapper);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 5. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 6. Let closure be a new Abstract Closure with no parameters that captures iterated and mapper and performs the following steps when called:
|
||||
const closure = function* closure() {
|
||||
// a. Let counter be 0.
|
||||
let counter = 0;
|
||||
// b. Repeat,
|
||||
while (true) {
|
||||
// i. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// ii. If value is done, return ReturnCompletion(undefined).
|
||||
if (value === 'done') {
|
||||
return ReturnCompletion(Value.undefined);
|
||||
}
|
||||
// iii. Let mapped be Completion(Call(mapper, undefined, « value, 𝔽(counter) »)).
|
||||
const mapped: ValueCompletion = yield* Call(mapper, Value.undefined, [value, Value(counter)]);
|
||||
// iv. IfAbruptCloseIterator(mapped, iterated).
|
||||
IfAbruptCloseIterator(mapped, iterated);
|
||||
// v. Let completion be Completion(Yield(mapped)).
|
||||
__ts_cast__<Value>(mapped);
|
||||
const completion = EnsureCompletion(yield* Yield(mapped));
|
||||
// vi. IfAbruptCloseIterator(completion, iterated).
|
||||
IfAbruptCloseIterator(completion, iterated);
|
||||
// vii. Set counter to counter + 1.
|
||||
counter += 1;
|
||||
}
|
||||
};
|
||||
// 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
|
||||
const result = CreateIteratorFromClosure(
|
||||
closure,
|
||||
Value('Iterator Helper'),
|
||||
surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'],
|
||||
['UnderlyingIterators'],
|
||||
);
|
||||
// 8. Set result.[[UnderlyingIterators]] to [iterated].
|
||||
result.UnderlyingIterators = [iterated];
|
||||
// 9. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.reduce */
|
||||
function* IteratorPrototype_reduce(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. If IsCallable(reducer) is false, then
|
||||
const reducer = args[0] ?? Value.undefined;
|
||||
if (IsCallable(reducer) === false) {
|
||||
// a. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAFunction', reducer);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 5. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 6. If initialValue is not present, then
|
||||
let accumulator: Value | 'done';
|
||||
let counter: number;
|
||||
if (args.length < 2) {
|
||||
// a. Let accumulator be ? IteratorStepValue(iterated).
|
||||
accumulator = Q(yield* IteratorStepValue(iterated));
|
||||
// b. If accumulator is done, throw a TypeError exception.
|
||||
if (accumulator === 'done') {
|
||||
return surroundingAgent.Throw('TypeError', 'IteratorCompleted');
|
||||
}
|
||||
// c. Let counter be 1.
|
||||
counter = 1;
|
||||
} else {
|
||||
// 7. Else,
|
||||
// a. Let accumulator be initialValue.
|
||||
// b. Let counter be 0.
|
||||
accumulator = args[1] ?? Value.undefined;
|
||||
counter = 0;
|
||||
}
|
||||
// 8. Repeat,
|
||||
while (true) {
|
||||
// a. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// b. If value is done, return accumulator.
|
||||
if (value === 'done') {
|
||||
return accumulator;
|
||||
}
|
||||
// c. Let result be Completion(Call(reducer, undefined, « accumulator, value, 𝔽(counter) »)).
|
||||
const result: ValueCompletion = yield* Call(reducer, Value.undefined, [accumulator, value, Value(counter)]);
|
||||
// d. IfAbruptCloseIterator(result, iterated).
|
||||
IfAbruptCloseIterator(result, iterated);
|
||||
// e. Set accumulator to result.
|
||||
__ts_cast__<Value>(result);
|
||||
accumulator = result;
|
||||
// f. Set counter to counter + 1.
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.some */
|
||||
function* IteratorPrototype_some([predicate = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. If IsCallable(predicate) is false, then
|
||||
if (IsCallable(predicate) === false) {
|
||||
// a. Let error be ThrowCompletion(a newly created TypeError object).
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAFunction', predicate);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 5. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 6. Let counter be 0.
|
||||
let counter = 0;
|
||||
// 7. Repeat,
|
||||
while (true) {
|
||||
// a. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// b. If value is done, return false.
|
||||
if (value === 'done') {
|
||||
return Value.false;
|
||||
}
|
||||
// c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)).
|
||||
const result: ValueCompletion = yield* Call(predicate, Value.undefined, [value, Value(counter)]);
|
||||
// d. IfAbruptCloseIterator(result, iterated).
|
||||
IfAbruptCloseIterator(result, iterated);
|
||||
__ts_cast__<BooleanValue>(result);
|
||||
// e. If ToBoolean(result) is true, return ? IteratorClose(iterated, NormalCompletion(true)).
|
||||
if (ToBoolean(result) === Value.true) {
|
||||
return Q(yield* IteratorClose(iterated, EnsureCompletion(Value.true)));
|
||||
}
|
||||
// f. Set counter to counter + 1.
|
||||
counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.take */
|
||||
function* IteratorPrototype_take([limit = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
// 4. Let numLimit be Completion(ToNumber(limit)).
|
||||
const numLimit: ValueCompletion<NumberValue> = yield* ToNumber(limit);
|
||||
// 5. IfAbruptCloseIterator(numLimit, iterated).
|
||||
IfAbruptCloseIterator(numLimit, iterated);
|
||||
__ts_cast__<Value>(numLimit);
|
||||
// 6. If numLimit is NaN, then
|
||||
if (numLimit.isNaN()) {
|
||||
// a. Let error be ThrowCompletion(a newly created RangeError object).
|
||||
const error = surroundingAgent.Throw('RangeError', 'OutOfRange', numLimit);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 7. Let integerLimit be ! ToIntegerOrInfinity(numLimit).
|
||||
const integerLimit: number = X(yield* ToIntegerOrInfinity(numLimit instanceof NormalCompletion ? numLimit.Value : numLimit));
|
||||
// 8. If integerLimit < 0, then
|
||||
if (integerLimit < 0) {
|
||||
// a. Let error be ThrowCompletion(a newly created RangeError object).
|
||||
const error = surroundingAgent.Throw('RangeError', 'OutOfRange', numLimit);
|
||||
// b. Return ? IteratorClose(iterated, error).
|
||||
return Q(yield* IteratorClose(iterated, error));
|
||||
}
|
||||
// 9. Set iterated to ? GetIteratorDirect(O).
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
// 10. Let closure be a new Abstract Closure with no parameters that captures iterated and integerLimit and performs the following steps when called:
|
||||
const closure = function* closure() {
|
||||
// a. Let remaining be integerLimit.
|
||||
let remaining: number = integerLimit;
|
||||
// b. Repeat,
|
||||
while (true) {
|
||||
// i. If remaining = 0, then
|
||||
// 1. Return ? IteratorClose(iterated, ReturnCompletion(undefined)).
|
||||
if (remaining === 0) {
|
||||
return Q(yield* IteratorClose(iterated, ReturnCompletion(Value.undefined)));
|
||||
}
|
||||
// ii. If remaining ≠ +∞, then
|
||||
// 1. Set remaining to remaining - 1.
|
||||
if (remaining !== +Infinity) {
|
||||
remaining -= 1;
|
||||
}
|
||||
// iii. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// iv. If value is done, return ReturnCompletion(undefined).
|
||||
if (value === 'done') {
|
||||
return ReturnCompletion(Value.undefined);
|
||||
}
|
||||
// v. Let completion be Completion(Yield(value)).
|
||||
const completion = EnsureCompletion(yield* Yield(value));
|
||||
// vi. IfAbruptCloseIterator(completion, iterated).
|
||||
IfAbruptCloseIterator(completion, iterated);
|
||||
}
|
||||
};
|
||||
// 11. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
|
||||
const result: Mutable<GeneratorObject> = CreateIteratorFromClosure(
|
||||
closure,
|
||||
Value('Iterator Helper'),
|
||||
surroundingAgent.currentRealmRecord.Intrinsics['%IteratorHelperPrototype%'],
|
||||
['UnderlyingIterators'],
|
||||
);
|
||||
// 12. Set result.[[UnderlyingIterators]] to iterated.
|
||||
result.UnderlyingIterators = [iterated];
|
||||
// 13. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-iterator.prototype.toarray */
|
||||
function* IteratorPrototype_toArray(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. If O is not an Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 3. Let iterated be ? GetIteratorDirect(O).
|
||||
const iterated: IteratorRecord = Q(yield* GetIteratorDirect(O));
|
||||
// 4. Let items be a new empty List.
|
||||
const items: Value[] = [];
|
||||
// 5. Repeat,
|
||||
while (true) {
|
||||
// a. Let value be ? IteratorStepValue(iterated).
|
||||
const value: Value | 'done' = Q(yield* IteratorStepValue(iterated));
|
||||
// b. If value is done, return CreateArrayFromList(items).
|
||||
if (value === 'done') {
|
||||
return CreateArrayFromList(items);
|
||||
}
|
||||
// c. Append value to items.
|
||||
items.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-get-iterator.prototype-%symbol.tostringtag% */
|
||||
function IteratorPrototype_toStringTagGetter() {
|
||||
return Value('Iterator');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-set-iterator.prototype-%symbol.tostringtag% */
|
||||
function* IteratorPrototype_toStringTagSetter([v = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator<UndefinedValue> {
|
||||
// 1. Perform ? SetterThatIgnoresPrototypeProperties(this value, %Iterator.prototype%, %Symbol.toStringTag%, v).
|
||||
Q(yield* SetterThatIgnoresPrototypeProperties(
|
||||
thisValue,
|
||||
surroundingAgent.intrinsic('%Iterator.prototype%'),
|
||||
wellKnownSymbols.toStringTag,
|
||||
v,
|
||||
));
|
||||
// 2. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-iterator-join/#sec-iterator.prototype.join */
|
||||
function* IteratorPrototype_join([separator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
let iterated: IteratorRecord = { Iterator: O, NextMethod: Value.undefined, Done: Value.false };
|
||||
let sep;
|
||||
if (separator === Value.undefined) {
|
||||
sep = ',';
|
||||
} else {
|
||||
const completion = yield* ToString(separator);
|
||||
IfAbruptCloseIterator(completion, iterated);
|
||||
sep = X(completion).stringValue();
|
||||
}
|
||||
iterated = Q(yield* GetIteratorDirect(O));
|
||||
let R = '';
|
||||
let first = true;
|
||||
while (true) {
|
||||
const value = Q(yield* IteratorStepValue(iterated));
|
||||
if (value === 'done') {
|
||||
return Value(R);
|
||||
}
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
R += sep;
|
||||
}
|
||||
if (value !== Value.undefined && value !== Value.null) {
|
||||
const S_completion = yield* ToString(value);
|
||||
IfAbruptCloseIterator(S_completion, iterated);
|
||||
const S = X(S_completion).stringValue();
|
||||
R += S;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function bootstrapIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['constructor', [IteratorProto_constructorGetter, IteratorProto_constructorSetter]],
|
||||
['drop', IteratorPrototype_drop, 1],
|
||||
['every', IteratorPrototype_every, 1],
|
||||
['filter', IteratorPrototype_filter, 1],
|
||||
['find', IteratorPrototype_find, 1],
|
||||
['flatMap', IteratorPrototype_flatMap, 1],
|
||||
['forEach', IteratorPrototype_forEach, 1],
|
||||
['map', IteratorPrototype_map, 1],
|
||||
['reduce', IteratorPrototype_reduce, 1],
|
||||
['some', IteratorPrototype_some, 1],
|
||||
['take', IteratorPrototype_take, 1],
|
||||
['toArray', IteratorPrototype_toArray, 0],
|
||||
[wellKnownSymbols.iterator, IteratorPrototype_iterator, 0],
|
||||
[wellKnownSymbols.toStringTag, [IteratorPrototype_toStringTagGetter, IteratorPrototype_toStringTagSetter]],
|
||||
surroundingAgent.feature('iterator.join') ? ['join', IteratorPrototype_join, 1] : undefined,
|
||||
], realmRec.Intrinsics['%Object.prototype%']);
|
||||
|
||||
realmRec.Intrinsics['%Iterator.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
BooleanValue,
|
||||
NullValue,
|
||||
NumberValue,
|
||||
ObjectValue,
|
||||
JSStringValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
CodePointsToString,
|
||||
PropName,
|
||||
UTF16EncodeCodePoint,
|
||||
} from '../static-semantics/all.mts';
|
||||
import {
|
||||
NormalCompletion,
|
||||
Q, X,
|
||||
} from '../completion.mts';
|
||||
import { isArray, JSStringSet, kInternal } from '../helpers.mts';
|
||||
import {
|
||||
BigIntValue, F, ParseScript, Realm, ScriptEvaluation, ThrowCompletion, skipDebugger, type Arguments,
|
||||
type CodePoint,
|
||||
type FunctionObject,
|
||||
type PlainCompletion,
|
||||
isLeadingSurrogate,
|
||||
isTrailingSurrogate,
|
||||
type ParseNode,
|
||||
type BuiltinFunctionObject,
|
||||
SetIntegrityLevel,
|
||||
SameValue,
|
||||
type PropertyKeyValue,
|
||||
} from '../index.mts';
|
||||
import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts';
|
||||
import {
|
||||
ArrayLiteralContentNodes, avoid_using_children, Contains, PropertyDefinitionNodes,
|
||||
} from '../parser/utils.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { isBooleanObject } from './Boolean.mts';
|
||||
import { isBigIntObject } from './BigInt.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
CreateDataProperty,
|
||||
CreateDataPropertyOrThrow,
|
||||
EnumerableOwnProperties,
|
||||
Get,
|
||||
GetV,
|
||||
IsArray,
|
||||
IsCallable,
|
||||
OrdinaryObjectCreate,
|
||||
LengthOfArrayLike,
|
||||
ToIntegerOrInfinity,
|
||||
ToNumber,
|
||||
ToString,
|
||||
} from '#self';
|
||||
|
||||
const WHITESPACE = [' ', '\t', '\r', '\n'];
|
||||
const NUMERIC = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
const VALID_HEX = [...NUMERIC, 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b', 'c', 'd', 'e', 'f'];
|
||||
const ESCAPABLE = ['"', '\\', '/', 'b', 'f', 'n', 'r', 't'];
|
||||
|
||||
class JSONValidator {
|
||||
input;
|
||||
|
||||
pos = 0;
|
||||
|
||||
char: string | null;
|
||||
|
||||
constructor(input: string) {
|
||||
this.input = input;
|
||||
this.char = input.charAt(0);
|
||||
}
|
||||
|
||||
validate() {
|
||||
X(this.eatWhitespace());
|
||||
Q(this.parseValue());
|
||||
if (this.pos < this.input.length) {
|
||||
return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedToken');
|
||||
}
|
||||
return NormalCompletion(undefined);
|
||||
}
|
||||
|
||||
advance() {
|
||||
this.pos += 1;
|
||||
if (this.pos === this.input.length) {
|
||||
this.char = null;
|
||||
} else if (this.pos > this.input.length) {
|
||||
return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedToken');
|
||||
} else {
|
||||
this.char = this.input.charAt(this.pos);
|
||||
}
|
||||
return this.char;
|
||||
}
|
||||
|
||||
eatWhitespace() {
|
||||
while (this.eat(WHITESPACE)) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
eat(c: string | readonly string[]) {
|
||||
if (Array.isArray(c) && c.includes(this.char)) {
|
||||
X(this.advance());
|
||||
return true;
|
||||
} else if (this.char === c) {
|
||||
X(this.advance());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
expect(c: string | readonly string[]) {
|
||||
const { char } = this;
|
||||
if (!this.eat(c)) {
|
||||
return surroundingAgent.Throw('SyntaxError', 'JSONExpected', c, this.char);
|
||||
}
|
||||
return char;
|
||||
}
|
||||
|
||||
parseValue() {
|
||||
switch (this.char) {
|
||||
case '"':
|
||||
return Q(this.parseString());
|
||||
case '{':
|
||||
return Q(this.parseObject());
|
||||
case '[':
|
||||
return Q(this.parseArray());
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-':
|
||||
return Q(this.parseNumber());
|
||||
case 'f':
|
||||
X(this.expect('f'));
|
||||
Q(this.expect('a'));
|
||||
Q(this.expect('l'));
|
||||
Q(this.expect('s'));
|
||||
Q(this.expect('e'));
|
||||
return X(this.eatWhitespace());
|
||||
case 't':
|
||||
X(this.expect('t'));
|
||||
Q(this.expect('r'));
|
||||
Q(this.expect('u'));
|
||||
Q(this.expect('e'));
|
||||
return X(this.eatWhitespace());
|
||||
case 'n':
|
||||
X(this.expect('n'));
|
||||
Q(this.expect('u'));
|
||||
Q(this.expect('l'));
|
||||
Q(this.expect('l'));
|
||||
return X(this.eatWhitespace());
|
||||
default:
|
||||
return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedChar', this.char);
|
||||
}
|
||||
}
|
||||
|
||||
parseString() {
|
||||
Q(this.expect('"'));
|
||||
while (!this.eat('"')) {
|
||||
if (this.eat('\\')) {
|
||||
if (!this.eat(ESCAPABLE)) {
|
||||
Q(this.expect('u'));
|
||||
Q(this.expect(VALID_HEX));
|
||||
Q(this.expect(VALID_HEX));
|
||||
Q(this.expect(VALID_HEX));
|
||||
Q(this.expect(VALID_HEX));
|
||||
}
|
||||
} else {
|
||||
if (this.char! < ' ') {
|
||||
return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedChar', this.char);
|
||||
}
|
||||
Q(this.advance());
|
||||
}
|
||||
}
|
||||
return X(this.eatWhitespace());
|
||||
}
|
||||
|
||||
parseNumber(): PlainCompletion<void> {
|
||||
this.eat('-');
|
||||
if (!this.eat('0')) {
|
||||
Q(this.expect(NUMERIC));
|
||||
while (this.eat(NUMERIC)) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
if (this.eat('.')) {
|
||||
Q(this.expect(NUMERIC));
|
||||
while (this.eat(NUMERIC)) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
if (this.eat(['e', 'E'])) {
|
||||
this.eat(['-', '+']);
|
||||
Q(this.expect(NUMERIC));
|
||||
while (this.eat(NUMERIC)) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
X(this.eatWhitespace());
|
||||
}
|
||||
|
||||
parseObject(): PlainCompletion<void> {
|
||||
Q(this.expect('{'));
|
||||
X(this.eatWhitespace());
|
||||
let first = true;
|
||||
while (!this.eat('}')) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
Q(this.expect(','));
|
||||
X(this.eatWhitespace());
|
||||
}
|
||||
Q(this.parseString());
|
||||
X(this.eatWhitespace());
|
||||
Q(this.expect(':'));
|
||||
X(this.eatWhitespace());
|
||||
Q(this.parseValue());
|
||||
X(this.eatWhitespace());
|
||||
}
|
||||
X(this.eatWhitespace());
|
||||
}
|
||||
|
||||
parseArray(): PlainCompletion<void> {
|
||||
Q(this.expect('['));
|
||||
X(this.eatWhitespace());
|
||||
let first = true;
|
||||
while (!this.eat(']')) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
Q(this.expect(','));
|
||||
X(this.eatWhitespace());
|
||||
}
|
||||
Q(this.parseValue());
|
||||
X(this.eatWhitespace());
|
||||
}
|
||||
X(this.eatWhitespace());
|
||||
}
|
||||
|
||||
static validate(input: string) {
|
||||
const v = new JSONValidator(input);
|
||||
return v.validate();
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/pr/3714/#sec-json-parse-record */
|
||||
interface JSONParseRecord {
|
||||
readonly ParseNode: ParseNode;
|
||||
readonly Key: PropertyKeyValue;
|
||||
readonly Value: Value;
|
||||
readonly Elements: readonly JSONParseRecord[];
|
||||
readonly Entries: readonly JSONParseRecord[];
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/pr/3714/#sec-internalizejsonproperty */
|
||||
function* InternalizeJSONProperty(holder: ObjectValue, name: JSStringValue, reviver: Value, parseRecord: JSONParseRecord | undefined): ValueEvaluator {
|
||||
const val = Q(yield* Get(holder, name));
|
||||
const context = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
let elementRecords: readonly JSONParseRecord[];
|
||||
let entryRecords: readonly JSONParseRecord[];
|
||||
if (parseRecord && SameValue(parseRecord.Value, val) === Value.true) {
|
||||
if (!(val instanceof ObjectValue)) {
|
||||
const parseNode = parseRecord.ParseNode;
|
||||
Assert(parseNode.type !== 'ArrayLiteral' && parseNode.type !== 'ObjectLiteral');
|
||||
const sourceText = parseNode.sourceText;
|
||||
X(CreateDataPropertyOrThrow(context, Value('source'), Value(CodePointsToString(sourceText))));
|
||||
}
|
||||
elementRecords = parseRecord.Elements;
|
||||
entryRecords = parseRecord.Entries;
|
||||
} else {
|
||||
elementRecords = [];
|
||||
entryRecords = [];
|
||||
}
|
||||
if (val instanceof ObjectValue) {
|
||||
const isArray = Q(IsArray(val));
|
||||
if (isArray === Value.true) {
|
||||
// Let _elementRecordsLen_ be the number of elements in _elementRecords_.
|
||||
const elementRecordsLen = elementRecords.length;
|
||||
let I = 0;
|
||||
const len = Q(yield* LengthOfArrayLike(val));
|
||||
while (I < len) {
|
||||
const prop = X(ToString(F(I)));
|
||||
const elementRecord = I < elementRecordsLen ? elementRecords[I] : undefined;
|
||||
const newElement = Q(yield* InternalizeJSONProperty(val, prop, reviver, elementRecord));
|
||||
if (newElement instanceof UndefinedValue) {
|
||||
Q(yield* val.Delete(prop));
|
||||
} else {
|
||||
Q(yield* CreateDataProperty(val, prop, newElement));
|
||||
}
|
||||
I += 1;
|
||||
}
|
||||
} else {
|
||||
const keys = Q(yield* EnumerableOwnProperties(val, 'key'));
|
||||
for (const P of keys) {
|
||||
const entryRecord = entryRecords.find((record) => SameValue(record.Key, P) === Value.true);
|
||||
const newElement = Q(yield* InternalizeJSONProperty(val, P, reviver, entryRecord));
|
||||
// const newElement = Q(yield* InternalizeJSONProperty(val, P, reviver));
|
||||
if (newElement instanceof UndefinedValue) {
|
||||
Q(yield* val.Delete(P));
|
||||
} else {
|
||||
Q(yield* CreateDataProperty(val, P, newElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Q(yield* Call(reviver, holder, [name, val, context]));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/pr/3714/#sec-createjsonparserecord */
|
||||
function CreateJSONParseRecord(parseNode: ParseNode, key: PropertyKeyValue, val: Value): JSONParseRecord {
|
||||
const typedValNode = ShallowestContainedJSONValue(parseNode);
|
||||
Assert(!!typedValNode);
|
||||
const elements = [];
|
||||
const entries = [];
|
||||
if (val instanceof ObjectValue) {
|
||||
const isArray = X(IsArray(val));
|
||||
if (isArray === Value.true) {
|
||||
Assert(typedValNode.type === 'ArrayLiteral');
|
||||
const contentNodes = ArrayLiteralContentNodes(typedValNode);
|
||||
const len = contentNodes.length;
|
||||
const valLen = X(LengthOfArrayLike(val));
|
||||
Assert(valLen === len);
|
||||
let I = 0;
|
||||
while (I < len) {
|
||||
const propName = X(ToString(F(I)));
|
||||
const elementParseRecord = CreateJSONParseRecord(contentNodes[I], propName, X(Get(val, propName)));
|
||||
elements.push(elementParseRecord);
|
||||
I += 1;
|
||||
}
|
||||
} else {
|
||||
Assert(typedValNode.type === 'ObjectLiteral');
|
||||
const propertyNodes = PropertyDefinitionNodes(typedValNode);
|
||||
const keys = X(EnumerableOwnProperties(val, 'key'));
|
||||
for (const P of keys) {
|
||||
let propertyDefinition: ParseNode;
|
||||
for (const propertyNode of propertyNodes) {
|
||||
const propName = PropName(propertyNode);
|
||||
if (propName === P.stringValue()) {
|
||||
propertyDefinition = propertyNode;
|
||||
}
|
||||
}
|
||||
Assert(!!(propertyDefinition!.type === 'PropertyDefinition' && propertyDefinition.PropertyName && propertyDefinition.AssignmentExpression));
|
||||
const propertyValueNode = propertyDefinition.AssignmentExpression;
|
||||
const entryParseRecord = CreateJSONParseRecord(propertyValueNode, P, X(Get(val, P)));
|
||||
entries.push(entryParseRecord);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Assert(typedValNode.type !== 'ArrayLiteral' && typedValNode.type !== 'ObjectLiteral');
|
||||
}
|
||||
return {
|
||||
ParseNode: typedValNode, Key: key, Value: val, Elements: elements, Entries: entries,
|
||||
};
|
||||
}
|
||||
|
||||
export function ParseJSON(text: string): PlainCompletion<{ ParseNode: ParseNode, Value: Value }> {
|
||||
// 1. If StringToCodePoints(text) is not a valid JSON text as specified in ECMA-404, throw a SyntaxError exception.
|
||||
Q(JSONValidator.validate(text));
|
||||
const scriptString = `(${text});`;
|
||||
const script = ParseScript(scriptString, surroundingAgent.currentRealmRecord, { [kInternal]: { json: true } });
|
||||
Assert(!isArray(script)); // array means parse error
|
||||
const result = X(skipDebugger(ScriptEvaluation(script)));
|
||||
Assert(result instanceof JSStringValue || result instanceof NumberValue || result instanceof BooleanValue || result instanceof ObjectValue || result === Value.null);
|
||||
return { ParseNode: script.ECMAScriptCode, Value: result };
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-json.parse */
|
||||
function* JSON_parse([text = Value.undefined, reviver = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const jsonString = Q(yield* ToString(text));
|
||||
const parseResult = Q(ParseJSON(jsonString.stringValue()));
|
||||
const unfiltered = parseResult.Value;
|
||||
Assert(unfiltered instanceof JSStringValue
|
||||
|| unfiltered instanceof NumberValue
|
||||
|| unfiltered instanceof BooleanValue
|
||||
|| unfiltered instanceof NullValue
|
||||
|| unfiltered instanceof ObjectValue);
|
||||
if (IsCallable(reviver)) {
|
||||
const root = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
const rootName = Value('');
|
||||
X(CreateDataPropertyOrThrow(root, rootName, unfiltered));
|
||||
const snapshot = CreateJSONParseRecord(parseResult.ParseNode, rootName, unfiltered);
|
||||
return Q(yield* InternalizeJSONProperty(root, rootName, reviver, snapshot));
|
||||
} else {
|
||||
return unfiltered;
|
||||
}
|
||||
}
|
||||
|
||||
const codeUnitTable = new Map([
|
||||
[0x0008, '\\b'],
|
||||
[0x0009, '\\t'],
|
||||
[0x000A, '\\n'],
|
||||
[0x000C, '\\f'],
|
||||
[0x000D, '\\r'],
|
||||
[0x0022, '\\"'],
|
||||
[0x005C, '\\\\'],
|
||||
]);
|
||||
|
||||
interface State {
|
||||
ReplacerFunction: ObjectValue | UndefinedValue;
|
||||
Stack: ObjectValue[];
|
||||
Indent: string;
|
||||
Gap: string;
|
||||
PropertyList: JSStringSet | UndefinedValue;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-serializejsonproperty */
|
||||
function* SerializeJSONProperty(state: State, key: JSStringValue, holder: ObjectValue): ValueEvaluator<JSStringValue | UndefinedValue> {
|
||||
let value = Q(yield* Get(holder, key)); // eslint-disable-line no-shadow
|
||||
if (value instanceof ObjectValue || value instanceof BigIntValue) {
|
||||
const toJSON = Q(yield* GetV(value, Value('toJSON')));
|
||||
if (IsCallable(toJSON)) {
|
||||
value = Q(yield* Call(toJSON, value, [key]));
|
||||
}
|
||||
}
|
||||
if (state.ReplacerFunction !== Value.undefined) {
|
||||
value = Q(yield* Call(state.ReplacerFunction, holder, [key, value]));
|
||||
}
|
||||
if (value instanceof ObjectValue) {
|
||||
if ('IsRawJSON' in value) {
|
||||
return X(Get(value, Value('rawJSON'))) as JSStringValue;
|
||||
}
|
||||
if ('NumberData' in value) {
|
||||
value = Q(yield* ToNumber(value));
|
||||
} else if ('StringData' in value) {
|
||||
value = Q(yield* ToString(value));
|
||||
} else if (isBooleanObject(value)) {
|
||||
value = value.BooleanData;
|
||||
} else if (isBigIntObject(value)) {
|
||||
value = value.BigIntData;
|
||||
}
|
||||
}
|
||||
if (value === Value.null) {
|
||||
return Value('null');
|
||||
}
|
||||
if (value === Value.true) {
|
||||
return Value('true');
|
||||
}
|
||||
if (value === Value.false) {
|
||||
return Value('false');
|
||||
}
|
||||
if (value instanceof JSStringValue) {
|
||||
return QuoteJSONString(value);
|
||||
}
|
||||
if (value instanceof NumberValue) {
|
||||
if (value.isFinite()) {
|
||||
return X(ToString(value));
|
||||
}
|
||||
return Value('null');
|
||||
}
|
||||
if (value instanceof BigIntValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'CannotJSONSerializeBigInt');
|
||||
}
|
||||
if (value instanceof ObjectValue && !IsCallable(value)) {
|
||||
const isArray = Q(IsArray(value));
|
||||
if (isArray === Value.true) {
|
||||
return Q(yield* SerializeJSONArray(state, value));
|
||||
}
|
||||
return Q(yield* SerializeJSONObject(state, value));
|
||||
}
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
export function UnicodeEscape(C: string) {
|
||||
const n = C.charCodeAt(0);
|
||||
Assert(n < 0xFFFF);
|
||||
return `\u005Cu${n.toString(16).padStart(4, '0')}`;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/pr/3714/#sec-quotejsonstring */
|
||||
function QuoteJSONString(value: JSStringValue) { // eslint-disable-line no-shadow
|
||||
let product = '\u0022';
|
||||
const cpList = [...value.stringValue()].map((c) => c.codePointAt(0)!);
|
||||
for (const C of cpList) {
|
||||
if (codeUnitTable.has(C)) {
|
||||
product = `${product}${codeUnitTable.get(C)}`;
|
||||
} else if (C < 0x0020 || isLeadingSurrogate(C) || isTrailingSurrogate(C)) {
|
||||
const unit = String.fromCodePoint(C);
|
||||
product += UnicodeEscape(unit);
|
||||
} else {
|
||||
product += UTF16EncodeCodePoint(C as CodePoint);
|
||||
}
|
||||
}
|
||||
product = `${product}\u0022`;
|
||||
return Value(product);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-serializejsonobject */
|
||||
function* SerializeJSONObject(state: State, value: ObjectValue): ValueEvaluator<JSStringValue> {
|
||||
if (state.Stack.includes(value)) {
|
||||
return surroundingAgent.Throw('TypeError', 'JSONCircular');
|
||||
}
|
||||
state.Stack.push(value);
|
||||
const stepback = state.Indent;
|
||||
state.Indent = `${state.Indent}${state.Gap}`;
|
||||
let K: IterableIterator<JSStringValue>;
|
||||
if (!(state.PropertyList instanceof UndefinedValue)) {
|
||||
K = state.PropertyList.keys();
|
||||
} else {
|
||||
K = Q(yield* EnumerableOwnProperties(value, 'key')).values();
|
||||
}
|
||||
const partial = [];
|
||||
for (const P of K) {
|
||||
const strP = Q(yield* SerializeJSONProperty(state, P, value));
|
||||
if (!(strP instanceof UndefinedValue)) {
|
||||
let member = QuoteJSONString(P).stringValue();
|
||||
member = `${member}:`;
|
||||
if (state.Gap !== '') {
|
||||
member = `${member} `;
|
||||
}
|
||||
member = `${member}${strP.stringValue()}`;
|
||||
partial.push(member);
|
||||
}
|
||||
}
|
||||
let final;
|
||||
if (partial.length === 0) {
|
||||
final = Value('{}');
|
||||
} else {
|
||||
if (state.Gap === '') {
|
||||
const properties = partial.join(',');
|
||||
final = Value(`{${properties}}`);
|
||||
} else {
|
||||
const separator = `,\u000A${state.Indent}`;
|
||||
const properties = partial.join(separator);
|
||||
final = Value(`{\u000A${state.Indent}${properties}\u000A${stepback}}`);
|
||||
}
|
||||
}
|
||||
state.Stack.pop();
|
||||
state.Indent = stepback;
|
||||
return final;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-serializejsonarray */
|
||||
function* SerializeJSONArray(state: State, value: ObjectValue): PlainEvaluator<JSStringValue | ThrowCompletion> {
|
||||
if (state.Stack.includes(value)) {
|
||||
return surroundingAgent.Throw('TypeError', 'JSONCircular');
|
||||
}
|
||||
state.Stack.push(value);
|
||||
const stepback = state.Indent;
|
||||
state.Indent = `${state.Indent}${state.Gap}`;
|
||||
const partial = [];
|
||||
const len = Q(yield* LengthOfArrayLike(value));
|
||||
let index = 0;
|
||||
while (index < len) {
|
||||
const indexStr = X(ToString(F(index)));
|
||||
const strP = Q(yield* SerializeJSONProperty(state, indexStr, value));
|
||||
if (strP instanceof UndefinedValue) {
|
||||
partial.push('null');
|
||||
} else {
|
||||
partial.push(strP.stringValue());
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
let final;
|
||||
if (partial.length === 0) {
|
||||
final = Value('[]');
|
||||
} else {
|
||||
if (state.Gap === '') {
|
||||
const properties = partial.join(',');
|
||||
final = Value(`[${properties}]`);
|
||||
} else {
|
||||
const separator = `,\u000A${state.Indent}`;
|
||||
const properties = partial.join(separator);
|
||||
final = Value(`[\u000A${state.Indent}${properties}\u000A${stepback}]`);
|
||||
}
|
||||
}
|
||||
state.Stack.pop();
|
||||
state.Indent = stepback;
|
||||
return final;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-json.stringify */
|
||||
function* JSON_stringify([value = Value.undefined, replacer = Value.undefined, _space = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const stack: ObjectValue[] = [];
|
||||
const indent = '';
|
||||
let PropertyList: JSStringSet | UndefinedValue = Value.undefined;
|
||||
let ReplacerFunction: ObjectValue | UndefinedValue = Value.undefined;
|
||||
if (replacer instanceof ObjectValue) {
|
||||
if (IsCallable(replacer)) {
|
||||
ReplacerFunction = replacer;
|
||||
} else {
|
||||
const isArray = Q(IsArray(replacer));
|
||||
if (isArray === Value.true) {
|
||||
PropertyList = new JSStringSet();
|
||||
const len = Q(yield* LengthOfArrayLike(replacer));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const vStr = X(ToString(F(k)));
|
||||
const v = Q(yield* Get(replacer, vStr));
|
||||
let item: JSStringValue | UndefinedValue = Value.undefined;
|
||||
if (v instanceof JSStringValue) {
|
||||
item = v;
|
||||
} else if (v instanceof NumberValue) {
|
||||
item = X(ToString(v));
|
||||
} else if (v instanceof ObjectValue) {
|
||||
if ('StringData' in v || 'NumberData' in v) {
|
||||
item = Q(yield* ToString(v));
|
||||
}
|
||||
}
|
||||
if (!(item instanceof UndefinedValue) && !PropertyList.has(item)) {
|
||||
PropertyList.add(item);
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let space: Value | number = _space;
|
||||
if (space instanceof ObjectValue) {
|
||||
if ('NumberData' in space) {
|
||||
space = Q(yield* ToNumber(space));
|
||||
} else if ('StringData' in space) {
|
||||
space = Q(yield* ToString(space));
|
||||
}
|
||||
}
|
||||
let gap: string;
|
||||
if (space instanceof NumberValue) {
|
||||
space = Math.min(10, X(ToIntegerOrInfinity(space)));
|
||||
if (space < 1) {
|
||||
gap = '';
|
||||
} else {
|
||||
gap = ' '.repeat(space);
|
||||
}
|
||||
} else if (space instanceof JSStringValue) {
|
||||
if (space.stringValue().length <= 10) {
|
||||
gap = space.stringValue();
|
||||
} else {
|
||||
gap = space.stringValue().slice(0, 10);
|
||||
}
|
||||
} else {
|
||||
gap = '';
|
||||
}
|
||||
const wrapper = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
X(CreateDataPropertyOrThrow(wrapper, Value(''), value));
|
||||
const state: State = {
|
||||
ReplacerFunction, Stack: stack, Indent: indent, Gap: gap, PropertyList,
|
||||
};
|
||||
return Q(yield* SerializeJSONProperty(state, Value(''), wrapper));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-json.rawjson */
|
||||
function* JSON_rawJSON([text = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const jsonString = Q(yield* ToString(text));
|
||||
const str = jsonString.stringValue();
|
||||
if (str === '') {
|
||||
return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedToken');
|
||||
}
|
||||
const forbiddenChar = ['\u0009', '\u000A', '\u000D', '\u0020', '\u005B', '\u007B'];
|
||||
if (forbiddenChar.includes(str[0]) || forbiddenChar.includes(str[str.length - 1])) {
|
||||
return surroundingAgent.Throw('SyntaxError', 'JSONUnexpectedToken');
|
||||
}
|
||||
const parseResult = Q(ParseJSON(jsonString.stringValue()));
|
||||
const value = parseResult.Value;
|
||||
Assert(value instanceof JSStringValue || value instanceof NumberValue || value instanceof BooleanValue || value === Value.null);
|
||||
{
|
||||
const firstCodeUnit = str[0].charCodeAt(0);
|
||||
Assert(
|
||||
(firstCodeUnit >= 0x0061 && firstCodeUnit <= 0x007A)
|
||||
|| (firstCodeUnit >= 0x0030 && firstCodeUnit <= 0x0039)
|
||||
|| firstCodeUnit === 0x0022
|
||||
|| firstCodeUnit === 0x002D,
|
||||
);
|
||||
}
|
||||
{
|
||||
const lastCodeUnit = str[str.length - 1].charCodeAt(0);
|
||||
Assert(
|
||||
(lastCodeUnit >= 0x0061 && lastCodeUnit <= 0x007A)
|
||||
|| (lastCodeUnit >= 0x0030 && lastCodeUnit <= 0x0039)
|
||||
|| lastCodeUnit === 0x0022,
|
||||
);
|
||||
}
|
||||
const obj = OrdinaryObjectCreate(Value.null, ['IsRawJSON']);
|
||||
X(CreateDataPropertyOrThrow(obj, Value('rawJSON'), jsonString));
|
||||
X(SetIntegrityLevel(obj, 'frozen'));
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/pr/3714/#sec-json.israwjson */
|
||||
function JSON_isRawJSON([value = Value.undefined]: Arguments) {
|
||||
if (value instanceof ObjectValue && 'IsRawJSON' in value) {
|
||||
return Value.true;
|
||||
}
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/pr/3714/#sec-static-semantics-shallowestcontainedjsonvalue */
|
||||
function ShallowestContainedJSONValue(node: ParseNode): ParseNode | undefined {
|
||||
const F = surroundingAgent.activeFunctionObject;
|
||||
Assert((F as BuiltinFunctionObject).nativeFunction === JSON_parse);
|
||||
const types: ParseNode['type'][] = [
|
||||
'NullLiteral', 'BooleanLiteral', 'NumericLiteral', 'StringLiteral', 'ArrayLiteral', 'ObjectLiteral', 'UnaryExpression',
|
||||
];
|
||||
let unaryExpression: ParseNode | undefined;
|
||||
let queue = [node];
|
||||
while (queue.length > 0) {
|
||||
const candidate = queue.shift()!;
|
||||
let queuedChildren = false;
|
||||
for (const type of types) {
|
||||
if (candidate?.type === type) {
|
||||
if (type === 'UnaryExpression') {
|
||||
unaryExpression = candidate;
|
||||
} else if (type === 'NumericLiteral') {
|
||||
// skip Assert: candidate is contained within unaryExpression
|
||||
// our AST is different from the spec's AST
|
||||
// Return unaryExpression.
|
||||
return unaryExpression || candidate;
|
||||
} else {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
const children = [...avoid_using_children(candidate)];
|
||||
if (!queuedChildren && children.length && Contains(candidate, type)) {
|
||||
queue = queue.concat(children);
|
||||
queuedChildren = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function bootstrapJSON(realmRec: Realm) {
|
||||
const json = bootstrapPrototype(realmRec, [
|
||||
['parse', JSON_parse, 2],
|
||||
['stringify', JSON_stringify, 3],
|
||||
['rawJSON', JSON_rawJSON, 1],
|
||||
['isRawJSON', JSON_isRawJSON, 1],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'JSON');
|
||||
|
||||
realmRec.Intrinsics['%JSON%'] = json;
|
||||
realmRec.Intrinsics['%JSON.parse%'] = X(Get(json, Value('parse'))) as FunctionObject;
|
||||
realmRec.Intrinsics['%JSON.stringify%'] = X(Get(json, Value('stringify'))) as FunctionObject;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
IfAbruptCloseIterator,
|
||||
Q,
|
||||
X,
|
||||
type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__, type Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
Construct,
|
||||
CreateArrayFromList,
|
||||
Get,
|
||||
GetIterator,
|
||||
GroupBy,
|
||||
IsCallable,
|
||||
IteratorClose,
|
||||
IteratorStepValue,
|
||||
OrdinaryCreateFromConstructor,
|
||||
Realm,
|
||||
type FunctionObject,
|
||||
type KeyedGroupRecord,
|
||||
type OrdinaryObject,
|
||||
} from '#self';
|
||||
|
||||
export function* AddEntriesFromIterable(target: ObjectValue, iterable: Value, adder: FunctionObject): ValueEvaluator {
|
||||
Assert(iterable !== Value.undefined && iterable !== Value.null);
|
||||
const iteratorRecord = Q(yield* GetIterator(iterable, 'sync'));
|
||||
while (true) {
|
||||
const next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
if (next === 'done') {
|
||||
return target;
|
||||
}
|
||||
if (!(next instanceof ObjectValue)) {
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotAnObject', next);
|
||||
return Q(yield* IteratorClose(iteratorRecord, error));
|
||||
}
|
||||
// e. Let k be Get(nextItem, "0").
|
||||
const k = yield* Get(next, Value('0'));
|
||||
// f. IfAbruptCloseIterator(k, iteratorRecord).
|
||||
IfAbruptCloseIterator(k, iteratorRecord);
|
||||
__ts_cast__<Value>(k);
|
||||
// g. Let v be Get(nextItem, "1").
|
||||
const v = yield* Get(next, Value('1'));
|
||||
// h. IfAbruptCloseIterator(v, iteratorRecord).
|
||||
IfAbruptCloseIterator(v, iteratorRecord);
|
||||
__ts_cast__<Value>(v);
|
||||
// i. Let status be Call(adder, target, « k, v »).
|
||||
const status = yield* Call(adder, target, [k, v]);
|
||||
// j. IfAbruptCloseIterator(status, iteratorRecord).
|
||||
IfAbruptCloseIterator(status, iteratorRecord);
|
||||
}
|
||||
}
|
||||
|
||||
export interface MapObject extends OrdinaryObject {
|
||||
readonly MapData: { Key: Value | undefined, Value: Value | undefined }[];
|
||||
}
|
||||
export function isMapObject(value: Value): value is MapObject {
|
||||
return 'MapData' in value;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-map-iterable */
|
||||
function* MapConstructor(this: FunctionObject, [iterable = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »).
|
||||
const map = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Map.prototype%', ['MapData'])) as Mutable<MapObject>;
|
||||
// 3. Set map.[[MapData]] to a new empty List.
|
||||
map.MapData = [];
|
||||
// 4. If iterable is either undefined or null, return map.
|
||||
if (iterable === Value.undefined || iterable === Value.null) {
|
||||
return map;
|
||||
}
|
||||
// 5. Let adder be ? Get(map, "set").
|
||||
const adder = Q(yield* Get(map, Value('set')));
|
||||
if (!IsCallable(adder)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', adder);
|
||||
}
|
||||
// 6. Return ? AddEntriesFromIterable(map, iterable, adder).
|
||||
return Q(yield* AddEntriesFromIterable(map, iterable, adder));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.groupby */
|
||||
function* Map_groupBy([items = Value.undefined, callback = Value.undefined]: Arguments): ValueEvaluator {
|
||||
/*
|
||||
1. Let groups be ? GroupBy(items, callback, collection).
|
||||
2. Let map be ! Construct(%Map%).
|
||||
3. For each Record { [[Key]], [[Elements]] } g of groups, do
|
||||
a. Let elements be CreateArrayFromList(g.[[Elements]]).
|
||||
b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }.
|
||||
c. Append entry to map.[[MapData]].
|
||||
4. Return map.
|
||||
*/
|
||||
const groups: KeyedGroupRecord[] = Q(yield* GroupBy(items, callback, 'collection'));
|
||||
const map: MapObject = X(Construct(surroundingAgent.intrinsic('%Map%'))) as MapObject;
|
||||
for (const g of groups) {
|
||||
const elements = CreateArrayFromList(g.Elements);
|
||||
const entry = { Key: g.Key, Value: elements };
|
||||
map.MapData.push(entry);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-map-@@species */
|
||||
function Map_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Return the this value.
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
export function bootstrapMap(realmRec: Realm) {
|
||||
const mapConstructor = bootstrapConstructor(realmRec, MapConstructor, 'Map', 0, realmRec.Intrinsics['%Map.prototype%'], [
|
||||
['groupBy', Map_groupBy, 2],
|
||||
[wellKnownSymbols.species, [Map_speciesGetter]],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Map%'] = mapConstructor;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { Q, X } from '../completion.mts';
|
||||
import { Value, type Arguments } from '../value.mts';
|
||||
import type { YieldEvaluator } from '../evaluator.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateArrayFromList,
|
||||
CreateIteratorFromClosure,
|
||||
GeneratorResume,
|
||||
Realm,
|
||||
RequireInternalSlot,
|
||||
Yield,
|
||||
} from '#self';
|
||||
import type {
|
||||
ValueEvaluator, FunctionCallContext, GeneratorObject, MapObject,
|
||||
ValueCompletion,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-createmapiterator */
|
||||
export function CreateMapIterator(map: Value, kind: 'key+value' | 'key' | 'value'): ValueCompletion<GeneratorObject> {
|
||||
Assert(kind === 'key+value' || kind === 'key' || kind === 'value');
|
||||
// 1. Perform ? RequireInternalSlot(map, [[MapData]]).
|
||||
Q(RequireInternalSlot(map, 'MapData'));
|
||||
// 2. Let closure be a new Abstract Closure with no parameters that captures map and kind and performs the following steps when called:
|
||||
const closure = function* closure(): YieldEvaluator {
|
||||
// a. Let entries be the List that is map.[[MapData]].
|
||||
const entries = (map as MapObject).MapData;
|
||||
// b. Let index be 0.
|
||||
let index = 0;
|
||||
// c. Let numEntries be the number of elements of entries.
|
||||
let numEntries = entries.length;
|
||||
// d. Repeat, while index < numEntries,
|
||||
while (index < numEntries) {
|
||||
// i. Let e be the Record { [[Key]], [[Value]] } that is the value of entries[index].
|
||||
const e = entries[index];
|
||||
// ii. Set index to index + 1.
|
||||
index += 1;
|
||||
// iii. If e.[[Key]] is not empty, then
|
||||
if (e.Key !== undefined) {
|
||||
let result;
|
||||
// 1. If kind is key, let result be e.[[Key]].
|
||||
if (kind === 'key') {
|
||||
result = e.Key;
|
||||
} else if (kind === 'value') { // 2. Else if kind is value, let result be e.[[Value]].
|
||||
result = e.Value;
|
||||
} else { // 3. Else,
|
||||
// a. Assert: kind is key+value.
|
||||
Assert(kind === 'key+value');
|
||||
// b. Let result be ! CreateArrayFromList(« e.[[Key]], e.[[Value]] »).
|
||||
result = X(CreateArrayFromList([e.Key, e.Value!]));
|
||||
}
|
||||
// 4. Perform ? Yield(result).
|
||||
Q(yield* Yield(result!));
|
||||
}
|
||||
// iv. Set numEntries to the number of elements of entries.
|
||||
numEntries = entries.length;
|
||||
}
|
||||
// NON-SPEC
|
||||
generator.HostCapturedValues = undefined;
|
||||
// e. Return undefined.
|
||||
return Value.undefined;
|
||||
};
|
||||
// 3. Return ! CreateIteratorFromClosure(closure, "%MapIteratorPrototype%", %MapIteratorPrototype%).
|
||||
const generator = X(CreateIteratorFromClosure(closure, Value('%MapIteratorPrototype%'), surroundingAgent.intrinsic('%MapIteratorPrototype%'), ['HostCapturedValues'], [map]));
|
||||
return generator;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next */
|
||||
function* MapIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Return ? GeneratorResume(this value, empty, "%MapIteratorPrototype%")
|
||||
return Q(yield* GeneratorResume(thisValue, undefined, Value('%MapIteratorPrototype%')));
|
||||
}
|
||||
|
||||
export function bootstrapMapIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', MapIteratorPrototype_next, 0],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%'], 'Map Iterator');
|
||||
|
||||
realmRec.Intrinsics['%MapIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
NumberValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
} from '../value.mts';
|
||||
import { Q, X } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { CreateMapIterator } from './MapIteratorPrototype.mts';
|
||||
import type { MapObject } from './Map.mts';
|
||||
import {
|
||||
Call,
|
||||
CanonicalizeKeyedCollectionKey,
|
||||
F,
|
||||
IsCallable,
|
||||
RequireInternalSlot,
|
||||
SameValue, SameValueZero, R,
|
||||
} from '#self';
|
||||
import type {
|
||||
Arguments, Descriptor, ValueEvaluator, FunctionCallContext, Realm,
|
||||
ValueCompletion,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.clear */
|
||||
function MapProto_clear(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
const entries = M.MapData;
|
||||
// 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do
|
||||
if (entries.length) {
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(M));
|
||||
}
|
||||
for (const p of entries) {
|
||||
// a. Set p.[[Key]] to empty.
|
||||
p.Key = undefined;
|
||||
// b. Set p.[[Value]] to empty.
|
||||
p.Value = undefined;
|
||||
}
|
||||
// 5. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.delete */
|
||||
function MapProto_delete([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. Let entires be M.[[MapData]].
|
||||
const entries = M.MapData;
|
||||
// 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then
|
||||
if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) {
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(M));
|
||||
// i. Set p.[[Key]] to empty.
|
||||
p.Key = undefined;
|
||||
// ii. Set p.[[Value]] to empty.
|
||||
p.Value = undefined;
|
||||
// iii. Return true.
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.entries */
|
||||
function MapProto_entries(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue;
|
||||
// 2. Return ? CreateMapIterator(M, key+value);
|
||||
return Q(CreateMapIterator(M, 'key+value'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.foreach */
|
||||
function* MapProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
// 4. Let entries be the List that is M.[[MapData]].
|
||||
const entries = M.MapData;
|
||||
// 5. For each Record { [[Key]], [[Value]] } e that is an element of entries, in original key insertion order, do
|
||||
for (const e of entries) {
|
||||
// a. If e.[[Key]] is not empty, then
|
||||
if (e.Key !== undefined) {
|
||||
// i. Perform ? Call(callbackfn, thisArg, « e.[[Value]], e.[[Key]], M »).
|
||||
Q(yield* Call(callbackfn, thisArg, [e.Value!, e.Key, M]));
|
||||
}
|
||||
}
|
||||
// 6. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.get */
|
||||
function MapProto_get([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
const entries = M.MapData;
|
||||
// 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]].
|
||||
if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) {
|
||||
// i. Return p.[[Value]].
|
||||
return p.Value!;
|
||||
}
|
||||
}
|
||||
// 5. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-upsert/#sec-map.prototype.getOrInsert */
|
||||
function MapProto_getOrInsert([key = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. Set key to CanonicalizeKeyedCollectionKey(key).
|
||||
key = CanonicalizeKeyedCollectionKey(key);
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do
|
||||
const entries = M.MapData;
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
|
||||
if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) {
|
||||
return p.Value!;
|
||||
}
|
||||
}
|
||||
// 5. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
const p = { Key: key, Value: value };
|
||||
// 6. Append p to M.[[MapData]].
|
||||
entries.push(p);
|
||||
// 7. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-upsert/#sec-map.prototype.getOrInsertComputed */
|
||||
function* MapProto_getOrInsertComputed([key = Value.undefined, callbackfn = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
// 4. Set key to CanonicalizeKeyedCollectionKey(key).
|
||||
key = CanonicalizeKeyedCollectionKey(key);
|
||||
// 5. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do
|
||||
const entries = M.MapData;
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
|
||||
if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) {
|
||||
return p.Value!;
|
||||
}
|
||||
}
|
||||
// 6. Let value be ? Call(callbackfn, undefined, « key »).
|
||||
const value = Q(yield* Call(callbackfn, Value.undefined, [key]));
|
||||
// 7. NOTE: The Map may have been modified during execution of callbackfn.
|
||||
// 8. For each Record { [[Key]], [[Value]] } p of M.[[MapData]], do
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then
|
||||
if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) {
|
||||
// i. Set p.[[Value]] to value.
|
||||
p.Value = value;
|
||||
// ii. Return value.
|
||||
return value;
|
||||
}
|
||||
}
|
||||
// 9. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
const p = { Key: key, Value: value };
|
||||
// 10. Append p to M.[[MapData]].
|
||||
entries.push(p);
|
||||
// 11. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.has */
|
||||
function MapProto_has([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
const entries = M.MapData;
|
||||
// 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true.
|
||||
if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) {
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
// 5. Return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.keys */
|
||||
function MapProto_keys(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue;
|
||||
// 2. Return ? CreateMapIterator(M, key).
|
||||
return Q(CreateMapIterator(M, 'key'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.set */
|
||||
function MapProto_set([key = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
const entries = M.MapData;
|
||||
// 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then
|
||||
if (p.Key !== undefined && SameValueZero(p.Key, key) === Value.true) {
|
||||
// i. Set p.[[Value]] to value.
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(M));
|
||||
p.Value = value;
|
||||
// ii. Return M.
|
||||
return M;
|
||||
}
|
||||
}
|
||||
// 5. If key is -0𝔽, set key to +0𝔽.
|
||||
if (key instanceof NumberValue && Object.is(R(key), -0)) {
|
||||
key = F(+0);
|
||||
}
|
||||
// 6. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
const p = { Key: key, Value: value };
|
||||
// 7. Append p as the last element of entries.
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(M));
|
||||
entries.push(p);
|
||||
// 8. Return M.
|
||||
return M;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-map.prototype.size */
|
||||
function MapProto_sizeGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as MapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
|
||||
Q(RequireInternalSlot(M, 'MapData'));
|
||||
// 3. Let entries be the List that is M.[[MapData]].
|
||||
const entries = M.MapData;
|
||||
// 4. Let count be 0.
|
||||
let count = 0;
|
||||
// 5. For each Record { [[Key]], [[Value]] } p that is an element of entries, do
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty, set count to count + 1.
|
||||
if (p.Key !== undefined) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
// 6. Return 𝔽(count).
|
||||
return F(count);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-map.prototype.values */
|
||||
function MapProto_values(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue;
|
||||
// 2. Return ? CreateMapIterator(M, value).
|
||||
return Q(CreateMapIterator(M, 'value'));
|
||||
}
|
||||
|
||||
export function bootstrapMapPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['clear', MapProto_clear, 0],
|
||||
['delete', MapProto_delete, 1],
|
||||
['entries', MapProto_entries, 0],
|
||||
['forEach', MapProto_forEach, 1],
|
||||
['get', MapProto_get, 1],
|
||||
['getOrInsert', MapProto_getOrInsert, 2],
|
||||
['getOrInsertComputed', MapProto_getOrInsertComputed, 2],
|
||||
['has', MapProto_has, 1],
|
||||
['keys', MapProto_keys, 0],
|
||||
['set', MapProto_set, 2],
|
||||
['size', [MapProto_sizeGetter]],
|
||||
['values', MapProto_values, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Map');
|
||||
|
||||
const entriesFunc = X(proto.GetOwnProperty(Value('entries')));
|
||||
X(proto.DefineOwnProperty(wellKnownSymbols.iterator, entriesFunc as Descriptor));
|
||||
|
||||
realmRec.Intrinsics['%Map.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
Descriptor,
|
||||
Value,
|
||||
NumberValue,
|
||||
type Arguments,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
CreateBuiltinFunction,
|
||||
ToNumber,
|
||||
F, R,
|
||||
Realm,
|
||||
RequireObjectCoercible,
|
||||
GetIterator,
|
||||
IteratorStepValue,
|
||||
IteratorClose,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-math.abs */
|
||||
function* Math_abs([x = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const n = Q(yield* ToNumber(x));
|
||||
if (n.isNaN()) {
|
||||
return n;
|
||||
} else if (Object.is(R(n), -0)) {
|
||||
return F(+0);
|
||||
} else if (n.isInfinity()) {
|
||||
return F(Infinity);
|
||||
}
|
||||
|
||||
if (R(n) < 0) {
|
||||
return F(-R(n));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-math.acos */
|
||||
function* Math_acos([x = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const n = Q(yield* ToNumber(x));
|
||||
if (n.isNaN()) {
|
||||
return n;
|
||||
} else if (R(n) > 1) {
|
||||
return F(NaN);
|
||||
} else if (R(n) < -1) {
|
||||
return F(NaN);
|
||||
} else if (R(n) === 1) {
|
||||
return F(+0);
|
||||
}
|
||||
|
||||
return F(Math.acos(R(n)));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-math.pow */
|
||||
function* Math_pow([base = Value.undefined, exponent = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Set base to ? ToNumber(base).
|
||||
base = Q(yield* ToNumber(base));
|
||||
// 2. Set exponent to ? ToNumber(exponent).
|
||||
exponent = Q(yield* ToNumber(exponent));
|
||||
// 3. Return ! Number::exponentiate(base, exponent).
|
||||
return X(NumberValue.exponentiate(base, exponent));
|
||||
}
|
||||
|
||||
/** @param {bigint} h */
|
||||
function fmix64(h: bigint) {
|
||||
h ^= h >> 33n;
|
||||
h *= 0xFF51AFD7ED558CCDn;
|
||||
h ^= h >> 33n;
|
||||
h *= 0xC4CEB9FE1A85EC53n;
|
||||
h ^= h >> 33n;
|
||||
return h;
|
||||
}
|
||||
|
||||
const floatView = new Float64Array(1);
|
||||
const big64View = new BigUint64Array(floatView.buffer);
|
||||
/** https://tc39.es/ecma262/#sec-math.random */
|
||||
function Math_random() {
|
||||
const realm = surroundingAgent.currentRealmRecord;
|
||||
if (realm.randomState === undefined) {
|
||||
const seed = realm.HostDefined.randomSeed
|
||||
? BigInt(X(realm.HostDefined.randomSeed()))
|
||||
: BigInt(Math.round(Math.random() * (2 ** 32)));
|
||||
realm.randomState = new BigUint64Array([
|
||||
fmix64(BigInt.asUintN(64, seed)),
|
||||
fmix64(BigInt.asUintN(64, ~seed)),
|
||||
]);
|
||||
}
|
||||
const s = realm.randomState;
|
||||
|
||||
// XorShift128+
|
||||
let s1 = s[0];
|
||||
const s0 = s[1];
|
||||
s[0] = s0;
|
||||
s1 ^= s1 << 23n;
|
||||
s1 ^= s1 >> 17n;
|
||||
s1 ^= s0;
|
||||
s1 ^= s0 >> 26n;
|
||||
s[1] = s1;
|
||||
|
||||
// Convert to double in [0, 1) range
|
||||
big64View[0] = (s0 >> 12n) | 0x3FF0000000000000n;
|
||||
const result = floatView[0] - 1;
|
||||
return F(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-math.sumprecise */
|
||||
function* Math_sumPrecise([items = Value.undefined]: Arguments): ValueEvaluator {
|
||||
Q(RequireObjectCoercible(items));
|
||||
const iteratorRecord = Q(yield* GetIterator(items, 'sync'));
|
||||
let state: 'minus-zero' | 'not-a-number' | 'minus-infinity' | 'plus-infinity' | 'finite' = 'minus-zero';
|
||||
const sums: number[] = [];
|
||||
let count = 0;
|
||||
let next: 'not-started' | 'done' | Value = 'not-started';
|
||||
while (next !== 'done') {
|
||||
next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
if (next !== 'done') {
|
||||
if (count >= 2 ** 53 - 1) {
|
||||
const error = surroundingAgent.Throw('RangeError', 'OutOfRange', '');
|
||||
return Q(yield* IteratorClose(iteratorRecord, error));
|
||||
}
|
||||
if (!(next instanceof NumberValue)) {
|
||||
const error = surroundingAgent.Throw('TypeError', 'NotANumber', next);
|
||||
return Q(yield* IteratorClose(iteratorRecord, error));
|
||||
}
|
||||
const n = R(next);
|
||||
if (state !== 'not-a-number') {
|
||||
if (Number.isNaN(n)) {
|
||||
state = 'not-a-number';
|
||||
} else if (n === Infinity) {
|
||||
if (state === 'minus-infinity') {
|
||||
state = 'not-a-number';
|
||||
} else {
|
||||
state = 'plus-infinity';
|
||||
}
|
||||
} else if (n === -Infinity) {
|
||||
if (state === 'plus-infinity') {
|
||||
state = 'not-a-number';
|
||||
} else {
|
||||
state = 'minus-infinity';
|
||||
}
|
||||
} else if (!Object.is(n, -0) && (state === 'minus-zero' || state === 'finite')) {
|
||||
state = 'finite';
|
||||
sums.push(n);
|
||||
}
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
if (state === 'not-a-number') {
|
||||
return F(NaN);
|
||||
}
|
||||
if (state === 'plus-infinity') {
|
||||
return F(Infinity);
|
||||
}
|
||||
if (state === 'minus-infinity') {
|
||||
return F(-Infinity);
|
||||
}
|
||||
if (state === 'minus-zero') {
|
||||
return F(-0);
|
||||
}
|
||||
return F(sum(sums));
|
||||
|
||||
function sum(items: number[]) {
|
||||
if ('sumPrecise' in Math) {
|
||||
// @ts-expect-error
|
||||
return Math.sumPrecise(items);
|
||||
}
|
||||
const fractional_parts: number[] = [];
|
||||
let whole_part_sum = 0n;
|
||||
items.forEach((n) => {
|
||||
const whole_num = Math.trunc(n);
|
||||
fractional_parts.push(n - whole_num);
|
||||
whole_part_sum += BigInt(whole_num);
|
||||
});
|
||||
const fractional_parts_as_hex = fractional_parts.map((n) => n.toString(32));
|
||||
|
||||
const fractional: number[] = [];
|
||||
for (const fractional_str of fractional_parts_as_hex) {
|
||||
const neg = fractional_str[0] === '-';
|
||||
const prefix = neg ? 3 : 2; // -0.xxx or 0.xxx
|
||||
for (let index = prefix; index < fractional_str.length; index += 1) {
|
||||
fractional[index - prefix] ??= 0;
|
||||
if (neg) {
|
||||
fractional[index - prefix] -= parseInt(fractional_str[index], 32);
|
||||
} else {
|
||||
fractional[index - prefix] += parseInt(fractional_str[index], 32);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let index = fractional.length - 1; index >= 0; index -= 1) {
|
||||
const element = fractional[index];
|
||||
if (element >= 32) {
|
||||
fractional[index] = element % 32;
|
||||
fractional[index - 1] ??= 0;
|
||||
fractional[index - 1] += Math.floor(element / 32);
|
||||
}
|
||||
if (element < 0) {
|
||||
fractional[index] = 32 + element;
|
||||
fractional[index - 1] ??= 0;
|
||||
fractional[index - 1] -= 1;
|
||||
}
|
||||
}
|
||||
const fractional_part = fractional.reduceRight((acc, digit, index) => acc + digit * 32 ** -(index + 1), 0);
|
||||
if (fractional[-1]) {
|
||||
whole_part_sum += BigInt(fractional[-1]);
|
||||
}
|
||||
return Number(whole_part_sum) + fractional_part;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-math-object */
|
||||
export function bootstrapMath(realmRec: Realm) {
|
||||
/** https://tc39.es/ecma262/#sec-value-properties-of-the-math-object */
|
||||
const readonly = { Writable: Value.false, Configurable: Value.false };
|
||||
|
||||
// @@toStringTag is handled in the bootstrapPrototype() call.
|
||||
const mathObj = bootstrapPrototype(realmRec, [
|
||||
['E', F(2.718281828459045), undefined, readonly],
|
||||
['LN10', F(2.302585092994046), undefined, readonly],
|
||||
['LN2', F(0.6931471805599453), undefined, readonly],
|
||||
['LOG10E', F(0.4342944819032518), undefined, readonly],
|
||||
['LOG2E', F(1.4426950408889634), undefined, readonly],
|
||||
['PI', F(3.141592653589793), undefined, readonly],
|
||||
['SQRT1_2', F(0.7071067811865476), undefined, readonly],
|
||||
['SQRT2', F(1.4142135623730951), undefined, readonly],
|
||||
['abs', Math_abs, 1],
|
||||
['acos', Math_acos, 1],
|
||||
['pow', Math_pow, 2],
|
||||
['random', Math_random, 0],
|
||||
['sumPrecise', Math_sumPrecise, 1],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Math');
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-function-properties-of-the-math-object */
|
||||
|
||||
([
|
||||
['acosh', 1],
|
||||
['asin', 1],
|
||||
['asinh', 1],
|
||||
['atan', 1],
|
||||
['atanh', 1],
|
||||
['atan2', 2],
|
||||
['cbrt', 1],
|
||||
['ceil', 1],
|
||||
['clz32', 1],
|
||||
['cos', 1],
|
||||
['cosh', 1],
|
||||
['exp', 1],
|
||||
['expm1', 1],
|
||||
['floor', 1],
|
||||
['fround', 1],
|
||||
['hypot', 2],
|
||||
['imul', 2],
|
||||
['log', 1],
|
||||
['log1p', 1],
|
||||
['log10', 1],
|
||||
['log2', 1],
|
||||
['max', 2],
|
||||
['min', 2],
|
||||
['round', 1],
|
||||
['sign', 1],
|
||||
['sin', 1],
|
||||
['sinh', 1],
|
||||
['sqrt', 1],
|
||||
['tan', 1],
|
||||
['tanh', 1],
|
||||
['trunc', 1],
|
||||
] as const).forEach(([name, length]) => {
|
||||
// TODO(18): Math
|
||||
/** https://tc39.es/ecma262/#sec-function-properties-of-the-math-object */
|
||||
const method = function* method(args: Arguments): ValueEvaluator {
|
||||
const nextArgs: number[] = [];
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
nextArgs[i] = R(Q(yield* ToNumber(args[i]!)));
|
||||
}
|
||||
// we're calling host Math functions here.
|
||||
return F((Math[name] as (...args: unknown[]) => number)(...nextArgs));
|
||||
};
|
||||
const func = CreateBuiltinFunction(method, length, Value(name), [], realmRec);
|
||||
X(mathObj.DefineOwnProperty(Value(name), Descriptor({
|
||||
Value: func,
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
});
|
||||
|
||||
realmRec.Intrinsics['%Math%'] = mathObj;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import {
|
||||
Descriptor,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { captureStack, callSiteToErrorString } from '../helpers.mts';
|
||||
import { bootstrapConstructor, bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { ErrorObject } from './Error.mts';
|
||||
import {
|
||||
DefinePropertyOrThrow,
|
||||
OrdinaryCreateFromConstructor,
|
||||
InstallErrorCause,
|
||||
ToString,
|
||||
Realm,
|
||||
type FunctionObject,
|
||||
} from '#self';
|
||||
|
||||
const nativeErrorNames = [
|
||||
'EvalError',
|
||||
'RangeError',
|
||||
'ReferenceError',
|
||||
'SyntaxError',
|
||||
'TypeError',
|
||||
'URIError',
|
||||
] as const;
|
||||
export type NativeErrorNames = typeof nativeErrorNames[number];
|
||||
export function bootstrapNativeError(realmRec: Realm) {
|
||||
for (const name of nativeErrorNames) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['name', Value(name)],
|
||||
['message', Value('')],
|
||||
], realmRec.Intrinsics['%Error.prototype%']);
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-nativeerror */
|
||||
const Constructor = function* Constructor([message = Value.undefined, options = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
|
||||
let newTarget;
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
newTarget = surroundingAgent.activeFunctionObject;
|
||||
} else {
|
||||
newTarget = NewTarget;
|
||||
}
|
||||
// 2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%NativeError.prototype%", « [[ErrorData]] »).
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(newTarget as FunctionObject, `%${name}.prototype%`, [
|
||||
'ErrorData',
|
||||
'HostDefinedErrorStack',
|
||||
])) as ErrorObject;
|
||||
// 3. If message is not undefined, then
|
||||
if (message !== Value.undefined) {
|
||||
// a. Let msg be ? ToString(message).
|
||||
const msg = Q(yield* ToString(message));
|
||||
// b. Let msgDesc be the PropertyDescriptor { [[Value]]: msg, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }.
|
||||
const msgDesc = Descriptor({
|
||||
Value: msg,
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
});
|
||||
// c. Perform ! DefinePropertyOrThrow(O, "message", msgDesc).
|
||||
X(DefinePropertyOrThrow(O, Value('message'), msgDesc));
|
||||
}
|
||||
// 4. Perform ? InstallErrorCause(O, options).
|
||||
Q(yield* InstallErrorCause(O, options));
|
||||
// NON-SPEC
|
||||
const S = captureStack();
|
||||
O.HostDefinedErrorStack = S.stack;
|
||||
O.ErrorData = X(callSiteToErrorString(O, S.stack, S.nativeStack));
|
||||
// 5. Return O.
|
||||
return O;
|
||||
};
|
||||
Object.defineProperty(Constructor, 'name', {
|
||||
value: `${name}Constructor`,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const cons = bootstrapConstructor(realmRec, Constructor, name, 1, proto, []);
|
||||
cons.Prototype = realmRec.Intrinsics['%Error%'];
|
||||
|
||||
realmRec.Intrinsics[`%${name}.prototype%`] = proto;
|
||||
realmRec.Intrinsics[`%${name}%`] = cons;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import {
|
||||
Descriptor,
|
||||
NumberValue,
|
||||
BigIntValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
UndefinedValue,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
IsIntegralNumber,
|
||||
OrdinaryCreateFromConstructor,
|
||||
ToNumeric,
|
||||
F, R,
|
||||
Realm,
|
||||
type OrdinaryObject,
|
||||
} from '#self';
|
||||
|
||||
export interface NumberObject extends OrdinaryObject {
|
||||
readonly NumberData: NumberValue;
|
||||
}
|
||||
export function isNumberObject(o: Value): o is NumberObject {
|
||||
return 'NumberData' in o;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number-constructor-number-value */
|
||||
function* NumberConstructor([value]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
let n;
|
||||
if (value !== undefined) {
|
||||
const prim = Q(yield* ToNumeric(value));
|
||||
if (prim instanceof BigIntValue) {
|
||||
n = F(Number(R(prim)));
|
||||
} else {
|
||||
n = prim;
|
||||
}
|
||||
} else {
|
||||
n = F(+0);
|
||||
}
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return n;
|
||||
}
|
||||
const O = (yield* OrdinaryCreateFromConstructor(NewTarget, '%Number.prototype%', ['NumberData'])) as Mutable<NumberObject>;
|
||||
O.NumberData = n;
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.isfinite */
|
||||
function Number_isFinite([number = Value.undefined]: Arguments) {
|
||||
if (!(number instanceof NumberValue)) {
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
if (number.isNaN() || number.isInfinity()) {
|
||||
return Value.false;
|
||||
}
|
||||
return Value.true;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.isinteger */
|
||||
function Number_isInteger([number = Value.undefined]: Arguments) {
|
||||
return X(IsIntegralNumber(number));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.isnan */
|
||||
function Number_isNaN([number = Value.undefined]: Arguments) {
|
||||
if (!(number instanceof NumberValue)) {
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
if (number.isNaN()) {
|
||||
return Value.true;
|
||||
}
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.issafeinteger */
|
||||
function Number_isSafeInteger([number = Value.undefined]: Arguments) {
|
||||
if (!(number instanceof NumberValue)) {
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
if (X(IsIntegralNumber(number)) === Value.true) {
|
||||
if (Math.abs(R(number)) <= (2 ** 53) - 1) {
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
export function bootstrapNumber(realmRec: Realm) {
|
||||
const override = {
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.false,
|
||||
};
|
||||
const numberConstructor = bootstrapConstructor(realmRec, NumberConstructor, 'Number', 1, realmRec.Intrinsics['%Number.prototype%'], [
|
||||
['EPSILON', F(Number.EPSILON), undefined, override],
|
||||
['MAX_SAFE_INTEGER', F(Number.MAX_SAFE_INTEGER), undefined, override],
|
||||
['MAX_VALUE', F(Number.MAX_VALUE), undefined, override],
|
||||
['MIN_SAFE_INTEGER', F(Number.MIN_SAFE_INTEGER), undefined, override],
|
||||
['MIN_VALUE', F(Number.MIN_VALUE), undefined, override],
|
||||
['NaN', F(NaN), undefined, override],
|
||||
['NEGATIVE_INFINITY', F(-Infinity), undefined, override],
|
||||
['POSITIVE_INFINITY', F(+Infinity), undefined, override],
|
||||
|
||||
['isFinite', Number_isFinite, 1],
|
||||
['isInteger', Number_isInteger, 1],
|
||||
['isNaN', Number_isNaN, 1],
|
||||
['isSafeInteger', Number_isSafeInteger, 1],
|
||||
]);
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.parsefloat */
|
||||
// The value of the Number.parseFloat data property is the same built-in function object that is the value of the parseFloat property of the global object defined in 18.2.4.
|
||||
X(numberConstructor.DefineOwnProperty(Value('parseFloat'), Descriptor({
|
||||
Value: realmRec.Intrinsics['%parseFloat%'],
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.parseint */
|
||||
// The value of the Number.parseInt data property is the same built-in function object that is the value of the parseInt property of the global object defined in 18.2.5.
|
||||
X(numberConstructor.DefineOwnProperty(Value('parseInt'), Descriptor({
|
||||
Value: realmRec.Intrinsics['%parseInt%'],
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
|
||||
realmRec.Intrinsics['%Number%'] = numberConstructor;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
ObjectValue,
|
||||
Value,
|
||||
NumberValue,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { NumberObject } from './Number.mts';
|
||||
import {
|
||||
Assert,
|
||||
ToIntegerOrInfinity,
|
||||
ToString,
|
||||
F, R,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
function thisNumberValue(value: Value) {
|
||||
if (value instanceof NumberValue) {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof ObjectValue && 'NumberData' in value) {
|
||||
const n = value.NumberData;
|
||||
Assert(n instanceof NumberValue);
|
||||
return n;
|
||||
}
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Number', value);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.prototype.toexponential */
|
||||
function* NumberProto_toExponential([fractionDigits = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const x = Q(thisNumberValue(thisValue));
|
||||
const f = Q(yield* ToIntegerOrInfinity(fractionDigits));
|
||||
Assert(fractionDigits !== Value.undefined || f === 0);
|
||||
if (!x.isFinite()) {
|
||||
return NumberValue.toString(x, 10);
|
||||
}
|
||||
if (f < 0 || f > 100) {
|
||||
return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toExponential');
|
||||
}
|
||||
return Value(R(x).toExponential(fractionDigits === Value.undefined ? undefined : f));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.prototype.tofixed */
|
||||
function* NumberProto_toFixed([fractionDigits = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const x = Q(thisNumberValue(thisValue));
|
||||
const f = Q(yield* ToIntegerOrInfinity(fractionDigits));
|
||||
Assert(fractionDigits !== Value.undefined || f === 0);
|
||||
if (f < 0 || f > 100) {
|
||||
return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toFixed');
|
||||
}
|
||||
if (!x.isFinite()) {
|
||||
return X(NumberValue.toString(x, 10));
|
||||
}
|
||||
return Value(R(x).toFixed(f));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.prototype.tolocalestring */
|
||||
function NumberProto_toLocaleString(_args: Arguments, context: FunctionCallContext): ValueEvaluator {
|
||||
return NumberProto_toString([], context);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.prototype.toprecision */
|
||||
function* NumberProto_toPrecision([precision = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const x = Q(thisNumberValue(thisValue));
|
||||
if (precision === Value.undefined) {
|
||||
return X(ToString(x));
|
||||
}
|
||||
const p = Q(yield* ToIntegerOrInfinity(precision));
|
||||
if (!x.isFinite()) {
|
||||
return X(NumberValue.toString(x, 10));
|
||||
}
|
||||
if (p < 1 || p > 100) {
|
||||
return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toPrecision');
|
||||
}
|
||||
return Value(R(x).toPrecision(p));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.prototype.tostring */
|
||||
function* NumberProto_toString([radix = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const x = Q(thisNumberValue(thisValue));
|
||||
let radixNumber;
|
||||
if (radix === Value.undefined) {
|
||||
radixNumber = 10;
|
||||
} else {
|
||||
radixNumber = Q(yield* ToIntegerOrInfinity(radix));
|
||||
}
|
||||
if (radixNumber < 2 || radixNumber > 36) {
|
||||
return surroundingAgent.Throw('RangeError', 'NumberFormatRange', 'toString');
|
||||
}
|
||||
if (radixNumber === 10) {
|
||||
return X(ToString(x));
|
||||
}
|
||||
// FIXME(devsnek): Return the String representation of this Number
|
||||
// value using the radix specified by radixNumber. Letters a-z are
|
||||
// used for digits with values 10 through 35. The precise algorithm
|
||||
// is implementation-dependent, however the algorithm should be a
|
||||
// generalization of that specified in 7.1.12.1.
|
||||
return Value(R(x).toString(radixNumber));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-number.prototype.valueof */
|
||||
function NumberProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
return Q(thisNumberValue(thisValue));
|
||||
}
|
||||
|
||||
export function bootstrapNumberPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['toExponential', NumberProto_toExponential, 1],
|
||||
['toFixed', NumberProto_toFixed, 1],
|
||||
['toLocaleString', NumberProto_toLocaleString, 0],
|
||||
['toPrecision', NumberProto_toPrecision, 1],
|
||||
['toString', NumberProto_toString, 1],
|
||||
['valueOf', NumberProto_valueOf, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%']);
|
||||
|
||||
(proto as Mutable<NumberObject>).NumberData = F(+0);
|
||||
|
||||
realmRec.Intrinsics['%Number.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
import {
|
||||
NullValue,
|
||||
ObjectValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
UndefinedValue,
|
||||
type PropertyKeyValue,
|
||||
Descriptor,
|
||||
SymbolValue,
|
||||
JSStringValue,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { AddEntriesFromIterable } from './Map.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateArrayFromList,
|
||||
CreateDataProperty,
|
||||
DefinePropertyOrThrow,
|
||||
CreateDataPropertyOrThrow,
|
||||
EnumerableOwnProperties,
|
||||
FromPropertyDescriptor,
|
||||
Get,
|
||||
HasOwnProperty,
|
||||
IsExtensible,
|
||||
OrdinaryObjectCreate,
|
||||
OrdinaryCreateFromConstructor,
|
||||
RequireObjectCoercible,
|
||||
SameValue,
|
||||
Set,
|
||||
SetIntegrityLevel,
|
||||
TestIntegrityLevel,
|
||||
ToObject,
|
||||
ToPropertyDescriptor,
|
||||
ToPropertyKey,
|
||||
CreateBuiltinFunction,
|
||||
Realm,
|
||||
type FunctionObject,
|
||||
GroupBy,
|
||||
type KeyedGroupRecord,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object-value */
|
||||
function* ObjectConstructor([value = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If NewTarget is neither undefined nor the active function, then
|
||||
if (NewTarget !== Value.undefined && NewTarget !== surroundingAgent.activeFunctionObject) {
|
||||
// a. Return ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%").
|
||||
return yield* OrdinaryCreateFromConstructor(NewTarget as FunctionObject, '%Object.prototype%');
|
||||
}
|
||||
// 2. If value is undefined or null, return OrdinaryObjectCreate(%Object.prototype%).
|
||||
if (value === Value.null || value === Value.undefined) {
|
||||
return OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
}
|
||||
// 3. Return ! ToObject(value).
|
||||
return X(ToObject(value));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.assign */
|
||||
function* Object_assign([target = Value.undefined, ...sources]: Arguments): ValueEvaluator {
|
||||
// 1. Let to be ? ToObject(target).
|
||||
const to = Q(ToObject(target));
|
||||
// 2. If only one argument was passed, return to.
|
||||
if (sources.length === 0) {
|
||||
return to;
|
||||
}
|
||||
// 3. Let sources be the List of argument values starting with the second argument.
|
||||
// 4. For each element nextSource of sources, in ascending index order, do
|
||||
for (const nextSource of (sources as Arguments).values()) {
|
||||
// a. If nextSource is neither undefined nor null, then
|
||||
if (nextSource !== Value.undefined && nextSource !== Value.null) {
|
||||
// i. Let from be ! ToObject(nextSource).
|
||||
const from = X(ToObject(nextSource));
|
||||
// ii. Let keys be ? from.[[OwnPropertyKeys]]().
|
||||
const keys = Q(yield* from.OwnPropertyKeys());
|
||||
// iii. For each element nextKey of keys in List order, do
|
||||
for (const nextKey of keys) {
|
||||
// 1. Let desc be ? from.[[GetOwnProperty]](nextKey).
|
||||
const desc = Q(yield* from.GetOwnProperty(nextKey));
|
||||
// 2. If desc is not undefined and desc.[[Enumerable]] is true, then
|
||||
if (!(desc instanceof UndefinedValue) && desc.Enumerable === Value.true) {
|
||||
// a. Let propValue be ? Get(from, nextKey).
|
||||
const propValue = Q(yield* Get(from, nextKey));
|
||||
// b. Perform ? Set(to, nextKey, propValue, true).
|
||||
Q(yield* Set(to, nextKey, propValue, Value.true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 5. Return to.
|
||||
return to;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.create */
|
||||
function* Object_create([O = Value.undefined, Properties = Value.undefined]: Arguments) {
|
||||
// 1. If Type(O) is neither Object nor Null, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue) && !(O instanceof NullValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType');
|
||||
}
|
||||
// 2. Let obj be OrdinaryObjectCreate(O).
|
||||
const obj = OrdinaryObjectCreate(O);
|
||||
// 3. If Properties is not undefined, then
|
||||
if (Properties !== Value.undefined) {
|
||||
// a. Return ? ObjectDefineProperties(obj, Properties).
|
||||
return Q(yield* ObjectDefineProperties(obj, Properties));
|
||||
}
|
||||
// 4. Return obj.
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.defineproperties */
|
||||
function* Object_defineProperties([O = Value.undefined, Properties = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Return ? ObjectDefineProperties(O, Properties).
|
||||
return Q(yield* ObjectDefineProperties(O, Properties));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-objectdefineproperties ObjectDefineProperties */
|
||||
function* ObjectDefineProperties(O: Value, Properties: Value) {
|
||||
// 1. If Type(O) is not Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 2. Let props be ? ToObject(Properties).
|
||||
const props = Q(ToObject(Properties));
|
||||
// 3. Let keys be ? props.[[OwnPropertyKeys]]().
|
||||
const keys = Q(yield* props.OwnPropertyKeys());
|
||||
// 4. Let descriptors be a new empty List.
|
||||
const descriptors: [PropertyKeyValue, Descriptor][] = [];
|
||||
// 5. For each element nextKey of keys in List order, do
|
||||
for (const nextKey of keys) {
|
||||
// a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
|
||||
const propDesc = Q(yield* props.GetOwnProperty(nextKey));
|
||||
// b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
|
||||
if (!(propDesc instanceof UndefinedValue) && propDesc.Enumerable === Value.true) {
|
||||
// i. Let descObj be ? Get(props, nextKey).
|
||||
const descObj = Q(yield* Get(props, nextKey));
|
||||
// ii. Let desc be ? ToPropertyDescriptor(descObj).
|
||||
const desc = Q(yield* ToPropertyDescriptor(descObj));
|
||||
// iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors.
|
||||
descriptors.push([nextKey, desc]);
|
||||
}
|
||||
}
|
||||
// 6. For each pair from descriptors in list order, do
|
||||
for (const pair of descriptors) {
|
||||
// a. Let P be the first element of pair.
|
||||
const P = pair[0];
|
||||
// b. Let desc be the second element of pair.
|
||||
const desc = pair[1];
|
||||
// c. Perform ? DefinePropertyOrThrow(O, P, desc).
|
||||
Q(yield* DefinePropertyOrThrow(O, P, desc));
|
||||
}
|
||||
// 7. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.defineproperty */
|
||||
function* Object_defineProperty([O = Value.undefined, P = Value.undefined, Attributes = Value.undefined]: Arguments) {
|
||||
// 1. If Type(O) is not Object, throw a TypeError exception.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', O);
|
||||
}
|
||||
// 2. Let key be ? ToPropertyKey(P).
|
||||
const key = Q(yield* ToPropertyKey(P));
|
||||
// 3. Let desc be ? ToPropertyDescriptor(Attributes).
|
||||
const desc = Q(yield* ToPropertyDescriptor(Attributes));
|
||||
// 4. Perform ? DefinePropertyOrThrow(O, key, desc).
|
||||
Q(yield* DefinePropertyOrThrow(O, key, desc));
|
||||
// 5. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.entries */
|
||||
function* Object_entries([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let obj be ? ToObject(O).
|
||||
const obj = Q(ToObject(O));
|
||||
// 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key+value).
|
||||
const nameList = Q(yield* EnumerableOwnProperties(obj, 'key+value'));
|
||||
// 3. Return CreateArrayFromList(nameList).
|
||||
return CreateArrayFromList(nameList);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.freeze */
|
||||
function* Object_freeze([O = Value.undefined]: Arguments) {
|
||||
// 1. If Type(O) is not Object, return O.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return O;
|
||||
}
|
||||
// 2. Let status be ? SetIntegrityLevel(O, frozen).
|
||||
const status = Q(yield* SetIntegrityLevel(O, 'frozen'));
|
||||
// 3. If status is false, throw a TypeError exception.
|
||||
if (status === Value.false) {
|
||||
return surroundingAgent.Throw('TypeError', 'UnableToFreeze', O);
|
||||
}
|
||||
// 4. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.fromentries */
|
||||
function* Object_fromEntries([iterable = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Perform ? RequireObjectCoercible(iterable).
|
||||
Q(RequireObjectCoercible(iterable));
|
||||
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
|
||||
const obj = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')));
|
||||
// 3. Assert: obj is an extensible ordinary object with no own properties.
|
||||
Assert(obj.Extensible === Value.true && obj.properties.size === 0);
|
||||
// 4. Let closure be a new Abstract Closure with parameters (key, value) that captures obj and performs the following steps when called:
|
||||
function* closure([key = Value.undefined, value = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// a. Let propertyKey be ? ToPropertyKey(key).
|
||||
const propertyKey = Q(yield* ToPropertyKey(key));
|
||||
// b. Perform ! CreateDataPropertyOrThrow(obj, propertyKey, value).
|
||||
X(CreateDataPropertyOrThrow(obj, propertyKey, value));
|
||||
// c. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
// 5. Let adder be ! CreateBuiltinFunction(closure, 2, "", « »).
|
||||
const adder = X(CreateBuiltinFunction(closure, 2, Value(''), []));
|
||||
// 6. Return ? AddEntriesFromIterable(obj, iterable, adder).
|
||||
return Q(yield* AddEntriesFromIterable(obj, iterable, adder));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.getownpropertydescriptor */
|
||||
function* Object_getOwnPropertyDescriptor([O = Value.undefined, P = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let obj be ? ToObject(O).
|
||||
const obj = Q(ToObject(O));
|
||||
// 2. Let key be ? ToPropertyKey(P).
|
||||
const key = Q(yield* ToPropertyKey(P));
|
||||
// 3. Let desc be ? obj.[[GetOwnProperty]](key).
|
||||
const desc = Q(yield* obj.GetOwnProperty(key));
|
||||
// 4. Return FromPropertyDescriptor(desc).
|
||||
return FromPropertyDescriptor(desc);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.getownpropertydescriptors */
|
||||
function* Object_getOwnPropertyDescriptors([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let obj be ? ToObject(O).
|
||||
const obj = Q(ToObject(O));
|
||||
// 2. Let ownKeys be ? obj.[[OwnPropertyKeys]]().
|
||||
const ownKeys = Q(yield* obj.OwnPropertyKeys());
|
||||
// 3. Let descriptors be ! OrdinaryObjectCreate(%Object.prototype%).
|
||||
const descriptors = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')));
|
||||
// 4. For each element key of ownKeys in List order, do
|
||||
for (const key of ownKeys) {
|
||||
// a. Let desc be ? obj.[[GetOwnProperty]](key).
|
||||
const desc = Q(yield* obj.GetOwnProperty(key));
|
||||
// b. Let descriptor be ! FromPropertyDescriptor(desc).
|
||||
const descriptor = X(FromPropertyDescriptor(desc));
|
||||
// c. If descriptor is not undefined, perform ! CreateDataPropertyOrThrow(descriptors, key, descriptor).
|
||||
if (descriptor !== Value.undefined) {
|
||||
X(CreateDataProperty(descriptors, key, descriptor));
|
||||
}
|
||||
}
|
||||
// 5. Return descriptors.
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-getownpropertykeys */
|
||||
function* GetOwnPropertyKeys(O: Value, type: 'String' | 'Symbol'): ValueEvaluator {
|
||||
// 1. Let obj be ? ToObject(O).
|
||||
const obj = Q(ToObject(O));
|
||||
// 2. Let keys be ? obj.[[OwnPropertyKeys]]().
|
||||
const keys = Q(yield* obj.OwnPropertyKeys());
|
||||
// 3. Let nameList be a new empty List.
|
||||
const nameList: PropertyKeyValue[] = [];
|
||||
// 4. For each element nextKey of keys in List order, do
|
||||
keys.forEach((nextKey) => {
|
||||
// a. If nextKey is a Symbol and type is symbol, or if nextKey is a String and type is string, then
|
||||
if ((type === 'Symbol' && nextKey instanceof SymbolValue) || (type === 'String' && nextKey instanceof JSStringValue)) {
|
||||
// i. Append nextKey as the last element of nameList.
|
||||
nameList.push(nextKey);
|
||||
}
|
||||
});
|
||||
return CreateArrayFromList(nameList);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.getownpropertynames */
|
||||
function* Object_getOwnPropertyNames([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Return ? GetOwnPropertyKeys(O, string).
|
||||
return Q(yield* GetOwnPropertyKeys(O, 'String'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.getownpropertysymbols */
|
||||
function* Object_getOwnPropertySymbols([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Return ? GetOwnPropertyKeys(O, symbol).
|
||||
return Q(yield* GetOwnPropertyKeys(O, 'Symbol'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.getprototypeof */
|
||||
function* Object_getPrototypeOf([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let obj be ? ToObject(O).
|
||||
const obj = Q(ToObject(O));
|
||||
// 2. Return ? obj.[[GetPrototypeOf]]().
|
||||
return Q(yield* obj.GetPrototypeOf());
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.groupby */
|
||||
function* Object_groupBy([items = Value.undefined, callback = Value.undefined]: Arguments): ValueEvaluator {
|
||||
/*
|
||||
1. Let groups be ? GroupBy(items, callback, property).
|
||||
2. Let obj be OrdinaryObjectCreate(null).
|
||||
3. For each Record { [[Key]], [[Elements]] } g of groups, do
|
||||
a. Let elements be CreateArrayFromList(g.[[Elements]]).
|
||||
b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
|
||||
4. Return obj.
|
||||
*/
|
||||
const groups: KeyedGroupRecord[] = Q(yield* GroupBy(items, callback, 'property'));
|
||||
const obj = OrdinaryObjectCreate(Value.null);
|
||||
for (const g of groups) {
|
||||
const elements = CreateArrayFromList(g.Elements);
|
||||
X(CreateDataPropertyOrThrow(obj, g.Key, elements));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.hasown */
|
||||
function* Object_hasOwn([O = Value.undefined, P = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let obj be ? ToObject(O).
|
||||
const obj = Q(ToObject(O));
|
||||
// 2. Let O be ? ToObject(this value).
|
||||
const key = Q(yield* ToPropertyKey(P));
|
||||
// 3. Return ? HasOwnProperty(obj, key).
|
||||
return yield* HasOwnProperty(obj, key);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.is */
|
||||
function Object_is([value1 = Value.undefined, value2 = Value.undefined]: Arguments) {
|
||||
// 1. Return SameValue(value1, value2).
|
||||
return SameValue(value1, value2);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.isextensible */
|
||||
function* Object_isExtensible([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. If Type(O) is not Object, return false.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return Value.false;
|
||||
}
|
||||
// 2. Return ? IsExtensible(O).
|
||||
return Q(yield* IsExtensible(O));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.isfrozen */
|
||||
function* Object_isFrozen([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. If Type(O) is not Object, return true.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return Value.true;
|
||||
}
|
||||
// 2. Return ? TestIntegrityLevel(O, frozen).
|
||||
return Q(yield* TestIntegrityLevel(O, 'frozen'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.issealed */
|
||||
function* Object_isSealed([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. If Type(O) is not Object, return true.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return Value.true;
|
||||
}
|
||||
// 2. Return ? TestIntegrityLevel(O, sealed).
|
||||
return Q(yield* TestIntegrityLevel(O, 'sealed'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.keys */
|
||||
function* Object_keys([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let obj be ? ToObject(O).
|
||||
const obj = Q(ToObject(O));
|
||||
// 2. Let nameList be ? EnumerableOwnPropertyNames(obj, key).
|
||||
const nameList = Q(yield* EnumerableOwnProperties(obj, 'key'));
|
||||
// 3. Return CreateArrayFromList(nameList).
|
||||
return CreateArrayFromList(nameList);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.preventextensions */
|
||||
function* Object_preventExtensions([O = Value.undefined]: Arguments) {
|
||||
// 1. If Type(O) is not Object, return O.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return O;
|
||||
}
|
||||
// 2. Let status be ? O.[[PreventExtensions]]().
|
||||
const status = Q(yield* O.PreventExtensions());
|
||||
// 3. If status is false, throw a TypeError exception.
|
||||
if (status === Value.false) {
|
||||
return surroundingAgent.Throw('TypeError', 'UnableToPreventExtensions', O);
|
||||
}
|
||||
// 4. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.seal */
|
||||
function* Object_seal([O = Value.undefined]: Arguments) {
|
||||
// 1. If Type(O) is not Object, return O.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return O;
|
||||
}
|
||||
// 2. Let status be ? SetIntegrityLevel(O, sealed).
|
||||
const status = Q(yield* SetIntegrityLevel(O, 'sealed'));
|
||||
// 3. If status is false, throw a TypeError exception.
|
||||
if (status === Value.false) {
|
||||
return surroundingAgent.Throw('TypeError', 'UnableToSeal', O);
|
||||
}
|
||||
// 4. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.setprototypeof */
|
||||
function* Object_setPrototypeOf([O = Value.undefined, proto = Value.undefined]: Arguments) {
|
||||
// 1. Perform ? RequireObjectCoercible(O).
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
|
||||
if (!(proto instanceof ObjectValue) && !(proto instanceof NullValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType');
|
||||
}
|
||||
// 3. If Type(O) is not Object, return O.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return O;
|
||||
}
|
||||
// 4. Let status be ? O.[[SetPrototypeOf]](proto).
|
||||
const status = Q(yield* O.SetPrototypeOf(proto));
|
||||
// 5. If status is false, throw a TypeError exception.
|
||||
if (status === Value.false) {
|
||||
return surroundingAgent.Throw('TypeError', 'ObjectSetPrototype');
|
||||
}
|
||||
// 6. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.values */
|
||||
function* Object_values([O = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let obj be ? ToObject(O).
|
||||
const obj = Q(ToObject(O));
|
||||
// 2. Let nameList be ? EnumerableOwnPropertyNames(obj, value).
|
||||
const nameList = Q(yield* EnumerableOwnProperties(obj, 'value'));
|
||||
// 3. Return CreateArrayFromList(nameList).
|
||||
return CreateArrayFromList(nameList);
|
||||
}
|
||||
|
||||
export function bootstrapObject(realmRec: Realm) {
|
||||
const objectConstructor = bootstrapConstructor(realmRec, ObjectConstructor, 'Object', 1, realmRec.Intrinsics['%Object.prototype%'], [
|
||||
['assign', Object_assign, 2],
|
||||
['create', Object_create, 2],
|
||||
['defineProperties', Object_defineProperties, 2],
|
||||
['defineProperty', Object_defineProperty, 3],
|
||||
['entries', Object_entries, 1],
|
||||
['freeze', Object_freeze, 1],
|
||||
['fromEntries', Object_fromEntries, 1],
|
||||
['getOwnPropertyDescriptor', Object_getOwnPropertyDescriptor, 2],
|
||||
['getOwnPropertyDescriptors', Object_getOwnPropertyDescriptors, 1],
|
||||
['getOwnPropertyNames', Object_getOwnPropertyNames, 1],
|
||||
['getOwnPropertySymbols', Object_getOwnPropertySymbols, 1],
|
||||
['getPrototypeOf', Object_getPrototypeOf, 1],
|
||||
['groupBy', Object_groupBy, 2],
|
||||
['hasOwn', Object_hasOwn, 2],
|
||||
['is', Object_is, 2],
|
||||
['isExtensible', Object_isExtensible, 1],
|
||||
['isFrozen', Object_isFrozen, 1],
|
||||
['isSealed', Object_isSealed, 1],
|
||||
['keys', Object_keys, 1],
|
||||
['preventExtensions', Object_preventExtensions, 1],
|
||||
['seal', Object_seal, 1],
|
||||
['setPrototypeOf', Object_setPrototypeOf, 2],
|
||||
['values', Object_values, 1],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Object%'] = objectConstructor;
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
NullValue,
|
||||
JSStringValue,
|
||||
UndefinedValue,
|
||||
ObjectValue,
|
||||
Value,
|
||||
Descriptor,
|
||||
wellKnownSymbols,
|
||||
type FunctionCallContext,
|
||||
type Arguments,
|
||||
type ObjectInternalMethods,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__, type Mutable } from '../helpers.mts';
|
||||
import { assignProps } from './bootstrap.mts';
|
||||
import {
|
||||
DefinePropertyOrThrow,
|
||||
Get,
|
||||
HasOwnProperty,
|
||||
Invoke,
|
||||
IsAccessorDescriptor,
|
||||
IsArray,
|
||||
IsCallable,
|
||||
MakeBasicObject,
|
||||
Realm,
|
||||
RequireObjectCoercible,
|
||||
SameValue,
|
||||
SetImmutablePrototype,
|
||||
ToObject,
|
||||
ToPropertyKey,
|
||||
type BuiltinFunctionObject,
|
||||
type FunctionObject,
|
||||
type ImmutablePrototypeObject,
|
||||
type OrdinaryObject,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.hasownproperty */
|
||||
function* ObjectProto_hasOwnProperty([V = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let P be ? ToPropertyKey(V).
|
||||
const P = Q(yield* ToPropertyKey(V));
|
||||
// 2. Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 3. Return ? HasOwnProperty(O, P).
|
||||
return yield* HasOwnProperty(O, P);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.isprototypeof */
|
||||
function* ObjectProto_isPrototypeOf([V = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If Type(V) is not Object, return false.
|
||||
if (!(V instanceof ObjectValue)) {
|
||||
return Value.false;
|
||||
}
|
||||
// 2. Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 3. Repeat,
|
||||
while (true) {
|
||||
// a. Set V to ? V.[[GetPrototypeOf]]().
|
||||
V = Q(yield* (V as ObjectValue).GetPrototypeOf());
|
||||
// b. If V is null, return false.
|
||||
if (V === Value.null) {
|
||||
return Value.false;
|
||||
}
|
||||
// c. If SameValue(O, V) is true, return true.
|
||||
if (SameValue(O, V) === Value.true) {
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.propertyisenumerable */
|
||||
function* ObjectProto_propertyIsEnumerable([V = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let P be ? ToPropertyKey(V).
|
||||
const P = Q(yield* ToPropertyKey(V));
|
||||
// 2. Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 3. Let desc be ? O.[[GetOwnProperty]](P).
|
||||
const desc = Q(yield* O.GetOwnProperty(P));
|
||||
// 4. If desc is undefined, return false.
|
||||
if (desc instanceof UndefinedValue) {
|
||||
return Value.false;
|
||||
}
|
||||
// 5. Return desc.[[Enumerable]].
|
||||
return desc.Enumerable!;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.tolocalestring */
|
||||
function* ObjectProto_toLocaleString(_argList: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
// 2. Return ? Invoke(O, "toString").
|
||||
return Q(yield* Invoke(O, Value('toString')));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.tostring */
|
||||
function* ObjectProto_toString(_argList: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If the this value is undefined, return "[object Undefined]".
|
||||
if (thisValue === Value.undefined) {
|
||||
return Value('[object Undefined]');
|
||||
}
|
||||
// 2. If the this value is null, return "[object Null]".
|
||||
if (thisValue === Value.null) {
|
||||
return Value('[object Null]');
|
||||
}
|
||||
// 3. Let O be ! ToObject(this value).
|
||||
const O = X(ToObject(thisValue));
|
||||
// 4. Let isArray be ? IsArray(O).
|
||||
const isArray = Q(IsArray(O));
|
||||
let builtinTag;
|
||||
// 5. If isArray is true, let builtinTag be "Array".
|
||||
if (isArray === Value.true) {
|
||||
builtinTag = 'Array';
|
||||
} else if ('ParameterMap' in O) { // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments".
|
||||
builtinTag = 'Arguments';
|
||||
} else if ('Call' in O) { // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function".
|
||||
builtinTag = 'Function';
|
||||
} else if ('ErrorData' in O) { // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
|
||||
builtinTag = 'Error';
|
||||
} else if ('BooleanData' in O) { // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
|
||||
builtinTag = 'Boolean';
|
||||
} else if ('NumberData' in O) { // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".
|
||||
builtinTag = 'Number';
|
||||
} else if ('StringData' in O) { // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String".
|
||||
builtinTag = 'String';
|
||||
} else if ('DateValue' in O) { // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date".
|
||||
builtinTag = 'Date';
|
||||
} else if ('RegExpMatcher' in O) { // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
|
||||
builtinTag = 'RegExp';
|
||||
} else { // 14. Else, let builtinTag be "Object".
|
||||
builtinTag = 'Object';
|
||||
}
|
||||
// 15. Let tag be ? Get(O, @@toStringTag).
|
||||
const tag = Q(yield* Get(O, wellKnownSymbols.toStringTag));
|
||||
let tagStr;
|
||||
// 16. If Type(tag) is not String, set tag to builtinTag.
|
||||
if (!(tag instanceof JSStringValue)) {
|
||||
tagStr = builtinTag;
|
||||
} else {
|
||||
tagStr = tag.stringValue();
|
||||
}
|
||||
// 17. Return the string-concatenation of "[object ", tag, and "]".
|
||||
return Value(`[object ${tagStr}]`);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.valueof */
|
||||
function ObjectProto_valueOf(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Return ? ToObject(this value).
|
||||
return Q(ToObject(thisValue));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.__defineGetter__ */
|
||||
function* ObjectProto__defineGetter__([P = Value.undefined, getter = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 2. If IsCallable(getter) is false, throw a TypeError exception.
|
||||
if (!IsCallable(getter)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', getter);
|
||||
}
|
||||
// 3. Let desc be PropertyDescriptor { [[Get]]: getter, [[Enumerable]]: true, [[Configurable]]: true }.
|
||||
const desc = Descriptor({
|
||||
Get: getter,
|
||||
Enumerable: Value.true,
|
||||
Configurable: Value.true,
|
||||
});
|
||||
// 4. Let key be ? ToPropertyKey(P).
|
||||
const key = Q(yield* ToPropertyKey(P));
|
||||
// 5. Perform ? DefinePropertyOrThrow(O, key, desc).
|
||||
Q(yield* DefinePropertyOrThrow(O, key, desc));
|
||||
// 6. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.__defineSetter__ */
|
||||
function* ObjectProto__defineSetter__([P = Value.undefined, setter = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 2. If IsCallable(setter) is false, throw a TypeError exception.
|
||||
if (!IsCallable(setter)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', setter);
|
||||
}
|
||||
// 3. Let desc be PropertyDescriptor { [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: true }.
|
||||
const desc = Descriptor({
|
||||
Set: setter,
|
||||
Enumerable: Value.true,
|
||||
Configurable: Value.true,
|
||||
});
|
||||
// 4. Let key be ? ToPropertyKey(P).
|
||||
const key = Q(yield* ToPropertyKey(P));
|
||||
// 5. Perform ? DefinePropertyOrThrow(O, key, desc).
|
||||
Q(yield* DefinePropertyOrThrow(O, key, desc));
|
||||
// 6. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.__lookupGetter__ */
|
||||
function* ObjectProto__lookupGetter__([P = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
let O: NullValue | ObjectValue = Q(ToObject(thisValue));
|
||||
// 2. Let key be ? ToPropertyKey(P).
|
||||
const key = Q(yield* ToPropertyKey(P));
|
||||
// 3. Repeat,
|
||||
while (true) {
|
||||
__ts_cast__<ObjectValue>(O);
|
||||
// a. Let desc be ? O.[[GetOwnProperty]](key).
|
||||
const desc = Q(yield* O.GetOwnProperty(key));
|
||||
// b. If desc is not undefined, then
|
||||
if (!(desc instanceof UndefinedValue)) {
|
||||
// i. If IsAccessorDescriptor(desc) is true, return desc.[[Get]].
|
||||
if (IsAccessorDescriptor(desc)) {
|
||||
return desc.Get;
|
||||
}
|
||||
// ii. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
// c. Set O to ? O.[[GetPrototypeOf]]().
|
||||
O = Q(yield* O.GetPrototypeOf());
|
||||
// d. If O is null, return undefined.
|
||||
if (O === Value.null) {
|
||||
return Value.undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-object.prototype.__lookupSetter__ */
|
||||
function* ObjectProto__lookupSetter__([P = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
let O: NullValue | ObjectValue = Q(ToObject(thisValue));
|
||||
// 2. Let key be ? ToPropertyKey(P).
|
||||
const key = Q(yield* ToPropertyKey(P));
|
||||
// 3. Repeat,
|
||||
while (true) {
|
||||
__ts_cast__<ObjectValue>(O);
|
||||
// a. Let desc be ? O.[[GetOwnProperty]](key).
|
||||
const desc = Q(yield* O.GetOwnProperty(key));
|
||||
// b. If desc is not undefined, then
|
||||
if (!(desc instanceof UndefinedValue)) {
|
||||
// i. If IsAccessorDescriptor(desc) is true, return desc.[[Set]].
|
||||
if (IsAccessorDescriptor(desc)) {
|
||||
return desc.Set;
|
||||
}
|
||||
// ii. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
// c. Set O to ? O.[[GetPrototypeOf]]().
|
||||
O = Q(yield* O.GetPrototypeOf());
|
||||
// d. If O is null, return undefined.
|
||||
if (O === Value.null) {
|
||||
return Value.undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-object.prototype.__proto__ */
|
||||
function* ObjectProto__proto__Get(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be ? ToObject(this value).
|
||||
const O = Q(ToObject(thisValue));
|
||||
// 2. Return ? O.[[GetPrototypeOf]]().
|
||||
return Q(yield* O.GetPrototypeOf());
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set-object.prototype.__proto__ */
|
||||
function* ObjectProto__proto__Set([proto = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the *this* value.
|
||||
// 2. Perform ? RequireObjectCoercible(this value).
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2. If Type(proto) is neither Object nor Null, return undefined.
|
||||
if (!(proto instanceof ObjectValue) && !(proto instanceof NullValue)) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 3. If Type(O) is not Object, return undefined.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 4. Let status be ? O.[[SetPrototypeOf]](proto).
|
||||
const status = Q(yield* O.SetPrototypeOf(proto));
|
||||
// 5. If status is false, throw a TypeError exception.
|
||||
if (status === Value.false) {
|
||||
return surroundingAgent.Throw('TypeError', 'ObjectSetPrototype');
|
||||
}
|
||||
// 6. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
const InternalMethods = {
|
||||
/** https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-immutable-prototype-exotic-objects-setprototypeof-v */
|
||||
* SetPrototypeOf(V) {
|
||||
// 1. Return ? SetImmutablePrototype(O, V).
|
||||
return Q(yield* SetImmutablePrototype(this, V));
|
||||
},
|
||||
} satisfies Partial<ObjectInternalMethods<ImmutablePrototypeObject>>;
|
||||
|
||||
/** https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-properties-of-the-object-prototype-object */
|
||||
export function makeObjectPrototype(realmRec: Realm) {
|
||||
// The Object prototype object:
|
||||
const proto = MakeBasicObject(['Prototype', 'Extensible']) as Mutable<ImmutablePrototypeObject & OrdinaryObject>;
|
||||
|
||||
// * has an [[Extensible]] internal slot whose value is true.
|
||||
proto.Extensible = Value.true;
|
||||
|
||||
// * has a [[Prototype]] internal slot whose value is null.
|
||||
proto.Prototype = Value.null;
|
||||
|
||||
// * has the internal methods defined for ordinary objects, except for the [[SetPrototypeOf]] method, which is as defined in 10.4.7.1.
|
||||
// (Thus, it is an immutable prototype exotic object.)
|
||||
proto.SetPrototypeOf = InternalMethods.SetPrototypeOf;
|
||||
|
||||
// * is %Object.prototype%.
|
||||
realmRec.Intrinsics['%Object.prototype%'] = proto;
|
||||
}
|
||||
|
||||
export function bootstrapObjectPrototype(realmRec: Realm) {
|
||||
const proto = realmRec.Intrinsics['%Object.prototype%'];
|
||||
|
||||
assignProps(realmRec, proto, [
|
||||
['hasOwnProperty', ObjectProto_hasOwnProperty, 1],
|
||||
['isPrototypeOf', ObjectProto_isPrototypeOf, 1],
|
||||
['propertyIsEnumerable', ObjectProto_propertyIsEnumerable, 1],
|
||||
['toLocaleString', ObjectProto_toLocaleString, 0],
|
||||
['toString', ObjectProto_toString, 0],
|
||||
['valueOf', ObjectProto_valueOf, 0],
|
||||
['__defineGetter__', ObjectProto__defineGetter__, 2],
|
||||
['__defineSetter__', ObjectProto__defineSetter__, 2],
|
||||
['__lookupGetter__', ObjectProto__lookupGetter__, 1],
|
||||
['__lookupSetter__', ObjectProto__lookupSetter__, 1],
|
||||
['__proto__', [ObjectProto__proto__Get, ObjectProto__proto__Set]],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Object.prototype.toString%'] = X(Get(proto, Value('toString'))) as BuiltinFunctionObject;
|
||||
realmRec.Intrinsics['%Object.prototype.valueOf%'] = X(Get(proto, Value('valueOf'))) as FunctionObject;
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import {
|
||||
BooleanValue,
|
||||
Descriptor,
|
||||
ObjectValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
AbruptCompletion,
|
||||
IfAbruptRejectPromise,
|
||||
EnsureCompletion,
|
||||
Q, X,
|
||||
type ValueEvaluator,
|
||||
type ValueCompletion,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__, type Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
CreateArrayFromList,
|
||||
CreateBuiltinFunction,
|
||||
CreateDataProperty,
|
||||
CreateDataPropertyOrThrow,
|
||||
CreateResolvingFunctions,
|
||||
DefinePropertyOrThrow,
|
||||
Get,
|
||||
GetIterator,
|
||||
Invoke,
|
||||
IsCallable,
|
||||
IsConstructor,
|
||||
IteratorClose,
|
||||
NewPromiseCapability,
|
||||
OrdinaryObjectCreate,
|
||||
OrdinaryCreateFromConstructor,
|
||||
PromiseCapabilityRecord,
|
||||
PromiseResolve,
|
||||
PromiseReactionRecord,
|
||||
type FunctionObject,
|
||||
Realm,
|
||||
type IteratorRecord,
|
||||
type OrdinaryObject,
|
||||
type PromiseAllResolveElementFunctionObject,
|
||||
type PromiseAllRejectElementFunctionObject,
|
||||
IteratorStepValue,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#table-internal-slots-of-promise-instances */
|
||||
export interface PromiseObject extends OrdinaryObject {
|
||||
PromiseState: 'pending' | 'fulfilled' | 'rejected';
|
||||
PromiseResult: Value | undefined;
|
||||
PromiseFulfillReactions: undefined | PromiseReactionRecord[];
|
||||
PromiseRejectReactions: undefined | PromiseReactionRecord[];
|
||||
PromiseIsHandled: BooleanValue;
|
||||
}
|
||||
|
||||
export function isPromiseObject(value: Value): value is PromiseObject {
|
||||
return 'PromiseState' in value;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise-executor */
|
||||
function* PromiseConstructor(this: FunctionObject, [executor = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. If IsCallable(executor) is false, throw a TypeError exception.
|
||||
if (!IsCallable(executor)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', executor);
|
||||
}
|
||||
// 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »).
|
||||
const promise = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Promise.prototype%', [
|
||||
'PromiseState',
|
||||
'PromiseResult',
|
||||
'PromiseFulfillReactions',
|
||||
'PromiseRejectReactions',
|
||||
'PromiseIsHandled',
|
||||
])) as Mutable<PromiseObject>;
|
||||
// 4. Set promise.[[PromiseState]] to pending.
|
||||
promise.PromiseState = 'pending';
|
||||
// 5. Set promise.[[PromiseFulfillReactions]] to a new empty List.
|
||||
promise.PromiseFulfillReactions = [];
|
||||
// 6. Set promise.[[PromiseFulfillReactions]] to a new empty List.
|
||||
promise.PromiseRejectReactions = [];
|
||||
// 7. Set promise.[[PromiseIsHandled]] to false.
|
||||
promise.PromiseIsHandled = Value.false;
|
||||
// 8. Let resolvingFunctions be CreateResolvingFunctions(promise).
|
||||
const resolvingFunctions = CreateResolvingFunctions(promise);
|
||||
// 9. Let completion be Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »).
|
||||
const completion = yield* Call(executor, Value.undefined, [
|
||||
resolvingFunctions.Resolve, resolvingFunctions.Reject,
|
||||
]);
|
||||
// 10. If completion is an abrupt completion, then
|
||||
if (completion instanceof AbruptCompletion) {
|
||||
// a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »).
|
||||
Q(yield* Call(resolvingFunctions.Reject, Value.undefined, [completion.Value]));
|
||||
}
|
||||
// 11. Return promise.
|
||||
return promise;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-getpromiseresolve */
|
||||
function* GetPromiseResolve(promiseConstructor: FunctionObject) {
|
||||
// 1. Assert: IsConstructor(promiseConstructor) is true.
|
||||
Assert(IsConstructor(promiseConstructor));
|
||||
// 2. Let promiseResolve be ? Get(promiseConstructor, "resolve").
|
||||
const promiseResolve = Q(yield* Get(promiseConstructor, Value('resolve')));
|
||||
// 3. If IsCallable(promiseResolve) is false, throw a TypeError exception.
|
||||
if (!IsCallable(promiseResolve)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', promiseResolve);
|
||||
}
|
||||
// 4. Return promiseResolve.
|
||||
return promiseResolve;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-performpromiseall */
|
||||
export function* PerformPromiseAll(iteratorRecord: IteratorRecord, constructor: FunctionObject, resultCapability: PromiseCapabilityRecord, promiseResolve: FunctionObject): ValueEvaluator {
|
||||
// 1. Assert: IsConstructor(constructor) is true.
|
||||
Assert(IsConstructor(constructor));
|
||||
// 2. Assert: resultCapability is a PromiseCapability Record.
|
||||
Assert(resultCapability instanceof PromiseCapabilityRecord);
|
||||
// 3. Assert: IsCallable(promiseResolve) is true.
|
||||
Assert(IsCallable(promiseResolve));
|
||||
// 4. Let values be a new empty List.
|
||||
const values: Value[] = [];
|
||||
// 5. Let remainingElementsCount be the Record { [[Value]]: 1 }.
|
||||
const remainingElementsCount = { Value: 1 };
|
||||
// 6. Let index be 0.
|
||||
let index = 0;
|
||||
// 7. Repeat,
|
||||
while (true) {
|
||||
// a. Let next be ? IteratorStepValue(iteratorRecord).
|
||||
const next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
// d. If next is done, then
|
||||
if (next === 'done') {
|
||||
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
|
||||
remainingElementsCount.Value -= 1;
|
||||
// iii. If remainingElementsCount.[[Value]] is 0, then
|
||||
if (remainingElementsCount.Value === 0) {
|
||||
// 1. Let valuesArray be ! CreateArrayFromList(values).
|
||||
const valuesArray = CreateArrayFromList(values);
|
||||
// 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »).
|
||||
Q(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray]));
|
||||
}
|
||||
// iv. Return resultCapability.[[Promise]].
|
||||
return resultCapability.Promise;
|
||||
}
|
||||
// h. Append undefined to values.
|
||||
values.push(Value.undefined);
|
||||
// i. Let nextPromise be ? Call(promiseResolve, constructor, « next »).
|
||||
const nextPromise = Q(yield* Call(promiseResolve, constructor, [next]));
|
||||
const fulfilledSteps = function* PromiseAllResolveElementFunctions([x = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const F = surroundingAgent.activeFunctionObject as PromiseAllResolveElementFunctionObject;
|
||||
const alreadyCalled = F.AlreadyCalled;
|
||||
if (alreadyCalled.Value === true) {
|
||||
return Value.undefined;
|
||||
}
|
||||
alreadyCalled.Value = true;
|
||||
const thisIndex = F.Index;
|
||||
values[thisIndex] = x;
|
||||
remainingElementsCount.Value -= 1;
|
||||
if (remainingElementsCount.Value === 0) {
|
||||
const valuesArray = CreateArrayFromList(values);
|
||||
return Q(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray]));
|
||||
}
|
||||
return Value.undefined;
|
||||
};
|
||||
const onFulfilled = X(CreateBuiltinFunction(fulfilledSteps, 1, Value(''), ['AlreadyCalled', 'Index'])) as Mutable<PromiseAllResolveElementFunctionObject>;
|
||||
onFulfilled.AlreadyCalled = { Value: false };
|
||||
onFulfilled.Index = index;
|
||||
index += 1;
|
||||
remainingElementsCount.Value += 1;
|
||||
Q(yield* Invoke(nextPromise, Value('then'), [onFulfilled, resultCapability.Reject]));
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.all */
|
||||
function* Promise_all([iterable = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 2. Let promiseCapability be ? NewPromiseCapability(C).
|
||||
const promiseCapability = Q(yield* NewPromiseCapability(C));
|
||||
__ts_cast__<FunctionObject>(C);
|
||||
// 3. Let promiseResolve be GetPromiseResolve(C).
|
||||
const promiseResolve = yield* GetPromiseResolve(C);
|
||||
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
|
||||
IfAbruptRejectPromise(promiseResolve, promiseCapability);
|
||||
__ts_cast__<FunctionObject>(promiseResolve);
|
||||
// 5. Let iteratorRecord be GetIterator(iterable).
|
||||
const iteratorRecord = yield* GetIterator(iterable, 'sync');
|
||||
// 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
|
||||
IfAbruptRejectPromise(iteratorRecord, promiseCapability);
|
||||
__ts_cast__<IteratorRecord>(iteratorRecord);
|
||||
// 7. Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve).
|
||||
let result: ValueCompletion = yield* PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve);
|
||||
// 8. If result is an abrupt completion, then
|
||||
if (result instanceof AbruptCompletion) {
|
||||
// a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
|
||||
if (iteratorRecord.Done === Value.false) {
|
||||
result = yield* IteratorClose(iteratorRecord, result);
|
||||
}
|
||||
// b. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
}
|
||||
// 9. Return ? result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-performpromiseallsettled */
|
||||
function* PerformPromiseAllSettled(iteratorRecord: IteratorRecord, constructor: FunctionObject, resultCapability: PromiseCapabilityRecord, promiseResolve: FunctionObject): ValueEvaluator {
|
||||
// 1. Assert: ! IsConstructor(constructor) is true.
|
||||
Assert(IsConstructor(constructor));
|
||||
// 2. Assert: resultCapability is a PromiseCapability Record.
|
||||
Assert(resultCapability instanceof PromiseCapabilityRecord);
|
||||
// 3. Assert: IsCallable(promiseResolve) is true.
|
||||
Assert(IsCallable(promiseResolve));
|
||||
// 4. Let values be a new empty List.
|
||||
const values: Value[] = [];
|
||||
// 5. Let remainingElementsCount be the Record { [[Value]]: 1 }.
|
||||
const remainingElementsCount = { Value: 1 };
|
||||
// 6. Let index be 0.
|
||||
let index = 0;
|
||||
// 7. Repeat,
|
||||
while (true) {
|
||||
// a. Let next be ? IteratorStepValue(iteratorRecord).
|
||||
const next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
// d. If next is done,
|
||||
if (next === 'done') {
|
||||
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
|
||||
remainingElementsCount.Value -= 1;
|
||||
// iii. If remainingElementsCount.[[Value]] is 0, then
|
||||
if (remainingElementsCount.Value === 0) {
|
||||
// 1. Let valuesArray be ! CreateArrayFromList(values).
|
||||
const valuesArray = X(CreateArrayFromList(values));
|
||||
// 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »).
|
||||
Q(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray]));
|
||||
}
|
||||
// iv. Return resultCapability.[[Promise]].
|
||||
return resultCapability.Promise;
|
||||
}
|
||||
// h. Append undefined to values.
|
||||
values.push(Value.undefined);
|
||||
// i. Let nextPromise be ? Call(promiseResolve, constructor, « next »).
|
||||
const nextPromise = Q(yield* Call(promiseResolve, constructor, [next]));
|
||||
// j. Let fulfilledSteps be the algorithm steps defined in Promise.allSettled Resolve Element Functions.
|
||||
const fulfilledSteps = function* PromiseAllSettledResolveElementFunctions([value = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const F = surroundingAgent.activeFunctionObject as PromiseAllResolveElementFunctionObject;
|
||||
const alreadyCalled = F.AlreadyCalled;
|
||||
if (alreadyCalled.Value === true) {
|
||||
return Value.undefined;
|
||||
}
|
||||
alreadyCalled.Value = true;
|
||||
const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
X(CreateDataProperty(obj, Value('status'), Value('fulfilled')));
|
||||
X(CreateDataProperty(obj, Value('value'), value));
|
||||
const thisIndex = F.Index;
|
||||
values[thisIndex] = obj;
|
||||
remainingElementsCount.Value -= 1;
|
||||
if (remainingElementsCount.Value === 0) {
|
||||
const valuesArray = CreateArrayFromList(values);
|
||||
return Q(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray]));
|
||||
}
|
||||
return Value.undefined;
|
||||
};
|
||||
// l. Let onFulfilled be ! CreateBuiltinFunction(fulfilledSteps, 1, "", « [[AlreadyCalled]], [[Index]] »).
|
||||
const onFulfilled = X(CreateBuiltinFunction(fulfilledSteps, 1, Value(''), [
|
||||
'AlreadyCalled',
|
||||
'Index',
|
||||
'Values',
|
||||
'Capability',
|
||||
'RemainingElements',
|
||||
])) as Mutable<PromiseAllResolveElementFunctionObject>;
|
||||
// m. Let alreadyCalled be the Record { [[Value]]: false }.
|
||||
const alreadyCalled = { Value: false };
|
||||
// n. Set onFulfilled.[[AlreadyCalled]] to alreadyCalled.
|
||||
onFulfilled.AlreadyCalled = alreadyCalled;
|
||||
// o. Set onFulfilled.[[Index]] to index.
|
||||
onFulfilled.Index = index;
|
||||
// s. Let rejectedSteps be the algorithm steps defined in Promise.allSettled Reject Element Functions.
|
||||
const rejectedSteps = function* PromiseAllSettledRejectElementFunctions([error = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const F = surroundingAgent.activeFunctionObject as PromiseAllResolveElementFunctionObject;
|
||||
const alreadyCalled = F.AlreadyCalled;
|
||||
if (alreadyCalled.Value === true) {
|
||||
return Value.undefined;
|
||||
}
|
||||
alreadyCalled.Value = true;
|
||||
const obj = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
X(CreateDataProperty(obj, Value('status'), Value('rejected')));
|
||||
X(CreateDataProperty(obj, Value('reason'), error));
|
||||
const thisIndex = F.Index;
|
||||
values[thisIndex] = obj;
|
||||
remainingElementsCount.Value -= 1;
|
||||
if (remainingElementsCount.Value === 0) {
|
||||
const valuesArray = X(CreateArrayFromList(values));
|
||||
return Q(yield* Call(resultCapability.Resolve, Value.undefined, [valuesArray]));
|
||||
}
|
||||
return Value.undefined;
|
||||
};
|
||||
// u. Let onRejected be ! CreateBuiltinFunction(rejectedSteps, 1, "", « [[AlreadyCalled]], [[Index]] »).
|
||||
const onRejected = X(CreateBuiltinFunction(rejectedSteps, 1, Value(''), ['AlreadyCalled', 'Index'])) as Mutable<PromiseAllResolveElementFunctionObject>;
|
||||
onRejected.AlreadyCalled = alreadyCalled;
|
||||
onRejected.Index = index;
|
||||
index += 1;
|
||||
remainingElementsCount.Value += 1;
|
||||
Q(yield* Invoke(nextPromise, Value('then'), [onFulfilled, onRejected]));
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.allsettled */
|
||||
function* Promise_allSettled([iterable = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 2. Let promiseCapability be ? NewPromiseCapability(C).
|
||||
const promiseCapability = Q(yield* NewPromiseCapability(C));
|
||||
__ts_cast__<FunctionObject>(C);
|
||||
// 3. Let promiseResolve be GetPromiseResolve(C).
|
||||
const promiseResolve = yield* GetPromiseResolve(C);
|
||||
__ts_cast__<FunctionObject>(promiseResolve);
|
||||
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
|
||||
IfAbruptRejectPromise(promiseResolve, promiseCapability);
|
||||
// 5. Let iteratorRecord be GetIterator(iterable).
|
||||
const iteratorRecord = yield* GetIterator(iterable, 'sync');
|
||||
// 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
|
||||
IfAbruptRejectPromise(iteratorRecord, promiseCapability);
|
||||
__ts_cast__<IteratorRecord>(iteratorRecord);
|
||||
// 7. Let result be PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve).
|
||||
let result: ValueCompletion = yield* PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve);
|
||||
// 8. If result is an abrupt completion, then
|
||||
if (result instanceof AbruptCompletion) {
|
||||
// a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
|
||||
if (iteratorRecord.Done === Value.false) {
|
||||
result = yield* IteratorClose(iteratorRecord, result);
|
||||
}
|
||||
// b. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
}
|
||||
// 9. Return ? result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-performpromiseany */
|
||||
function* PerformPromiseAny(iteratorRecord: IteratorRecord, constructor: FunctionObject, resultCapability: PromiseCapabilityRecord, promiseResolve: FunctionObject): ValueEvaluator {
|
||||
// 1. Assert: ! IsConstructor(constructor) is true.
|
||||
Assert(IsConstructor(constructor));
|
||||
// 2. Assert: resultCapability is a PromiseCapability Record.
|
||||
Assert(resultCapability instanceof PromiseCapabilityRecord);
|
||||
// 3. Assert: ! IsCallable(promiseResolve) is true.
|
||||
Assert(IsCallable(promiseResolve));
|
||||
// 4. Let errors be a new empty List.
|
||||
const errors: Value[] = [];
|
||||
// 5. Let remainingElementsCount be a new Record { [[Value]]: 1 }.
|
||||
const remainingElementsCount = { Value: 1 };
|
||||
// 6. Let index be 0.
|
||||
let index = 0;
|
||||
// 7. Repeat,
|
||||
while (true) {
|
||||
// a. Let next be ? IteratorStepValue(iteratorRecord).
|
||||
const next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
// d. If next is done, then
|
||||
if (next === 'done') {
|
||||
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
|
||||
remainingElementsCount.Value -= 1;
|
||||
// iii. If remainingElementsCount.[[Value]] is 0, then
|
||||
if (remainingElementsCount.Value === 0) {
|
||||
// 1. Let aggregateError be a newly created AggregateError object.
|
||||
const aggregateError = surroundingAgent.Throw('AggregateError', 'PromiseAnyRejected').Value as ObjectValue;
|
||||
// 2. Perform ! DefinePropertyOrThrow(aggregateError, "errors", Property Descriptor { [[Configurable]]: true, [[Enumerable]]: false, [[Writable]]: true, [[Value]]: errors }).
|
||||
X(DefinePropertyOrThrow(aggregateError, Value('errors'), Descriptor({
|
||||
Configurable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Writable: Value.true,
|
||||
Value: X(CreateArrayFromList(errors)),
|
||||
})));
|
||||
// 3. Perform ? Call(resultCapability.[[Reject]], *undefined*, « _aggregateError_ »).
|
||||
Q(yield* Call(resultCapability.Reject, Value.undefined, [aggregateError]));
|
||||
}
|
||||
// iv. Return resultCapability.[[Promise]].
|
||||
return resultCapability.Promise;
|
||||
}
|
||||
// h. Append undefined to errors.
|
||||
errors.push(Value.undefined);
|
||||
// i. Let nextPromise be ? Call(promiseResolve, constructor, « next »).
|
||||
const nextPromise = Q(yield* Call(promiseResolve, constructor, [next]));
|
||||
const rejectedSteps = function* PromiseAnyRejectElementFunctions([error = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const F = surroundingAgent.activeFunctionObject as PromiseAllRejectElementFunctionObject;
|
||||
const alreadyCalled = F.AlreadyCalled;
|
||||
if (alreadyCalled.Value) {
|
||||
return Value.undefined;
|
||||
}
|
||||
alreadyCalled.Value = true;
|
||||
const thisIndex = F.Index;
|
||||
errors[thisIndex] = error;
|
||||
remainingElementsCount.Value -= 1;
|
||||
if (remainingElementsCount.Value === 0) {
|
||||
const aggregateError = surroundingAgent.Throw('AggregateError', 'PromiseAnyRejected').Value as ObjectValue;
|
||||
X(DefinePropertyOrThrow(aggregateError, Value('errors'), Descriptor({
|
||||
Configurable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Writable: Value.true,
|
||||
Value: X(CreateArrayFromList(errors)),
|
||||
})));
|
||||
return Q(yield* Call(resultCapability.Reject, Value.undefined, [aggregateError]));
|
||||
}
|
||||
return Value.undefined;
|
||||
};
|
||||
// l. Let onRejected be ! CreateBuiltinFunction(stepsRejected, lengthRejected, "", « [[AlreadyCalled]], [[Index]], [[Errors]], [[Capability]], [[RemainingElements]] »).
|
||||
const onRejected = X(CreateBuiltinFunction(rejectedSteps, 1, Value(''), ['AlreadyCalled', 'Index'])) as Mutable<PromiseAllRejectElementFunctionObject>;
|
||||
onRejected.AlreadyCalled = { Value: false };
|
||||
onRejected.Index = index;
|
||||
index += 1;
|
||||
remainingElementsCount.Value += 1;
|
||||
Q(yield* Invoke(nextPromise, Value('then'), [resultCapability.Resolve, onRejected]));
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.any */
|
||||
function* Promise_any([iterable = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 2. Let promiseCapability be ? NewPromiseCapability(C).
|
||||
const promiseCapability = Q(yield* NewPromiseCapability(C));
|
||||
__ts_cast__<FunctionObject>(C);
|
||||
// 3. Let promiseResolve be GetPromiseResolve(C).
|
||||
const promiseResolve = yield* GetPromiseResolve(C);
|
||||
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
|
||||
IfAbruptRejectPromise(promiseResolve, promiseCapability);
|
||||
__ts_cast__<FunctionObject>(promiseResolve);
|
||||
// 5. Let iteratorRecord be GetIterator(iterable).
|
||||
const iteratorRecord = yield* GetIterator(iterable, 'sync');
|
||||
// 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
|
||||
IfAbruptRejectPromise(iteratorRecord, promiseCapability);
|
||||
__ts_cast__<IteratorRecord>(iteratorRecord);
|
||||
// 7. Let result be PerformPromiseAny(iteratorRecord, C, promiseCapability).
|
||||
let result: ValueCompletion = yield* PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve);
|
||||
// 8. If result is an abrupt completion, then
|
||||
if (result instanceof AbruptCompletion) {
|
||||
// a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
|
||||
if (iteratorRecord.Done === Value.false) {
|
||||
result = yield* IteratorClose(iteratorRecord, result);
|
||||
}
|
||||
// b. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
}
|
||||
// 9. Return ? result.
|
||||
return result;
|
||||
}
|
||||
|
||||
function* PerformPromiseRace(iteratorRecord: IteratorRecord, constructor: FunctionObject, resultCapability: PromiseCapabilityRecord, promiseResolve: FunctionObject): ValueEvaluator {
|
||||
// 1. Assert: IsConstructor(constructor) is true.
|
||||
Assert(IsConstructor(constructor));
|
||||
// 2. Assert: resultCapability is a PromiseCapability Record.
|
||||
Assert(resultCapability instanceof PromiseCapabilityRecord);
|
||||
// 3. Assert: IsCallable(promiseResolve) is true.
|
||||
Assert(IsCallable(promiseResolve));
|
||||
// 4. Repeat,
|
||||
while (true) {
|
||||
// a. Let next be ? IteratorStepValue(iteratorRecord).
|
||||
const next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
// d. If next is done, then
|
||||
if (next === 'done') {
|
||||
// ii. Return resultCapability.[[Promise]].
|
||||
return resultCapability.Promise;
|
||||
}
|
||||
// h. Let nextPromise be ? Call(promiseResolve, constructor, « next »).
|
||||
const nextPromise = Q(yield* Call(promiseResolve, constructor, [next]));
|
||||
// i. Perform ? Invoke(nextPromise, "then", « resultCapability.[[Resolve]], resultCapability.[[Reject]] »).
|
||||
Q(yield* Invoke(nextPromise, Value('then'), [resultCapability.Resolve, resultCapability.Reject]));
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.race */
|
||||
function* Promise_race([iterable = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 2. Let promiseCapability be ? NewPromiseCapability(C).
|
||||
const promiseCapability = Q(yield* NewPromiseCapability(C));
|
||||
__ts_cast__<FunctionObject>(C);
|
||||
// 3. Let promiseResolve be GetPromiseResolve(C).
|
||||
const promiseResolve = yield* GetPromiseResolve(C);
|
||||
__ts_cast__<FunctionObject>(promiseResolve);
|
||||
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
|
||||
IfAbruptRejectPromise(promiseResolve, promiseCapability);
|
||||
// 5. Let iteratorRecord be GetIterator(iterable).
|
||||
const iteratorRecord = yield* GetIterator(iterable, 'sync');
|
||||
// 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
|
||||
IfAbruptRejectPromise(iteratorRecord, promiseCapability);
|
||||
__ts_cast__<IteratorRecord>(iteratorRecord);
|
||||
// 7. Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve).
|
||||
let result: ValueCompletion = yield* PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve);
|
||||
// 8. If result is an abrupt completion, then
|
||||
if (result instanceof AbruptCompletion) {
|
||||
// a. If iteratorRecord.[[Done]] is false, set result to IteratorClose(iteratorRecord, result).
|
||||
if (iteratorRecord.Done === Value.false) {
|
||||
result = yield* IteratorClose(iteratorRecord, result);
|
||||
}
|
||||
// b. IfAbruptRejectPromise(result, promiseCapability).
|
||||
IfAbruptRejectPromise(result, promiseCapability);
|
||||
}
|
||||
// 9. Return ? result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.reject */
|
||||
function* Promise_reject([r = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let C be this value.
|
||||
const C = thisValue;
|
||||
// 2. Let promiseCapability be ? NewPromiseCapability(C).
|
||||
const promiseCapability = Q(yield* NewPromiseCapability(C));
|
||||
// 3. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
|
||||
Q(yield* Call(promiseCapability.Reject, Value.undefined, [r]));
|
||||
// 4. Return promiseCapability.[[Promise]].
|
||||
return promiseCapability.Promise;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.resolve */
|
||||
function* Promise_resolve([x = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 2. If Type(C) is not Object, throw a TypeError exception.
|
||||
if (!(C instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidReceiver', 'Promise.resolve', C);
|
||||
}
|
||||
// 3. Return ? PromiseResolve(C, x).
|
||||
return Q(yield* PromiseResolve(C, x));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-promise-@@species */
|
||||
function Promise_symbolSpecies(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Return the this value.
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.try */
|
||||
function* Promise_try([callback = Value.undefined, ...args]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 2. If C is not an Object, throw a TypeError exception.
|
||||
if (!(C instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidReceiver', 'Promise.try', C);
|
||||
}
|
||||
// 3. Let promiseCapability be ? NewPromiseCapability(C).
|
||||
const promiseCapability: PromiseCapabilityRecord = Q(yield* NewPromiseCapability(C));
|
||||
// 4. Let status be Completion(Call(callback, undefined, args)).
|
||||
const status = EnsureCompletion(yield* Call(callback, Value.undefined, args as Arguments));
|
||||
|
||||
if (status instanceof AbruptCompletion) {
|
||||
// 5. If status is an abrupt completion, then
|
||||
// a. Perform ? Call(promiseCapability.[[Reject]], undefined, « status.[[Value]] »).
|
||||
Q(yield* Call(promiseCapability.Reject, Value.undefined, [status.Value]));
|
||||
} else {
|
||||
// 6. Else,
|
||||
// a. Perform ? Call(promiseCapability.[[Resolve]], undefined, « status.[[Value]] »).
|
||||
Q(yield* Call(promiseCapability.Resolve, Value.undefined, [status.Value]));
|
||||
}
|
||||
// 7. Return promiseCapability.[[Promise]].
|
||||
return EnsureCompletion(promiseCapability.Promise);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.withResolvers */
|
||||
function* Promise_withResolvers(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 2. Let promiseCapability be ? NewPromiseCapability(C).
|
||||
const promiseCapability: PromiseCapabilityRecord = Q(yield* NewPromiseCapability(C));
|
||||
// 3. Let obj be OrdinaryObjectCreate(%Object.prototype%).
|
||||
const obj = X(OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%')));
|
||||
// 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]).
|
||||
X(CreateDataPropertyOrThrow(obj, Value('promise'), promiseCapability.Promise));
|
||||
// 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]).
|
||||
X(CreateDataPropertyOrThrow(obj, Value('resolve'), promiseCapability.Resolve));
|
||||
// 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]).
|
||||
X(CreateDataPropertyOrThrow(obj, Value('reject'), promiseCapability.Reject));
|
||||
// 7. Return obj.
|
||||
return EnsureCompletion(obj);
|
||||
}
|
||||
|
||||
export function bootstrapPromise(realmRec: Realm) {
|
||||
const promiseConstructor = bootstrapConstructor(realmRec, PromiseConstructor, 'Promise', 1, realmRec.Intrinsics['%Promise.prototype%'], [
|
||||
['all', Promise_all, 1],
|
||||
['allSettled', Promise_allSettled, 1],
|
||||
['any', Promise_any, 1],
|
||||
['race', Promise_race, 1],
|
||||
['reject', Promise_reject, 1],
|
||||
['resolve', Promise_resolve, 1],
|
||||
['try', Promise_try, 1],
|
||||
['withResolvers', Promise_withResolvers, 0],
|
||||
[wellKnownSymbols.species, [Promise_symbolSpecies]],
|
||||
]);
|
||||
|
||||
X(promiseConstructor.DefineOwnProperty(Value('prototype'), Descriptor({
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.false,
|
||||
})));
|
||||
|
||||
realmRec.Intrinsics['%Promise%'] = promiseConstructor;
|
||||
realmRec.Intrinsics['%Promise.resolve%'] = X(Get(promiseConstructor, Value('resolve'))) as FunctionObject;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
Q, ThrowCompletion, X,
|
||||
} from '../completion.mts';
|
||||
import type { ValueEvaluator } from '../evaluator.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { PromiseObject } from './Promise.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
CreateBuiltinFunction,
|
||||
Get,
|
||||
Invoke,
|
||||
IsCallable,
|
||||
IsConstructor,
|
||||
IsPromise,
|
||||
NewPromiseCapability,
|
||||
PerformPromiseThen,
|
||||
PromiseResolve,
|
||||
Realm,
|
||||
SpeciesConstructor,
|
||||
type FunctionObject,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.prototype.catch */
|
||||
function* PromiseProto_catch([onRejected = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let promise be the this value.
|
||||
const promise = thisValue;
|
||||
// 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
|
||||
return Q(yield* Invoke(promise, Value('then'), [Value.undefined, onRejected]));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.prototype.finally */
|
||||
function* PromiseProto_finally([onFinally = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let promise be the this value.
|
||||
const promise = thisValue;
|
||||
// 2. If Type(promise) is not Object, throw a TypeError exception.
|
||||
if (!(promise instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Promise', promise);
|
||||
}
|
||||
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
|
||||
const C = Q(yield* SpeciesConstructor(promise, surroundingAgent.intrinsic('%Promise%')));
|
||||
// 4. Assert: IsConstructor(C) is true.
|
||||
Assert(IsConstructor(C));
|
||||
let thenFinally;
|
||||
let catchFinally;
|
||||
// 5. If IsCallable(onFinally) is false, then
|
||||
if (!IsCallable(onFinally)) {
|
||||
// a. Let thenFinally be onFinally.
|
||||
thenFinally = onFinally;
|
||||
// b. Let catchFinally be onFinally.
|
||||
catchFinally = onFinally;
|
||||
} else { // 6. Else,
|
||||
// a. Let thenFinallyClosure be a new Abstract Closure with parameters (value) that captures onFinally and C and performs the following steps when called:
|
||||
const thenFinallyClosure = function* thenFinallyClosure([value = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// i. Let result be ? Call(onFinally, undefined).
|
||||
const result = Q(yield* Call(onFinally, Value.undefined));
|
||||
// ii. Let promise be ? PromiseResolve(C, result).
|
||||
const promiseInner = Q(yield* PromiseResolve(C, result));
|
||||
// iii. Let returnValue be a new Abstract Closure with no parameters that captures value and performs the following steps when called:
|
||||
// 1. Return value.
|
||||
const returnValue = () => value;
|
||||
// iv. Let valueThunk be ! CreateBuiltinFunction(returnValue, 0, "", « »).
|
||||
const valueThunk = X(CreateBuiltinFunction(returnValue, 0, Value(''), []));
|
||||
// v. Return ? Invoke(promise, "then", « valueThunk »).
|
||||
return Q(yield* Invoke(promiseInner, Value('then'), [valueThunk]));
|
||||
};
|
||||
// b. Let thenFinally be ! CreateBuiltinFunction(thenFinallyClosure, 1, "", « »).
|
||||
thenFinally = X(CreateBuiltinFunction(thenFinallyClosure, 1, Value(''), ['HostCapturedValues']));
|
||||
// NON-SPEC
|
||||
thenFinally.HostCapturedValues = [onFinally];
|
||||
// c. Let catchFinallyClosure be a new Abstract Closure with parameters (reason) that captures onFinally and C and performs the following steps when called:
|
||||
const catchFinallyClosure = function* catchFinallyClosure([reason = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// i. Let result be ? Call(onFinally, undefined).
|
||||
const result = Q(yield* Call(onFinally, Value.undefined));
|
||||
// ii. Let promise be ? PromiseResolve(C, result).
|
||||
const promiseInner = Q(yield* PromiseResolve(C, result));
|
||||
// iii. Let throwReason be a new Abstract Closure with no parameters that captures reason and performs the following steps when called:
|
||||
// 1. Return ThrowCompletion(reason).
|
||||
const throwReason = () => ThrowCompletion(reason);
|
||||
// iv. Let thrower be ! CreateBuiltinFunction(throwReason, 0, "", « »).
|
||||
const thrower = X(CreateBuiltinFunction(throwReason, 0, Value(''), []));
|
||||
// v. Return ? Invoke(promise, "then", « thrower »).
|
||||
return Q(yield* Invoke(promiseInner, Value('then'), [thrower]));
|
||||
};
|
||||
// d. Let catchFinally be ! CreateBuiltinFunction(catchFinallyClosure, 1, "", « »).
|
||||
catchFinally = X(CreateBuiltinFunction(catchFinallyClosure, 1, Value(''), ['HostCapturedValues']));
|
||||
// NON-SPEC
|
||||
catchFinally.HostCapturedValues = [onFinally];
|
||||
}
|
||||
// 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
|
||||
return Q(yield* Invoke(promise, Value('then'), [thenFinally, catchFinally]));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-promise.prototype.then */
|
||||
function* PromiseProto_then([onFulfilled = Value.undefined, onRejected = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let promise be the this value.
|
||||
const promise = thisValue as PromiseObject;
|
||||
// 2. If IsPromise(promise) is false, throw a TypeError exception.
|
||||
if (IsPromise(promise) === Value.false) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Promise', promise);
|
||||
}
|
||||
// 3. Let C be ? SpeciesConstructor(promise, %Promise%).
|
||||
const C = Q(yield* SpeciesConstructor(promise, surroundingAgent.intrinsic('%Promise%')));
|
||||
// 4. Let resultCapability be ? NewPromiseCapability(C).
|
||||
const resultCapability = Q(yield* NewPromiseCapability(C));
|
||||
// 5. Return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability).
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(promise));
|
||||
return PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability);
|
||||
}
|
||||
|
||||
export function bootstrapPromisePrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['catch', PromiseProto_catch, 1],
|
||||
['finally', PromiseProto_finally, 1],
|
||||
['then', PromiseProto_then, 2],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Promise');
|
||||
|
||||
realmRec.Intrinsics['%Promise.prototype.then%'] = X(Get(proto, Value('then'))) as FunctionObject;
|
||||
|
||||
realmRec.Intrinsics['%Promise.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import {
|
||||
NullValue, ObjectValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueCompletion } from '../completion.mts';
|
||||
import { assignProps } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateBuiltinFunction,
|
||||
CreateDataProperty,
|
||||
markBuiltinFunctionAsConstructor,
|
||||
OrdinaryObjectCreate,
|
||||
ProxyCreate,
|
||||
Realm,
|
||||
type BuiltinFunctionObject,
|
||||
type ExoticObject,
|
||||
type FunctionObject,
|
||||
} from '#self';
|
||||
|
||||
export interface ProxyObject extends ExoticObject, BuiltinFunctionObject {
|
||||
ProxyHandler: Value | NullValue;
|
||||
ProxyTarget: ObjectValue | NullValue;
|
||||
}
|
||||
export function isProxyExoticObject(O: Value): O is ProxyObject {
|
||||
return 'ProxyHandler' in O;
|
||||
}
|
||||
export interface RevocableProxyRevokeFunctionObject extends BuiltinFunctionObject {
|
||||
RevocableProxy: ProxyObject | NullValue;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-proxy-target-handler */
|
||||
function ProxyConstructor(this: FunctionObject, [target = Value.undefined, handler = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. f NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget === Value.undefined) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. Return ? ProxyCreate(target, handler).
|
||||
return ProxyCreate(target, handler);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-proxy-revocation-functions */
|
||||
function ProxyRevocationFunctions() {
|
||||
// 1. Let F be the active function object.
|
||||
const F = surroundingAgent.activeFunctionObject as RevocableProxyRevokeFunctionObject;
|
||||
// 2. Let p be F.[[RevocableProxy]].
|
||||
const p = F.RevocableProxy;
|
||||
// 3. If p is null, return undefined.
|
||||
if (p === Value.null) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 4. Set F.[[RevocableProxy]] to null.
|
||||
F.RevocableProxy = Value.null;
|
||||
// 5. Assert: p is a Proxy object.
|
||||
Assert(isProxyExoticObject(p));
|
||||
// 6. Set p.[[ProxyTarget]] to null.
|
||||
p.ProxyTarget = Value.null;
|
||||
// 7. Set p.[[ProxyHandler]] to null.
|
||||
p.ProxyHandler = Value.null;
|
||||
// 8. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-proxy.revocable */
|
||||
function Proxy_revocable([target = Value.undefined, handler = Value.undefined]: Arguments): ValueCompletion {
|
||||
// 1. Let p be ? ProxyCreate(target, handler).
|
||||
const p = Q(ProxyCreate(target, handler));
|
||||
/** https://tc39.es/ecma262/#sec-proxy-revocation-functions. */
|
||||
const steps = ProxyRevocationFunctions;
|
||||
// 3. Let length be the number of non-optional parameters of the function definition in Proxy Revocation Functions.
|
||||
const length = 0;
|
||||
// 4. Let revoker be ! CreateBuiltinFunction(steps, length, "", « [[RevocableProxy]] »).
|
||||
const revoker = X(CreateBuiltinFunction(steps, length, Value(''), ['RevocableProxy'])) as RevocableProxyRevokeFunctionObject;
|
||||
// 5. Set revoker.[[RevocableProxy]] to p.
|
||||
revoker.RevocableProxy = p;
|
||||
// 6. Let result be OrdinaryObjectCreate(%Object.prototype%).
|
||||
const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
// 7. Perform ! CreateDataPropertyOrThrow(result, "proxy", p).
|
||||
X(CreateDataProperty(result, Value('proxy'), p));
|
||||
// 8. Perform ! CreateDataPropertyOrThrow(result, "revoke", revoker).
|
||||
X(CreateDataProperty(result, Value('revoke'), revoker));
|
||||
// 9. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
export function bootstrapProxy(realmRec: Realm) {
|
||||
const proxyConstructor = CreateBuiltinFunction(
|
||||
markBuiltinFunctionAsConstructor(ProxyConstructor),
|
||||
2,
|
||||
Value('Proxy'),
|
||||
[],
|
||||
realmRec,
|
||||
);
|
||||
|
||||
assignProps(realmRec, proxyConstructor, [
|
||||
['revocable', Proxy_revocable, 2],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Proxy%'] = proxyConstructor;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { ObjectValue, Value, type Arguments } from '../value.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
Call,
|
||||
Construct,
|
||||
CreateArrayFromList,
|
||||
CreateListFromArrayLike,
|
||||
FromPropertyDescriptor,
|
||||
IsCallable,
|
||||
IsConstructor,
|
||||
PrepareForTailCall,
|
||||
Realm,
|
||||
ToPropertyDescriptor,
|
||||
ToPropertyKey,
|
||||
type FunctionObject,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.apply */
|
||||
function* Reflect_apply([target = Value.undefined, thisArgument = Value.undefined, argumentsList = Value.undefined]: Arguments) {
|
||||
// 1. If IsCallable(target) is false, throw a TypeError exception.
|
||||
if (!IsCallable(target)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', target);
|
||||
}
|
||||
// 2. Let args be ? CreateListFromArrayLike(argumentsList).
|
||||
const args = Q(yield* CreateListFromArrayLike(argumentsList));
|
||||
// 3. Perform PrepareForTailCall().
|
||||
PrepareForTailCall();
|
||||
// 4. Return ? Call(target, thisArgument, args).
|
||||
return Q(yield* Call(target, thisArgument, args));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.construct */
|
||||
function* Reflect_construct([target = Value.undefined, argumentsList = Value.undefined, newTarget]: Arguments) {
|
||||
// 1. If IsConstructor(target) is false, throw a TypeError exception.
|
||||
if (!IsConstructor(target)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAConstructor', target);
|
||||
}
|
||||
// 2. If newTarget is not present, set newTarget to target.
|
||||
if (newTarget === undefined) {
|
||||
newTarget = target;
|
||||
} else if (!IsConstructor(newTarget)) { // 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception.
|
||||
return surroundingAgent.Throw('TypeError', 'NotAConstructor', newTarget);
|
||||
}
|
||||
// 4. Let args be ? CreateListFromArrayLike(argumentsList).
|
||||
const args = Q(yield* CreateListFromArrayLike(argumentsList));
|
||||
// 5. Return ? Construct(target, args, newTarget).
|
||||
return Q(yield* Construct(target, args, newTarget as FunctionObject));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.defineproperty */
|
||||
function* Reflect_defineProperty([target = Value.undefined, propertyKey = Value.undefined, attributes = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Let key be ? ToPropertyKey(propertyKey).
|
||||
const key = Q(yield* ToPropertyKey(propertyKey));
|
||||
// 3. Let desc be ? ToPropertyDescriptor(attributes).
|
||||
const desc = Q(yield* ToPropertyDescriptor(attributes));
|
||||
// 4. Return ? target.[[DefineOwnProperty]](key, desc).
|
||||
return Q(yield* target.DefineOwnProperty(key, desc));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.deleteproperty */
|
||||
function* Reflect_deleteProperty([target = Value.undefined, propertyKey = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Let key be ? ToPropertyKey(propertyKey).
|
||||
const key = Q(yield* ToPropertyKey(propertyKey));
|
||||
// 3. Return ? target.[[Delete]](key).
|
||||
return Q(yield* target.Delete(key));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.get */
|
||||
function* Reflect_get([target = Value.undefined, propertyKey = Value.undefined, receiver]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Let key be ? ToPropertyKey(propertyKey).
|
||||
const key = Q(yield* ToPropertyKey(propertyKey));
|
||||
// 3. If receiver is not present, then
|
||||
if (receiver === undefined) {
|
||||
// a. Set receiver to target.
|
||||
receiver = target;
|
||||
}
|
||||
// 4. Return ? target.[[Get]](key, receiver).
|
||||
return Q(yield* target.Get(key, receiver));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.getownpropertydescriptor */
|
||||
function* Reflect_getOwnPropertyDescriptor([target = Value.undefined, propertyKey = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Let key be ? ToPropertyKey(propertyKey).
|
||||
const key = Q(yield* ToPropertyKey(propertyKey));
|
||||
// 3. Let desc be ? target.[[GetOwnProperty]](key).
|
||||
const desc = Q(yield* target.GetOwnProperty(key));
|
||||
// 4. Return FromPropertyDescriptor(desc).
|
||||
return FromPropertyDescriptor(desc);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.getprototypeof */
|
||||
function* Reflect_getPrototypeOf([target = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Return ? target.[[GetPrototypeOf]]().
|
||||
return Q(yield* target.GetPrototypeOf());
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.has */
|
||||
function* Reflect_has([target = Value.undefined, propertyKey = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Let key be ? ToPropertyKey(propertyKey).
|
||||
const key = Q(yield* ToPropertyKey(propertyKey));
|
||||
// 3. Return ? target.[[HasProperty]](key).
|
||||
return Q(yield* target.HasProperty(key));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.isextensible */
|
||||
function* Reflect_isExtensible([target = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Return ? target.[[IsExtensible]]().
|
||||
return Q(yield* target.IsExtensible());
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.ownkeys */
|
||||
function* Reflect_ownKeys([target = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Let keys be ? target.[[OwnPropertyKeys]]().
|
||||
const keys = Q(yield* target.OwnPropertyKeys());
|
||||
// 3. Return CreateArrayFromList(keys).
|
||||
return CreateArrayFromList(keys);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.preventextensions */
|
||||
function* Reflect_preventExtensions([target = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Return ? target.[[PreventExtensions]]().
|
||||
return Q(yield* target.PreventExtensions());
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.set */
|
||||
function* Reflect_set([target = Value.undefined, propertyKey = Value.undefined, V = Value.undefined, receiver]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. Let key be ? ToPropertyKey(propertyKey).
|
||||
const key = Q(yield* ToPropertyKey(propertyKey));
|
||||
// 3. If receiver is not present, then
|
||||
if (receiver === undefined) {
|
||||
receiver = target;
|
||||
}
|
||||
// 4. Return ? target.[[Set]](key, V, receiver).
|
||||
return Q(yield* target.Set(key, V, receiver));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-reflect.setprototypeof */
|
||||
function* Reflect_setPrototypeOf([target = Value.undefined, proto = Value.undefined]: Arguments) {
|
||||
// 1. If Type(target) is not Object, throw a TypeError exception.
|
||||
if (!(target instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', target);
|
||||
}
|
||||
// 2. If Type(proto) is not Object and proto is not null, throw a TypeError exception.
|
||||
if (!(proto instanceof ObjectValue) && proto !== Value.null) {
|
||||
return surroundingAgent.Throw('TypeError', 'ObjectPrototypeType');
|
||||
}
|
||||
// 3. Return ? target.[[SetPrototypeOf]](proto).
|
||||
return Q(yield* target.SetPrototypeOf(proto));
|
||||
}
|
||||
|
||||
export function bootstrapReflect(realmRec: Realm) {
|
||||
const reflect = bootstrapPrototype(realmRec, [
|
||||
['apply', Reflect_apply, 3],
|
||||
['construct', Reflect_construct, 2],
|
||||
['defineProperty', Reflect_defineProperty, 3],
|
||||
['deleteProperty', Reflect_deleteProperty, 2],
|
||||
['get', Reflect_get, 2],
|
||||
['getOwnPropertyDescriptor', Reflect_getOwnPropertyDescriptor, 2],
|
||||
['getPrototypeOf', Reflect_getPrototypeOf, 1],
|
||||
['has', Reflect_has, 2],
|
||||
['isExtensible', Reflect_isExtensible, 1],
|
||||
['ownKeys', Reflect_ownKeys, 1],
|
||||
['preventExtensions', Reflect_preventExtensions, 1],
|
||||
['set', Reflect_set, 3],
|
||||
['setPrototypeOf', Reflect_setPrototypeOf, 2],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Reflect');
|
||||
|
||||
realmRec.Intrinsics['%Reflect%'] = reflect;
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import {
|
||||
JSStringValue,
|
||||
ObjectValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import type { ParseNode } from '../parser/ParseNode.mts';
|
||||
import type { ValueEvaluator } from '../evaluator.mts';
|
||||
import {
|
||||
isDecimalDigit, isLineTerminator, isWhitespace,
|
||||
} from '../parser/Lexer.mts';
|
||||
import { isAsciiLetter, isControlEscape, isSyntaxCharacter } from '../parser/RegExpParser.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import { UnicodeEscape } from './JSON.mts';
|
||||
import {
|
||||
Get,
|
||||
IsRegExp,
|
||||
Realm,
|
||||
RegExpAlloc,
|
||||
RegExpInitialize,
|
||||
SameValue,
|
||||
type FunctionObject,
|
||||
type OrdinaryObject,
|
||||
|
||||
Assert, isLeadingSurrogate, isTrailingSurrogate, StringToCodePoints, UTF16EncodeCodePoint, type CodePoint, type RegExpMatcher, type RegExpRecord,
|
||||
} from '#self';
|
||||
|
||||
export interface RegExpObject extends OrdinaryObject {
|
||||
readonly OriginalSource: JSStringValue;
|
||||
readonly OriginalFlags: JSStringValue;
|
||||
readonly RegExpMatcher: RegExpMatcher;
|
||||
readonly RegExpRecord: RegExpRecord;
|
||||
readonly parsedPattern: ParseNode.RegExp.Pattern;
|
||||
}
|
||||
export function isRegExpObject(o: Value): o is RegExpObject {
|
||||
return 'RegExpMatcher' in o;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-regexp-constructor */
|
||||
function* RegExpConstructor([pattern = Value.undefined, flags = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let patternIsRegExp be ? IsRegExp(pattern).
|
||||
const patternIsRegExp = Q(yield* IsRegExp(pattern));
|
||||
let newTarget;
|
||||
// 2. If NewTarget is undefined, then
|
||||
if (NewTarget === Value.undefined) {
|
||||
// a. Let newTarget be the active function object.
|
||||
newTarget = surroundingAgent.activeFunctionObject;
|
||||
// b. If patternIsRegExp is true and flags is undefined, then
|
||||
if (patternIsRegExp === Value.true && flags === Value.undefined) {
|
||||
// i. Let patternConstructor be ? Get(pattern, "constructor").
|
||||
const patternConstructor = Q(yield* Get(pattern as ObjectValue, Value('constructor')));
|
||||
// ii. If SameValue(newTarget, patternConstructor) is true, return pattern.
|
||||
if (SameValue(newTarget, patternConstructor) === Value.true) {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
} else { // 3. Else, let newTarget be NewTarget.
|
||||
newTarget = NewTarget;
|
||||
}
|
||||
let P;
|
||||
let F;
|
||||
// 4. If Type(pattern) is Object and pattern has a [[RegExpMatcher]] internal slot, then
|
||||
if (isRegExpObject(pattern)) {
|
||||
// a. Let P be pattern.[[OriginalSource]].
|
||||
P = pattern.OriginalSource;
|
||||
// b. If flags is undefined, let F be pattern.[[OriginalFlags]].
|
||||
if (flags === Value.undefined) {
|
||||
F = pattern.OriginalFlags;
|
||||
} else { // c. Else, let F be flags.
|
||||
F = flags;
|
||||
}
|
||||
} else if (patternIsRegExp === Value.true) { // 5. Else if patternIsRegExp is true, then
|
||||
// a. Else if patternIsRegExp is true, then
|
||||
P = Q(yield* Get(pattern as ObjectValue, Value('source')));
|
||||
// b. If flags is undefined, then
|
||||
if (flags === Value.undefined) {
|
||||
// i. Let F be ? Get(pattern, "flags").
|
||||
F = Q(yield* Get(pattern as ObjectValue, Value('flags')));
|
||||
} else { // c. Else, let F be flags.
|
||||
F = flags;
|
||||
}
|
||||
} else { // 6. Else,
|
||||
// a. Let P be pattern.
|
||||
P = pattern;
|
||||
// b. Let F be flags.
|
||||
F = flags;
|
||||
}
|
||||
// 7. Let O be ? RegExpAlloc(newTarget).
|
||||
const O = Q(yield* RegExpAlloc(newTarget as FunctionObject));
|
||||
// 8. Return ? RegExpInitialize(O, P, F).
|
||||
return Q(yield* RegExpInitialize(O, P, F));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp.escape */
|
||||
function* RegExp_escape([S = Value.undefined]: Arguments) {
|
||||
if (!(S instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAString', S);
|
||||
}
|
||||
let escaped = '';
|
||||
const cpList = StringToCodePoints(S.stringValue());
|
||||
for (const cp of cpList) {
|
||||
if (escaped === '' && (isDecimalDigit(String.fromCodePoint(cp)) || isAsciiLetter(cp))) {
|
||||
const numericValue = cp;
|
||||
const hex = numericValue.toString(16);
|
||||
Assert(hex.length === 2);
|
||||
escaped += `\u{005C}x${hex}`;
|
||||
} else {
|
||||
escaped += EncodeForRegExpEscape(cp);
|
||||
}
|
||||
}
|
||||
return Value(escaped);
|
||||
}
|
||||
|
||||
const table67: Record<number, string> = {
|
||||
9: 't',
|
||||
10: 'n',
|
||||
11: 'v',
|
||||
12: 'f',
|
||||
13: 'r',
|
||||
};
|
||||
function EncodeForRegExpEscape(cp: CodePoint) {
|
||||
const ch = String.fromCharCode(cp);
|
||||
if (cp === 0x002F || isSyntaxCharacter(ch)) {
|
||||
return `\u{005C}${UTF16EncodeCodePoint(cp)}`;
|
||||
} else if (isControlEscape(cp)) {
|
||||
return `\u{005C}${table67[cp]!}`;
|
||||
}
|
||||
const otherPunctuators = ",-=<>#&!%:;@~'`\u{0022}";
|
||||
const toEscape = StringToCodePoints(otherPunctuators);
|
||||
if (toEscape.includes(cp) || isWhitespace(ch) || isLineTerminator(ch) || isLeadingSurrogate(cp) || isTrailingSurrogate(cp)) {
|
||||
const cpNum = cp;
|
||||
if (cpNum <= 0xFF) {
|
||||
const hex = cpNum.toString(16);
|
||||
return `\u{005C}x${hex.padStart(2, '0')}`;
|
||||
}
|
||||
let escaped = '';
|
||||
const codeUnits = UTF16EncodeCodePoint(cp);
|
||||
for (const cu of codeUnits) {
|
||||
escaped += UnicodeEscape(cu);
|
||||
}
|
||||
return escaped;
|
||||
}
|
||||
return UTF16EncodeCodePoint(cp);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp-@@species */
|
||||
function RegExp_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) {
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
export function bootstrapRegExp(realmRec: Realm) {
|
||||
const proto = realmRec.Intrinsics['%RegExp.prototype%'];
|
||||
|
||||
const cons = bootstrapConstructor(realmRec, RegExpConstructor, 'RegExp', 2, proto, [
|
||||
[wellKnownSymbols.species, [RegExp_speciesGetter]],
|
||||
['escape', RegExp_escape, 1],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%RegExp%'] = cons;
|
||||
}
|
||||
@@ -0,0 +1,761 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
NullValue,
|
||||
JSStringValue,
|
||||
ObjectValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
UndefinedValue,
|
||||
NumberValue,
|
||||
} from '../value.mts';
|
||||
import { RegExpState, GetSubstitution } from '../runtime-semantics/all.mts';
|
||||
import { CodePointAt } from '../static-semantics/all.mts';
|
||||
import {
|
||||
Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { CreateRegExpStringIterator } from './RegExpStringIteratorPrototype.mts';
|
||||
import { isRegExpObject, type RegExpObject } from './RegExp.mts';
|
||||
import {
|
||||
ArrayCreate,
|
||||
Assert,
|
||||
Call,
|
||||
Construct,
|
||||
CreateDataProperty,
|
||||
CreateDataPropertyOrThrow,
|
||||
EscapeRegExpPattern,
|
||||
Get,
|
||||
GetMatchString,
|
||||
GetStringIndex,
|
||||
IsCallable,
|
||||
LengthOfArrayLike,
|
||||
MakeMatchIndicesIndexPairArray,
|
||||
OrdinaryObjectCreate,
|
||||
RequireInternalSlot,
|
||||
SameValue,
|
||||
Set,
|
||||
SpeciesConstructor,
|
||||
ToBoolean,
|
||||
ToIntegerOrInfinity,
|
||||
ToLength,
|
||||
ToObject,
|
||||
ToString,
|
||||
ToUint32,
|
||||
RegExpHasFlag,
|
||||
F, R, R as MathematicalValue,
|
||||
Realm,
|
||||
type MatchRecord,
|
||||
type OrdinaryObject,
|
||||
} from '#self';
|
||||
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp.prototype.exec */
|
||||
function* RegExpProto_exec([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const R = thisValue as RegExpObject;
|
||||
Q(RequireInternalSlot(R, 'RegExpMatcher'));
|
||||
const S = Q(yield* ToString(string));
|
||||
return Q(yield* RegExpBuiltinExec(R, S));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexpexec */
|
||||
export function* RegExpExec(R: ObjectValue, S: JSStringValue) {
|
||||
Assert(R instanceof ObjectValue);
|
||||
Assert(S instanceof JSStringValue);
|
||||
|
||||
const exec = Q(yield* Get(R, Value('exec')));
|
||||
if (IsCallable(exec)) {
|
||||
const result = Q(yield* Call(exec, R, [S]));
|
||||
if (!(result instanceof ObjectValue) && !(result instanceof NullValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'RegExpExecNotObject', result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Q(RequireInternalSlot(R, 'RegExpMatcher'));
|
||||
return Q(yield* RegExpBuiltinExec(R as RegExpObject, S));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexpbuiltinexec */
|
||||
export function* RegExpBuiltinExec(R: RegExpObject, S: JSStringValue): ValueEvaluator<NullValue | OrdinaryObject> {
|
||||
// Let length be the number of code units in S.
|
||||
const length = S.stringValue().length;
|
||||
let lastIndex = MathematicalValue(Q(yield* ToLength(X(Get(R, Value('lastIndex'))))));
|
||||
const flags = R.OriginalFlags.stringValue();
|
||||
const global = flags.includes('g');
|
||||
const sticky = flags.includes('y');
|
||||
const hasIndices = flags.includes('d');
|
||||
if (!global && !sticky) {
|
||||
lastIndex = 0;
|
||||
}
|
||||
const matcher = R.RegExpMatcher;
|
||||
const fullUnicode = flags.includes('u') || flags.includes('v');
|
||||
let matchSucceeded = false;
|
||||
// If fullUnicode is true, let input be StringToCodePoints(S). Otherwise, let input be a List whose elements are the code units that are the elements of S.
|
||||
const input = RegExpState.createRegExpMatchingSource(fullUnicode ? Array.from(S.stringValue()) : S.stringValue().split(''), S.stringValue());
|
||||
|
||||
// used to calculate inputIndex below
|
||||
const accumulatedInputLength: number[] = [];
|
||||
if (fullUnicode) {
|
||||
for (let index = 0; index < input.length; index += 1) {
|
||||
const codePoint = input[index];
|
||||
accumulatedInputLength[index] = (accumulatedInputLength[index - 1] ?? 0) + codePoint.length;
|
||||
}
|
||||
}
|
||||
let r;
|
||||
while (matchSucceeded === false) {
|
||||
if (lastIndex > length) {
|
||||
if (global || sticky) {
|
||||
Q(yield* Set(R, Value('lastIndex'), F(+0), Value.true));
|
||||
}
|
||||
return Value.null;
|
||||
}
|
||||
// Let inputIndex be the index into input of the character that was obtained from element lastIndex of S.
|
||||
let inputIndex;
|
||||
if (fullUnicode) {
|
||||
inputIndex = accumulatedInputLength.findIndex((x) => lastIndex < x);
|
||||
if (inputIndex === -1) {
|
||||
// lastIndex is greater than all code points
|
||||
inputIndex = accumulatedInputLength.length;
|
||||
}
|
||||
} else {
|
||||
inputIndex = lastIndex;
|
||||
}
|
||||
|
||||
r = matcher(input, inputIndex);
|
||||
if (r === 'failure') {
|
||||
if (sticky) {
|
||||
Q(yield* Set(R, Value('lastIndex'), F(+0), Value.true));
|
||||
return Value.null;
|
||||
}
|
||||
lastIndex = AdvanceStringIndex(S, lastIndex, fullUnicode);
|
||||
} else {
|
||||
Assert(r instanceof RegExpState);
|
||||
matchSucceeded = true;
|
||||
}
|
||||
}
|
||||
__ts_cast__<RegExpState>(r);
|
||||
let e = r.endIndex;
|
||||
if (fullUnicode) {
|
||||
e = GetStringIndex(S, input, e);
|
||||
}
|
||||
if (global || sticky) {
|
||||
Q(yield* Set(R, Value('lastIndex'), F(e), Value.true));
|
||||
}
|
||||
// Let n be the number of elements in r's captures List.
|
||||
// Note: this list is used as 1-indexed, so the 0th element is a hole and do not count as "the number of elements"
|
||||
const n = Math.max(0, r.captures.length - 1);
|
||||
Assert(r.captures[0] === undefined);
|
||||
Assert(n === R.RegExpRecord.CapturingGroupsCount);
|
||||
Assert(n < (2 ** 32) - 1);
|
||||
const A = X(ArrayCreate(n + 1));
|
||||
Assert(MathematicalValue(X(Get(A, Value('length'))) as NumberValue) === n + 1);
|
||||
X(CreateDataPropertyOrThrow(A, Value('index'), F(lastIndex)));
|
||||
X(CreateDataPropertyOrThrow(A, Value('input'), S));
|
||||
const match: MatchRecord = { StartIndex: lastIndex, EndIndex: e };
|
||||
const indices: (MatchRecord | UndefinedValue)[] = [];
|
||||
const groupNames = [];
|
||||
indices.push(match);
|
||||
const matchedSubStr = GetMatchString(S, match);
|
||||
X(CreateDataPropertyOrThrow(A, Value('0'), matchedSubStr));
|
||||
let groups;
|
||||
let hasGroups;
|
||||
if (R.parsedPattern.capturingGroups.filter((x) => x.GroupName).length > 0) {
|
||||
groups = OrdinaryObjectCreate(Value.null);
|
||||
hasGroups = Value.true;
|
||||
} else {
|
||||
groups = Value.undefined;
|
||||
hasGroups = Value.false;
|
||||
}
|
||||
X(CreateDataPropertyOrThrow(A, Value('groups'), groups));
|
||||
const matchedGroupNames: string[] = [];
|
||||
for (let i = 1; i <= n; i += 1) {
|
||||
const captureI = r.captures[i];
|
||||
let capturedValue;
|
||||
if (!captureI) {
|
||||
capturedValue = Value.undefined;
|
||||
indices.push(Value.undefined);
|
||||
} else {
|
||||
let captureStart = captureI.startIndex;
|
||||
let captureEnd = captureI.endIndex;
|
||||
if (fullUnicode) {
|
||||
captureStart = GetStringIndex(S, input, captureStart);
|
||||
captureEnd = GetStringIndex(S, input, captureEnd);
|
||||
}
|
||||
const capture: MatchRecord = { StartIndex: captureStart, EndIndex: captureEnd };
|
||||
capturedValue = GetMatchString(S, capture);
|
||||
indices.push(capture);
|
||||
}
|
||||
X(CreateDataPropertyOrThrow(A, X(ToString(F(i))), capturedValue));
|
||||
const i_th = i - 1;
|
||||
if (R.parsedPattern.capturingGroups[i_th].GroupName) {
|
||||
const s = Value(R.parsedPattern.capturingGroups[i_th].GroupName);
|
||||
if (matchedGroupNames.includes(s.stringValue())) {
|
||||
Assert(capturedValue === Value.undefined);
|
||||
groupNames.push(Value.undefined);
|
||||
} else {
|
||||
if (capturedValue !== Value.undefined) {
|
||||
matchedGroupNames.push(s.stringValue());
|
||||
}
|
||||
X(CreateDataPropertyOrThrow(groups as ObjectValue, s, capturedValue));
|
||||
groupNames.push(s);
|
||||
}
|
||||
} else {
|
||||
groupNames.push(Value.undefined);
|
||||
}
|
||||
}
|
||||
if (hasIndices) {
|
||||
const indicesArray = MakeMatchIndicesIndexPairArray(S, indices, groupNames, hasGroups);
|
||||
X(CreateDataPropertyOrThrow(A, Value('indices'), indicesArray));
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-advancestringindex */
|
||||
export function AdvanceStringIndex(S: JSStringValue, index: number, unicode: boolean) {
|
||||
Assert(index <= (2 ** 53) - 1);
|
||||
if (!unicode) {
|
||||
return index + 1;
|
||||
}
|
||||
const length = S.stringValue().length;
|
||||
if (index + 1 >= length) {
|
||||
return index + 1;
|
||||
}
|
||||
const cp = CodePointAt(S.stringValue(), index);
|
||||
return index + cp.CodeUnitCount;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll */
|
||||
function RegExpProto_dotAllGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let R be the this value.
|
||||
const R = thisValue;
|
||||
// 2. Let cu be the code unit 0x0073 (LATIN SMALL LETTER S).
|
||||
const cu = 's';
|
||||
// 3. Return ? RegExpHasFlag(R, cu).
|
||||
return Q(RegExpHasFlag(R, cu));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.flags */
|
||||
function* RegExpProto_flagsGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const R = thisValue;
|
||||
if (!(R instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R);
|
||||
}
|
||||
let result = '';
|
||||
const hasIndices = ToBoolean(Q(yield* Get(R, Value('hasIndices'))));
|
||||
if (hasIndices === Value.true) {
|
||||
result += 'd';
|
||||
}
|
||||
const global = ToBoolean(Q(yield* Get(R, Value('global'))));
|
||||
if (global === Value.true) {
|
||||
result += 'g';
|
||||
}
|
||||
const ignoreCase = ToBoolean(Q(yield* Get(R, Value('ignoreCase'))));
|
||||
if (ignoreCase === Value.true) {
|
||||
result += 'i';
|
||||
}
|
||||
const multiline = ToBoolean(Q(yield* Get(R, Value('multiline'))));
|
||||
if (multiline === Value.true) {
|
||||
result += 'm';
|
||||
}
|
||||
const dotAll = ToBoolean(Q(yield* Get(R, Value('dotAll'))));
|
||||
if (dotAll === Value.true) {
|
||||
result += 's';
|
||||
}
|
||||
const unicode = ToBoolean(Q(yield* Get(R, Value('unicode'))));
|
||||
if (unicode === Value.true) {
|
||||
result += 'u';
|
||||
}
|
||||
const unicodeSet = ToBoolean(Q(yield* Get(R, Value('unicodeSets'))));
|
||||
if (unicodeSet === Value.true) {
|
||||
result += 'v';
|
||||
}
|
||||
const sticky = ToBoolean(Q(yield* Get(R, Value('sticky'))));
|
||||
if (sticky === Value.true) {
|
||||
result += 'y';
|
||||
}
|
||||
return Value(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.global */
|
||||
function RegExpProto_globalGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const R = thisValue as RegExpObject;
|
||||
if (!(R instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R);
|
||||
}
|
||||
if (!('OriginalFlags' in R)) {
|
||||
if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) {
|
||||
return Value.undefined;
|
||||
}
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R);
|
||||
}
|
||||
const flags = R.OriginalFlags;
|
||||
if (flags.stringValue().includes('g')) {
|
||||
return Value.true;
|
||||
}
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.hasIndices */
|
||||
function RegExpProto_hasIndicesGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let R be the this value.
|
||||
const R = thisValue;
|
||||
// 2. Let cu be the code unit 0x0073 (LATIN SMALL LETTER D).
|
||||
const cu = 'd';
|
||||
// 3. Return ? RegExpHasFlag(R, cu).
|
||||
return Q(RegExpHasFlag(R, cu));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase */
|
||||
function RegExpProto_ignoreCaseGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let R be the this value.
|
||||
const R = thisValue;
|
||||
// 2. Let cu be the code unit 0x0069 (LATIN SMALL LETTER I).
|
||||
const cu = 'i';
|
||||
// 3. Return ? RegExpHasFlag(R, cu).
|
||||
return Q(RegExpHasFlag(R, cu));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp.prototype-@@match */
|
||||
function* RegExpProto_match([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let rx be the this value.
|
||||
const rx = thisValue;
|
||||
// 2. If Type(rx) is not Object, throw a TypeError exception.
|
||||
if (!(rx instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx);
|
||||
}
|
||||
// 3. Let S be ? ToString(string).
|
||||
const S = Q(yield* ToString(string));
|
||||
// 4. Let flags be ? ToString(? Get(rx, "flags")).
|
||||
const flags = Q(yield* ToString(Q(yield* Get(rx, Value('flags')))));
|
||||
// 5. If flags does not contain "g", then
|
||||
if (!flags.stringValue().includes('g')) {
|
||||
// a. Return ? RegExpExec(rx, S).
|
||||
return Q(yield* RegExpExec(rx, S));
|
||||
} else { // 6. Else,
|
||||
// a. If flags contains "u", let fullUnicode be true. Otherwise, let fullUnicode be false.
|
||||
const fullUnicode = flags.stringValue().includes('u');
|
||||
// b. Perform ? Set(rx, "lastIndex", +0𝔽, true).
|
||||
Q(yield* Set(rx, Value('lastIndex'), F(+0), Value.true));
|
||||
// c. Let A be ! ArrayCreate(0).
|
||||
const A = X(ArrayCreate(0));
|
||||
// d. Let n be 0.
|
||||
let n = 0;
|
||||
// e. Repeat,
|
||||
while (true) {
|
||||
// i. Let result be ? RegExpExec(rx, S).
|
||||
const result = Q(yield* RegExpExec(rx, S));
|
||||
// ii. If result is null, then
|
||||
if (result instanceof NullValue) {
|
||||
// 1. If n = 0, return null.
|
||||
if (n === 0) {
|
||||
return Value.null;
|
||||
}
|
||||
// 2. Return A.
|
||||
return A;
|
||||
} else { // iii. Else,
|
||||
// 1. Let matchStr be ? ToString(? Get(result, "0")).
|
||||
const matchStr = Q(yield* ToString(Q(yield* Get(result, Value('0')))));
|
||||
// 2. Perform ! CreateDataPropertyOrThrow(A, ! ToString(𝔽(n)), matchStr).
|
||||
X(CreateDataPropertyOrThrow(A, X(ToString(F(n))), matchStr));
|
||||
// 3. If matchStr is the empty String, then
|
||||
if (matchStr.stringValue() === '') {
|
||||
// a. Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))).
|
||||
const thisIndex = R(Q(yield* ToLength(Q(yield* Get(rx, Value('lastIndex'))))));
|
||||
// b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
|
||||
const nextIndex = AdvanceStringIndex(S, thisIndex, fullUnicode);
|
||||
// c. Perform ? Set(rx, "lastIndex", 𝔽(nextIndex), true).
|
||||
Q(yield* Set(rx, Value('lastIndex'), F(nextIndex), Value.true));
|
||||
}
|
||||
// 4. Set n to n + 1.
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp-prototype-matchall */
|
||||
function* RegExpProto_matchAll([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const R = thisValue;
|
||||
if (!(R instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R);
|
||||
}
|
||||
const S = Q(yield* ToString(string));
|
||||
const C = Q(yield* SpeciesConstructor(R, surroundingAgent.intrinsic('%RegExp%')));
|
||||
const flags = Q(yield* ToString(Q(yield* Get(R, Value('flags')))));
|
||||
const matcher = Q(yield* Construct(C, [R, flags]));
|
||||
const lastIndex = Q(yield* ToLength(Q(yield* Get(R, Value('lastIndex')))));
|
||||
Q(yield* Set(matcher, Value('lastIndex'), lastIndex, Value.true));
|
||||
const global = flags.stringValue().includes('g');
|
||||
const fullUnicode = flags.stringValue().includes('u') || flags.stringValue().includes('v');
|
||||
return CreateRegExpStringIterator(matcher, S, global, fullUnicode);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline */
|
||||
function RegExpProto_multilineGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let R be the this value.
|
||||
const R = thisValue;
|
||||
// 2. Let cu be the code unit 0x006D (LATIN SMALL LETTER M).
|
||||
const cu = 'm';
|
||||
// 3. Return ? RegExpHasFlag(R, cu).
|
||||
return Q(RegExpHasFlag(R, cu));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp.prototype-@@replace */
|
||||
function* RegExpProto_replace([string = Value.undefined, replaceValue = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let rx be the this value.
|
||||
const rx = thisValue;
|
||||
// 2. If rx is not an Object, throw a TypeError exception.
|
||||
if (!(rx instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx);
|
||||
}
|
||||
// 3. Let S be ? ToString(string).
|
||||
const S = Q(yield* ToString(string));
|
||||
// 4. Let lengthS be the length of S.
|
||||
const lengthS = S.stringValue().length;
|
||||
// 5. Let functionalReplace be IsCallable(replaceValue).
|
||||
const functionalReplace = IsCallable(replaceValue);
|
||||
// 6. If functionalReplace is false, then
|
||||
if (!functionalReplace) {
|
||||
// a. Set replaceValue to ? ToString(replaceValue).
|
||||
replaceValue = Q(yield* ToString(replaceValue));
|
||||
}
|
||||
// 7. Let flags be ? ToString(? Get(rx, "flags")).
|
||||
const flags = Q(yield* ToString(Q(yield* Get(rx, Value('flags')))));
|
||||
// 8. If flags contains "g", let global be true. Otherwise, let global be false.
|
||||
const global = flags.stringValue().includes('g') ? Value.true : Value.false;
|
||||
let fullUnicode;
|
||||
// 9. If global is true, then
|
||||
if (global === Value.true) {
|
||||
// a. If flags contains "u", let fullUnicode be true. Otherwise, let fullUnicode be false.
|
||||
fullUnicode = flags.stringValue().includes('u');
|
||||
// b. Perform ? Set(rx, "lastIndex", +0𝔽, true).
|
||||
Q(yield* Set(rx, Value('lastIndex'), F(+0), Value.true));
|
||||
}
|
||||
// 10. Let results be a new empty List.
|
||||
const results = [];
|
||||
// 11. Let done be false.
|
||||
let done = false;
|
||||
// 12. Repeat, while done is false,
|
||||
while (!done) {
|
||||
// a. Let result be ? RegExpExec(rx, S).
|
||||
const result = Q(yield* RegExpExec(rx, S));
|
||||
// b. If result is null, set done to true.
|
||||
if (result instanceof NullValue) {
|
||||
done = true;
|
||||
} else { // c. Else,
|
||||
// i. Append result to results.
|
||||
results.push(result);
|
||||
// ii. If global is false, set done to true.
|
||||
if (global === Value.false) {
|
||||
done = true;
|
||||
} else { // iii. Else,
|
||||
// 1. Let matchStr be ? ToString(? Get(result, "0")).
|
||||
const matchStr = Q(yield* ToString(Q(yield* Get(result, Value('0')))));
|
||||
// 2. If matchStr is the empty String, then
|
||||
if (matchStr.stringValue() === '') {
|
||||
// a. Let thisIndex be ℝ(? ToLength(? Get(rx, "lastIndex"))).
|
||||
const thisIndex = R(Q(yield* ToLength(Q(yield* Get(rx, Value('lastIndex'))))));
|
||||
// b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
|
||||
const nextIndex = AdvanceStringIndex(S, thisIndex, fullUnicode!);
|
||||
// c. Perform ? Set(rx, "lastIndex", 𝔽(nextIndex), true).
|
||||
Q(yield* Set(rx, Value('lastIndex'), F(nextIndex), Value.true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 13. Let accumulatedResult be the empty String.
|
||||
let accumulatedResult = '';
|
||||
// 14. Let nextSourcePosition be 0.
|
||||
let nextSourcePosition = 0;
|
||||
// 15. For each element result of results, do
|
||||
for (const result of results) {
|
||||
// a. Let resultLength be ? LengthOfArrayLike(result).
|
||||
let nCaptures = Q(yield* LengthOfArrayLike(result));
|
||||
// b. Let nCaptures be max(resultLength - 1, 0).
|
||||
nCaptures = Math.max(nCaptures - 1, 0);
|
||||
// c. Let matched be ? ToString(? Get(result, "0")).
|
||||
const matched = Q(yield* ToString(Q(yield* Get(result, Value('0')))));
|
||||
// d. Let matchLength be the length of matched.
|
||||
const matchLength = matched.stringValue().length;
|
||||
// e. Let position be ? ToIntegerOrInfinity(? Get(result, "index")).
|
||||
let position = Q(yield* ToIntegerOrInfinity(Q(yield* Get(result, Value('index')))));
|
||||
// f. Set position to the result of clamping position between 0 and lengthS.
|
||||
position = Math.max(Math.min(position, lengthS), 0);
|
||||
// g. Let captures be a new empty List.
|
||||
const captures = [];
|
||||
// h. Let n be 1.
|
||||
let n = 1;
|
||||
// i. Repeat, while n ≤ nCaptures,
|
||||
while (n <= nCaptures) {
|
||||
// i. Let capN be ? Get(result, ! ToString(𝔽(n))).
|
||||
let capN = Q(yield* Get(result, X(ToString(F(n)))));
|
||||
// ii. If capN is not undefined, then
|
||||
if (capN !== Value.undefined) {
|
||||
// 1. Set capN to ? ToString(capN).
|
||||
capN = Q(yield* ToString(capN));
|
||||
}
|
||||
// iii. Append capN to captures.
|
||||
captures.push(capN);
|
||||
// iv. NOTE: When n = 1, the preceding step puts the first element into captures
|
||||
// (at index 0). More generally, the nth capture (the characters captured by
|
||||
// the nth set of capturing parentheses) is at captures[n - 1].
|
||||
// v. Set n to n + 1.
|
||||
n += 1;
|
||||
}
|
||||
// j. Let namedCaptures be ? Get(result, "groups").
|
||||
let namedCaptures = Q(yield* Get(result, Value('groups')));
|
||||
let replacement;
|
||||
// k. If functionalReplace is true, then
|
||||
if (functionalReplace) {
|
||||
// i. Let replacerArgs be the list-concatenation of « matched », captures, and « 𝔽(position), S ».
|
||||
const replacerArgs: Value[] = [matched, ...captures, F(position), S];
|
||||
// ii. If namedCaptures is not undefined, then
|
||||
if (namedCaptures !== Value.undefined) {
|
||||
// 1. Append namedCaptures to replacerArgs.
|
||||
replacerArgs.push(namedCaptures);
|
||||
}
|
||||
// iii. Let replValue be ? Call(replaceValue, undefined, replacerArgs).
|
||||
const replValue = Q(yield* Call(replaceValue, Value.undefined, replacerArgs));
|
||||
// iv. Let replacement be ? ToString(replValue).
|
||||
replacement = Q(yield* ToString(replValue));
|
||||
} else { // l. Else,
|
||||
// i. If namedCaptures is not undefined, then
|
||||
if (namedCaptures !== Value.undefined) {
|
||||
// 1. Set namedCaptures to ? ToObject(namedCaptures).
|
||||
namedCaptures = Q(ToObject(namedCaptures));
|
||||
}
|
||||
// ii. Let replacement be ? GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue).
|
||||
replacement = Q(yield* GetSubstitution(matched, S, position, captures, namedCaptures, replaceValue as JSStringValue));
|
||||
}
|
||||
// m. If position ≥ nextSourcePosition, then
|
||||
if (position >= nextSourcePosition) {
|
||||
// i. NOTE: position should not normally move backwards. If it does, it is an indication of an
|
||||
// ill-behaving RegExp subclass or use of an access triggered side-effect to change the
|
||||
// global flag or other characteristics of rx. In such cases, the corresponding substitution is ignored.
|
||||
// ii. Set accumulatedResult to the string-concatenation of accumulatedResult, the substring of S from nextSourcePosition to position, and replacement.
|
||||
accumulatedResult = accumulatedResult + S.stringValue().substring(nextSourcePosition, position) + replacement.stringValue();
|
||||
// iii. Set nextSourcePosition to position + matchLength.
|
||||
nextSourcePosition = position + matchLength;
|
||||
}
|
||||
}
|
||||
// 16. If nextSourcePosition ≥ lengthS, return accumulatedResult.
|
||||
if (nextSourcePosition >= lengthS) {
|
||||
return Value(accumulatedResult);
|
||||
}
|
||||
// 17. Return the string-concatenation of accumulatedResult and the substring of S from nextSourcePosition.
|
||||
return Value(accumulatedResult + S.stringValue().substring(nextSourcePosition));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp.prototype-@@search */
|
||||
function* RegExpProto_search([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const rx = thisValue;
|
||||
if (!(rx instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx);
|
||||
}
|
||||
const S = Q(yield* ToString(string));
|
||||
|
||||
const previousLastIndex = Q(yield* Get(rx, Value('lastIndex')));
|
||||
if (SameValue(previousLastIndex, F(+0)) === Value.false) {
|
||||
Q(yield* Set(rx, Value('lastIndex'), F(+0), Value.true));
|
||||
}
|
||||
|
||||
const result = Q(yield* RegExpExec(rx, S));
|
||||
const currentLastIndex = Q(yield* Get(rx, Value('lastIndex')));
|
||||
if (SameValue(currentLastIndex, previousLastIndex) === Value.false) {
|
||||
Q(yield* Set(rx, Value('lastIndex'), previousLastIndex, Value.true));
|
||||
}
|
||||
|
||||
if (result instanceof NullValue) {
|
||||
return F(-1);
|
||||
}
|
||||
|
||||
return Q(yield* Get(result, Value('index')));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.source */
|
||||
function RegExpProto_sourceGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const R = thisValue;
|
||||
if (!(R instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R);
|
||||
}
|
||||
if (!('OriginalSource' in R)) {
|
||||
if (SameValue(R, surroundingAgent.intrinsic('%RegExp.prototype%')) === Value.true) {
|
||||
return Value('(?:)');
|
||||
}
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R);
|
||||
}
|
||||
Assert(isRegExpObject(R));
|
||||
const src = R.OriginalSource;
|
||||
const flags = R.OriginalFlags;
|
||||
return EscapeRegExpPattern(src, flags);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp.prototype-@@split */
|
||||
function* RegExpProto_split([string = Value.undefined, limit = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const rx = thisValue;
|
||||
if (!(rx instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', rx);
|
||||
}
|
||||
const S = Q(yield* ToString(string));
|
||||
|
||||
const C = Q(yield* SpeciesConstructor(rx, surroundingAgent.intrinsic('%RegExp%')));
|
||||
const flagsValue = Q(yield* Get(rx, Value('flags')));
|
||||
const flags = Q(yield* ToString(flagsValue)).stringValue();
|
||||
const unicodeMatching = flags.includes('u');
|
||||
const newFlags = flags.includes('y') ? Value(flags) : Value(`${flags}y`);
|
||||
const splitter = Q(yield* Construct(C, [rx, newFlags]));
|
||||
|
||||
const A = X(ArrayCreate(0));
|
||||
let lengthA = 0;
|
||||
|
||||
let lim;
|
||||
if (limit === Value.undefined) {
|
||||
lim = (2 ** 32) - 1;
|
||||
} else {
|
||||
lim = R(Q(yield* ToUint32(limit)));
|
||||
}
|
||||
|
||||
const size = S.stringValue().length;
|
||||
let p = 0;
|
||||
|
||||
if (lim === 0) {
|
||||
return A;
|
||||
}
|
||||
|
||||
if (size === 0) {
|
||||
const z = Q(yield* RegExpExec(splitter, S));
|
||||
if (z !== Value.null) {
|
||||
return A;
|
||||
}
|
||||
X(CreateDataProperty(A, Value('0'), S));
|
||||
return A;
|
||||
}
|
||||
|
||||
let q = p;
|
||||
while (q < size) {
|
||||
Q(yield* Set(splitter, Value('lastIndex'), F(q), Value.true));
|
||||
const z = Q(yield* RegExpExec(splitter, S));
|
||||
if (z instanceof NullValue) {
|
||||
q = AdvanceStringIndex(S, q, unicodeMatching);
|
||||
} else {
|
||||
const lastIndex = Q(yield* Get(splitter, Value('lastIndex')));
|
||||
let e = R(Q(yield* ToLength(lastIndex)));
|
||||
e = Math.min(e, size);
|
||||
if (e === p) {
|
||||
q = AdvanceStringIndex(S, q, unicodeMatching);
|
||||
} else {
|
||||
const T = Value(S.stringValue().substring(p, q));
|
||||
X(CreateDataProperty(A, X(ToString(F(lengthA))), T));
|
||||
lengthA += 1;
|
||||
if (lengthA === lim) {
|
||||
return A;
|
||||
}
|
||||
p = e;
|
||||
let numberOfCaptures = Q(yield* LengthOfArrayLike(z));
|
||||
numberOfCaptures = Math.max(numberOfCaptures - 1, 0);
|
||||
let i = 1;
|
||||
while (i <= numberOfCaptures) {
|
||||
const nextCapture = Q(yield* Get(z, X(ToString(F(i)))));
|
||||
X(CreateDataProperty(A, X(ToString(F(lengthA))), nextCapture));
|
||||
i += 1;
|
||||
lengthA += 1;
|
||||
if (lengthA === lim) {
|
||||
return A;
|
||||
}
|
||||
}
|
||||
q = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const T = Value(S.stringValue().substring(p, size));
|
||||
X(CreateDataProperty(A, X(ToString(F(lengthA))), T));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky */
|
||||
function RegExpProto_stickyGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let R be the this value.
|
||||
const R = thisValue;
|
||||
// 2. Let cu be the code unit 0x0097 (LATIN SMALL LETTER Y).
|
||||
const cu = 'y';
|
||||
// 3. Return ? RegExpHasFlag(R, cu).
|
||||
return Q(RegExpHasFlag(R, cu));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp.prototype.test */
|
||||
function* RegExpProto_test([S = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const R = thisValue;
|
||||
if (!(R instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R);
|
||||
}
|
||||
const string = Q(yield* ToString(S));
|
||||
const match = Q(yield* RegExpExec(R, string));
|
||||
if (match !== Value.null) {
|
||||
return Value.true;
|
||||
}
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-regexp.prototype.tostring */
|
||||
function* RegExpProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const R = thisValue;
|
||||
if (!(R instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'RegExp', R);
|
||||
}
|
||||
const pattern = Q(yield* ToString(Q(yield* Get(R, Value('source')))));
|
||||
const flags = Q(yield* ToString(Q(yield* Get(R, Value('flags')))));
|
||||
const result = `/${pattern.stringValue()}/${flags.stringValue()}`;
|
||||
return Value(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode */
|
||||
function RegExpProto_unicodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let R be the this value.
|
||||
const R = thisValue;
|
||||
// 2. Let cu be the code unit 0x0075 (LATIN SMALL LETTER U).
|
||||
const cu = 'u';
|
||||
// 3. Return ? RegExpHasFlag(R, cu).
|
||||
return Q(RegExpHasFlag(R, cu));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-regexp.prototype.unicodeSets */
|
||||
function RegExpProto_unicodeSetsGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let R be the this value.
|
||||
const R = thisValue;
|
||||
// 2. Let cu be the code unit 0x0076 (LATIN SMALL LETTER V).
|
||||
const cu = 'v';
|
||||
// 3. Return ? RegExpHasFlag(R, cu).
|
||||
return Q(RegExpHasFlag(R, cu));
|
||||
}
|
||||
|
||||
export function bootstrapRegExpPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(
|
||||
realmRec,
|
||||
[
|
||||
['exec', RegExpProto_exec, 1],
|
||||
['dotAll', [RegExpProto_dotAllGetter]],
|
||||
['flags', [RegExpProto_flagsGetter]],
|
||||
['global', [RegExpProto_globalGetter]],
|
||||
['hasIndices', [RegExpProto_hasIndicesGetter]],
|
||||
['ignoreCase', [RegExpProto_ignoreCaseGetter]],
|
||||
[wellKnownSymbols.match, RegExpProto_match, 1],
|
||||
[wellKnownSymbols.matchAll, RegExpProto_matchAll, 1],
|
||||
['multiline', [RegExpProto_multilineGetter]],
|
||||
[wellKnownSymbols.replace, RegExpProto_replace, 2],
|
||||
[wellKnownSymbols.search, RegExpProto_search, 1],
|
||||
['source', [RegExpProto_sourceGetter]],
|
||||
[wellKnownSymbols.split, RegExpProto_split, 2],
|
||||
['sticky', [RegExpProto_stickyGetter]],
|
||||
['test', RegExpProto_test, 1],
|
||||
['toString', RegExpProto_toString, 0],
|
||||
['unicode', [RegExpProto_unicodeGetter]],
|
||||
['unicodeSets', [RegExpProto_unicodeSetsGetter]],
|
||||
],
|
||||
realmRec.Intrinsics['%Object.prototype%'],
|
||||
);
|
||||
|
||||
realmRec.Intrinsics['%RegExp.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
JSStringValue, NullValue, ObjectValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { RegExpExec, AdvanceStringIndex } from './RegExpPrototype.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
CreateIteratorFromClosure,
|
||||
GeneratorResume,
|
||||
ToString,
|
||||
ToLength,
|
||||
Get,
|
||||
Set,
|
||||
Yield,
|
||||
F, R as MathematicalValue,
|
||||
Realm,
|
||||
type GeneratorObject,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-createregexpstringiterator */
|
||||
export function CreateRegExpStringIterator(R: ObjectValue, S: JSStringValue, global: boolean, fullUnicode: boolean): ValueCompletion<GeneratorObject> {
|
||||
// 4. Let closure be a new Abstract Closure with no parameters that captures R, S, global, and fullUnicode and performs the following steps when called:
|
||||
const closure = function* closure(): ValueEvaluator {
|
||||
// a. Repeat,
|
||||
while (true) {
|
||||
// i. Let match be ? RegExpExec(R, S).
|
||||
const match = Q(yield* RegExpExec(R, S));
|
||||
// ii. If match is null, return undefined.
|
||||
if (match instanceof NullValue) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// iii. If global is false, then
|
||||
if (!global) {
|
||||
// 1. Perform ? Yield(match).
|
||||
Q(yield* Yield(match));
|
||||
// 2. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
// iv. Let matchStr be ? ToString(? Get(match, "0")).
|
||||
const matchStr = Q(yield* ToString(Q(yield* Get(match, Value('0')))));
|
||||
// v. If matchStr is the empty String, then
|
||||
if (matchStr.stringValue() === '') {
|
||||
// i. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
|
||||
const thisIndex = MathematicalValue(Q(yield* ToLength(Q(yield* Get(R, Value('lastIndex'))))));
|
||||
// ii. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode).
|
||||
const nextIndex = X(AdvanceStringIndex(S, thisIndex, fullUnicode));
|
||||
// iii. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true).
|
||||
Q(yield* Set(R, Value('lastIndex'), F(nextIndex), Value.true));
|
||||
}
|
||||
// vi. Perform ? Yield(match).
|
||||
Q(yield* Yield(match));
|
||||
}
|
||||
};
|
||||
// 4. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%).
|
||||
return X(CreateIteratorFromClosure(closure, Value('%RegExpStringIteratorPrototype%'), surroundingAgent.intrinsic('%RegExpStringIteratorPrototype%')));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%regexpstringiteratorprototype%.next */
|
||||
function* RegExpStringIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Return ? GeneratorResume(this value, empty, "%RegExpStringIteratorPrototype%").
|
||||
return Q(yield* GeneratorResume(thisValue, undefined, Value('%RegExpStringIteratorPrototype%')));
|
||||
}
|
||||
|
||||
export function bootstrapRegExpStringIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', RegExpStringIteratorPrototype_next, 0],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%'], 'RegExp String Iterator');
|
||||
|
||||
realmRec.Intrinsics['%RegExpStringIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
UndefinedValue, Value, wellKnownSymbols, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { IfAbruptCloseIterator, Q } from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Call,
|
||||
Get,
|
||||
GetIterator,
|
||||
IsCallable,
|
||||
IteratorStepValue,
|
||||
OrdinaryCreateFromConstructor,
|
||||
Realm,
|
||||
type FunctionObject,
|
||||
type OrdinaryObject,
|
||||
} from '#self';
|
||||
|
||||
export interface SetObject extends OrdinaryObject {
|
||||
readonly SetData: (Value | undefined)[];
|
||||
}
|
||||
export function isSetObject(value: Value): value is SetObject {
|
||||
return 'SetData' in value;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-set-iterable */
|
||||
function* SetConstructor(this: FunctionObject, [iterable = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%Set.prototype%", « [[SetData]] »).
|
||||
const set = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%Set.prototype%', ['SetData'])) as Mutable<SetObject>;
|
||||
// 3. Set set.[[SetData]] to a new empty List.
|
||||
set.SetData = [];
|
||||
// 4. If iterable is either undefined or null, return set.
|
||||
if (iterable === Value.undefined || iterable === Value.null) {
|
||||
return set;
|
||||
}
|
||||
// 5. Let adder be ? Get(set, "add").
|
||||
const adder = Q(yield* Get(set, Value('add')));
|
||||
// 6. If IsCallable(adder) is false, throw a TypeError exception.
|
||||
if (!IsCallable(adder)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', adder);
|
||||
}
|
||||
// 7. Let iteratorRecord be ? GetIterator(iterable).
|
||||
const iteratorRecord = Q(yield* GetIterator(iterable, 'sync'));
|
||||
// 8. Repeat,
|
||||
while (true) {
|
||||
// a. Let next be ? IteratorStepValue(iteratorRecord).
|
||||
const next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
// b. If next is false, return set.
|
||||
if (next === 'done') {
|
||||
return set;
|
||||
}
|
||||
// d. Let status be Call(adder, set, « next »).
|
||||
const status = yield* Call(adder, set, [next]);
|
||||
// e. IfAbruptCloseIterator(status, iteratorRecord).
|
||||
IfAbruptCloseIterator(status, iteratorRecord);
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-set-@@species */
|
||||
function Set_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// Return the this value.
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
export function bootstrapSet(realmRec: Realm) {
|
||||
const setConstructor = bootstrapConstructor(realmRec, SetConstructor, 'Set', 0, realmRec.Intrinsics['%Set.prototype%'], [
|
||||
[wellKnownSymbols.species, [Set_speciesGetter]],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%Set%'] = setConstructor;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { Q, X, type ValueCompletion } from '../completion.mts';
|
||||
import {
|
||||
Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import type { ValueEvaluator, YieldEvaluator } from '../evaluator.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { SetObject } from './Set.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateArrayFromList,
|
||||
CreateIteratorFromClosure,
|
||||
GeneratorResume,
|
||||
Realm,
|
||||
RequireInternalSlot,
|
||||
Yield,
|
||||
type GeneratorObject,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-createsetiterator */
|
||||
export function CreateSetIterator(set: Value, kind: 'key+value' | 'value'): ValueCompletion<GeneratorObject> {
|
||||
// 1. Assert: kind is key+value or value.
|
||||
Assert(kind === 'key+value' || kind === 'value');
|
||||
// 2. Perform ? RequireInternalSlot(set, [[SetData]]).
|
||||
Q(RequireInternalSlot(set, 'SetData'));
|
||||
// 3. Let closure be a new Abstract Closure with no parameters that captures set and kind and performs the following steps when called:
|
||||
const closure = function* closure(): YieldEvaluator {
|
||||
// a. Let index be 0.
|
||||
let index = 0;
|
||||
// b. Let entries be the List that is set.[[SetData]].
|
||||
const entries = (set as SetObject).SetData;
|
||||
// c. Let numEntries be the number of elements of entries.
|
||||
let numEntries = entries.length;
|
||||
// d. Repeat, while index < numEntries,
|
||||
while (index < numEntries) {
|
||||
// i. Let e be entries[index].
|
||||
const e = entries[index];
|
||||
// ii. Set index to index + 1.
|
||||
index += 1;
|
||||
// iii. If e is not empty, then
|
||||
if (e !== undefined) {
|
||||
// 1. If kind is key+value, then
|
||||
if (kind === 'key+value') {
|
||||
// a. Perform ? Yield(! CreateArrayFromList(« e, e »)).
|
||||
Q(yield* Yield(X(CreateArrayFromList([e, e]))));
|
||||
} else { // 2. Else,
|
||||
// a. Assert: kind is value.
|
||||
Assert(kind === 'value');
|
||||
// b. Perform ? Yield(e).
|
||||
Q(yield* Yield(e));
|
||||
}
|
||||
}
|
||||
// iv. Set numEntries to the number of elements of entries.
|
||||
numEntries = entries.length;
|
||||
}
|
||||
// NON-SPEC
|
||||
generator.HostCapturedValues = undefined;
|
||||
// e. Return undefined.
|
||||
return Value.undefined;
|
||||
};
|
||||
// 4. Return ! CreateIteratorFromClosure(closure, "%SetIteratorPrototype%", %SetIteratorPrototype%).
|
||||
const generator = X(CreateIteratorFromClosure(closure, Value('%SetIteratorPrototype%'), surroundingAgent.intrinsic('%SetIteratorPrototype%'), ['HostCapturedValues'], [set]));
|
||||
return generator;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%setiteratorprototype%.next */
|
||||
function* SetIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Return ? GeneratorResume(this value, empty, "%SetIteratorPrototype%").
|
||||
return Q(yield* GeneratorResume(thisValue, undefined, Value('%SetIteratorPrototype%')));
|
||||
}
|
||||
|
||||
export function bootstrapSetIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', SetIteratorPrototype_next, 0],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%'], 'Set Iterator');
|
||||
|
||||
realmRec.Intrinsics['%SetIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,715 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
Descriptor,
|
||||
NumberValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
ObjectValue,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
BooleanValue,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
EnsureCompletion, NormalCompletion, Q, X, type ValueCompletion, type ValueEvaluator,
|
||||
} from '../completion.mts';
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { CreateSetIterator } from './SetIteratorPrototype.mts';
|
||||
import type { SetObject } from './Set.mts';
|
||||
import {
|
||||
Call,
|
||||
F,
|
||||
IsCallable,
|
||||
RequireInternalSlot,
|
||||
Get,
|
||||
ToNumber,
|
||||
ToIntegerOrInfinity,
|
||||
IteratorStep,
|
||||
IteratorValue,
|
||||
OrdinaryObjectCreate,
|
||||
SameValueZero, R,
|
||||
Realm,
|
||||
ToBoolean,
|
||||
GetIteratorFromMethod,
|
||||
CanonicalizeKeyedCollectionKey,
|
||||
IteratorStepValue,
|
||||
IteratorClose,
|
||||
} from '#self';
|
||||
import type {
|
||||
FunctionObject,
|
||||
Mutable,
|
||||
PlainEvaluator,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.add */
|
||||
function SetProto_add([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue as SetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[SetData]]).
|
||||
Q(RequireInternalSlot(S, 'SetData'));
|
||||
// 3. Let entries be the List that is S.[[SetData]].
|
||||
const entries = S.SetData;
|
||||
// 4. For each e that is an element of entries, do
|
||||
for (const e of entries) {
|
||||
// a. For each e that is an element of entries, do
|
||||
if (e !== undefined && SameValueZero(e, value) === Value.true) {
|
||||
// i. Return S.
|
||||
return S;
|
||||
}
|
||||
}
|
||||
// 5. If value is -0𝔽, set value to +0𝔽.
|
||||
if (value instanceof NumberValue && Object.is(R(value), -0)) {
|
||||
value = F(+0);
|
||||
}
|
||||
// 6. Append value as the last element of entries.
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(S));
|
||||
entries.push(value);
|
||||
// 7. Return S.
|
||||
return S;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.clear */
|
||||
function SetProto_clear(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue as SetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[SetData]]).
|
||||
Q(RequireInternalSlot(S, 'SetData'));
|
||||
// 3. Let entries be the List that is S.[[SetData]].
|
||||
const entries = S.SetData;
|
||||
// 4. For each e that is an element of entries, do
|
||||
if (entries.length) {
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(S));
|
||||
}
|
||||
for (let i = 0; i < entries.length; i += 1) {
|
||||
// a. Replace the element of entries whose value is e with an element whose value is empty.
|
||||
entries[i] = undefined;
|
||||
}
|
||||
// 5. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.delete */
|
||||
function SetProto_delete([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue as SetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[SetData]]).
|
||||
Q(RequireInternalSlot(S, 'SetData'));
|
||||
// 3. Let entries be the List that is S.[[SetData]].
|
||||
const entries = S.SetData;
|
||||
// 4. For each e that is an element of entries, do
|
||||
for (let i = 0; i < entries.length; i += 1) {
|
||||
const e = entries[i];
|
||||
// a. If e is not empty and SameValueZero(e, value) is true, then
|
||||
if (e !== undefined && SameValueZero(e, value) === Value.true) {
|
||||
// i. Replace the element of entries whose value is e with an element whose value is empty.
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(S));
|
||||
entries[i] = undefined;
|
||||
// ii. Return true.
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
// 5. Return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.difference */
|
||||
function* SetProto_difference([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
|
||||
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
|
||||
Q(RequireInternalSlot(O, 'SetData'));
|
||||
__ts_cast__<SetObject>(O);
|
||||
|
||||
// 3. Let otherRec be ? GetSetRecord(other).
|
||||
const otherRec = Q(yield* GetSetRecord(other));
|
||||
|
||||
// 4. Let resultSetData be a copy of O.[[SetData]].
|
||||
const resultSetData = [...O.SetData];
|
||||
|
||||
// 5. If SetDataSize(O.[[SetData]]) ≤ otherRec.[[Size]], then
|
||||
if (R(SetDataSize(O.SetData)) <= otherRec.Size) {
|
||||
/*
|
||||
a. Let thisSize be the number of elements in O.[[SetData]].
|
||||
b. Let index be 0.
|
||||
c. Repeat, while index < thisSize,
|
||||
i. Let e be resultSetData[index].
|
||||
ii. If e is not empty, then
|
||||
1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
|
||||
2. If inOther is true, then
|
||||
a. Set resultSetData[index] to empty.
|
||||
iii. Set index to index + 1.
|
||||
*/
|
||||
const thisSize = O.SetData.length;
|
||||
let index = 0;
|
||||
while (index < thisSize) {
|
||||
const e = resultSetData[index];
|
||||
if (e !== undefined) {
|
||||
const inOther = ToBoolean(Q(yield* Call(otherRec.Has, otherRec.SetObject, [e])));
|
||||
if (inOther === Value.true) {
|
||||
resultSetData[index] = undefined;
|
||||
}
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
|
||||
b. Let next be not-started.
|
||||
c. Repeat, while next is not done,
|
||||
i. Set next to ? IteratorStepValue(keysIter).
|
||||
ii. If next is not done, then
|
||||
1. Set next to CanonicalizeKeyedCollectionKey(next).
|
||||
2. Let valueIndex be SetDataIndex(resultSetData, next).
|
||||
3. If valueIndex is not not-found, then
|
||||
a. Set resultSetData[valueIndex] to empty.
|
||||
*/
|
||||
const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys));
|
||||
let next: Value | 'done' | 'not-started' = 'not-started';
|
||||
while (next !== 'done') {
|
||||
next = Q(yield* IteratorStepValue(keysIter));
|
||||
if (next !== 'done') {
|
||||
next = CanonicalizeKeyedCollectionKey(next);
|
||||
const valueIndex = SetDataIndex(resultSetData, next);
|
||||
if (valueIndex !== 'not-found') {
|
||||
resultSetData[valueIndex] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
7. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
|
||||
8. Set result.[[SetData]] to resultSetData.
|
||||
9. Return result.
|
||||
*/
|
||||
const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Set.prototype%'), ['SetData']) as Mutable<SetObject>;
|
||||
result.SetData = resultSetData;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.entries */
|
||||
function SetProto_entries(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue;
|
||||
// 2. Return ? CreateSetIterator(S, key+value).
|
||||
return Q(CreateSetIterator(S, 'key+value'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.foreach */
|
||||
function* SetProto_forEach([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue as SetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[SetData]]).
|
||||
Q(RequireInternalSlot(S, 'SetData'));
|
||||
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
// 4. Let entries be the List that is S.[[SetData]].
|
||||
const entries = S.SetData;
|
||||
// 5. For each element _e_ of _entries_, do
|
||||
for (const e of entries) {
|
||||
// a. If e is not empty, then
|
||||
if (e !== undefined) {
|
||||
// i. Perform ? Call(callbackfn, thisArg, « e, e, S »).
|
||||
Q(yield* Call(callbackfn, thisArg, [e, e, S]));
|
||||
}
|
||||
}
|
||||
// 6. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.has */
|
||||
function SetProto_has([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue as SetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[SetData]]).
|
||||
Q(RequireInternalSlot(S, 'SetData'));
|
||||
// 3. Let entries be the List that is S.[[SetData]].
|
||||
const entries = S.SetData;
|
||||
// 4. Let entries be the List that is S.[[SetData]].
|
||||
for (const e of entries) {
|
||||
// a. If e is not empty and SameValueZero(e, value) is true, return true.
|
||||
if (e !== undefined && SameValueZero(e, value) === Value.true) {
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
// 5. Return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-set.prototype.size */
|
||||
function SetProto_sizeGetter(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue as SetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[SetData]]).
|
||||
Q(RequireInternalSlot(S, 'SetData'));
|
||||
// 3. Let entries be the List that is S.[[SetData]].
|
||||
const entries = S.SetData;
|
||||
|
||||
return SetDataSize(entries);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.intersection */
|
||||
function* SetProto_intersection([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
|
||||
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
|
||||
Q(RequireInternalSlot(O, 'SetData'));
|
||||
__ts_cast__<SetObject>(O);
|
||||
|
||||
// 3. Let otherRec be ? GetSetRecord(other).
|
||||
const otherRec = Q(yield* GetSetRecord(other));
|
||||
|
||||
// 4. Let resultSetData be a new empty List.
|
||||
const resultSetData: Value[] = [];
|
||||
|
||||
if (R(SetDataSize(O.SetData)) <= otherRec.Size) {
|
||||
/*
|
||||
a. Let thisSize be the number of elements in O.[[SetData]].
|
||||
b. Let index be 0.
|
||||
c. Repeat, while index < thisSize,
|
||||
i. Let e be O.[[SetData]][index].
|
||||
ii. Set index to index + 1.
|
||||
iii. If e is not empty, then
|
||||
1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
|
||||
2. If inOther is true, then
|
||||
a. NOTE: It is possible for earlier calls to otherRec.[[Has]] to remove and re-add an element of O.[[SetData]], which can cause the same element to be visited twice during this iteration.
|
||||
b. If SetDataHas(resultSetData, e) is false, then
|
||||
i. Append e to resultSetData.
|
||||
3. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
|
||||
4. Set thisSize to the number of elements in O.[[SetData]].
|
||||
*/
|
||||
let thisSize = O.SetData.length;
|
||||
let index = 0;
|
||||
while (index < thisSize) {
|
||||
const e: Value | undefined = O.SetData[index];
|
||||
index += 1;
|
||||
if (e !== undefined) {
|
||||
const inOther = ToBoolean(Q(yield* Call(otherRec.Has, otherRec.SetObject, [e])));
|
||||
if (inOther === Value.true && !SetDataHas(resultSetData, e)) {
|
||||
resultSetData.push(e);
|
||||
}
|
||||
}
|
||||
thisSize = O.SetData.length;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
|
||||
b. Let next be not-started.
|
||||
c. Repeat, while next is not done,
|
||||
i. Set next to ? IteratorStepValue(keysIter).
|
||||
ii. If next is not done, then
|
||||
1. Set next to CanonicalizeKeyedCollectionKey(next).
|
||||
2. Let inThis be SetDataHas(O.[[SetData]], next).
|
||||
3. If inThis is true, then
|
||||
a. NOTE: Because other is an arbitrary object, it is possible for its "keys" iterator to produce the same value more than once.
|
||||
b. If SetDataHas(resultSetData, next) is false, then
|
||||
i. Append next to resultSetData.
|
||||
*/
|
||||
const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys));
|
||||
let next: Value | 'done' | 'not-started' = 'not-started';
|
||||
while (next !== 'done') {
|
||||
next = Q(yield* IteratorStepValue(keysIter));
|
||||
if (next !== 'done') {
|
||||
next = CanonicalizeKeyedCollectionKey(next);
|
||||
const inThis = SetDataHas(O.SetData, next);
|
||||
if (inThis && !SetDataHas(resultSetData, next)) {
|
||||
resultSetData.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
7. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
|
||||
8. Set result.[[SetData]] to resultSetData.
|
||||
9. Return result.
|
||||
*/
|
||||
const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Set.prototype%'), ['SetData']) as Mutable<SetObject>;
|
||||
result.SetData = resultSetData;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.isdisjointfrom */
|
||||
function* SetProto_isDisjointFrom([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator<BooleanValue> {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
|
||||
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
|
||||
Q(RequireInternalSlot(O, 'SetData'));
|
||||
__ts_cast__<SetObject>(O);
|
||||
|
||||
// 3. Let otherRec be ? GetSetRecord(other).
|
||||
const otherRec = Q(yield* GetSetRecord(other));
|
||||
|
||||
if (R(SetDataSize(O.SetData)) <= otherRec.Size) {
|
||||
/*
|
||||
a. Let thisSize be the number of elements in O.[[SetData]].
|
||||
b. Let index be 0.
|
||||
c. Repeat, while index < thisSize,
|
||||
i. Let e be O.[[SetData]][index].
|
||||
ii. Set index to index + 1.
|
||||
iii. If e is not empty, then
|
||||
1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
|
||||
2. If inOther is true, return false.
|
||||
3. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
|
||||
4. Set thisSize to the number of elements in O.[[SetData]].
|
||||
*/
|
||||
let thisSize = O.SetData.length;
|
||||
let index = 0;
|
||||
while (index < thisSize) {
|
||||
const e = O.SetData[index];
|
||||
index += 1;
|
||||
if (e !== undefined) {
|
||||
const inOther = ToBoolean(Q(yield* Call(otherRec.Has, otherRec.SetObject, [e])));
|
||||
if (inOther === Value.true) {
|
||||
return BooleanValue.false;
|
||||
}
|
||||
thisSize = O.SetData.length;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
|
||||
b. Let next be not-started.
|
||||
c. Repeat, while next is not done,
|
||||
i. Set next to ? IteratorStepValue(keysIter).
|
||||
ii. If next is not done, then
|
||||
1. If SetDataHas(O.[[SetData]], next) is true, then
|
||||
a. Perform ? IteratorClose(keysIter, NormalCompletion(unused)).
|
||||
b. Return false.
|
||||
*/
|
||||
const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys));
|
||||
let next: Value | 'done' | 'not-started' = 'not-started';
|
||||
while (next !== 'done') {
|
||||
next = Q(yield* IteratorStepValue(keysIter));
|
||||
if (next !== 'done' && SetDataHas(O.SetData, next)) {
|
||||
Q(yield* IteratorClose(keysIter, NormalCompletion(undefined)));
|
||||
return Value.false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Value.true;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.issubsetof */
|
||||
function* SetProto_isSubsetOf([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator<BooleanValue> {
|
||||
const O = thisValue;
|
||||
|
||||
Q(RequireInternalSlot(O, 'SetData'));
|
||||
__ts_cast__<SetObject>(O);
|
||||
|
||||
const otherRec = Q(yield* GetSetRecord(other));
|
||||
if (SetDataSize(O.SetData).value > otherRec.Size) {
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
let thisSize = O.SetData.length;
|
||||
let index = 0;
|
||||
while (index < thisSize) {
|
||||
/*
|
||||
a. Let e be O.[[SetData]][index].
|
||||
b. Set index to index + 1.
|
||||
c. If e is not empty, then
|
||||
i. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
|
||||
ii. If inOther is false, return false.
|
||||
iii. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
|
||||
iv. Set thisSize to the number of elements in O.[[SetData]].
|
||||
*/
|
||||
const e = O.SetData[index];
|
||||
index += 1;
|
||||
if (e !== undefined) {
|
||||
const inOther = ToBoolean(Q(yield* Call(otherRec.Has, otherRec.SetObject, [e])));
|
||||
if (inOther === Value.false) {
|
||||
return Value.false;
|
||||
}
|
||||
thisSize = O.SetData.length;
|
||||
}
|
||||
}
|
||||
|
||||
return Value.true;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.issupersetof */
|
||||
function* SetProto_isSupersetOf([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator<BooleanValue> {
|
||||
const O = thisValue;
|
||||
|
||||
Q(RequireInternalSlot(O, 'SetData'));
|
||||
__ts_cast__<SetObject>(O);
|
||||
|
||||
const otherRec = Q(yield* GetSetRecord(other));
|
||||
if (SetDataSize(O.SetData).value < otherRec.Size) {
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys));
|
||||
let next: Value | 'done' | 'not-started' = 'not-started';
|
||||
while (next !== 'done') {
|
||||
/*
|
||||
a. Set next to ? IteratorStepValue(keysIter).
|
||||
b. If next is not done, then
|
||||
i. If SetDataHas(O.[[SetData]], next) is false, then
|
||||
1. Perform ? IteratorClose(keysIter, NormalCompletion(unused)).
|
||||
2. Return false.
|
||||
*/
|
||||
next = Q(yield* IteratorStepValue(keysIter));
|
||||
if (next !== 'done' && !SetDataHas(O.SetData, next)) {
|
||||
Q(yield* IteratorClose(keysIter, NormalCompletion(undefined)));
|
||||
return Value.false;
|
||||
}
|
||||
}
|
||||
|
||||
return Value.true;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.symmetricdifference */
|
||||
function* SetProto_symmetricDifference([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
|
||||
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
|
||||
Q(RequireInternalSlot(O, 'SetData'));
|
||||
__ts_cast__<SetObject>(O);
|
||||
|
||||
// 3. Let otherRec be ? GetSetRecord(other).
|
||||
const otherRec = Q(yield* GetSetRecord(other));
|
||||
|
||||
// 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
|
||||
const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys));
|
||||
// 5. Let resultSetData be a copy of O.[[SetData]].
|
||||
const resultSetData = [...O.SetData];
|
||||
// 6. Let next be not-started.
|
||||
let next: Value | 'done' | 'not-started' = 'not-started';
|
||||
|
||||
while (next !== 'done') {
|
||||
/*
|
||||
a. Set next to ? IteratorStepValue(keysIter).
|
||||
b. If next is not done, then
|
||||
i. Set next to CanonicalizeKeyedCollectionKey(next).
|
||||
ii. Let resultIndex be SetDataIndex(resultSetData, next).
|
||||
iii. If resultIndex is not-found, let alreadyInResult be false. Otherwise let alreadyInResult be true.
|
||||
iv. If SetDataHas(O.[[SetData]], next) is true, then
|
||||
1. If alreadyInResult is true, set resultSetData[resultIndex] to empty.
|
||||
v. Else,
|
||||
1. If alreadyInResult is false, append next to resultSetData.
|
||||
*/
|
||||
next = Q(yield* IteratorStepValue(keysIter));
|
||||
if (next !== 'done') {
|
||||
next = CanonicalizeKeyedCollectionKey(next);
|
||||
const resultIndex: number | 'not-found' = SetDataIndex(resultSetData, next);
|
||||
if (SetDataHas(O.SetData, next) === true) {
|
||||
if (resultIndex !== 'not-found') {
|
||||
resultSetData[resultIndex] = undefined;
|
||||
}
|
||||
} else {
|
||||
if ((resultIndex === 'not-found')) {
|
||||
resultSetData.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
|
||||
9. Set result.[[SetData]] to resultSetData.
|
||||
10. Return result.
|
||||
*/
|
||||
|
||||
const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Set.prototype%'), ['SetData']) as Mutable<SetObject>;
|
||||
result.SetData = resultSetData;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.values */
|
||||
function SetProto_values(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue;
|
||||
// 2. Return ? CreateSetIterator(S, value).
|
||||
return Q(CreateSetIterator(S, 'value'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-set.prototype.union */
|
||||
function* SetProto_union([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue;
|
||||
|
||||
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
|
||||
Q(RequireInternalSlot(O, 'SetData'));
|
||||
__ts_cast__<SetObject>(O);
|
||||
|
||||
// 3. Let otherRec be ? GetSetRecord(other).
|
||||
const otherRec = Q(yield* GetSetRecord(other));
|
||||
|
||||
// 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
|
||||
const keysIter = Q(yield* GetIteratorFromMethod(otherRec.SetObject, otherRec.Keys));
|
||||
|
||||
// 5. Let resultSetData be a copy of O.[[SetData]].
|
||||
const resultSetData = [...O.SetData];
|
||||
|
||||
// 6. Let next be true.
|
||||
let next: Value | 'done' = Value.true;
|
||||
|
||||
// 7. Repeat, while next is not DONE,
|
||||
while (next !== 'done') {
|
||||
// a. Set next to ? IteratorStep(keysIter).
|
||||
next = Q(yield* IteratorStep(keysIter));
|
||||
|
||||
// b. If next is not DONE, then
|
||||
if (next !== 'done') {
|
||||
// i. Let nextValue be ? IteratorValue(next).
|
||||
let nextValue = Q(yield* IteratorValue(next));
|
||||
|
||||
// ii. If nextValue is -0𝔽, set nextValue to +0𝔽.
|
||||
if (nextValue instanceof NumberValue && Object.is(R(nextValue), -0)) {
|
||||
nextValue = F(+0);
|
||||
}
|
||||
|
||||
// iii. If SetDataHas(resultSetData, nextValue) is false, then
|
||||
if (!SetDataHas(resultSetData, nextValue)) {
|
||||
// 1. Append nextValue to resultSetData.
|
||||
resultSetData.push(nextValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
|
||||
const result = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Set.prototype%'), ['SetData']) as Mutable<SetObject>;
|
||||
|
||||
// 9. Set result.[[SetData]] to resultSetData.
|
||||
result.SetData = resultSetData;
|
||||
|
||||
// 10. Return result.
|
||||
return EnsureCompletion(result);
|
||||
}
|
||||
|
||||
export function bootstrapSetPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['add', SetProto_add, 1],
|
||||
['clear', SetProto_clear, 0],
|
||||
['delete', SetProto_delete, 1],
|
||||
['difference', SetProto_difference, 1],
|
||||
['entries', SetProto_entries, 0],
|
||||
['forEach', SetProto_forEach, 1],
|
||||
['has', SetProto_has, 1],
|
||||
['intersection', SetProto_intersection, 1],
|
||||
['isDisjointFrom', SetProto_isDisjointFrom, 1],
|
||||
['isSubsetOf', SetProto_isSubsetOf, 1],
|
||||
['isSupersetOf', SetProto_isSupersetOf, 1],
|
||||
['size', [SetProto_sizeGetter]],
|
||||
['symmetricDifference', SetProto_symmetricDifference, 1],
|
||||
['values', SetProto_values, 0],
|
||||
['union', SetProto_union, 1],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Set');
|
||||
|
||||
const valuesFunc = X(proto.GetOwnProperty(Value('values'))) as Descriptor;
|
||||
X(proto.DefineOwnProperty(Value('keys'), valuesFunc));
|
||||
X(proto.DefineOwnProperty(wellKnownSymbols.iterator, valuesFunc));
|
||||
|
||||
realmRec.Intrinsics['%Set.prototype%'] = proto;
|
||||
}
|
||||
|
||||
interface SetRecord {
|
||||
readonly SetObject: ObjectValue;
|
||||
readonly Size: number;
|
||||
readonly Has: Value;
|
||||
readonly Keys: FunctionObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-getsetrecord */
|
||||
function* GetSetRecord(obj: Value): PlainEvaluator<SetRecord> {
|
||||
// 1. If obj is not an Object, throw a TypeError exception.
|
||||
if (!(obj instanceof ObjectValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', obj);
|
||||
}
|
||||
|
||||
// 2. Let rawSize be ? Get(obj, "size").
|
||||
const rawSize = Q(yield* Get(obj, Value('size')));
|
||||
|
||||
// 3. Let numSize be ? ToNumber(rawSize).
|
||||
// 4. NOTE: If rawSize is undefined, then numSize will be NaN.
|
||||
const numSize = Q(yield* ToNumber(rawSize));
|
||||
|
||||
// 5. If numSize is NaN, throw a TypeError exception.
|
||||
if (numSize.isNaN()) {
|
||||
return surroundingAgent.Throw('TypeError', 'SizeIsNaN');
|
||||
}
|
||||
|
||||
// 6. Let intSize be ! ToIntegerOrInfinity(numSize).
|
||||
const intSize = X(ToIntegerOrInfinity(numSize));
|
||||
|
||||
// 7. If intSize < 0, throw a RangeError exception.
|
||||
if (intSize < 0) {
|
||||
return surroundingAgent.Throw('RangeError', 'SizeMustBePositiveInteger');
|
||||
}
|
||||
|
||||
// 8. Let has be ? Get(obj, "has").
|
||||
const has = Q(yield* Get(obj, Value('has')));
|
||||
|
||||
// 9. If IsCallable(has) is false, throw a TypeError exception.
|
||||
if (!IsCallable(has)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', has);
|
||||
}
|
||||
|
||||
// 10. Let keys be ? Get(obj, "keys").
|
||||
const keys = Q(yield* Get(obj, Value('keys')));
|
||||
|
||||
// 11. If IsCallable(keys) is false, throw a TypeError exception.
|
||||
if (!IsCallable(keys)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', keys);
|
||||
}
|
||||
|
||||
// 12. Return a new Set Record { [[Set]]: obj, [[Size]]: intSize, [[Has]]: has, [[Keys]]: keys }.
|
||||
const setRecord: SetRecord = {
|
||||
SetObject: obj,
|
||||
Size: intSize,
|
||||
Has: has,
|
||||
Keys: keys,
|
||||
};
|
||||
|
||||
return EnsureCompletion(setRecord);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-setdatahas */
|
||||
function SetDataHas(resultSetData: (Value | undefined)[], value: Value): boolean {
|
||||
return SetDataIndex(resultSetData, value) !== 'not-found';
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-setdataindex */
|
||||
function SetDataIndex(setData: (Value | undefined)[], value: Value): number | 'not-found' {
|
||||
/*
|
||||
1. Set value to CanonicalizeKeyedCollectionKey(value).
|
||||
2. Let size be the number of elements in setData.
|
||||
3. Let index be 0.
|
||||
4. Repeat, while index < size,
|
||||
a. Let e be setData[index].
|
||||
b. If e is not empty and e is value, then
|
||||
i. Return index.
|
||||
c. Set index to index + 1.
|
||||
5. Return not-found.
|
||||
*/
|
||||
value = CanonicalizeKeyedCollectionKey(value);
|
||||
const size = setData.length;
|
||||
let index = 0;
|
||||
while (index < size) {
|
||||
const e = setData[index];
|
||||
if (e !== undefined && SameValueZero(e, value) === Value.true) {
|
||||
return index;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
return 'not-found';
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-setdatasize */
|
||||
function SetDataSize(setData: (Value | undefined)[]) {
|
||||
let count = 0;
|
||||
for (const e of setData) {
|
||||
if (e !== undefined) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return F(count);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
Descriptor,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
MakeRealm,
|
||||
isOrdinaryObject,
|
||||
OrdinaryCreateFromConstructor,
|
||||
Realm,
|
||||
type FunctionObject,
|
||||
type OrdinaryObject,
|
||||
type Mutable,
|
||||
} from '#self';
|
||||
|
||||
export interface ShadowRealmObject extends OrdinaryObject {
|
||||
readonly ShadowRealm: Realm;
|
||||
}
|
||||
|
||||
export function isShadowRealmObject(value: Value): value is ShadowRealmObject {
|
||||
return 'ShadowRealm' in value;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-symbol-description */
|
||||
function* ShadowRealmConstructor(this: FunctionObject, _args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
Q(surroundingAgent.debugger_cannotPreview);
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
const O = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%ShadowRealm.prototype%', ['ShadowRealm'])) as Mutable<ShadowRealmObject>;
|
||||
// Note: wait for https://github.com/tc39/ecma262/pull/3728
|
||||
const realm = Q(MakeRealm({
|
||||
name: 'ShadowRealm',
|
||||
specifier: surroundingAgent.currentRealmRecord.HostDefined.specifier,
|
||||
}));
|
||||
const innerContext = realm.topContext;
|
||||
|
||||
const realmRec = innerContext.Realm;
|
||||
O.ShadowRealm = realmRec;
|
||||
|
||||
const hostHookCompletion = surroundingAgent.hostDefinedOptions.hostHooks?.HostInitializeShadowRealm?.(realmRec, innerContext, O);
|
||||
if (typeof hostHookCompletion === 'object' && hostHookCompletion && 'next' in hostHookCompletion) {
|
||||
Q(yield* hostHookCompletion);
|
||||
} else {
|
||||
Q(hostHookCompletion);
|
||||
}
|
||||
|
||||
Assert(isOrdinaryObject(realmRec.GlobalObject));
|
||||
return O;
|
||||
}
|
||||
|
||||
export function bootstrapShadowRealm(realmRec: Realm) {
|
||||
const shadowRealmConstructor = bootstrapConstructor(realmRec, ShadowRealmConstructor, 'ShadowRealm', 0, realmRec.Intrinsics['%ShadowRealm.prototype%'], [
|
||||
]);
|
||||
|
||||
X(shadowRealmConstructor.DefineOwnProperty(Value('prototype'), Descriptor({
|
||||
Value: realmRec.Intrinsics['%ShadowRealm.prototype%'],
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
|
||||
realmRec.Intrinsics['%ShadowRealm%'] = shadowRealmConstructor;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { PerformShadowRealmEval, ShadowRealmImportValue, ValidateShadowRealmObject } from '../abstract-ops/shadow-realm.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { type ShadowRealmObject } from './ShadowRealm.mts';
|
||||
import {
|
||||
Realm,
|
||||
} from '#self';
|
||||
import {
|
||||
JSStringValue, Q, surroundingAgent, ToString, Value, type Arguments, type FunctionCallContext, type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.evaluate */
|
||||
function* ShadowRealmPrototype_evaluate([sourceText = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
Q(surroundingAgent.debugger_cannotPreview);
|
||||
const O = thisValue;
|
||||
Q(ValidateShadowRealmObject(O));
|
||||
__ts_cast__<ShadowRealmObject>(O);
|
||||
if (!(sourceText instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAString', sourceText);
|
||||
}
|
||||
const callerRealm = surroundingAgent.currentRealmRecord;
|
||||
const evalRealm = O.ShadowRealm;
|
||||
return Q(yield* PerformShadowRealmEval(sourceText.stringValue(), callerRealm, evalRealm));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue */
|
||||
function* ShadowRealmPrototype_importValue([specifier = Value.undefined, exportName = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
Q(surroundingAgent.debugger_cannotPreview);
|
||||
const O = thisValue;
|
||||
Q(ValidateShadowRealmObject(O));
|
||||
__ts_cast__<ShadowRealmObject>(O);
|
||||
const specifierString = Q(yield* ToString(specifier));
|
||||
if (!(exportName instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAString', exportName);
|
||||
}
|
||||
const callerRealm = surroundingAgent.currentRealmRecord;
|
||||
const evalRealm = O.ShadowRealm;
|
||||
return ShadowRealmImportValue(specifierString, exportName, callerRealm, evalRealm);
|
||||
}
|
||||
|
||||
export function bootstrapShadowRealmPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['evaluate', ShadowRealmPrototype_evaluate, 1],
|
||||
['importValue', ShadowRealmPrototype_importValue, 2],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'ShadowRealm');
|
||||
realmRec.Intrinsics['%ShadowRealm.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
BooleanValue,
|
||||
JSStringValue,
|
||||
NullValue,
|
||||
ObjectValue,
|
||||
SymbolValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { UTF16EncodeCodePoint } from '../static-semantics/all.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
Get,
|
||||
GetPrototypeFromConstructor,
|
||||
IsIntegralNumber,
|
||||
StringCreate,
|
||||
SymbolDescriptiveString,
|
||||
LengthOfArrayLike,
|
||||
ToNumber,
|
||||
ToObject,
|
||||
ToString,
|
||||
ToUint16,
|
||||
F, R,
|
||||
type ExoticObject,
|
||||
Realm,
|
||||
type CodePoint,
|
||||
} from '#self';
|
||||
|
||||
export interface StringObject extends ExoticObject {
|
||||
readonly StringData: JSStringValue;
|
||||
Prototype: ObjectValue | NullValue;
|
||||
Extensible: BooleanValue;
|
||||
}
|
||||
export function isStringObject(o: Value): o is StringObject {
|
||||
return 'StringData' in o;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-string-constructor-string-value */
|
||||
function* StringConstructor([value]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
let s;
|
||||
if (value === undefined) {
|
||||
s = Value('');
|
||||
} else {
|
||||
if (NewTarget === Value.undefined && value instanceof SymbolValue) {
|
||||
return X(SymbolDescriptiveString(value));
|
||||
}
|
||||
s = Q(yield* ToString(value));
|
||||
}
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return s;
|
||||
}
|
||||
return X(StringCreate(s, Q(yield* GetPrototypeFromConstructor(NewTarget, '%String.prototype%'))));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.fromcharcode */
|
||||
function* String_fromCharCode(codeUnits: Arguments): ValueEvaluator {
|
||||
const length = codeUnits.length;
|
||||
const elements = [];
|
||||
let nextIndex = 0;
|
||||
while (nextIndex < length) {
|
||||
const next = codeUnits[nextIndex]!;
|
||||
const nextCU = Q(yield* ToUint16(next));
|
||||
elements.push(nextCU);
|
||||
nextIndex += 1;
|
||||
}
|
||||
const result = elements.reduce((previous, current) => previous + String.fromCharCode(R(current)), '');
|
||||
return Value(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.fromcodepoint */
|
||||
function* String_fromCodePoint(codePoints: Arguments) {
|
||||
// 1. Let result be the empty String.
|
||||
let result = '';
|
||||
// 2. For each element next of codePoints, do
|
||||
for (const next of codePoints.values()) {
|
||||
// a. Let nextCP be ? ToNumber(next).
|
||||
const nextCP = Q(yield* ToNumber(next));
|
||||
// b. If IsIntegralNumber(nextCP) is false, throw a RangeError exception.
|
||||
if (X(IsIntegralNumber(nextCP)) === Value.false) {
|
||||
return surroundingAgent.Throw('RangeError', 'StringCodePointInvalid', next);
|
||||
}
|
||||
// c. If ℝ(nextCP) < 0 or ℝ(nextCP) > 0x10FFFF, throw a RangeError exception.
|
||||
if (R(nextCP) < 0 || R(nextCP) > 0x10FFFF) {
|
||||
return surroundingAgent.Throw('RangeError', 'StringCodePointInvalid', nextCP);
|
||||
}
|
||||
// d. Set result to the string-concatenation of result and UTF16EncodeCodePoint(ℝ(nextCP)).
|
||||
result += UTF16EncodeCodePoint(R(nextCP) as CodePoint);
|
||||
}
|
||||
// 3. Assert: If codePoints is empty, then result is the empty String.
|
||||
Assert(!(codePoints.length === 0) || result.length === 0);
|
||||
// 4. Return result.
|
||||
return Value(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.raw */
|
||||
function* String_raw([template = Value.undefined, ...substitutions]: Arguments): ValueEvaluator {
|
||||
const numberOfSubstitutions = substitutions.length;
|
||||
const cooked = Q(ToObject(template));
|
||||
const raw = Q(ToObject(Q(yield* Get(cooked, Value('raw')))));
|
||||
const literalSegments = Q(yield* LengthOfArrayLike(raw));
|
||||
if (literalSegments <= 0) {
|
||||
return Value('');
|
||||
}
|
||||
// Not sure why the spec uses a List, but this is really just a String.
|
||||
const stringElements = [];
|
||||
let nextIndex = 0;
|
||||
while (true) {
|
||||
const nextKey = X(ToString(F(nextIndex)));
|
||||
const nextSeg = Q(yield* ToString(Q(yield* Get(raw, nextKey))));
|
||||
stringElements.push(nextSeg.stringValue());
|
||||
if (nextIndex + 1 === literalSegments) {
|
||||
return Value(stringElements.join(''));
|
||||
}
|
||||
let next;
|
||||
if (nextIndex < numberOfSubstitutions) {
|
||||
next = substitutions[nextIndex];
|
||||
} else {
|
||||
next = Value('');
|
||||
}
|
||||
const nextSub = Q(yield* ToString(next!));
|
||||
stringElements.push(nextSub.stringValue());
|
||||
nextIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
export function bootstrapString(realmRec: Realm) {
|
||||
const stringConstructor = bootstrapConstructor(realmRec, StringConstructor, 'String', 1, realmRec.Intrinsics['%String.prototype%'], [
|
||||
['fromCharCode', String_fromCharCode, 1],
|
||||
['fromCodePoint', String_fromCodePoint, 1],
|
||||
['raw', String_raw, 1],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%String%'] = stringConstructor;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Q, type ValueEvaluator } from '../completion.mts';
|
||||
import {
|
||||
Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
GeneratorResume,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%stringiteratorprototype%.next */
|
||||
function* StringIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Return ? GeneratorResume(this value, empty, "%StringIteratorPrototype%").
|
||||
return Q(yield* GeneratorResume(thisValue, undefined, Value('%StringIteratorPrototype%')));
|
||||
}
|
||||
|
||||
export function bootstrapStringIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', StringIteratorPrototype_next, 0],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%'], 'String Iterator');
|
||||
|
||||
realmRec.Intrinsics['%StringIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,858 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue,
|
||||
JSStringValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
UndefinedValue,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
GetSubstitution,
|
||||
TrimString,
|
||||
StringPad,
|
||||
StringIndexOf,
|
||||
} from '../runtime-semantics/all.mts';
|
||||
import {
|
||||
CodePointAt,
|
||||
IsStringWellFormedUnicode,
|
||||
UTF16EncodeCodePoint,
|
||||
} from '../static-semantics/all.mts';
|
||||
import { Q, X } from '../completion.mts';
|
||||
import type { ValueEvaluator, YieldEvaluator } from '../evaluator.mts';
|
||||
import { assignProps } from './bootstrap.mts';
|
||||
import {
|
||||
ArrayCreate,
|
||||
Assert,
|
||||
Call,
|
||||
CreateDataPropertyOrThrow,
|
||||
CreateIteratorFromClosure,
|
||||
Get,
|
||||
GetMethod,
|
||||
Invoke,
|
||||
IsCallable,
|
||||
IsRegExp,
|
||||
RegExpCreate,
|
||||
RequireObjectCoercible,
|
||||
ToIntegerOrInfinity,
|
||||
ToNumber,
|
||||
ToString,
|
||||
ToUint32,
|
||||
StringCreate,
|
||||
Yield,
|
||||
F, R, R as MathematicalValue,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
|
||||
function thisStringValue(value: Value) {
|
||||
if (value instanceof JSStringValue) {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof ObjectValue && 'StringData' in value) {
|
||||
const s = value.StringData;
|
||||
Assert(s instanceof JSStringValue);
|
||||
return s;
|
||||
}
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'String', value);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.charat */
|
||||
function* StringProto_charAt([pos = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
const position = Q(yield* ToIntegerOrInfinity(pos));
|
||||
const size = S.stringValue().length;
|
||||
if (position < 0 || position >= size) {
|
||||
return Value('');
|
||||
}
|
||||
return Value(S.stringValue()[position]);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.charcodeat */
|
||||
function* StringProto_charCodeAt([pos = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
const position = Q(yield* ToIntegerOrInfinity(pos));
|
||||
const size = S.stringValue().length;
|
||||
if (position < 0 || position >= size) {
|
||||
return F(NaN);
|
||||
}
|
||||
return F(S.stringValue().charCodeAt(position));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.codepointat */
|
||||
function* StringProto_codePointAt([pos = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
const position = Q(yield* ToIntegerOrInfinity(pos));
|
||||
const size = S.stringValue().length;
|
||||
if (position < 0 || position >= size) {
|
||||
return Value.undefined;
|
||||
}
|
||||
const cp = X(CodePointAt(S.stringValue(), position));
|
||||
return F(cp.CodePoint);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.concat */
|
||||
function* StringProto_concat(args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
let R = S.stringValue();
|
||||
const _args = [...args];
|
||||
while (_args.length > 0) {
|
||||
const next = _args.shift()!;
|
||||
const nextString = Q(yield* ToString(next));
|
||||
R = `${R}${nextString.stringValue()}`;
|
||||
}
|
||||
return Value(R);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.endswith */
|
||||
function* StringProto_endsWith([searchString = Value.undefined, endPosition = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O)).stringValue();
|
||||
const isRegExp = Q(yield* IsRegExp(searchString));
|
||||
if (isRegExp === Value.true) {
|
||||
return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.endsWith');
|
||||
}
|
||||
const searchStr = Q(yield* ToString(searchString)).stringValue();
|
||||
const len = S.length;
|
||||
let pos;
|
||||
if (endPosition === Value.undefined) {
|
||||
pos = len;
|
||||
} else {
|
||||
pos = Q(yield* ToIntegerOrInfinity(endPosition));
|
||||
}
|
||||
const end = Math.min(Math.max(pos, 0), len);
|
||||
const searchLength = searchStr.length;
|
||||
const start = end - searchLength;
|
||||
if (start < 0) {
|
||||
return Value.false;
|
||||
}
|
||||
for (let i = 0; i < searchLength; i += 1) {
|
||||
if (S.charCodeAt(start + i) !== searchStr.charCodeAt(i)) {
|
||||
return Value.false;
|
||||
}
|
||||
}
|
||||
return Value.true;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.includes */
|
||||
function* StringProto_includes([searchString = Value.undefined, position = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O)).stringValue();
|
||||
const isRegExp = Q(yield* IsRegExp(searchString));
|
||||
if (isRegExp === Value.true) {
|
||||
return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.includes');
|
||||
}
|
||||
const searchStr = Q(yield* ToString(searchString)).stringValue();
|
||||
const pos = Q(yield* ToIntegerOrInfinity(position));
|
||||
Assert(!(position === Value.undefined) || pos === 0);
|
||||
const len = S.length;
|
||||
const start = Math.min(Math.max(pos, 0), len);
|
||||
const searchLen = searchStr.length;
|
||||
let k = start;
|
||||
while (k + searchLen <= len) {
|
||||
let match = true;
|
||||
for (let j = 0; j < searchLen; j += 1) {
|
||||
if (searchStr[j] !== S[k + j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return Value.true;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.indexof */
|
||||
function* StringProto_indexOf([searchString = Value.undefined, position = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2. Let S be ? ToString(O).
|
||||
const S = Q(yield* ToString(O));
|
||||
// 3. Let searchStr be ? ToString(searchString).
|
||||
const searchStr = Q(yield* ToString(searchString));
|
||||
// 4. Let pos be ? ToIntegerOrInfinity(position).
|
||||
const pos = Q(yield* ToIntegerOrInfinity(position));
|
||||
// 5. Assert: If position is undefined, then pos is 0.
|
||||
Assert(!(position === Value.undefined) || pos === 0);
|
||||
// 6. Let len be the length of S.
|
||||
const len = S.stringValue().length;
|
||||
// 7. Let start be min(max(pos, 0), len).
|
||||
const start = Math.min(Math.max(pos, 0), len);
|
||||
// 8. Return ! StringIndexOf(S, searchStr, start).
|
||||
return X(StringIndexOf(S, searchStr, start));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.iswellformed */
|
||||
function* StringProto_isWellFormed(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2. Let S be ? ToString(O).
|
||||
const S = Q(yield* ToString(O));
|
||||
// 3. Return IsStringWellFormedUnicode(S).
|
||||
return IsStringWellFormedUnicode(S) ? Value.true : Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.lastindexof */
|
||||
function* StringProto_lastIndexOf([searchString = Value.undefined, position = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O)).stringValue();
|
||||
const searchStr = Q(yield* ToString(searchString)).stringValue();
|
||||
const numPos = Q(yield* ToNumber(position));
|
||||
Assert(!(position === Value.undefined) || numPos.isNaN());
|
||||
let pos;
|
||||
if (numPos.isNaN()) {
|
||||
pos = Infinity;
|
||||
} else {
|
||||
pos = X(ToIntegerOrInfinity(numPos));
|
||||
}
|
||||
const len = S.length;
|
||||
const start = Math.min(Math.max(pos, 0), len);
|
||||
const searchLen = searchStr.length;
|
||||
if (len < searchLen) {
|
||||
return F(-1);
|
||||
}
|
||||
let k = start;
|
||||
while (k >= 0) {
|
||||
if (k + searchLen <= len) {
|
||||
let match = true;
|
||||
for (let j = 0; j < searchLen; j += 1) {
|
||||
if (searchStr[j] !== S[k + j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return F(k);
|
||||
}
|
||||
}
|
||||
k -= 1;
|
||||
}
|
||||
return F(-1);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.localecompare */
|
||||
function* StringProto_localeCompare([that = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O)).stringValue();
|
||||
const That = Q(yield* ToString(that)).stringValue();
|
||||
if (S === That) {
|
||||
return F(+0);
|
||||
} else if (S < That) {
|
||||
return F(-1);
|
||||
} else {
|
||||
return F(1);
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.match */
|
||||
function* StringProto_match([regexp = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
|
||||
if (regexp instanceof ObjectValue) {
|
||||
const matcher = Q(yield* GetMethod(regexp, wellKnownSymbols.match));
|
||||
if (matcher !== Value.undefined) {
|
||||
return Q(yield* Call(matcher, regexp, [O]));
|
||||
}
|
||||
}
|
||||
|
||||
const S = Q(yield* ToString(O));
|
||||
const rx = Q(yield* RegExpCreate(regexp, Value.undefined));
|
||||
return Q(yield* Invoke(rx, wellKnownSymbols.match, [S]));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.matchall */
|
||||
function* StringProto_matchAll([regexp = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2. If regexp is an Object, then
|
||||
if (regexp instanceof ObjectValue) {
|
||||
// a. Let isRegExp be ? IsRegExp(regexp).
|
||||
const isRegExp = Q(yield* IsRegExp(regexp));
|
||||
// b. If isRegExp is true, then
|
||||
if (isRegExp === Value.true) {
|
||||
// i. Let flags be ? Get(regexp, "flags").
|
||||
const flags = Q(yield* Get(regexp as ObjectValue, Value('flags')));
|
||||
// ii. Perform ? RequireObjectCoercible(flags).
|
||||
Q(RequireObjectCoercible(flags));
|
||||
// iii. If ? ToString(flags) does not contain "g", throw a TypeError exception.
|
||||
if (!Q(yield* ToString(flags)).stringValue().includes('g')) {
|
||||
return surroundingAgent.Throw('TypeError', 'StringPrototypeMethodGlobalRegExp', 'matchAll');
|
||||
}
|
||||
}
|
||||
// c. Let matcher be ? GetMethod(regexp, @@matchAll).
|
||||
const matcher = Q(yield* GetMethod(regexp, wellKnownSymbols.matchAll));
|
||||
// d. If matcher is not undefined, then
|
||||
if (matcher !== Value.undefined) {
|
||||
// i. Return ? Call(matcher, regexp, « O »).
|
||||
return Q(yield* Call(matcher, regexp, [O]));
|
||||
}
|
||||
}
|
||||
// 3. Let S be ? ToString(O).
|
||||
const S = Q(yield* ToString(O));
|
||||
// 4. Let rx be ? RegExpCreate(regexp, "g").
|
||||
const rx = Q(yield* RegExpCreate(regexp, Value('g')));
|
||||
// 5. Return ? Invoke(rx, @@matchAll, « S »).
|
||||
return Q(yield* Invoke(rx, wellKnownSymbols.matchAll, [S]));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.normalize */
|
||||
function* StringProto_normalize([form = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
if (form === Value.undefined) {
|
||||
form = Value('NFC');
|
||||
} else {
|
||||
form = Q(yield* ToString(form));
|
||||
}
|
||||
const f = form.stringValue();
|
||||
if (!['NFC', 'NFD', 'NFKC', 'NFKD'].includes(f)) {
|
||||
return surroundingAgent.Throw('RangeError', 'NormalizeInvalidForm');
|
||||
}
|
||||
const ns = S.stringValue().normalize(f);
|
||||
return Value(ns);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.padend */
|
||||
function* StringProto_padEnd([maxLength = Value.undefined, fillString = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
return Q(yield* StringPad(O, maxLength, fillString, 'end'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.padstart */
|
||||
function* StringProto_padStart([maxLength = Value.undefined, fillString = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
return Q(yield* StringPad(O, maxLength, fillString, 'start'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.repeat */
|
||||
function* StringProto_repeat([count = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
const n = Q(yield* ToIntegerOrInfinity(count));
|
||||
if (n < 0) {
|
||||
return surroundingAgent.Throw('RangeError', 'StringRepeatCount', n);
|
||||
}
|
||||
if (n === Infinity || n === -Infinity) {
|
||||
return surroundingAgent.Throw('RangeError', 'StringRepeatCount', n);
|
||||
}
|
||||
if (n === 0) {
|
||||
return Value('');
|
||||
}
|
||||
let T = '';
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
T += S.stringValue();
|
||||
}
|
||||
return Value(T);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.replace */
|
||||
function* StringProto_replace([searchValue = Value.undefined, replaceValue = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
if (searchValue instanceof ObjectValue) {
|
||||
const replacer = Q(yield* GetMethod(searchValue, wellKnownSymbols.replace));
|
||||
if (replacer !== Value.undefined) {
|
||||
return Q(yield* Call(replacer, searchValue, [O, replaceValue]));
|
||||
}
|
||||
}
|
||||
const string = Q(yield* ToString(O));
|
||||
const searchString = Q(yield* ToString(searchValue));
|
||||
const functionalReplace = IsCallable(replaceValue);
|
||||
if (!functionalReplace) {
|
||||
replaceValue = Q(yield* ToString(replaceValue));
|
||||
}
|
||||
const searchLength = searchString.stringValue().length;
|
||||
const position = string.stringValue().indexOf(searchString.stringValue(), 0);
|
||||
if (position === -1) {
|
||||
return string;
|
||||
}
|
||||
const preceding = string.stringValue().slice(0, position);
|
||||
const following = string.stringValue().slice(position + searchLength);
|
||||
let replacement: JSStringValue;
|
||||
if (functionalReplace) {
|
||||
replacement = Q(yield* ToString(Q(yield* Call(replaceValue, Value.undefined, [searchString, F(position), string]))));
|
||||
} else {
|
||||
Assert(replaceValue instanceof JSStringValue);
|
||||
const captures: readonly (JSStringValue | UndefinedValue)[] = [];
|
||||
replacement = X(GetSubstitution(searchString, string, position, captures, Value.undefined, replaceValue));
|
||||
}
|
||||
return Value(preceding + replacement.stringValue() + following);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.replaceall */
|
||||
function* StringProto_replaceAll([searchValue = Value.undefined, replaceValue = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2.If searchValue is an Object, then
|
||||
if (searchValue instanceof ObjectValue) {
|
||||
// a. Let isRegExp be ? IsRegExp(searchValue).
|
||||
const isRegExp = Q(yield* IsRegExp(searchValue));
|
||||
// b. If isRegExp is true, then
|
||||
if (isRegExp === Value.true) {
|
||||
// i. Let flags be ? Get(searchValue, "flags").
|
||||
const flags = Q(yield* Get(searchValue as ObjectValue, Value('flags')));
|
||||
// ii. Perform ? RequireObjectCoercible(flags).
|
||||
Q(RequireObjectCoercible(flags));
|
||||
// iii. If ? ToString(flags) does not contain "g", throw a TypeError exception.
|
||||
if (!Q(yield* ToString(flags)).stringValue().includes('g')) {
|
||||
return surroundingAgent.Throw('TypeError', 'StringPrototypeMethodGlobalRegExp', 'replaceAll');
|
||||
}
|
||||
}
|
||||
// c. Let replacer be ? GetMethod(searchValue, @@replace).
|
||||
const replacer = Q(yield* GetMethod(searchValue, wellKnownSymbols.replace));
|
||||
// d. If replacer is not undefined, then
|
||||
if (replacer !== Value.undefined) {
|
||||
// i. Return ? Call(replacer, searchValue, « O, replaceValue »).
|
||||
return Q(yield* Call(replacer, searchValue, [O, replaceValue]));
|
||||
}
|
||||
}
|
||||
// 3. Let string be ? ToString(O).
|
||||
const string = Q(yield* ToString(O));
|
||||
// 4. Let searchString be ? ToString(searchValue).
|
||||
const searchString = Q(yield* ToString(searchValue));
|
||||
// 5. Let functionalReplace be IsCallable(replaceValue).
|
||||
const functionalReplace = IsCallable(replaceValue);
|
||||
// 6. If functionalReplace is false, then
|
||||
if (!functionalReplace) {
|
||||
// a. Let replaceValue be ? ToString(replaceValue).
|
||||
replaceValue = Q(yield* ToString(replaceValue));
|
||||
}
|
||||
// 7. Let searchLength be the length of searchString.
|
||||
const searchLength = searchString.stringValue().length;
|
||||
// 8. Let advanceBy be max(1, searchLength).
|
||||
const advanceBy = Math.max(1, searchLength);
|
||||
// 9. Let matchPositions be a new empty List.
|
||||
const matchPositions = [];
|
||||
// 10. Let position be ! StringIndexOf(string, searchString, 0).
|
||||
let position = R(X(StringIndexOf(string, searchString, 0)));
|
||||
// 11. Repeat, while position is not -1
|
||||
while (position !== -1) {
|
||||
// a. Append position to the end of matchPositions.
|
||||
matchPositions.push(position);
|
||||
// b. Let position be ! StringIndexOf(string, searchString, position + advanceBy).
|
||||
position = R(X(StringIndexOf(string, searchString, position + advanceBy)));
|
||||
}
|
||||
// 12. Let endOfLastMatch be 0.
|
||||
let endOfLastMatch = 0;
|
||||
// 13. Let result be the empty string value.
|
||||
let result = '';
|
||||
// 14. For each position in matchPositions, do
|
||||
for (position of matchPositions) {
|
||||
let replacement;
|
||||
// a. If functionalReplace is true, then
|
||||
if (functionalReplace) {
|
||||
// i. Let replacement be ? ToString(? Call(replaceValue, undefined, « searchString, 𝔽(position), string »).
|
||||
replacement = Q(yield* ToString(Q(yield* Call(replaceValue, Value.undefined, [searchString, F(position), string]))));
|
||||
} else { // b. Else,
|
||||
// i. Assert: Type(replaceValue) is String.
|
||||
Assert(replaceValue instanceof JSStringValue);
|
||||
// ii. Let captures be a new empty List.
|
||||
const captures: readonly (JSStringValue | UndefinedValue)[] = [];
|
||||
// iii. Let replacement be GetSubstitution(searchString, string, position, captures, undefined, replaceValue).
|
||||
replacement = X(GetSubstitution(searchString, string, position, captures, Value.undefined, replaceValue));
|
||||
}
|
||||
// c. Let stringSlice be the substring of string consisting of the code units from endOfLastMatch (inclusive) up through position (exclusive).
|
||||
const stringSlice = string.stringValue().slice(endOfLastMatch, position);
|
||||
// d. Let result be the string-concatenation of result, stringSlice, and replacement.
|
||||
result = result + stringSlice + replacement.stringValue();
|
||||
// e. Let endOfLastMatch be position + searchLength.
|
||||
endOfLastMatch = position + searchLength;
|
||||
}
|
||||
// 15. If endOfLastMatch < the length of string, then
|
||||
if (endOfLastMatch < string.stringValue().length) {
|
||||
// a. Let result be the string-concatenation of result and the substring of string consisting of the code units from endOfLastMatch (inclusive) up through the final code unit of string (inclusive).
|
||||
result += string.stringValue().slice(endOfLastMatch);
|
||||
}
|
||||
// 16. Return result.
|
||||
return Value(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.slice */
|
||||
function* StringProto_search([regexp = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
|
||||
if (regexp instanceof ObjectValue) {
|
||||
const searcher = Q(yield* GetMethod(regexp, wellKnownSymbols.search));
|
||||
if (searcher !== Value.undefined) {
|
||||
return Q(yield* Call(searcher, regexp, [O]));
|
||||
}
|
||||
}
|
||||
|
||||
const string = Q(yield* ToString(O));
|
||||
const rx = Q(yield* RegExpCreate(regexp, Value.undefined));
|
||||
return Q(yield* Invoke(rx, wellKnownSymbols.search, [string]));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.slice */
|
||||
function* StringProto_slice([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O)).stringValue();
|
||||
const len = S.length;
|
||||
const intStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let intEnd;
|
||||
if (end === Value.undefined) {
|
||||
intEnd = len;
|
||||
} else {
|
||||
intEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let from;
|
||||
if (intStart < 0) {
|
||||
from = Math.max(len + intStart, 0);
|
||||
} else {
|
||||
from = Math.min(intStart, len);
|
||||
}
|
||||
let to;
|
||||
if (intEnd < 0) {
|
||||
to = Math.max(len + intEnd, 0);
|
||||
} else {
|
||||
to = Math.min(intEnd, len);
|
||||
}
|
||||
const span = Math.max(to - from, 0);
|
||||
return Value(S.slice(from, from + span));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.split */
|
||||
function* StringProto_split([separator = Value.undefined, limit = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
if (separator instanceof ObjectValue) {
|
||||
const splitter = Q(yield* GetMethod(separator, wellKnownSymbols.split));
|
||||
if (splitter !== Value.undefined) {
|
||||
return Q(yield* Call(splitter, separator, [O, limit]));
|
||||
}
|
||||
}
|
||||
const S = Q(yield* ToString(O));
|
||||
const A = X(ArrayCreate(0));
|
||||
let lengthA = 0;
|
||||
let lim;
|
||||
if (limit === Value.undefined) {
|
||||
lim = F((2 ** 32) - 1);
|
||||
} else {
|
||||
lim = Q(yield* ToUint32(limit));
|
||||
}
|
||||
const s = S.stringValue().length;
|
||||
let p = 0;
|
||||
const R = Q(yield* ToString(separator));
|
||||
if (MathematicalValue(lim) === 0) {
|
||||
return A;
|
||||
}
|
||||
if (separator === Value.undefined) {
|
||||
X(CreateDataPropertyOrThrow(A, Value('0'), S));
|
||||
return A;
|
||||
}
|
||||
if (s === 0) {
|
||||
if (R.stringValue() !== '') {
|
||||
X(CreateDataPropertyOrThrow(A, Value('0'), S));
|
||||
}
|
||||
return A;
|
||||
}
|
||||
let q = p;
|
||||
while (q !== s) {
|
||||
const e = yield* SplitMatch(S, q, R);
|
||||
if (e === false) {
|
||||
q += 1;
|
||||
} else {
|
||||
if (e === p) {
|
||||
q += 1;
|
||||
} else {
|
||||
const T = Value(S.stringValue().substring(p, q));
|
||||
X(CreateDataPropertyOrThrow(A, X(ToString(F(lengthA))), T));
|
||||
lengthA += 1;
|
||||
if (lengthA === MathematicalValue(lim)) {
|
||||
return A;
|
||||
}
|
||||
p = e;
|
||||
q = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
const T = Value(S.stringValue().substring(p, s));
|
||||
X(CreateDataPropertyOrThrow(A, X(ToString(F(lengthA))), T));
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-splitmatch */
|
||||
function* SplitMatch(S: JSStringValue, q: number, R: JSStringValue) {
|
||||
Assert(R instanceof JSStringValue);
|
||||
const r = R.stringValue().length;
|
||||
const s = S.stringValue().length;
|
||||
if (q + r > s) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < r; i += 1) {
|
||||
if (S.stringValue().charCodeAt(q + i) !== R.stringValue().charCodeAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return q + r;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.startswith */
|
||||
function* StringProto_startsWith([searchString = Value.undefined, position = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O)).stringValue();
|
||||
const isRegExp = Q(yield* IsRegExp(searchString));
|
||||
if (isRegExp === Value.true) {
|
||||
return surroundingAgent.Throw('TypeError', 'RegExpArgumentNotAllowed', 'String.prototype.startsWith');
|
||||
}
|
||||
const searchStr = Q(yield* ToString(searchString)).stringValue();
|
||||
const pos = Q(yield* ToIntegerOrInfinity(position));
|
||||
Assert(!(position === Value.undefined) || pos === 0);
|
||||
const len = S.length;
|
||||
const start = Math.min(Math.max(pos, 0), len);
|
||||
const searchLength = searchStr.length;
|
||||
if (searchLength + start > len) {
|
||||
return Value.false;
|
||||
}
|
||||
for (let i = 0; i < searchLength; i += 1) {
|
||||
if (S.charCodeAt(start + i) !== searchStr.charCodeAt(i)) {
|
||||
return Value.false;
|
||||
}
|
||||
}
|
||||
return Value.true;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.substring */
|
||||
function* StringProto_substring([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O)).stringValue();
|
||||
const len = S.length;
|
||||
const intStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let intEnd;
|
||||
if (end === Value.undefined) {
|
||||
intEnd = len;
|
||||
} else {
|
||||
intEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
const finalStart = Math.min(Math.max(intStart, 0), len);
|
||||
const finalEnd = Math.min(Math.max(intEnd, 0), len);
|
||||
const from = Math.min(finalStart, finalEnd);
|
||||
const to = Math.max(finalStart, finalEnd);
|
||||
return Value(S.slice(from, to));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.tolocalelowercase */
|
||||
function* StringProto_toLocaleLowerCase(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
const L = S.stringValue().toLocaleLowerCase();
|
||||
return Value(L);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.tolocaleuppercase */
|
||||
function* StringProto_toLocaleUpperCase(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
const L = S.stringValue().toLocaleUpperCase();
|
||||
return Value(L);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.tolowercase */
|
||||
function* StringProto_toLowerCase(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
const L = S.stringValue().toLowerCase();
|
||||
return Value(L);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.tostring */
|
||||
function* StringProto_toString(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
return Q(thisStringValue(thisValue));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.touppercase */
|
||||
function* StringProto_toUpperCase(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
const S = Q(yield* ToString(O));
|
||||
const L = S.stringValue().toUpperCase();
|
||||
return Value(L);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.towellformed */
|
||||
function* StringProto_toWellFormed(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2. Let S be ? ToString(O).
|
||||
const S = Q(yield* ToString(O));
|
||||
// 3. Let strLen be the length of S.
|
||||
const strLen = S.stringValue().length;
|
||||
// 4. Let k be 0.
|
||||
let k = 0;
|
||||
// 5. Let result be the empty String.
|
||||
let result = '';
|
||||
// 6. Repeat, while k < strLen,
|
||||
while (k < strLen) {
|
||||
// a. Let cp be CodePointAt(S, k).
|
||||
const cp = CodePointAt(S.stringValue(), k);
|
||||
// b. If cp.[[IsUnpairedSurrogate]] is true, then
|
||||
if (cp.IsUnpairedSurrogate) {
|
||||
// i. Set result to the string-concatenation of result and 0xFFFD (REPLACEMENT CHARACTER).
|
||||
result += '\uFFFD';
|
||||
} else { // c. Else,
|
||||
// i. Set result to the string-concatenation of result and UTF16EncodeCodePoint(cp.[[CodePoint]]).
|
||||
result += UTF16EncodeCodePoint(cp.CodePoint);
|
||||
}
|
||||
// d. Set k to k + cp.[[CodeUnitCount]].
|
||||
k += cp.CodeUnitCount;
|
||||
}
|
||||
// 7. Return result.
|
||||
return Value(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.trim */
|
||||
function* StringProto_trim(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const S = thisValue;
|
||||
return Q(yield* TrimString(S, 'start+end'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.trimend */
|
||||
function* StringProto_trimEnd(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const S = thisValue;
|
||||
return Q(yield* TrimString(S, 'end'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.trimstart */
|
||||
function* StringProto_trimStart(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const S = thisValue;
|
||||
return Q(yield* TrimString(S, 'start'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.valueof */
|
||||
function* StringProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
return Q(thisStringValue(thisValue));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype-@@iterator */
|
||||
function* StringProto_iterator(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2. Let s be ? ToString(O).
|
||||
const s = Q(yield* ToString(O)).stringValue();
|
||||
// 3. Let closure be a new Abstract Closure with no parameters that captures s and performs the following steps when called:
|
||||
const closure = function* closure(): YieldEvaluator {
|
||||
// a. Let position be 0.
|
||||
let position = 0;
|
||||
// b. Let len be the length of s.
|
||||
const len = s.length;
|
||||
// c. Repeat, while position < len,
|
||||
while (position < len) {
|
||||
// i. Let cp be ! CodePointAt(s, position).
|
||||
const cp = X(CodePointAt(s, position));
|
||||
// ii. Let nextIndex be position + cp.[[CodeUnitCount]].
|
||||
const nextIndex = position + cp.CodeUnitCount;
|
||||
// iii. Let resultString be the substring of s from position to nextIndex.
|
||||
const resultString = Value(s.slice(position, nextIndex));
|
||||
// iv. Set position to nextIndex.
|
||||
position = nextIndex;
|
||||
// v. Perform ? Yield(resultString).
|
||||
Q(yield* Yield(resultString));
|
||||
}
|
||||
// NON-SPEC
|
||||
generator.HostCapturedValues = undefined;
|
||||
// d. Return undefined.
|
||||
return Value.undefined;
|
||||
};
|
||||
// 4. Return ! CreateIteratorFromClosure(closure, "%StringIteratorPrototype%", %StringIteratorPrototype%).
|
||||
const generator = X(CreateIteratorFromClosure(closure, Value('%StringIteratorPrototype%'), surroundingAgent.intrinsic('%StringIteratorPrototype%'), ['HostCapturedValues'], [O]));
|
||||
return generator;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-string.prototype.at */
|
||||
function* StringProto_at([index = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
Q(RequireObjectCoercible(O));
|
||||
// 2. Let S be ? ToString(O).
|
||||
const S = Q(yield* ToString(O));
|
||||
// 3. Let len be the length of S.
|
||||
const len = S.stringValue().length;
|
||||
// 4. Let relativeIndex be ? ToIntegerOrInfinity(index).
|
||||
const relativeIndex = Q(yield* ToIntegerOrInfinity(index));
|
||||
let k;
|
||||
// 5. If relativeIndex ≥ 0, then
|
||||
if (relativeIndex >= 0) {
|
||||
// a. Let k be relativeIndex.
|
||||
k = relativeIndex;
|
||||
} else { // 6. Else,
|
||||
// a. Let k be len + relativeIndex.
|
||||
k = len + relativeIndex;
|
||||
}
|
||||
// 7. If k < 0 or k ≥ len, then return undefined.
|
||||
if (k < 0 || k >= len) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 8. Return the String value consisting of only the code unit at position k in S.
|
||||
return Value(S.stringValue()[k]);
|
||||
}
|
||||
|
||||
export function bootstrapStringPrototype(realmRec: Realm) {
|
||||
const proto = StringCreate(Value(''), realmRec.Intrinsics['%Object.prototype%']);
|
||||
|
||||
assignProps(realmRec, proto, [
|
||||
['charAt', StringProto_charAt, 1],
|
||||
['charCodeAt', StringProto_charCodeAt, 1],
|
||||
['codePointAt', StringProto_codePointAt, 1],
|
||||
['concat', StringProto_concat, 1],
|
||||
['endsWith', StringProto_endsWith, 1],
|
||||
['includes', StringProto_includes, 1],
|
||||
['indexOf', StringProto_indexOf, 1],
|
||||
['isWellFormed', StringProto_isWellFormed, 0],
|
||||
['at', StringProto_at, 1],
|
||||
['lastIndexOf', StringProto_lastIndexOf, 1],
|
||||
['localeCompare', StringProto_localeCompare, 1],
|
||||
['match', StringProto_match, 1],
|
||||
['matchAll', StringProto_matchAll, 1],
|
||||
['normalize', StringProto_normalize, 0],
|
||||
['padEnd', StringProto_padEnd, 1],
|
||||
['padStart', StringProto_padStart, 1],
|
||||
['repeat', StringProto_repeat, 1],
|
||||
['replace', StringProto_replace, 2],
|
||||
['replaceAll', StringProto_replaceAll, 2],
|
||||
['search', StringProto_search, 1],
|
||||
['slice', StringProto_slice, 2],
|
||||
['split', StringProto_split, 2],
|
||||
['startsWith', StringProto_startsWith, 1],
|
||||
['substring', StringProto_substring, 2],
|
||||
['toLocaleLowerCase', StringProto_toLocaleLowerCase, 0],
|
||||
['toLocaleUpperCase', StringProto_toLocaleUpperCase, 0],
|
||||
['toLowerCase', StringProto_toLowerCase, 0],
|
||||
['toString', StringProto_toString, 0],
|
||||
['toUpperCase', StringProto_toUpperCase, 0],
|
||||
['toWellFormed', StringProto_toWellFormed, 0],
|
||||
['trim', StringProto_trim, 0],
|
||||
['trimEnd', StringProto_trimEnd, 0],
|
||||
['trimStart', StringProto_trimStart, 0],
|
||||
['valueOf', StringProto_valueOf, 0],
|
||||
[wellKnownSymbols.iterator, StringProto_iterator, 0],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%String.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import {
|
||||
Descriptor,
|
||||
type JSStringValue,
|
||||
SymbolValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
KeyForSymbol,
|
||||
Realm,
|
||||
SameValue,
|
||||
ToString,
|
||||
type FunctionObject,
|
||||
type OrdinaryObject,
|
||||
} from '#self';
|
||||
|
||||
export interface GlobalSymbolRegistryRecord {
|
||||
readonly Key: JSStringValue;
|
||||
readonly Symbol: SymbolValue;
|
||||
}
|
||||
export const GlobalSymbolRegistry: GlobalSymbolRegistryRecord[] = [];
|
||||
|
||||
export interface SymbolObject extends OrdinaryObject {
|
||||
readonly SymbolData: SymbolValue;
|
||||
}
|
||||
export function isSymbolObject(o: Value): o is SymbolObject {
|
||||
return 'SymbolData' in o;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-symbol-description */
|
||||
function* SymbolConstructor(this: FunctionObject, [description = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. If NewTarget is not undefined, throw a TypeError exception.
|
||||
if (NewTarget !== Value.undefined) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAConstructor', this);
|
||||
}
|
||||
// 2. If description is undefined, let descString be undefined.
|
||||
let descString;
|
||||
if (description === Value.undefined) {
|
||||
descString = Value.undefined;
|
||||
} else { // 3. Else, let descString be ? ToString(description).
|
||||
descString = Q(yield* ToString(description));
|
||||
}
|
||||
// 4. Return a new unique Symbol value whose [[Description]] value is descString.
|
||||
return new SymbolValue(descString);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-symbol.for */
|
||||
function* Symbol_for([key = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let stringKey be ? ToString(key).
|
||||
const stringKey = Q(yield* ToString(key));
|
||||
// 2. For each element e of the GlobalSymbolRegistry List, do
|
||||
for (const e of GlobalSymbolRegistry) {
|
||||
// a. If SameValue(e.[[Key]], stringKey) is true, return e.[[Symbol]].
|
||||
if (SameValue(e.Key, stringKey) === Value.true) {
|
||||
return e.Symbol;
|
||||
}
|
||||
}
|
||||
// 3. Assert: GlobalSymbolRegistry does not currently contain an entry for stringKey.
|
||||
// 4. Let newSymbol be a new unique Symbol value whose [[Description]] value is stringKey.
|
||||
const newSymbol = new SymbolValue(stringKey);
|
||||
// 5. Append the Record { [[Key]]: stringKey, [[Symbol]]: newSymbol } to the GlobalSymbolRegistry List.
|
||||
GlobalSymbolRegistry.push({ Key: stringKey, Symbol: newSymbol });
|
||||
// 6. Return newSymbol.
|
||||
return newSymbol;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-symbol.keyfor */
|
||||
function Symbol_keyFor([sym = Value.undefined]: Arguments) {
|
||||
// 1. If Type(sym) is not Symbol, throw a TypeError exception.
|
||||
if (!(sym instanceof SymbolValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotASymbol', sym);
|
||||
}
|
||||
// 2. Return KeyForSymbol(sym).
|
||||
return KeyForSymbol(sym);
|
||||
}
|
||||
|
||||
export function bootstrapSymbol(realmRec: Realm) {
|
||||
const symbolConstructor = bootstrapConstructor(realmRec, SymbolConstructor, 'Symbol', 0, realmRec.Intrinsics['%Symbol.prototype%'], [
|
||||
['for', Symbol_for, 1],
|
||||
['keyFor', Symbol_keyFor, 1],
|
||||
]);
|
||||
|
||||
for (const [name, sym] of Object.entries(wellKnownSymbols)) {
|
||||
X(symbolConstructor.DefineOwnProperty(Value(name), Descriptor({
|
||||
Value: sym,
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.false,
|
||||
})));
|
||||
}
|
||||
|
||||
X(symbolConstructor.DefineOwnProperty(Value('prototype'), Descriptor({
|
||||
Value: realmRec.Intrinsics['%Symbol.prototype%'],
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
|
||||
realmRec.Intrinsics['%Symbol%'] = symbolConstructor;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
ObjectValue,
|
||||
SymbolValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
surroundingAgent,
|
||||
} from '../host-defined/engine.mts';
|
||||
import { Q, type ValueCompletion } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
Realm,
|
||||
SymbolDescriptiveString,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-thissymbolvalue */
|
||||
function thisSymbolValue(value: Value) {
|
||||
// 1. If Type(value) is Symbol, return value.
|
||||
if (value instanceof SymbolValue) {
|
||||
return value;
|
||||
}
|
||||
// 2. If Type(value) is Object and value has a [[SymbolData]] internal slot, then
|
||||
if (value instanceof ObjectValue && 'SymbolData' in value) {
|
||||
// a. Let s be value.[[SymbolData]].
|
||||
const s = value.SymbolData;
|
||||
// b. Assert: Type(s) is Symbol.
|
||||
Assert(s instanceof SymbolValue);
|
||||
// c. Return s.
|
||||
return s;
|
||||
}
|
||||
// 3. Throw a TypeError exception.
|
||||
return surroundingAgent.Throw('TypeError', 'NotATypeObject', 'Symbol', value);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-symbol.prototype.description */
|
||||
function SymbolProto_descriptionGetter(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let s be the this value.
|
||||
const s = thisValue;
|
||||
// 2. Let sym be ? thisSymbolValue(s).
|
||||
const sym = Q(thisSymbolValue(s));
|
||||
// 3. Return sym.[[Description]].
|
||||
return sym.Description;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-symbol.prototype.tostring */
|
||||
function SymbolProto_toString(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let sym be ? thisSymbolValue(this value).
|
||||
const sym = Q(thisSymbolValue(thisValue));
|
||||
// 2. Return SymbolDescriptiveString(sym).
|
||||
return SymbolDescriptiveString(sym);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-symbol.prototype.valueof */
|
||||
function SymbolProto_valueOf(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Return ? thisSymbolValue(this value).
|
||||
return Q(thisSymbolValue(thisValue));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-symbol.prototype-@@toprimitive */
|
||||
function SymbolProto_toPrimitive(_argList: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Return ? thisSymbolValue(this value).
|
||||
return Q(thisSymbolValue(thisValue));
|
||||
}
|
||||
|
||||
export function bootstrapSymbolPrototype(realmRec: Realm) {
|
||||
const override = {
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
};
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['toString', SymbolProto_toString, 0],
|
||||
['description', [SymbolProto_descriptionGetter]],
|
||||
['valueOf', SymbolProto_valueOf, 0],
|
||||
[wellKnownSymbols.toPrimitive, SymbolProto_toPrimitive, 1, override],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Symbol');
|
||||
|
||||
realmRec.Intrinsics['%Symbol.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { bootstrapConstructor } from '../bootstrap.mts';
|
||||
import { __ts_cast__ } from '../../helpers.mts';
|
||||
import { ToIntegerIfIntegral, GetOptionsObject } from '../../abstract-ops/temporal/addition.mts';
|
||||
import { bootstrapTemporalDurationPrototype } from './DurationPrototype.mts';
|
||||
import {
|
||||
ObjectValue, Q, Value, type OrdinaryObject, type ValueEvaluator,
|
||||
type Realm,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
F,
|
||||
UndefinedValue,
|
||||
Throw,
|
||||
Add24HourDaysToTimeDuration,
|
||||
CompareTimeDuration,
|
||||
CreateTemporalDuration,
|
||||
DateDurationDays,
|
||||
DefaultTemporalLargestUnit,
|
||||
ToInternalDurationRecord,
|
||||
ToTemporalDuration,
|
||||
GetTemporalRelativeToOption,
|
||||
IsCalendarUnit,
|
||||
TemporalUnitCategory,
|
||||
AddZonedDateTime,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances */
|
||||
export interface TemporalDurationObject extends OrdinaryObject {
|
||||
readonly InitializedTemporalDuration: never;
|
||||
readonly Years: number;
|
||||
readonly Months: number;
|
||||
readonly Weeks: number;
|
||||
readonly Days: number;
|
||||
readonly Hours: number;
|
||||
readonly Minutes: number;
|
||||
readonly Seconds: number;
|
||||
readonly Milliseconds: number;
|
||||
readonly Microseconds: number;
|
||||
readonly Nanoseconds: number;
|
||||
}
|
||||
|
||||
export function isTemporalDurationObject(item: Value): item is TemporalDurationObject {
|
||||
return item instanceof ObjectValue && 'InitializedTemporalDuration' in item;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration */
|
||||
function* DurationConstructor([
|
||||
years = Value.undefined,
|
||||
months = Value.undefined,
|
||||
weeks = Value.undefined,
|
||||
days = Value.undefined,
|
||||
hours = Value.undefined,
|
||||
minutes = Value.undefined,
|
||||
seconds = Value.undefined,
|
||||
milliseconds = Value.undefined,
|
||||
microseconds = Value.undefined,
|
||||
nanoseconds = Value.undefined,
|
||||
]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Temporal.Duration constructor cannot be called without new');
|
||||
}
|
||||
const y = years instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(years));
|
||||
const mo = months instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(months));
|
||||
const w = weeks instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(weeks));
|
||||
const d = days instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(days));
|
||||
const h = hours instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(hours));
|
||||
const m = minutes instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(minutes));
|
||||
const s = seconds instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(seconds));
|
||||
const ms = milliseconds instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(milliseconds));
|
||||
const mis = microseconds instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(microseconds));
|
||||
const ns = nanoseconds instanceof UndefinedValue ? 0 : Q(yield* ToIntegerIfIntegral(nanoseconds));
|
||||
return Q(yield* CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.from */
|
||||
function* Duration_From([item = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* ToTemporalDuration(item));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.compare */
|
||||
function* Duration_Compare([_one = Value.undefined, _two = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const one = Q(yield* ToTemporalDuration(_one));
|
||||
const two = Q(yield* ToTemporalDuration(_two));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const relativeToRecord = Q(yield* GetTemporalRelativeToOption(resolvedOptions));
|
||||
if (one.Years === two.Years
|
||||
&& one.Months === two.Months
|
||||
&& one.Weeks === two.Weeks
|
||||
&& one.Days === two.Days
|
||||
&& one.Hours === two.Hours
|
||||
&& one.Minutes === two.Minutes
|
||||
&& one.Seconds === two.Seconds
|
||||
&& one.Milliseconds === two.Milliseconds
|
||||
&& one.Microseconds === two.Microseconds
|
||||
&& one.Nanoseconds === two.Nanoseconds) {
|
||||
return F(0);
|
||||
}
|
||||
const zonedRelativeTo = relativeToRecord.ZonedRelativeTo;
|
||||
const plainRelativeTo = relativeToRecord.PlainRelativeTo;
|
||||
const largestUnit1 = DefaultTemporalLargestUnit(one);
|
||||
const largestUnit2 = DefaultTemporalLargestUnit(two);
|
||||
const duration1 = ToInternalDurationRecord(one);
|
||||
const duration2 = ToInternalDurationRecord(two);
|
||||
if (zonedRelativeTo !== undefined
|
||||
&& (TemporalUnitCategory(largestUnit1) === 'date' || TemporalUnitCategory(largestUnit2) === 'date')) {
|
||||
const timeZone = zonedRelativeTo.TimeZone;
|
||||
const calendar = zonedRelativeTo.Calendar;
|
||||
const after1 = Q(AddZonedDateTime(zonedRelativeTo.EpochNanoseconds, timeZone, calendar, duration1, 'constrain'));
|
||||
const after2 = Q(AddZonedDateTime(zonedRelativeTo.EpochNanoseconds, timeZone, calendar, duration2, 'constrain'));
|
||||
if (after1 > after2) return F(1);
|
||||
if (after1 < after2) return F(-1);
|
||||
return F(0);
|
||||
}
|
||||
let days1;
|
||||
let days2;
|
||||
if (IsCalendarUnit(largestUnit1) || IsCalendarUnit(largestUnit2)) {
|
||||
if (plainRelativeTo === undefined) {
|
||||
return Throw.RangeError('relativeTo option is required when comparing durations with calendar units');
|
||||
}
|
||||
days1 = Q(DateDurationDays(duration1.Date, plainRelativeTo));
|
||||
days2 = Q(DateDurationDays(duration2.Date, plainRelativeTo));
|
||||
} else {
|
||||
days1 = one.Days;
|
||||
days2 = two.Days;
|
||||
}
|
||||
const timeDuration1 = Q(Add24HourDaysToTimeDuration(duration1.Time, days1));
|
||||
const timeDuration2 = Q(Add24HourDaysToTimeDuration(duration2.Time, days2));
|
||||
return F(CompareTimeDuration(timeDuration1, timeDuration2));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalDuration(realmRec: Realm) {
|
||||
const prototype = bootstrapTemporalDurationPrototype(realmRec);
|
||||
|
||||
const constructor = bootstrapConstructor(realmRec, DurationConstructor, 'Duration', 0, prototype, [
|
||||
['from', Duration_From, 1],
|
||||
['compare', Duration_Compare, 2],
|
||||
]);
|
||||
realmRec.Intrinsics['%Temporal.Duration%'] = constructor;
|
||||
return constructor;
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import { abs } from '../../abstract-ops/math.mts';
|
||||
import { __ts_cast__ } from '../../helpers.mts';
|
||||
import {
|
||||
GetOptionsObject, GetRoundingIncrementOption, GetRoundingModeOption, RoundingMode,
|
||||
} from '../../abstract-ops/temporal/addition.mts';
|
||||
import {
|
||||
type TemporalDurationObject,
|
||||
} from './Duration.mts';
|
||||
import {
|
||||
AddDurations,
|
||||
AddTime,
|
||||
AddZonedDateTime,
|
||||
AdjustDateDurationRecord,
|
||||
Assert,
|
||||
CalendarDateAdd,
|
||||
CombineDateAndTimeDuration,
|
||||
CombineISODateAndTimeRecord,
|
||||
CreateDataPropertyOrThrow,
|
||||
CreateDateDurationRecord,
|
||||
CreateNegatedTemporalDuration,
|
||||
CreateTemporalDuration,
|
||||
DefaultTemporalLargestUnit,
|
||||
DifferencePlainDateTimeWithRounding,
|
||||
DifferencePlainDateTimeWithTotal,
|
||||
DifferenceZonedDateTimeWithRounding,
|
||||
DifferenceZonedDateTimeWithTotal,
|
||||
DurationSign,
|
||||
F,
|
||||
GetTemporalFractionalSecondDigitsOption,
|
||||
GetTemporalRelativeToOption,
|
||||
GetTemporalUnitValuedOption,
|
||||
IsCalendarUnit,
|
||||
JSStringValue,
|
||||
LargerOfTwoTemporalUnits,
|
||||
MaximumTemporalDurationRoundingIncrement,
|
||||
MidnightTimeRecord,
|
||||
OrdinaryObjectCreate,
|
||||
Q,
|
||||
Realm,
|
||||
RequireInternalSlot,
|
||||
RoundNumberToIncrement,
|
||||
RoundTimeDuration,
|
||||
TemporalDurationFromInternal,
|
||||
TemporalDurationToString,
|
||||
TemporalUnit,
|
||||
TemporalUnitCategory,
|
||||
Throw,
|
||||
ToInternalDurationRecord,
|
||||
ToInternalDurationRecordWith24HourDays,
|
||||
ToSecondsStringPrecisionRecord,
|
||||
TotalTimeDuration,
|
||||
ToTemporalPartialDurationRecord,
|
||||
UndefinedValue,
|
||||
ValidateTemporalRoundingIncrement,
|
||||
ValidateTemporalUnitValue,
|
||||
Value,
|
||||
X,
|
||||
ZeroDateDuration,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type PlainCompletion,
|
||||
type TimeDuration,
|
||||
type TimeUnit,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
function thisTemporalDurationValue(value: Value): PlainCompletion<TemporalDurationObject> {
|
||||
Q(RequireInternalSlot(value, 'InitializedTemporalDuration'));
|
||||
return value as TemporalDurationObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years */
|
||||
function DurationProto_yearsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Years);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.months */
|
||||
function DurationProto_monthsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Months);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.weeks */
|
||||
function DurationProto_weeksGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Weeks);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.days */
|
||||
function DurationProto_daysGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Days);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.hours */
|
||||
function DurationProto_hoursGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Hours);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.minutes */
|
||||
function DurationProto_minutesGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Minutes);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.seconds */
|
||||
function DurationProto_secondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Seconds);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.milliseconds */
|
||||
function DurationProto_millisecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Milliseconds);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.microseconds */
|
||||
function DurationProto_microsecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Microseconds);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.nanoseconds */
|
||||
function DurationProto_nanosecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(duration.Nanoseconds);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.sign */
|
||||
function DurationProto_signGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return F(DurationSign(duration));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.blank */
|
||||
function DurationProto_blankGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return DurationSign(duration) === 0 ? Value.true : Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.with */
|
||||
function* DurationProto_with([_temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
const temporalDurationLike = Q(yield* ToTemporalPartialDurationRecord(_temporalDurationLike));
|
||||
const years = temporalDurationLike.Years ?? duration.Years;
|
||||
const months = temporalDurationLike.Months ?? duration.Months;
|
||||
const weeks = temporalDurationLike.Weeks ?? duration.Weeks;
|
||||
const days = temporalDurationLike.Days ?? duration.Days;
|
||||
const hours = temporalDurationLike.Hours ?? duration.Hours;
|
||||
const minutes = temporalDurationLike.Minutes ?? duration.Minutes;
|
||||
const seconds = temporalDurationLike.Seconds ?? duration.Seconds;
|
||||
const milliseconds = temporalDurationLike.Milliseconds ?? duration.Milliseconds;
|
||||
const microseconds = temporalDurationLike.Microseconds ?? duration.Microseconds;
|
||||
const nanoseconds = temporalDurationLike.Nanoseconds ?? duration.Nanoseconds;
|
||||
return Q(yield* CreateTemporalDuration(years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.negated */
|
||||
function DurationProto_negated(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return CreateNegatedTemporalDuration(duration);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.abs */
|
||||
function* DurationProto_abs(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return X(CreateTemporalDuration(
|
||||
abs(duration.Years),
|
||||
abs(duration.Months),
|
||||
abs(duration.Weeks),
|
||||
abs(duration.Days),
|
||||
abs(duration.Hours),
|
||||
abs(duration.Minutes),
|
||||
abs(duration.Seconds),
|
||||
abs(duration.Milliseconds),
|
||||
abs(duration.Microseconds),
|
||||
abs(duration.Nanoseconds),
|
||||
));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add */
|
||||
function* DurationProto_add([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return Q(yield* AddDurations('add', duration, other));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract */
|
||||
function* DurationProto_subtract([other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return Q(yield* AddDurations('subtract', duration, other));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.round */
|
||||
function* DurationProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
if (roundTo instanceof UndefinedValue) {
|
||||
return Throw.TypeError('roundTo is required');
|
||||
}
|
||||
if (roundTo instanceof JSStringValue) {
|
||||
const paramString = roundTo;
|
||||
roundTo = OrdinaryObjectCreate(Value.null);
|
||||
X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString));
|
||||
} else {
|
||||
roundTo = Q(GetOptionsObject(roundTo));
|
||||
}
|
||||
|
||||
let smallestUnitPresent = true;
|
||||
let largestUnitPresent = true;
|
||||
|
||||
const largestUnitOption = Q(yield* GetTemporalUnitValuedOption(roundTo, 'largestUnit', 'unset'));
|
||||
const relativeToRecord = Q(yield* GetTemporalRelativeToOption(roundTo));
|
||||
const zonedRelativeTo = relativeToRecord.ZonedRelativeTo;
|
||||
const plainRelativeTo = relativeToRecord.PlainRelativeTo;
|
||||
const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand));
|
||||
let smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'unset'));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'datetime'));
|
||||
|
||||
if (smallestUnit === 'unset') {
|
||||
smallestUnitPresent = false;
|
||||
smallestUnit = TemporalUnit.Nanosecond;
|
||||
}
|
||||
|
||||
const existingLargestUnit = DefaultTemporalLargestUnit(duration);
|
||||
// TODO(temporal): this assert does not in the spec.
|
||||
Assert(smallestUnit !== 'auto');
|
||||
const defaultLargestUnit = LargerOfTwoTemporalUnits(existingLargestUnit, smallestUnit);
|
||||
let largestUnit;
|
||||
if (largestUnitOption === 'unset') {
|
||||
largestUnitPresent = false;
|
||||
largestUnit = defaultLargestUnit;
|
||||
} else if (largestUnitOption === 'auto') {
|
||||
largestUnit = defaultLargestUnit;
|
||||
} else {
|
||||
largestUnit = largestUnitOption;
|
||||
}
|
||||
|
||||
if (!smallestUnitPresent && !largestUnitPresent) {
|
||||
return Throw.RangeError('smallestUnit and largestUnit cannot both be omitted');
|
||||
}
|
||||
if (LargerOfTwoTemporalUnits(largestUnit, smallestUnit) !== largestUnit) {
|
||||
return Throw.RangeError('largestUnit must be larger than smallestUnit');
|
||||
}
|
||||
|
||||
const maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
|
||||
if (maximum !== 'unset') {
|
||||
Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false));
|
||||
}
|
||||
if (roundingIncrement > 1 && largestUnit !== smallestUnit && TemporalUnitCategory(smallestUnit) === 'date') {
|
||||
return Throw.RangeError('roundingIncrement must be 1 when rounding a date unit to a larger unit');
|
||||
}
|
||||
|
||||
if (zonedRelativeTo !== undefined) {
|
||||
let internalDuration = ToInternalDurationRecord(duration);
|
||||
const timeZone = zonedRelativeTo.TimeZone;
|
||||
const calendar = zonedRelativeTo.Calendar;
|
||||
const relativeEpochNs = zonedRelativeTo.EpochNanoseconds;
|
||||
const targetEpochNs = Q(AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, 'constrain'));
|
||||
internalDuration = Q(DifferenceZonedDateTimeWithRounding(relativeEpochNs, targetEpochNs, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode));
|
||||
if (TemporalUnitCategory(largestUnit) === 'date') {
|
||||
largestUnit = TemporalUnit.Hour;
|
||||
}
|
||||
return Q(yield* TemporalDurationFromInternal(internalDuration, largestUnit));
|
||||
}
|
||||
|
||||
if (plainRelativeTo !== undefined) {
|
||||
let internalDuration = ToInternalDurationRecordWith24HourDays(duration);
|
||||
const targetTime = AddTime(MidnightTimeRecord(), internalDuration.Time);
|
||||
const calendar = plainRelativeTo.Calendar;
|
||||
const dateDuration = X(AdjustDateDurationRecord(internalDuration.Date, targetTime.Days));
|
||||
const targetDate = Q(CalendarDateAdd(calendar, plainRelativeTo.ISODate, dateDuration, 'constrain'));
|
||||
const isoDateTime = CombineISODateAndTimeRecord(plainRelativeTo.ISODate, MidnightTimeRecord());
|
||||
const targetDateTime = CombineISODateAndTimeRecord(targetDate, targetTime);
|
||||
internalDuration = Q(DifferencePlainDateTimeWithRounding(isoDateTime, targetDateTime, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode));
|
||||
return Q(yield* TemporalDurationFromInternal(internalDuration, largestUnit));
|
||||
}
|
||||
|
||||
if (IsCalendarUnit(existingLargestUnit) || IsCalendarUnit(largestUnit)) {
|
||||
return Throw.RangeError('relativeTo is required for calendar units');
|
||||
}
|
||||
Assert(IsCalendarUnit(smallestUnit) === false);
|
||||
|
||||
let internalDuration = ToInternalDurationRecordWith24HourDays(duration);
|
||||
if (smallestUnit === TemporalUnit.Day) {
|
||||
const fractionalDays = TotalTimeDuration(internalDuration.Time, TemporalUnit.Day);
|
||||
const days = RoundNumberToIncrement(fractionalDays, roundingIncrement, roundingMode);
|
||||
const dateDuration = Q(CreateDateDurationRecord(0, 0, 0, days));
|
||||
internalDuration = CombineDateAndTimeDuration(dateDuration, 0 as TimeDuration);
|
||||
} else {
|
||||
const timeDuration = Q(RoundTimeDuration(internalDuration.Time, roundingIncrement, smallestUnit, roundingMode));
|
||||
internalDuration = CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration);
|
||||
}
|
||||
return Q(yield* TemporalDurationFromInternal(internalDuration, largestUnit));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total */
|
||||
function* DurationProto_total([totalOf = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
if (totalOf instanceof UndefinedValue) {
|
||||
return Throw.TypeError('totalOf is required');
|
||||
}
|
||||
if (totalOf instanceof JSStringValue) {
|
||||
const paramString = totalOf;
|
||||
totalOf = OrdinaryObjectCreate(Value.null);
|
||||
X(CreateDataPropertyOrThrow(totalOf, Value('unit'), paramString));
|
||||
} else {
|
||||
totalOf = Q(GetOptionsObject(totalOf));
|
||||
}
|
||||
|
||||
const relativeToRecord = Q(yield* GetTemporalRelativeToOption(totalOf));
|
||||
const zonedRelativeTo = relativeToRecord.ZonedRelativeTo;
|
||||
const plainRelativeTo = relativeToRecord.PlainRelativeTo;
|
||||
const unit = Q(yield* GetTemporalUnitValuedOption(totalOf, 'unit', 'required'));
|
||||
Q(ValidateTemporalUnitValue(unit, 'datetime'));
|
||||
Assert(unit !== 'auto' && unit !== 'unset'); // TODO(temporal): missing assert in spec?
|
||||
|
||||
let total;
|
||||
if (zonedRelativeTo !== undefined) {
|
||||
const internalDuration = ToInternalDurationRecord(duration);
|
||||
const timeZone = zonedRelativeTo.TimeZone;
|
||||
const calendar = zonedRelativeTo.Calendar;
|
||||
const relativeEpochNs = zonedRelativeTo.EpochNanoseconds;
|
||||
const targetEpochNs = Q(AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, 'constrain'));
|
||||
total = Q(DifferenceZonedDateTimeWithTotal(relativeEpochNs, targetEpochNs, timeZone, calendar, unit));
|
||||
} else if (plainRelativeTo !== undefined) {
|
||||
const internalDuration = ToInternalDurationRecordWith24HourDays(duration);
|
||||
const targetTime = AddTime(MidnightTimeRecord(), internalDuration.Time);
|
||||
const calendar = plainRelativeTo.Calendar;
|
||||
const dateDuration = X(AdjustDateDurationRecord(internalDuration.Date, targetTime.Days));
|
||||
const targetDate = Q(CalendarDateAdd(calendar, plainRelativeTo.ISODate, dateDuration, 'constrain'));
|
||||
const isoDateTime = CombineISODateAndTimeRecord(plainRelativeTo.ISODate, MidnightTimeRecord());
|
||||
const targetDateTime = CombineISODateAndTimeRecord(targetDate, targetTime);
|
||||
total = Q(DifferencePlainDateTimeWithTotal(isoDateTime, targetDateTime, calendar, unit));
|
||||
} else {
|
||||
const largestUnit = DefaultTemporalLargestUnit(duration);
|
||||
if (IsCalendarUnit(largestUnit) || IsCalendarUnit(unit)) {
|
||||
return Throw.RangeError('relativeTo is required for calendar units');
|
||||
}
|
||||
const internalDuration = ToInternalDurationRecordWith24HourDays(duration);
|
||||
total = TotalTimeDuration(internalDuration.Time, unit);
|
||||
}
|
||||
return F(total);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring */
|
||||
function* DurationProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, RoundingMode.Trunc));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset'));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time'));
|
||||
__ts_cast__<TimeUnit | 'unset'>(smallestUnit);
|
||||
|
||||
if (smallestUnit === TemporalUnit.Hour || smallestUnit === TemporalUnit.Minute) {
|
||||
return Throw.RangeError('smallestUnit cannot be hour or minute');
|
||||
}
|
||||
|
||||
const precision = ToSecondsStringPrecisionRecord(smallestUnit, digits);
|
||||
|
||||
if (precision.Unit === TemporalUnit.Nanosecond && precision.Increment === 1) {
|
||||
return Value(TemporalDurationToString(duration, precision.Precision));
|
||||
}
|
||||
|
||||
const largestUnit = DefaultTemporalLargestUnit(duration);
|
||||
let internalDuration = ToInternalDurationRecord(duration);
|
||||
const timeDuration = Q(RoundTimeDuration(internalDuration.Time, precision.Increment, precision.Unit, roundingMode));
|
||||
internalDuration = CombineDateAndTimeDuration(internalDuration.Date, timeDuration);
|
||||
const roundedLargestUnit = LargerOfTwoTemporalUnits(largestUnit, TemporalUnit.Second);
|
||||
const roundedDuration = Q(yield* TemporalDurationFromInternal(internalDuration, roundedLargestUnit));
|
||||
return Value(TemporalDurationToString(roundedDuration, precision.Precision));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tojson */
|
||||
function DurationProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return Value(TemporalDurationToString(duration, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tolocalestring */
|
||||
function DurationProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const duration = Q(thisTemporalDurationValue(thisValue));
|
||||
return Value(TemporalDurationToString(duration, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.valueof */
|
||||
function DurationProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
Q(thisTemporalDurationValue(thisValue));
|
||||
return Throw.TypeError('Temporal.Duration cannot be converted to primitive value. If you are comparing two Temporal.Duration objects with > or <, use Temporal.Duration.compare() instead.');
|
||||
}
|
||||
|
||||
export function bootstrapTemporalDurationPrototype(realmRec: Realm) {
|
||||
const prototype = bootstrapPrototype(realmRec, [
|
||||
['years', [DurationProto_yearsGetter]],
|
||||
['months', [DurationProto_monthsGetter]],
|
||||
['weeks', [DurationProto_weeksGetter]],
|
||||
['days', [DurationProto_daysGetter]],
|
||||
['hours', [DurationProto_hoursGetter]],
|
||||
['minutes', [DurationProto_minutesGetter]],
|
||||
['seconds', [DurationProto_secondsGetter]],
|
||||
['milliseconds', [DurationProto_millisecondsGetter]],
|
||||
['microseconds', [DurationProto_microsecondsGetter]],
|
||||
['nanoseconds', [DurationProto_nanosecondsGetter]],
|
||||
['sign', [DurationProto_signGetter]],
|
||||
['blank', [DurationProto_blankGetter]],
|
||||
['with', DurationProto_with, 1],
|
||||
['negated', DurationProto_negated, 0],
|
||||
['abs', DurationProto_abs, 0],
|
||||
['add', DurationProto_add, 1],
|
||||
['subtract', DurationProto_subtract, 1],
|
||||
['round', DurationProto_round, 1],
|
||||
['total', DurationProto_total, 1],
|
||||
['toString', DurationProto_toString, 0],
|
||||
['toJSON', DurationProto_toJSON, 0],
|
||||
['toLocaleString', DurationProto_toLocaleString, 0],
|
||||
['valueOf', DurationProto_valueOf, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.Duration');
|
||||
realmRec.Intrinsics['%Temporal.Duration.prototype%'] = prototype;
|
||||
return prototype;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { bootstrapConstructor } from '../bootstrap.mts';
|
||||
import { NumberToBigInt } from '../../runtime-semantics/all.mts';
|
||||
import { bootstrapTemporalInstantPrototype } from './InstantPrototype.mts';
|
||||
import {
|
||||
Q,
|
||||
Throw,
|
||||
X,
|
||||
type OrdinaryObject,
|
||||
type Realm,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
F,
|
||||
UndefinedValue,
|
||||
ToNumber,
|
||||
ToBigInt,
|
||||
R,
|
||||
Value,
|
||||
type ValueEvaluator,
|
||||
CompareEpochNanoseconds,
|
||||
CreateTemporalInstant,
|
||||
IsValidEpochNanoseconds,
|
||||
ToTemporalInstant,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-instant-instances */
|
||||
export interface TemporalInstantObject extends OrdinaryObject {
|
||||
readonly InitializedTemporalInstant: never;
|
||||
readonly EpochNanoseconds: bigint;
|
||||
}
|
||||
|
||||
export function isTemporalInstantObject(o: Value): o is TemporalInstantObject {
|
||||
return 'InitializedTemporalInstant' in o;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant */
|
||||
function* InstantConstructor([_epochNanoseconds = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Temporal.Instant cannot be called without new');
|
||||
}
|
||||
const epochNanoseconds = R(Q(yield* ToBigInt(_epochNanoseconds)));
|
||||
if (!IsValidEpochNanoseconds(epochNanoseconds)) {
|
||||
return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochNanoseconds);
|
||||
}
|
||||
return Q(yield* CreateTemporalInstant(epochNanoseconds, NewTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.from */
|
||||
function* Instant_from([item = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* ToTemporalInstant(item));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds */
|
||||
function* Instant_fromEpochMilliseconds([___epochMilliseconds = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const __epochMilliseconds = Q(yield* ToNumber(___epochMilliseconds));
|
||||
const _epochMilliseconds = R(Q(NumberToBigInt(__epochMilliseconds)));
|
||||
const epochMilliseconds = _epochMilliseconds * BigInt(10e6);
|
||||
if (!IsValidEpochNanoseconds(epochMilliseconds)) {
|
||||
return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochMilliseconds);
|
||||
}
|
||||
return X(CreateTemporalInstant(epochMilliseconds));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds */
|
||||
function* Instant_fromEpochNanoseconds([_epochNanoseconds = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const epochNanoseconds = R(Q(yield* ToBigInt(_epochNanoseconds)));
|
||||
if (!IsValidEpochNanoseconds(epochNanoseconds)) {
|
||||
return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochNanoseconds);
|
||||
}
|
||||
return X(CreateTemporalInstant(epochNanoseconds));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.compare */
|
||||
function* Instant_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const one = Q(yield* ToTemporalInstant(_one));
|
||||
const two = Q(yield* ToTemporalInstant(_two));
|
||||
return F(CompareEpochNanoseconds(one.EpochNanoseconds, two.EpochNanoseconds));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalInstant(realmRec: Realm) {
|
||||
const prototype = bootstrapTemporalInstantPrototype(realmRec);
|
||||
|
||||
const constructor = bootstrapConstructor(realmRec, InstantConstructor, 'Instant', 1, prototype, [
|
||||
['from', Instant_from, 1],
|
||||
['fromEpochMilliseconds', Instant_fromEpochMilliseconds, 1],
|
||||
['fromEpochNanoseconds', Instant_fromEpochNanoseconds, 1],
|
||||
['compare', Instant_compare, 2],
|
||||
]);
|
||||
realmRec.Intrinsics['%Temporal.Instant%'] = constructor;
|
||||
return constructor;
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import {
|
||||
GetOptionsObject,
|
||||
GetRoundingIncrementOption,
|
||||
GetRoundingModeOption,
|
||||
RoundingMode,
|
||||
type TimeZoneIdentifier,
|
||||
} from '../../abstract-ops/temporal/addition.mts';
|
||||
import {
|
||||
GetTemporalFractionalSecondDigitsOption,
|
||||
GetTemporalUnitValuedOption,
|
||||
TemporalUnit,
|
||||
ToSecondsStringPrecisionRecord,
|
||||
ValidateTemporalRoundingIncrement,
|
||||
ValidateTemporalUnitValue,
|
||||
type TimeUnit,
|
||||
} from '../../abstract-ops/temporal/temporal.mts';
|
||||
import {
|
||||
AddDurationToInstant,
|
||||
CreateTemporalInstant,
|
||||
DifferenceTemporalInstant,
|
||||
nsPerDay,
|
||||
RoundTemporalInstant,
|
||||
TemporalInstantToString,
|
||||
ToTemporalInstant,
|
||||
} from '../../abstract-ops/temporal/instant.mts';
|
||||
import { CreateTemporalZonedDateTime } from '../../abstract-ops/temporal/zoned-datetime.mts';
|
||||
import { ToTemporalTimeZoneIdentifier } from '../../abstract-ops/temporal/time-zone.mts';
|
||||
import type { TemporalInstantObject } from './Instant.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateDataPropertyOrThrow,
|
||||
Get,
|
||||
HoursPerDay,
|
||||
JSStringValue,
|
||||
MinutesPerHour,
|
||||
msPerDay,
|
||||
OrdinaryObjectCreate,
|
||||
Q,
|
||||
RequireInternalSlot,
|
||||
SecondsPerMinute,
|
||||
Throw,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
X,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type PlainCompletion,
|
||||
type Realm,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
function thisTemporalInstantValue(value: Value): PlainCompletion<TemporalInstantObject> {
|
||||
Q(RequireInternalSlot(value, 'InitializedTemporalInstant'));
|
||||
return value as TemporalInstantObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds */
|
||||
function InstantProto_epochMillisecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
const ns = instant.EpochNanoseconds;
|
||||
const ms = Math.floor(Number(ns) / 10e6);
|
||||
return Value(ms);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochnanoseconds */
|
||||
function InstantProto_epochNanosecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
return Value(instant.EpochNanoseconds);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.add */
|
||||
function* InstantProto_add([temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
return Q(yield* AddDurationToInstant('add', instant, temporalDurationLike));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.subtract */
|
||||
function* InstantProto_subtract([temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
return Q(yield* AddDurationToInstant('subtract', instant, temporalDurationLike));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until */
|
||||
function* InstantProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalInstant('until', instant, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.since */
|
||||
function* InstantProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalInstant('since', instant, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round */
|
||||
function* InstantProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
if (roundTo instanceof UndefinedValue) {
|
||||
return Throw.TypeError('roundTo is required');
|
||||
}
|
||||
if (roundTo instanceof JSStringValue) {
|
||||
const paramString = roundTo;
|
||||
roundTo = OrdinaryObjectCreate(Value.null);
|
||||
X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString));
|
||||
} else {
|
||||
roundTo = Q(GetOptionsObject(roundTo));
|
||||
}
|
||||
const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'required'));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time'));
|
||||
let maximum: number;
|
||||
if (smallestUnit === TemporalUnit.Hour) {
|
||||
maximum = HoursPerDay;
|
||||
} else if (smallestUnit === TemporalUnit.Minute) {
|
||||
maximum = MinutesPerHour * HoursPerDay;
|
||||
} else if (smallestUnit === TemporalUnit.Second) {
|
||||
maximum = SecondsPerMinute * MinutesPerHour * HoursPerDay;
|
||||
} else if (smallestUnit === TemporalUnit.Millisecond) {
|
||||
maximum = msPerDay;
|
||||
} else if (smallestUnit === TemporalUnit.Microsecond) {
|
||||
maximum = 1e3 * msPerDay;
|
||||
} else {
|
||||
Assert(smallestUnit === TemporalUnit.Nanosecond);
|
||||
maximum = nsPerDay;
|
||||
}
|
||||
Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true));
|
||||
const roundedNs = RoundTemporalInstant(instant.EpochNanoseconds, roundingIncrement, smallestUnit, roundingMode);
|
||||
return X(CreateTemporalInstant(roundedNs));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals */
|
||||
function* InstantProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
const other = Q(yield* ToTemporalInstant(_other));
|
||||
return instant.EpochNanoseconds === other.EpochNanoseconds ? Value.true : Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring */
|
||||
function* InstantProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, RoundingMode.Trunc));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset'));
|
||||
const _timeZone = Q(yield* Get(resolvedOptions, Value('timeZone')));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time'));
|
||||
if (smallestUnit === TemporalUnit.Hour) {
|
||||
return Throw.RangeError('smallestUnit cannot be hour');
|
||||
}
|
||||
let timeZone: TimeZoneIdentifier | undefined;
|
||||
if (!(_timeZone instanceof UndefinedValue)) {
|
||||
timeZone = Q(ToTemporalTimeZoneIdentifier(_timeZone));
|
||||
}
|
||||
const precision = ToSecondsStringPrecisionRecord(
|
||||
smallestUnit as Exclude<TimeUnit, TemporalUnit.Hour> | 'unset',
|
||||
digits,
|
||||
);
|
||||
const roundedNs = RoundTemporalInstant(instant.EpochNanoseconds, precision.Increment, precision.Unit, roundingMode);
|
||||
const roundedInstant = X(CreateTemporalInstant(roundedNs));
|
||||
return Value(TemporalInstantToString(roundedInstant, timeZone, precision.Precision));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring */
|
||||
function InstantProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
return Value(TemporalInstantToString(instant, undefined, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tojson */
|
||||
function InstantProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
return Value(TemporalInstantToString(instant, undefined, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof */
|
||||
function InstantProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
Q(thisTemporalInstantValue(thisValue));
|
||||
return Throw.TypeError('Temporal.Instant cannot be converted to primitive value If you are comparing two Temporal.Duration objects with > or <, use Temporal.Instant.compare() instead.');
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetimeiso */
|
||||
function InstantProto_toZonedDateTimeISO([_timeZone = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const instant = Q(thisTemporalInstantValue(thisValue));
|
||||
const timeZone = Q(ToTemporalTimeZoneIdentifier(_timeZone));
|
||||
return X(CreateTemporalZonedDateTime(instant.EpochNanoseconds, timeZone, 'iso8601'));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalInstantPrototype(realmRec: Realm) {
|
||||
const prototype = bootstrapPrototype(realmRec, [
|
||||
['epochMilliseconds', [InstantProto_epochMillisecondsGetter]],
|
||||
['epochNanoseconds', [InstantProto_epochNanosecondsGetter]],
|
||||
['add', InstantProto_add, 1],
|
||||
['subtract', InstantProto_subtract, 1],
|
||||
['until', InstantProto_until, 1],
|
||||
['since', InstantProto_since, 1],
|
||||
['round', InstantProto_round, 1],
|
||||
['equals', InstantProto_equals, 1],
|
||||
['toString', InstantProto_toString, 0],
|
||||
['toLocaleString', InstantProto_toLocaleString, 0],
|
||||
['toJSON', InstantProto_toJSON, 0],
|
||||
['valueOf', InstantProto_valueOf, 0],
|
||||
['toZonedDateTimeISO', InstantProto_toZonedDateTimeISO, 1],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.Instant');
|
||||
realmRec.Intrinsics['%Temporal.Instant.prototype%'] = prototype;
|
||||
return prototype;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { SystemTimeZoneIdentifier } from '../../abstract-ops/temporal/addition.mts';
|
||||
import { ToTemporalTimeZoneIdentifier } from '../../abstract-ops/temporal/time-zone.mts';
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import {
|
||||
type Realm, X, Value, Q, type Arguments, type PlainCompletion,
|
||||
CreateTemporalInstant,
|
||||
SystemDateTime,
|
||||
SystemUTCEpochNanoseconds,
|
||||
CreateTemporalDate,
|
||||
CreateTemporalZonedDateTime,
|
||||
CreateTemporalDateTime,
|
||||
CreateTemporalTime,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.now.timezoneid */
|
||||
function TemporalNow_timeZoneId(): Value {
|
||||
return Value(SystemTimeZoneIdentifier());
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.now.instant */
|
||||
function TemporalNow_instant(): Value {
|
||||
const ns = SystemUTCEpochNanoseconds();
|
||||
return X(CreateTemporalInstant(ns));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.now.plaindatetimeiso */
|
||||
function TemporalNow_plainDateTimeISO([temporalTimeZoneLike = Value.undefined]: Arguments): PlainCompletion<Value> {
|
||||
const isoDateTime = Q(SystemDateTime(temporalTimeZoneLike));
|
||||
return X(CreateTemporalDateTime(isoDateTime, 'iso8601'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.now.zoneddatetimeiso */
|
||||
function TemporalNow_zonedDateTimeISO([temporalTimeZoneLike = Value.undefined]: Arguments): PlainCompletion<Value> {
|
||||
let timeZone;
|
||||
if (temporalTimeZoneLike === Value.undefined) {
|
||||
timeZone = SystemTimeZoneIdentifier();
|
||||
} else {
|
||||
timeZone = Q(ToTemporalTimeZoneIdentifier(temporalTimeZoneLike));
|
||||
}
|
||||
const ns = SystemUTCEpochNanoseconds();
|
||||
return X(CreateTemporalZonedDateTime(ns, timeZone, 'iso8601'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.now.plaindateiso */
|
||||
function TemporalNow_plainDateISO([temporalTimeZoneLike = Value.undefined]: Arguments): PlainCompletion<Value> {
|
||||
const isoDateTime = Q(SystemDateTime(temporalTimeZoneLike));
|
||||
return X(CreateTemporalDate(isoDateTime.ISODate, 'iso8601'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.now.plaintimeiso */
|
||||
function TemporalNow_plainTimeISO([temporalTimeZoneLike = Value.undefined]: Arguments): PlainCompletion<Value> {
|
||||
const isoDateTime = Q(SystemDateTime(temporalTimeZoneLike));
|
||||
return X(CreateTemporalTime(isoDateTime.Time));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalNow(realmRec: Realm) {
|
||||
const Now = bootstrapPrototype(realmRec, [
|
||||
['timeZoneId', TemporalNow_timeZoneId, 0],
|
||||
['instant', TemporalNow_instant, 0],
|
||||
['plainDateTimeISO', TemporalNow_plainDateTimeISO, 0],
|
||||
['zonedDateTimeISO', TemporalNow_zonedDateTimeISO, 0],
|
||||
['plainDateISO', TemporalNow_plainDateISO, 0],
|
||||
['plainTimeISO', TemporalNow_plainTimeISO, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.Now');
|
||||
return Now;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { bootstrapConstructor } from '../bootstrap.mts';
|
||||
import { bootstrapTemporalPlainDatePrototype } from './PlainDatePrototype.mts';
|
||||
import {
|
||||
type Realm, Value, UndefinedValue, Q, JSStringValue, type FunctionCallContext, type Arguments, F, type OrdinaryObject, type ValueEvaluator,
|
||||
Throw,
|
||||
CompareISODate,
|
||||
CreateISODateRecord,
|
||||
CreateTemporalDate,
|
||||
IsValidISODate,
|
||||
ToTemporalDate,
|
||||
type CalendarType,
|
||||
CanonicalizeCalendar,
|
||||
ToIntegerWithTruncation,
|
||||
} from '#self';
|
||||
|
||||
export interface TemporalPlainDateObject extends OrdinaryObject {
|
||||
/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaindate-instances */
|
||||
readonly InitializedTemporalDate: never;
|
||||
readonly ISODate: ISODateRecord;
|
||||
readonly Calendar: CalendarType;
|
||||
}
|
||||
export function isTemporalPlainDateObject(o: Value): o is TemporalPlainDateObject {
|
||||
return 'InitializedTemporalDate' in o;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-iso-date-records */
|
||||
export interface ISODateRecord {
|
||||
readonly Year: number;
|
||||
readonly Month: number;
|
||||
readonly Day: number;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate */
|
||||
function* PlainDateConstructor([isoYear = Value.undefined, isoMonth = Value.undefined, isoDay = Value.undefined, _calendar = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Temporal.PlainDate constructor cannot be called without new');
|
||||
}
|
||||
const y = Q(yield* ToIntegerWithTruncation(isoYear));
|
||||
const m = Q(yield* ToIntegerWithTruncation(isoMonth));
|
||||
const d = Q(yield* ToIntegerWithTruncation(isoDay));
|
||||
if (_calendar instanceof UndefinedValue) {
|
||||
_calendar = Value('iso8601');
|
||||
}
|
||||
if (!(_calendar instanceof JSStringValue)) {
|
||||
return Throw.TypeError('calendar must be a string, but $1', _calendar);
|
||||
}
|
||||
const calendar = Q(CanonicalizeCalendar(_calendar.stringValue()));
|
||||
if (!IsValidISODate(y, m, d)) {
|
||||
return Throw.RangeError('Invalid date');
|
||||
}
|
||||
const isoDate = CreateISODateRecord(y, m, d);
|
||||
return Q(yield* CreateTemporalDate(isoDate, calendar, NewTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.from */
|
||||
function* PlainDate_From([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* ToTemporalDate(item, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.compare */
|
||||
function* PlainDate_Compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const one = Q(yield* ToTemporalDate(_one));
|
||||
const two = Q(yield* ToTemporalDate(_two));
|
||||
return F(CompareISODate(one.ISODate, two.ISODate));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainDate(realmRec: Realm) {
|
||||
const prototype = bootstrapTemporalPlainDatePrototype(realmRec);
|
||||
realmRec.Intrinsics['%Temporal.PlainDate.prototype%'] = prototype;
|
||||
|
||||
const constructor = bootstrapConstructor(realmRec, PlainDateConstructor, 'PlainDate', 3, prototype, [
|
||||
['from', PlainDate_From, 1],
|
||||
['compare', PlainDate_Compare, 2],
|
||||
]);
|
||||
realmRec.Intrinsics['%Temporal.PlainDate%'] = constructor;
|
||||
|
||||
return constructor;
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import { GetOptionsObject } from '../../abstract-ops/temporal/addition.mts';
|
||||
import type { TimeZoneIdentifier } from '../../abstract-ops/temporal/addition.mts';
|
||||
import type { TemporalPlainDateObject } from './PlainDate.mts';
|
||||
import {
|
||||
AddDurationToDate,
|
||||
CalendarDateFromFields,
|
||||
CalendarEquals,
|
||||
CalendarISOToDate,
|
||||
CalendarMergeFields,
|
||||
CalendarMonthDayFromFields,
|
||||
CalendarYearMonthFromFields,
|
||||
CompareISODate,
|
||||
CombineISODateAndTimeRecord,
|
||||
CreateTemporalDate,
|
||||
CreateTemporalDateTime,
|
||||
CreateTemporalMonthDay,
|
||||
CreateTemporalYearMonth,
|
||||
CreateTemporalZonedDateTime,
|
||||
DifferenceTemporalPlainDate,
|
||||
Get,
|
||||
GetEpochNanosecondsFor,
|
||||
GetStartOfDay,
|
||||
GetTemporalOverflowOption,
|
||||
GetTemporalShowCalendarNameOption,
|
||||
ISODateTimeWithinLimits,
|
||||
ISODateToFields,
|
||||
IsPartialTemporalObject,
|
||||
PrepareCalendarFields,
|
||||
Q,
|
||||
RequireInternalSlot,
|
||||
Throw,
|
||||
ToTemporalCalendarIdentifier,
|
||||
ToTemporalDate,
|
||||
ToTemporalTime,
|
||||
ToTemporalTimeZoneIdentifier,
|
||||
ToTimeRecordOrMidnight,
|
||||
Value,
|
||||
X,
|
||||
F,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type PlainCompletion,
|
||||
type Realm,
|
||||
type ValueEvaluator,
|
||||
ObjectValue,
|
||||
TemporalDateToString,
|
||||
} from '#self';
|
||||
|
||||
function thisTemporalDateValue(value: Value): PlainCompletion<TemporalPlainDateObject> {
|
||||
Q(RequireInternalSlot(value, 'InitializedTemporalDate'));
|
||||
return value as TemporalPlainDateObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.calendarid */
|
||||
function PlainDateProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Value(plainDate.Calendar);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.era */
|
||||
function PlainDateProto_eraGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).Era);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.erayear */
|
||||
function PlainDateProto_eraYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const result = CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).EraYear;
|
||||
return result === undefined ? Value.undefined : F(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.year */
|
||||
function PlainDateProto_yearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).Year);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.month */
|
||||
function PlainDateProto_monthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).Month);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.monthcode */
|
||||
function PlainDateProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).MonthCode);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.day */
|
||||
function PlainDateProto_dayGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).Day);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.dayofweek */
|
||||
function PlainDateProto_dayOfWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DayOfWeek);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.dayofyear */
|
||||
function PlainDateProto_dayOfYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DayOfYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.weekofyear */
|
||||
function PlainDateProto_weekOfYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const result = CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).WeekOfYear.Week;
|
||||
return result === undefined ? Value.undefined : F(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.yearofweek */
|
||||
function PlainDateProto_yearOfWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const result = CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).WeekOfYear.Year;
|
||||
return result === undefined ? Value.undefined : F(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinweek */
|
||||
function PlainDateProto_daysInWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DaysInWeek);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinmonth */
|
||||
function PlainDateProto_daysInMonthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DaysInMonth);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.daysinyear */
|
||||
function PlainDateProto_daysInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).DaysInYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.monthsinyear */
|
||||
function PlainDateProto_monthsInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).MonthsInYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindate.prototype.inleapyear */
|
||||
function PlainDateProto_inLeapYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainDate.Calendar, plainDate.ISODate).InLeapYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplainyearmonth */
|
||||
function* PlainDateProto_toPlainYearMonth(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const calendar = plainDate.Calendar;
|
||||
const fields = ISODateToFields(calendar, plainDate.ISODate, 'date');
|
||||
const isoDate = Q(yield* CalendarYearMonthFromFields(calendar, fields, 'constrain'));
|
||||
return X(CreateTemporalYearMonth(isoDate, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplainmonthday */
|
||||
function* PlainDateProto_toPlainMonthDay(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const calendar = plainDate.Calendar;
|
||||
const fields = ISODateToFields(calendar, plainDate.ISODate, 'date');
|
||||
const isoDate = Q(yield* CalendarMonthDayFromFields(calendar, fields, 'constrain'));
|
||||
return X(CreateTemporalMonthDay(isoDate, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.add */
|
||||
function* PlainDateProto_add([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Q(yield* AddDurationToDate('add', plainDate, temporalDurationLike, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.subtract */
|
||||
function* PlainDateProto_subtract([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Q(yield* AddDurationToDate('subtract', plainDate, temporalDurationLike, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.with */
|
||||
function* PlainDateProto_with([temporalDateLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
if (!Q(yield* IsPartialTemporalObject(temporalDateLike))) {
|
||||
return Throw.TypeError('$1 is not a partial Temporal object', temporalDateLike);
|
||||
}
|
||||
const calendar = plainDate.Calendar;
|
||||
let fields = ISODateToFields(calendar, plainDate.ISODate, 'date');
|
||||
const partialDate = Q(yield* PrepareCalendarFields(calendar, temporalDateLike as ObjectValue, ['year', 'month', 'month-code', 'day'], [], 'partial'));
|
||||
fields = CalendarMergeFields(calendar, fields, partialDate);
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions));
|
||||
const isoDate = Q(yield* CalendarDateFromFields(calendar, fields, overflow));
|
||||
return X(CreateTemporalDate(isoDate, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.withcalendar */
|
||||
function PlainDateProto_withCalendar([calendarLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const calendar = Q(ToTemporalCalendarIdentifier(calendarLike));
|
||||
return X(CreateTemporalDate(plainDate.ISODate, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.until */
|
||||
function* PlainDateProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalPlainDate('until', plainDate, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.since */
|
||||
function* PlainDateProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalPlainDate('since', plainDate, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.equals */
|
||||
function* PlainDateProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const other = Q(yield* ToTemporalDate(_other));
|
||||
if (CompareISODate(plainDate.ISODate, other.ISODate) !== 0) {
|
||||
return Value.false;
|
||||
}
|
||||
return Value(CalendarEquals(plainDate.Calendar, other.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplaindatetime */
|
||||
function* PlainDateProto_toPlainDateTime([temporalTime = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const time = Q(yield* ToTimeRecordOrMidnight(temporalTime));
|
||||
const isoDateTime = CombineISODateAndTimeRecord(plainDate.ISODate, time);
|
||||
return Q(yield* CreateTemporalDateTime(isoDateTime, plainDate.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tozoneddatetime */
|
||||
function* PlainDateProto_toZonedDateTime([item = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
let timeZone: TimeZoneIdentifier;
|
||||
let temporalTime: Value;
|
||||
if (item instanceof ObjectValue) {
|
||||
const timeZoneLike = Q(yield* Get(item, Value('timeZone')));
|
||||
if (timeZoneLike === Value.undefined) {
|
||||
timeZone = Q(ToTemporalTimeZoneIdentifier(item));
|
||||
temporalTime = Value.undefined;
|
||||
} else {
|
||||
timeZone = Q(ToTemporalTimeZoneIdentifier(timeZoneLike));
|
||||
temporalTime = Q(yield* Get(item, Value('plainTime')));
|
||||
}
|
||||
} else {
|
||||
timeZone = Q(ToTemporalTimeZoneIdentifier(item));
|
||||
temporalTime = Value.undefined;
|
||||
}
|
||||
let epochNs: bigint;
|
||||
if (temporalTime === Value.undefined) {
|
||||
epochNs = Q(GetStartOfDay(timeZone, plainDate.ISODate));
|
||||
} else {
|
||||
const temporalTime2 = Q(yield* ToTemporalTime(temporalTime));
|
||||
const isoDateTime = CombineISODateAndTimeRecord(plainDate.ISODate, temporalTime2.Time);
|
||||
if (!ISODateTimeWithinLimits(isoDateTime)) {
|
||||
return Throw.RangeError('DateTime outside of range');
|
||||
}
|
||||
epochNs = Q(GetEpochNanosecondsFor(timeZone, isoDateTime, 'compatible'));
|
||||
}
|
||||
return X(CreateTemporalZonedDateTime(epochNs, timeZone, plainDate.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tostring */
|
||||
function* PlainDateProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions));
|
||||
return Value(TemporalDateToString(plainDate, showCalendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tolocalestring */
|
||||
function PlainDateProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Value(TemporalDateToString(plainDate, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tojson */
|
||||
function PlainDateProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDate = Q(thisTemporalDateValue(thisValue));
|
||||
return Value(TemporalDateToString(plainDate, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.valueof */
|
||||
function PlainDateProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
Q(thisTemporalDateValue(thisValue));
|
||||
return Throw.TypeError('Temporal.PlainDate cannot be converted to primitive value. If you are comparing two Temporal.PlainDate objects with > or <, use Temporal.PlainDate.compare() instead.');
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainDatePrototype(realmRec: Realm) {
|
||||
const prototype = bootstrapPrototype(realmRec, [
|
||||
['calendarId', [PlainDateProto_calendarIdGetter]],
|
||||
['era', [PlainDateProto_eraGetter]],
|
||||
['eraYear', [PlainDateProto_eraYearGetter]],
|
||||
['year', [PlainDateProto_yearGetter]],
|
||||
['month', [PlainDateProto_monthGetter]],
|
||||
['monthCode', [PlainDateProto_monthCodeGetter]],
|
||||
['day', [PlainDateProto_dayGetter]],
|
||||
['dayOfWeek', [PlainDateProto_dayOfWeekGetter]],
|
||||
['dayOfYear', [PlainDateProto_dayOfYearGetter]],
|
||||
['weekOfYear', [PlainDateProto_weekOfYearGetter]],
|
||||
['yearOfWeek', [PlainDateProto_yearOfWeekGetter]],
|
||||
['daysInWeek', [PlainDateProto_daysInWeekGetter]],
|
||||
['daysInMonth', [PlainDateProto_daysInMonthGetter]],
|
||||
['daysInYear', [PlainDateProto_daysInYearGetter]],
|
||||
['monthsInYear', [PlainDateProto_monthsInYearGetter]],
|
||||
['inLeapYear', [PlainDateProto_inLeapYearGetter]],
|
||||
['toPlainYearMonth', PlainDateProto_toPlainYearMonth, 0],
|
||||
['toPlainMonthDay', PlainDateProto_toPlainMonthDay, 0],
|
||||
['add', PlainDateProto_add, 1],
|
||||
['subtract', PlainDateProto_subtract, 1],
|
||||
['with', PlainDateProto_with, 1],
|
||||
['withCalendar', PlainDateProto_withCalendar, 1],
|
||||
['until', PlainDateProto_until, 1],
|
||||
['since', PlainDateProto_since, 1],
|
||||
['equals', PlainDateProto_equals, 1],
|
||||
['toPlainDateTime', PlainDateProto_toPlainDateTime, 0],
|
||||
['toZonedDateTime', PlainDateProto_toZonedDateTime, 1],
|
||||
['toString', PlainDateProto_toString, 0],
|
||||
['toLocaleString', PlainDateProto_toLocaleString, 0],
|
||||
['toJSON', PlainDateProto_toJSON, 0],
|
||||
['valueOf', PlainDateProto_valueOf, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainDate');
|
||||
realmRec.Intrinsics['%Temporal.PlainDate.prototype%'] = prototype;
|
||||
return prototype;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { bootstrapConstructor } from '../bootstrap.mts';
|
||||
import {
|
||||
CanonicalizeCalendar,
|
||||
type CalendarType,
|
||||
} from '../../abstract-ops/temporal/calendar.mts';
|
||||
import { bootstrapTemporalPlainDateTimePrototype } from './PlainDateTimePrototype.mts';
|
||||
import type { ISODateRecord } from './PlainDate.mts';
|
||||
import {
|
||||
JSStringValue,
|
||||
Q,
|
||||
Throw,
|
||||
Value,
|
||||
type OrdinaryObject,
|
||||
type ValueEvaluator,
|
||||
type Realm,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
UndefinedValue,
|
||||
F,
|
||||
CombineISODateAndTimeRecord,
|
||||
CompareISODateTime,
|
||||
CreateISODateRecord,
|
||||
CreateTemporalDateTime,
|
||||
CreateTimeRecord,
|
||||
IsValidISODate,
|
||||
IsValidTime,
|
||||
ToIntegerWithTruncation,
|
||||
ToTemporalDateTime,
|
||||
type TimeRecord,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaindatetime-instances */
|
||||
export interface TemporalPlainDateTimeObject extends OrdinaryObject {
|
||||
readonly InitializedTemporalDateTime: never;
|
||||
readonly ISODateTime: ISODateTimeRecord;
|
||||
readonly Calendar: CalendarType;
|
||||
}
|
||||
export function isTemporalPlainDateTimeObject(o: Value): o is TemporalPlainDateTimeObject {
|
||||
return 'InitializedTemporalDateTime' in o;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-iso-date-time-records */
|
||||
export interface ISODateTimeRecord {
|
||||
readonly ISODate: ISODateRecord;
|
||||
readonly Time: TimeRecord;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime */
|
||||
function* PlainDateTimeConstructor([
|
||||
_isoYear = Value.undefined,
|
||||
_isoMonth = Value.undefined,
|
||||
_isoDay = Value.undefined,
|
||||
_hour = Value.undefined,
|
||||
_minute = Value.undefined,
|
||||
_second = Value.undefined,
|
||||
_millisecond = Value.undefined,
|
||||
_microsecond = Value.undefined,
|
||||
_nanosecond = Value.undefined,
|
||||
_calendar = Value.undefined,
|
||||
]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Temporal.PlainDateTime cannot be called without new');
|
||||
}
|
||||
const isoYear = Q(yield* ToIntegerWithTruncation(_isoYear));
|
||||
const isoMonth = Q(yield* ToIntegerWithTruncation(_isoMonth));
|
||||
const isoDay = Q(yield* ToIntegerWithTruncation(_isoDay));
|
||||
const hour = _hour instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_hour));
|
||||
const minute = _minute instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_minute));
|
||||
const second = _second instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_second));
|
||||
const millisecond = _millisecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_millisecond));
|
||||
const microsecond = _microsecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_microsecond));
|
||||
const nanosecond = _nanosecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_nanosecond));
|
||||
if (_calendar instanceof UndefinedValue) {
|
||||
_calendar = Value('iso8601');
|
||||
}
|
||||
if (!(_calendar instanceof JSStringValue)) {
|
||||
return Throw.TypeError('calendar is not a string');
|
||||
}
|
||||
const calendar = Q(CanonicalizeCalendar(_calendar.stringValue()));
|
||||
if (!IsValidISODate(isoYear, isoMonth, isoDay)) {
|
||||
return Throw.RangeError('$1-$2-$3 is not a valid date', isoYear, isoMonth, isoDay);
|
||||
}
|
||||
const isoDate = CreateISODateRecord(isoYear, isoMonth, isoDay);
|
||||
if (!IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond)) {
|
||||
return Throw.RangeError('Invalid time');
|
||||
}
|
||||
const time = CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond);
|
||||
const isoDateTime = CombineISODateAndTimeRecord(isoDate, time);
|
||||
return Q(yield* CreateTemporalDateTime(isoDateTime, calendar, NewTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.from */
|
||||
function* PlainDateTime_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* ToTemporalDateTime(item, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.compare */
|
||||
function* PlainDateTime_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const one = Q(yield* ToTemporalDateTime(_one));
|
||||
const two = Q(yield* ToTemporalDateTime(_two));
|
||||
return F(CompareISODateTime(one.ISODateTime, two.ISODateTime));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainDateTime(realmRec: Realm) {
|
||||
const prototype = bootstrapTemporalPlainDateTimePrototype(realmRec);
|
||||
|
||||
const constructor = bootstrapConstructor(realmRec, PlainDateTimeConstructor, 'PlainDateTime', 3, prototype, [
|
||||
['from', PlainDateTime_from, 1],
|
||||
['compare', PlainDateTime_compare, 2],
|
||||
]);
|
||||
realmRec.Intrinsics['%Temporal.PlainDateTime%'] = constructor;
|
||||
return constructor;
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import {
|
||||
GetOptionsObject, GetRoundingIncrementOption, GetRoundingModeOption, RoundingMode,
|
||||
} from '../../abstract-ops/temporal/addition.mts';
|
||||
import {
|
||||
GetTemporalFractionalSecondDigitsOption,
|
||||
GetTemporalShowCalendarNameOption,
|
||||
GetTemporalUnitValuedOption,
|
||||
GetTemporalOverflowOption,
|
||||
GetTemporalDisambiguationOption,
|
||||
IsPartialTemporalObject,
|
||||
ISODateToFields,
|
||||
MaximumTemporalDurationRoundingIncrement,
|
||||
TemporalUnit,
|
||||
ToSecondsStringPrecisionRecord,
|
||||
ValidateTemporalRoundingIncrement,
|
||||
ValidateTemporalUnitValue,
|
||||
type TimeUnit,
|
||||
__IsTimeUnit,
|
||||
} from '../../abstract-ops/temporal/temporal.mts';
|
||||
import {
|
||||
CalendarEquals, CalendarISOToDate, CalendarMergeFields, PrepareCalendarFields,
|
||||
ToTemporalCalendarIdentifier,
|
||||
} from '../../abstract-ops/temporal/calendar.mts';
|
||||
import {
|
||||
CombineISODateAndTimeRecord,
|
||||
CompareISODateTime,
|
||||
CreateTemporalDateTime,
|
||||
DifferenceTemporalPlainDateTime,
|
||||
AddDurationToDateTime,
|
||||
InterpretTemporalDateTimeFields,
|
||||
ISODateTimeToString,
|
||||
ISODateTimeWithinLimits,
|
||||
RoundISODateTime,
|
||||
ToTemporalDateTime,
|
||||
} from '../../abstract-ops/temporal/plain-date-time.mts';
|
||||
import { ToTimeRecordOrMidnight, CreateTemporalTime } from '../../abstract-ops/temporal/plain-time.mts';
|
||||
import { CreateTemporalDate } from '../../abstract-ops/temporal/plain-date.mts';
|
||||
import { CreateTemporalZonedDateTime } from '../../abstract-ops/temporal/zoned-datetime.mts';
|
||||
import { GetEpochNanosecondsFor, ToTemporalTimeZoneIdentifier } from '../../abstract-ops/temporal/time-zone.mts';
|
||||
import type { TimeZoneIdentifier } from '../../abstract-ops/temporal/addition.mts';
|
||||
import type { TemporalPlainDateTimeObject } from './PlainDateTime.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateDataPropertyOrThrow,
|
||||
F,
|
||||
JSStringValue,
|
||||
ObjectValue,
|
||||
OrdinaryObjectCreate,
|
||||
Q,
|
||||
RequireInternalSlot,
|
||||
Throw,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
X,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type PlainCompletion,
|
||||
type Realm,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
function thisTemporalDateTimeValue(value: Value): PlainCompletion<TemporalPlainDateTimeObject> {
|
||||
Q(RequireInternalSlot(value, 'InitializedTemporalDateTime'));
|
||||
return value as TemporalPlainDateTimeObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.calendarid */
|
||||
function PlainDateTimeProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Value(plainDateTime.Calendar);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.era */
|
||||
function PlainDateTimeProto_eraGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).Era);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.erayear */
|
||||
function PlainDateTimeProto_eraYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
const result = CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).EraYear;
|
||||
return result === undefined ? Value.undefined : F(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.year */
|
||||
function PlainDateTimeProto_yearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).Year);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.month */
|
||||
function PlainDateTimeProto_monthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).Month);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.monthcode */
|
||||
function PlainDateTimeProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).MonthCode);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.day */
|
||||
function PlainDateTimeProto_dayGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).Day);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.hour */
|
||||
function PlainDateTimeProto_hourGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(plainDateTime.ISODateTime.Time.Hour);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.minute */
|
||||
function PlainDateTimeProto_minuteGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(plainDateTime.ISODateTime.Time.Minute);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.second */
|
||||
function PlainDateTimeProto_secondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(plainDateTime.ISODateTime.Time.Second);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.millisecond */
|
||||
function PlainDateTimeProto_millisecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(plainDateTime.ISODateTime.Time.Millisecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.microsecond */
|
||||
function PlainDateTimeProto_microsecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(plainDateTime.ISODateTime.Time.Microsecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.nanosecond */
|
||||
function PlainDateTimeProto_nanosecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(plainDateTime.ISODateTime.Time.Nanosecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.dayofweek */
|
||||
function PlainDateTimeProto_dayOfWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DayOfWeek);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.dayofyear */
|
||||
function PlainDateTimeProto_dayOfYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DayOfYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.weekofyear */
|
||||
function PlainDateTimeProto_weekOfYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
const result = CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).WeekOfYear.Week;
|
||||
return result === undefined ? Value.undefined : F(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.yearofweek */
|
||||
function PlainDateTimeProto_yearOfWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
const result = CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).WeekOfYear.Year;
|
||||
return result === undefined ? Value.undefined : F(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.daysinweek */
|
||||
function PlainDateTimeProto_daysInWeekGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DaysInWeek);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.daysinmonth */
|
||||
function PlainDateTimeProto_daysInMonthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DaysInMonth);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.daysinyear */
|
||||
function PlainDateTimeProto_daysInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).DaysInYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.monthsinyear */
|
||||
function PlainDateTimeProto_monthsInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return F(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).MonthsInYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaindatetime.prototype.inleapyear */
|
||||
function PlainDateTimeProto_inLeapYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainDateTime.Calendar, plainDateTime.ISODateTime.ISODate).InLeapYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.with */
|
||||
function* PlainDateTimeProto_with([temporalDateTimeLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
if (!Q(yield* IsPartialTemporalObject(temporalDateTimeLike))) {
|
||||
return Throw.TypeError('$1 is not a partial Temporal object', temporalDateTimeLike);
|
||||
}
|
||||
const calendar = plainDateTime.Calendar;
|
||||
let fields = ISODateToFields(calendar, plainDateTime.ISODateTime.ISODate, 'date');
|
||||
fields.Hour = plainDateTime.ISODateTime.Time.Hour;
|
||||
fields.Minute = plainDateTime.ISODateTime.Time.Minute;
|
||||
fields.Second = plainDateTime.ISODateTime.Time.Second;
|
||||
fields.Millisecond = plainDateTime.ISODateTime.Time.Millisecond;
|
||||
fields.Microsecond = plainDateTime.ISODateTime.Time.Microsecond;
|
||||
fields.Nanosecond = plainDateTime.ISODateTime.Time.Nanosecond;
|
||||
const partialDateTime = Q(yield* PrepareCalendarFields(calendar, temporalDateTimeLike as ObjectValue, ['year', 'month', 'month-code', 'day'], ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'], 'partial'));
|
||||
fields = CalendarMergeFields(calendar, fields, partialDateTime);
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions));
|
||||
const result = Q(yield* InterpretTemporalDateTimeFields(calendar, fields, overflow));
|
||||
return Q(yield* CreateTemporalDateTime(result, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.withplaintime */
|
||||
function* PlainDateTimeProto_withPlainTime([plainTimeLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
const time = Q(yield* ToTimeRecordOrMidnight(plainTimeLike));
|
||||
const isoDateTime = CombineISODateAndTimeRecord(plainDateTime.ISODateTime.ISODate, time);
|
||||
return Q(yield* CreateTemporalDateTime(isoDateTime, plainDateTime.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.withcalendar */
|
||||
function PlainDateTimeProto_withCalendar([calendarLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
const calendar = Q(ToTemporalCalendarIdentifier(calendarLike));
|
||||
return X(CreateTemporalDateTime(plainDateTime.ISODateTime, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.add */
|
||||
function* PlainDateTimeProto_add([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Q(yield* AddDurationToDateTime('add', plainDateTime, temporalDurationLike, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.subtract */
|
||||
function* PlainDateTimeProto_subtract([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Q(yield* AddDurationToDateTime('subtract', plainDateTime, temporalDurationLike, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.until */
|
||||
function* PlainDateTimeProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalPlainDateTime('until', plainDateTime, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.since */
|
||||
function* PlainDateTimeProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalPlainDateTime('since', plainDateTime, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round */
|
||||
function* PlainDateTimeProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
if (roundTo instanceof UndefinedValue) {
|
||||
return Throw.TypeError('roundTo is required');
|
||||
}
|
||||
if (roundTo instanceof JSStringValue) {
|
||||
const paramString = roundTo;
|
||||
roundTo = OrdinaryObjectCreate(Value.null);
|
||||
X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString));
|
||||
} else {
|
||||
roundTo = Q(GetOptionsObject(roundTo));
|
||||
}
|
||||
const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'required'));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time', [TemporalUnit.Day]));
|
||||
let maximum: number;
|
||||
let inclusive: boolean;
|
||||
if (smallestUnit === TemporalUnit.Day) {
|
||||
maximum = 1;
|
||||
inclusive = true;
|
||||
} else {
|
||||
const maximum2 = MaximumTemporalDurationRoundingIncrement(smallestUnit as TemporalUnit);
|
||||
Assert(maximum2 !== 'unset');
|
||||
maximum = maximum2;
|
||||
inclusive = false;
|
||||
}
|
||||
Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, inclusive));
|
||||
if (smallestUnit === TemporalUnit.Nanosecond && roundingIncrement === 1) {
|
||||
return X(CreateTemporalDateTime(plainDateTime.ISODateTime, plainDateTime.Calendar));
|
||||
}
|
||||
const result = RoundISODateTime(
|
||||
plainDateTime.ISODateTime,
|
||||
roundingIncrement,
|
||||
smallestUnit as TimeUnit,
|
||||
roundingMode,
|
||||
);
|
||||
return Q(yield* CreateTemporalDateTime(result, plainDateTime.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.equals */
|
||||
function* PlainDateTimeProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
const other = Q(yield* ToTemporalDateTime(_other));
|
||||
if (CompareISODateTime(plainDateTime.ISODateTime, other.ISODateTime) !== 0) {
|
||||
return Value.false;
|
||||
}
|
||||
return Value(CalendarEquals(plainDateTime.Calendar, other.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tostring */
|
||||
function* PlainDateTimeProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions));
|
||||
const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, 3));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset'));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time'));
|
||||
if (smallestUnit === TemporalUnit.Hour) {
|
||||
return Throw.RangeError('smallestUnit cannot be hour');
|
||||
}
|
||||
Assert(smallestUnit !== 'auto' && (smallestUnit === 'unset' || __IsTimeUnit(smallestUnit))); // TODO(temporal): not in spec
|
||||
const precision = ToSecondsStringPrecisionRecord(smallestUnit, digits);
|
||||
const result = RoundISODateTime(plainDateTime.ISODateTime, precision.Increment, precision.Unit, roundingMode);
|
||||
if (!ISODateTimeWithinLimits(result)) {
|
||||
return Throw.RangeError('DateTime outside of range');
|
||||
}
|
||||
return Value(ISODateTimeToString(result, plainDateTime.Calendar, precision.Precision, showCalendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tolocalestring */
|
||||
function PlainDateTimeProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Value(ISODateTimeToString(plainDateTime.ISODateTime, plainDateTime.Calendar, 'auto', 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tojson */
|
||||
function PlainDateTimeProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Value(ISODateTimeToString(plainDateTime.ISODateTime, plainDateTime.Calendar, 'auto', 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.valueof */
|
||||
function PlainDateTimeProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
Q(thisTemporalDateTimeValue(thisValue));
|
||||
return Throw.TypeError('Temporal.PlainDateTime cannot be converted to primitive value. If you are comparing two Temporal.PlainDateTime objects with > or <, use Temporal.PlainDateTime.compare() instead.');
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tozoneddatetime */
|
||||
function* PlainDateTimeProto_toZonedDateTime([temporalTimeZoneLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
const timeZone = Q(ToTemporalTimeZoneIdentifier(temporalTimeZoneLike)) as TimeZoneIdentifier;
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const disambiguation = Q(yield* GetTemporalDisambiguationOption(resolvedOptions));
|
||||
const epochNs = Q(GetEpochNanosecondsFor(timeZone, plainDateTime.ISODateTime, disambiguation));
|
||||
return X(CreateTemporalZonedDateTime(epochNs, timeZone, plainDateTime.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.toplaindate */
|
||||
function PlainDateTimeProto_toPlainDate(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return X(CreateTemporalDate(plainDateTime.ISODateTime.ISODate, plainDateTime.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.toplaintime */
|
||||
function PlainDateTimeProto_toPlainTime(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainDateTime = Q(thisTemporalDateTimeValue(thisValue));
|
||||
return X(CreateTemporalTime(plainDateTime.ISODateTime.Time));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainDateTimePrototype(realmRec: Realm) {
|
||||
const prototype = bootstrapPrototype(realmRec, [
|
||||
['calendarId', [PlainDateTimeProto_calendarIdGetter]],
|
||||
['era', [PlainDateTimeProto_eraGetter]],
|
||||
['eraYear', [PlainDateTimeProto_eraYearGetter]],
|
||||
['year', [PlainDateTimeProto_yearGetter]],
|
||||
['month', [PlainDateTimeProto_monthGetter]],
|
||||
['monthCode', [PlainDateTimeProto_monthCodeGetter]],
|
||||
['day', [PlainDateTimeProto_dayGetter]],
|
||||
['hour', [PlainDateTimeProto_hourGetter]],
|
||||
['minute', [PlainDateTimeProto_minuteGetter]],
|
||||
['second', [PlainDateTimeProto_secondGetter]],
|
||||
['millisecond', [PlainDateTimeProto_millisecondGetter]],
|
||||
['microsecond', [PlainDateTimeProto_microsecondGetter]],
|
||||
['nanosecond', [PlainDateTimeProto_nanosecondGetter]],
|
||||
['dayOfWeek', [PlainDateTimeProto_dayOfWeekGetter]],
|
||||
['dayOfYear', [PlainDateTimeProto_dayOfYearGetter]],
|
||||
['weekOfYear', [PlainDateTimeProto_weekOfYearGetter]],
|
||||
['yearOfWeek', [PlainDateTimeProto_yearOfWeekGetter]],
|
||||
['daysInWeek', [PlainDateTimeProto_daysInWeekGetter]],
|
||||
['daysInMonth', [PlainDateTimeProto_daysInMonthGetter]],
|
||||
['daysInYear', [PlainDateTimeProto_daysInYearGetter]],
|
||||
['monthsInYear', [PlainDateTimeProto_monthsInYearGetter]],
|
||||
['inLeapYear', [PlainDateTimeProto_inLeapYearGetter]],
|
||||
['with', PlainDateTimeProto_with, 1],
|
||||
['withPlainTime', PlainDateTimeProto_withPlainTime, 0],
|
||||
['withCalendar', PlainDateTimeProto_withCalendar, 1],
|
||||
['add', PlainDateTimeProto_add, 1],
|
||||
['subtract', PlainDateTimeProto_subtract, 1],
|
||||
['until', PlainDateTimeProto_until, 1],
|
||||
['since', PlainDateTimeProto_since, 1],
|
||||
['round', PlainDateTimeProto_round, 1],
|
||||
['equals', PlainDateTimeProto_equals, 1],
|
||||
['toString', PlainDateTimeProto_toString, 0],
|
||||
['toLocaleString', PlainDateTimeProto_toLocaleString, 0],
|
||||
['toJSON', PlainDateTimeProto_toJSON, 0],
|
||||
['valueOf', PlainDateTimeProto_valueOf, 0],
|
||||
['toZonedDateTime', PlainDateTimeProto_toZonedDateTime, 1],
|
||||
['toPlainDate', PlainDateTimeProto_toPlainDate, 0],
|
||||
['toPlainTime', PlainDateTimeProto_toPlainTime, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainDateTime');
|
||||
realmRec.Intrinsics['%Temporal.PlainDateTime.prototype%'] = prototype;
|
||||
return prototype;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { bootstrapConstructor } from '../bootstrap.mts';
|
||||
import {
|
||||
CanonicalizeCalendar,
|
||||
type CalendarType,
|
||||
} from '../../abstract-ops/temporal/calendar.mts';
|
||||
import { bootstrapTemporalPlainMonthDayPrototype } from './PlainMonthDayPrototype.mts';
|
||||
import type { ISODateRecord } from './PlainDate.mts';
|
||||
import {
|
||||
JSStringValue,
|
||||
Q,
|
||||
Throw,
|
||||
Value,
|
||||
UndefinedValue,
|
||||
F,
|
||||
ToIntegerWithTruncation,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type Realm,
|
||||
type OrdinaryObject,
|
||||
type ValueEvaluator,
|
||||
CreateISODateRecord,
|
||||
CreateTemporalMonthDay,
|
||||
IsValidISODate,
|
||||
ToTemporalMonthDay,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plainmonthday-instances */
|
||||
export interface TemporalPlainMonthDayObject extends OrdinaryObject {
|
||||
readonly InitializedTemporalMonthDay: never;
|
||||
readonly ISODate: ISODateRecord;
|
||||
readonly Calendar: CalendarType;
|
||||
}
|
||||
|
||||
export function isTemporalPlainMonthDayObject(o: Value): o is TemporalPlainMonthDayObject {
|
||||
return 'InitializedTemporalMonthDay' in o;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday */
|
||||
function* PlainMonthDayConstructor([
|
||||
isoMonth = Value.undefined,
|
||||
isoDay = Value.undefined,
|
||||
_calendar = Value.undefined,
|
||||
referenceISOYear = Value.undefined,
|
||||
]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Temporal.PlainMonthDay cannot be called without new');
|
||||
}
|
||||
if (referenceISOYear instanceof UndefinedValue) {
|
||||
referenceISOYear = F(1972);
|
||||
}
|
||||
const m = Q(yield* ToIntegerWithTruncation(isoMonth));
|
||||
const d = Q(yield* ToIntegerWithTruncation(isoDay));
|
||||
if (_calendar instanceof UndefinedValue) {
|
||||
_calendar = Value('iso8601');
|
||||
}
|
||||
if (!(_calendar instanceof JSStringValue)) {
|
||||
return Throw.TypeError('calendar is not a string');
|
||||
}
|
||||
const calendar = Q(CanonicalizeCalendar(_calendar.stringValue()));
|
||||
const y = Q(yield* ToIntegerWithTruncation(referenceISOYear));
|
||||
if (!IsValidISODate(y, m, d)) {
|
||||
return Throw.RangeError('$1-$2-$3 is not a valid date', y, m, d);
|
||||
}
|
||||
const isoDate = CreateISODateRecord(y, m, d);
|
||||
return Q(yield* CreateTemporalMonthDay(isoDate, calendar, NewTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.from */
|
||||
function* PlainMonthDay_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* ToTemporalMonthDay(item, options));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainMonthDay(realmRec: Realm) {
|
||||
const prototype = bootstrapTemporalPlainMonthDayPrototype(realmRec);
|
||||
|
||||
const constructor = bootstrapConstructor(realmRec, PlainMonthDayConstructor, 'PlainMonthDay', 2, prototype, [
|
||||
['from', PlainMonthDay_from, 1],
|
||||
]);
|
||||
realmRec.Intrinsics['%Temporal.PlainMonthDay%'] = constructor;
|
||||
return constructor;
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import { GetOptionsObject } from '../../abstract-ops/temporal/addition.mts';
|
||||
import {
|
||||
GetTemporalOverflowOption,
|
||||
GetTemporalShowCalendarNameOption,
|
||||
IsPartialTemporalObject,
|
||||
ISODateToFields,
|
||||
} from '../../abstract-ops/temporal/temporal.mts';
|
||||
import {
|
||||
CalendarDateFromFields,
|
||||
CalendarEquals,
|
||||
CalendarISOToDate,
|
||||
CalendarMergeFields,
|
||||
CalendarMonthDayFromFields,
|
||||
PrepareCalendarFields,
|
||||
} from '../../abstract-ops/temporal/calendar.mts';
|
||||
import { CompareISODate, CreateTemporalDate } from '../../abstract-ops/temporal/plain-date.mts';
|
||||
import { CreateTemporalMonthDay, TemporalMonthDayToString, ToTemporalMonthDay } from '../../abstract-ops/temporal/plain-month-day.mts';
|
||||
import type { TemporalPlainMonthDayObject } from './PlainMonthDay.mts';
|
||||
import {
|
||||
F,
|
||||
ObjectValue,
|
||||
Q,
|
||||
RequireInternalSlot,
|
||||
Throw,
|
||||
Value,
|
||||
X,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type PlainCompletion,
|
||||
type Realm,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
function thisTemporalMonthDayValue(value: Value): PlainCompletion<TemporalPlainMonthDayObject> {
|
||||
Q(RequireInternalSlot(value, 'InitializedTemporalMonthDay'));
|
||||
return value as TemporalPlainMonthDayObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.calendarid */
|
||||
function PlainMonthDayProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
return Value(plainMonthDay.Calendar);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.monthcode */
|
||||
function PlainMonthDayProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainMonthDay.Calendar, plainMonthDay.ISODate).MonthCode);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainmonthday.prototype.day */
|
||||
function PlainMonthDayProto_dayGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
return F(CalendarISOToDate(plainMonthDay.Calendar, plainMonthDay.ISODate).Day);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.with */
|
||||
function* PlainMonthDayProto_with([temporalMonthDayLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
if (!Q(yield* IsPartialTemporalObject(temporalMonthDayLike))) {
|
||||
return Throw.TypeError('$1 is not a partial Temporal object', temporalMonthDayLike);
|
||||
}
|
||||
const calendar = plainMonthDay.Calendar;
|
||||
let fields = ISODateToFields(calendar, plainMonthDay.ISODate, 'month-day');
|
||||
const partialMonthDay = Q(yield* PrepareCalendarFields(calendar, temporalMonthDayLike as ObjectValue, ['year', 'month', 'month-code', 'day'], [], 'partial'));
|
||||
fields = CalendarMergeFields(calendar, fields, partialMonthDay);
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions));
|
||||
const isoDate = Q(yield* CalendarMonthDayFromFields(calendar, fields, overflow));
|
||||
return X(CreateTemporalMonthDay(isoDate, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.equals */
|
||||
function* PlainMonthDayProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
const other = Q(yield* ToTemporalMonthDay(_other));
|
||||
if (CompareISODate(plainMonthDay.ISODate, other.ISODate) !== 0) {
|
||||
return Value.false;
|
||||
}
|
||||
return Value(CalendarEquals(plainMonthDay.Calendar, other.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tostring */
|
||||
function* PlainMonthDayProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions));
|
||||
return Value(TemporalMonthDayToString(plainMonthDay, showCalendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring */
|
||||
function PlainMonthDayProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
return Value(TemporalMonthDayToString(plainMonthDay, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tojson */
|
||||
function PlainMonthDayProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
return Value(TemporalMonthDayToString(plainMonthDay, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.valueof */
|
||||
function PlainMonthDayProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
Q(thisTemporalMonthDayValue(thisValue));
|
||||
return Throw.TypeError('Temporal.PlainMonthDay cannot be converted to primitive value. If you are comparing two Temporal.PlainMonthDay objects with > or <, use Temporal.PlainMonthDay.compare() instead.');
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.toplaindate */
|
||||
function* PlainMonthDayProto_toPlainDate([item = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainMonthDay = Q(thisTemporalMonthDayValue(thisValue));
|
||||
if (!(item instanceof ObjectValue)) {
|
||||
return Throw.TypeError('$1 is not an object', item);
|
||||
}
|
||||
const calendar = plainMonthDay.Calendar;
|
||||
const fields = ISODateToFields(calendar, plainMonthDay.ISODate, 'month-day');
|
||||
const inputFields = Q(yield* PrepareCalendarFields(calendar, item, ['year'], [], []));
|
||||
const mergedFields = CalendarMergeFields(calendar, fields, inputFields);
|
||||
const isoDate = Q(yield* CalendarDateFromFields(calendar, mergedFields, 'constrain'));
|
||||
return X(CreateTemporalDate(isoDate, calendar));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainMonthDayPrototype(realmRec: Realm) {
|
||||
const prototype = bootstrapPrototype(realmRec, [
|
||||
['calendarId', [PlainMonthDayProto_calendarIdGetter]],
|
||||
['monthCode', [PlainMonthDayProto_monthCodeGetter]],
|
||||
['day', [PlainMonthDayProto_dayGetter]],
|
||||
['with', PlainMonthDayProto_with, 1],
|
||||
['equals', PlainMonthDayProto_equals, 1],
|
||||
['toString', PlainMonthDayProto_toString, 0],
|
||||
['toLocaleString', PlainMonthDayProto_toLocaleString, 0],
|
||||
['toJSON', PlainMonthDayProto_toJSON, 0],
|
||||
['valueOf', PlainMonthDayProto_valueOf, 0],
|
||||
['toPlainDate', PlainMonthDayProto_toPlainDate, 1],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainMonthDay');
|
||||
realmRec.Intrinsics['%Temporal.PlainMonthDay.prototype%'] = prototype;
|
||||
return prototype;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { bootstrapConstructor } from '../bootstrap.mts';
|
||||
import {
|
||||
ToIntegerWithTruncation,
|
||||
} from '../../abstract-ops/temporal/temporal.mts';
|
||||
import { bootstrapTemporalPlainTimePrototype } from './PlainTimePrototype.mts';
|
||||
import {
|
||||
Q, Throw, UndefinedValue, Value, type OrdinaryObject, type ValueEvaluator,
|
||||
type Realm,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
F,
|
||||
CompareTimeRecord,
|
||||
CreateTemporalTime,
|
||||
CreateTimeRecord,
|
||||
IsValidTime,
|
||||
ToTemporalTime,
|
||||
type TimeRecord,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaintime-instances */
|
||||
export interface TemporalPlainTimeObject extends OrdinaryObject {
|
||||
readonly InitializedTemporalTime: never;
|
||||
readonly Time: TimeRecord;
|
||||
}
|
||||
|
||||
export function isTemporalPlainTimeObject(value: Value): value is TemporalPlainTimeObject {
|
||||
return 'InitializedTemporalTime' in value;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime */
|
||||
function* PlainTimeConstructor([
|
||||
_hour = Value.undefined,
|
||||
_minute = Value.undefined,
|
||||
_second = Value.undefined,
|
||||
_millisecond = Value.undefined,
|
||||
_microsecond = Value.undefined,
|
||||
_nanosecond = Value.undefined,
|
||||
]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Temporal.PlainTime cannot be called without new');
|
||||
}
|
||||
const hour = _hour instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_hour));
|
||||
const minute = _minute instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_minute));
|
||||
const second = _second instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_second));
|
||||
const millisecond = _millisecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_millisecond));
|
||||
const microsecond = _microsecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_microsecond));
|
||||
const nanosecond = _nanosecond instanceof UndefinedValue ? 0 : Q(yield* ToIntegerWithTruncation(_nanosecond));
|
||||
if (!IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond)) {
|
||||
return Throw.RangeError('Invalid time');
|
||||
}
|
||||
const time = CreateTimeRecord(hour, minute, second, millisecond, microsecond, nanosecond);
|
||||
return Q(yield* CreateTemporalTime(time, NewTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.from */
|
||||
function* PlainTime_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* ToTemporalTime(item, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.compare */
|
||||
function* PlainTime_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const one = Q(yield* ToTemporalTime(_one));
|
||||
const two = Q(yield* ToTemporalTime(_two));
|
||||
return F(CompareTimeRecord(one.Time, two.Time));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainTime(realmRec: Realm) {
|
||||
const prototype = bootstrapTemporalPlainTimePrototype(realmRec);
|
||||
|
||||
const constructor = bootstrapConstructor(realmRec, PlainTimeConstructor, 'PlainTime', 0, prototype, [
|
||||
['from', PlainTime_from, 1],
|
||||
['compare', PlainTime_compare, 2],
|
||||
]);
|
||||
realmRec.Intrinsics['%Temporal.PlainTime%'] = constructor;
|
||||
return constructor;
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import {
|
||||
GetOptionsObject, GetRoundingIncrementOption, GetRoundingModeOption, RoundingMode,
|
||||
} from '../../abstract-ops/temporal/addition.mts';
|
||||
import {
|
||||
GetTemporalFractionalSecondDigitsOption,
|
||||
GetTemporalOverflowOption,
|
||||
GetTemporalUnitValuedOption,
|
||||
IsPartialTemporalObject,
|
||||
MaximumTemporalDurationRoundingIncrement,
|
||||
TemporalUnit,
|
||||
ToSecondsStringPrecisionRecord,
|
||||
ValidateTemporalRoundingIncrement,
|
||||
ValidateTemporalUnitValue,
|
||||
type TimeUnit,
|
||||
} from '../../abstract-ops/temporal/temporal.mts';
|
||||
import {
|
||||
AddDurationToTime,
|
||||
CompareTimeRecord,
|
||||
CreateTemporalTime,
|
||||
DifferenceTemporalPlainTime,
|
||||
RegulateTime,
|
||||
RoundTime,
|
||||
TimeRecordToString,
|
||||
ToTemporalTime,
|
||||
ToTemporalTimeRecord,
|
||||
} from '../../abstract-ops/temporal/plain-time.mts';
|
||||
import type { TemporalPlainTimeObject } from './PlainTime.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateDataPropertyOrThrow,
|
||||
F,
|
||||
JSStringValue,
|
||||
OrdinaryObjectCreate,
|
||||
Q,
|
||||
RequireInternalSlot,
|
||||
Throw,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
X,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type PlainCompletion,
|
||||
type Realm,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
function thisTemporalTimeValue(value: Value): PlainCompletion<TemporalPlainTimeObject> {
|
||||
Q(RequireInternalSlot(value, 'InitializedTemporalTime'));
|
||||
return value as TemporalPlainTimeObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.hour */
|
||||
function PlainTimeProto_hourGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return F(plainTime.Time.Hour);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.minute */
|
||||
function PlainTimeProto_minuteGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return F(plainTime.Time.Minute);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.second */
|
||||
function PlainTimeProto_secondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return F(plainTime.Time.Second);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.millisecond */
|
||||
function PlainTimeProto_millisecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return F(plainTime.Time.Millisecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.microsecond */
|
||||
function PlainTimeProto_microsecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return F(plainTime.Time.Microsecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plaintime.prototype.nanosecond */
|
||||
function PlainTimeProto_nanosecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return F(plainTime.Time.Nanosecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.add */
|
||||
function* PlainTimeProto_add([temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return Q(yield* AddDurationToTime('add', plainTime, temporalDurationLike));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.subtract */
|
||||
function* PlainTimeProto_subtract([temporalDurationLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return Q(yield* AddDurationToTime('subtract', plainTime, temporalDurationLike));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.with */
|
||||
function* PlainTimeProto_with([temporalTimeLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
if (!Q(yield* IsPartialTemporalObject(temporalTimeLike))) {
|
||||
return Throw.TypeError('$1 is not a partial Temporal object', temporalTimeLike);
|
||||
}
|
||||
const partialTime = Q(yield* ToTemporalTimeRecord(temporalTimeLike as never, 'partial'));
|
||||
const hour = partialTime.Hour ?? plainTime.Time.Hour;
|
||||
const minute = partialTime.Minute ?? plainTime.Time.Minute;
|
||||
const second = partialTime.Second ?? plainTime.Time.Second;
|
||||
const millisecond = partialTime.Millisecond ?? plainTime.Time.Millisecond;
|
||||
const microsecond = partialTime.Microsecond ?? plainTime.Time.Microsecond;
|
||||
const nanosecond = partialTime.Nanosecond ?? plainTime.Time.Nanosecond;
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions));
|
||||
const result = Q(RegulateTime(hour, minute, second, millisecond, microsecond, nanosecond, overflow));
|
||||
return Q(yield* CreateTemporalTime(result));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.until */
|
||||
function* PlainTimeProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalPlainTime('until', plainTime, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.since */
|
||||
function* PlainTimeProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalPlainTime('since', plainTime, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round */
|
||||
function* PlainTimeProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
if (roundTo instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Options parameter is required');
|
||||
}
|
||||
if (roundTo instanceof JSStringValue) {
|
||||
const paramString = roundTo;
|
||||
roundTo = OrdinaryObjectCreate(Value.null);
|
||||
X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString));
|
||||
} else {
|
||||
roundTo = Q(GetOptionsObject(roundTo));
|
||||
}
|
||||
const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'required'));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time'));
|
||||
const maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit as TemporalUnit);
|
||||
Assert(maximum !== 'unset');
|
||||
Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false));
|
||||
const result = RoundTime(plainTime.Time, roundingIncrement, smallestUnit as TimeUnit | TemporalUnit.Day, roundingMode);
|
||||
return Q(yield* CreateTemporalTime(result));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.equals */
|
||||
function* PlainTimeProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
const other = Q(yield* ToTemporalTime(_other));
|
||||
return CompareTimeRecord(plainTime.Time, other.Time) === 0 ? Value.true : Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tostring */
|
||||
function* PlainTimeProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, 3));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset'));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time'));
|
||||
if (smallestUnit === TemporalUnit.Hour) {
|
||||
return Throw.RangeError('smallestUnit cannot be hour');
|
||||
}
|
||||
const precision = ToSecondsStringPrecisionRecord(
|
||||
smallestUnit as Exclude<TimeUnit, TemporalUnit.Hour> | 'unset',
|
||||
digits,
|
||||
);
|
||||
const roundResult = RoundTime(plainTime.Time, precision.Increment, precision.Unit, roundingMode);
|
||||
return Value(TimeRecordToString(roundResult, precision.Precision));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tolocalestring */
|
||||
function PlainTimeProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return Value(Q(TimeRecordToString(plainTime.Time, 'auto')));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tojson */
|
||||
function PlainTimeProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainTime = Q(thisTemporalTimeValue(thisValue));
|
||||
return Value(Q(TimeRecordToString(plainTime.Time, 'auto')));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.valueof */
|
||||
function PlainTimeProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
Q(thisTemporalTimeValue(thisValue));
|
||||
return Throw.TypeError('Temporal.PlainTime cannot be converted to primitive value. If you are comparing two Temporal.PlainTime objects with > or <, use Temporal.PlainTime.compare() instead.');
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainTimePrototype(realmRec: Realm) {
|
||||
const prototype = bootstrapPrototype(realmRec, [
|
||||
['hour', [PlainTimeProto_hourGetter]],
|
||||
['minute', [PlainTimeProto_minuteGetter]],
|
||||
['second', [PlainTimeProto_secondGetter]],
|
||||
['millisecond', [PlainTimeProto_millisecondGetter]],
|
||||
['microsecond', [PlainTimeProto_microsecondGetter]],
|
||||
['nanosecond', [PlainTimeProto_nanosecondGetter]],
|
||||
['add', PlainTimeProto_add, 1],
|
||||
['subtract', PlainTimeProto_subtract, 1],
|
||||
['with', PlainTimeProto_with, 1],
|
||||
['until', PlainTimeProto_until, 1],
|
||||
['since', PlainTimeProto_since, 1],
|
||||
['round', PlainTimeProto_round, 1],
|
||||
['equals', PlainTimeProto_equals, 1],
|
||||
['toString', PlainTimeProto_toString, 0],
|
||||
['toLocaleString', PlainTimeProto_toLocaleString, 0],
|
||||
['toJSON', PlainTimeProto_toJSON, 0],
|
||||
['valueOf', PlainTimeProto_valueOf, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainTime');
|
||||
realmRec.Intrinsics['%Temporal.PlainTime.prototype%'] = prototype;
|
||||
return prototype;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { bootstrapConstructor } from '../bootstrap.mts';
|
||||
import {
|
||||
CanonicalizeCalendar,
|
||||
type CalendarType,
|
||||
} from '../../abstract-ops/temporal/calendar.mts';
|
||||
import { bootstrapTemporalPlainYearMonthPrototype } from './PlainYearMonthPrototype.mts';
|
||||
import type { ISODateRecord } from './PlainDate.mts';
|
||||
import {
|
||||
JSStringValue,
|
||||
Q,
|
||||
Throw,
|
||||
Value,
|
||||
UndefinedValue,
|
||||
F,
|
||||
ToIntegerWithTruncation,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type Realm,
|
||||
type OrdinaryObject,
|
||||
type ValueEvaluator,
|
||||
CompareISODate,
|
||||
CreateISODateRecord,
|
||||
CreateTemporalYearMonth,
|
||||
IsValidISODate,
|
||||
ToTemporalYearMonth,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plainyearmonth-instances */
|
||||
export interface TemporalPlainYearMonthObject extends OrdinaryObject {
|
||||
readonly InitializedTemporalYearMonth: never;
|
||||
readonly ISODate: ISODateRecord;
|
||||
readonly Calendar: CalendarType;
|
||||
}
|
||||
|
||||
export function isTemporalPlainYearMonthObject(o: Value): o is TemporalPlainYearMonthObject {
|
||||
return 'InitializedTemporalYearMonth' in o;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal-iso-year-month-records */
|
||||
export interface ISOYearMonthRecord {
|
||||
readonly Year: number;
|
||||
readonly Month: number;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth */
|
||||
function* PlainYearMonthConstructor([
|
||||
isoYear = Value.undefined,
|
||||
isoMonth = Value.undefined,
|
||||
_calendar = Value.undefined,
|
||||
referenceISODay = Value.undefined,
|
||||
]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Temporal.PlainYearMonth cannot be called without new');
|
||||
}
|
||||
if (referenceISODay instanceof UndefinedValue) {
|
||||
referenceISODay = F(1);
|
||||
}
|
||||
const y = Q(yield* ToIntegerWithTruncation(isoYear));
|
||||
const m = Q(yield* ToIntegerWithTruncation(isoMonth));
|
||||
if (_calendar instanceof UndefinedValue) {
|
||||
_calendar = Value('iso8601');
|
||||
}
|
||||
if (!(_calendar instanceof JSStringValue)) {
|
||||
return Throw.TypeError('calendar is not a string');
|
||||
}
|
||||
const calendar = Q(CanonicalizeCalendar(_calendar.stringValue()));
|
||||
const ref = Q(yield* ToIntegerWithTruncation(referenceISODay));
|
||||
if (!IsValidISODate(y, m, ref)) {
|
||||
return Throw.RangeError('$1-$2-$3 is not a valid date', y, m, ref);
|
||||
}
|
||||
const isoDate = CreateISODateRecord(y, m, ref);
|
||||
return Q(yield* CreateTemporalYearMonth(isoDate, calendar, NewTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.from */
|
||||
function* PlainYearMonth_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* ToTemporalYearMonth(item, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.compare */
|
||||
function* PlainYearMonth_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const one = Q(yield* ToTemporalYearMonth(_one));
|
||||
const two = Q(yield* ToTemporalYearMonth(_two));
|
||||
return F(CompareISODate(one.ISODate, two.ISODate));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainYearMonth(realmRec: Realm) {
|
||||
const prototype = bootstrapTemporalPlainYearMonthPrototype(realmRec);
|
||||
|
||||
const constructor = bootstrapConstructor(realmRec, PlainYearMonthConstructor, 'PlainYearMonth', 2, prototype, [
|
||||
['from', PlainYearMonth_from, 1],
|
||||
['compare', PlainYearMonth_compare, 2],
|
||||
]);
|
||||
realmRec.Intrinsics['%Temporal.PlainYearMonth%'] = constructor;
|
||||
return constructor;
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import { GetOptionsObject } from '../../abstract-ops/temporal/addition.mts';
|
||||
import {
|
||||
GetTemporalOverflowOption,
|
||||
GetTemporalShowCalendarNameOption,
|
||||
IsPartialTemporalObject,
|
||||
ISODateToFields,
|
||||
} from '../../abstract-ops/temporal/temporal.mts';
|
||||
import {
|
||||
CalendarDateFromFields,
|
||||
CalendarEquals,
|
||||
CalendarISOToDate,
|
||||
CalendarMergeFields,
|
||||
CalendarYearMonthFromFields,
|
||||
PrepareCalendarFields,
|
||||
} from '../../abstract-ops/temporal/calendar.mts';
|
||||
import { CompareISODate, CreateTemporalDate } from '../../abstract-ops/temporal/plain-date.mts';
|
||||
import {
|
||||
AddDurationToYearMonth,
|
||||
CreateTemporalYearMonth,
|
||||
DifferenceTemporalPlainYearMonth,
|
||||
TemporalYearMonthToString,
|
||||
ToTemporalYearMonth,
|
||||
} from '../../abstract-ops/temporal/plain-year-month.mts';
|
||||
import type { TemporalPlainYearMonthObject } from './PlainYearMonth.mts';
|
||||
import {
|
||||
F,
|
||||
ObjectValue,
|
||||
Q,
|
||||
RequireInternalSlot,
|
||||
Throw,
|
||||
Value,
|
||||
X,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type PlainCompletion,
|
||||
type Realm,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
function thisTemporalYearMonthValue(value: Value): PlainCompletion<TemporalPlainYearMonthObject> {
|
||||
Q(RequireInternalSlot(value, 'InitializedTemporalYearMonth'));
|
||||
return value as TemporalPlainYearMonthObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.calendarid */
|
||||
function PlainYearMonthProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Value(plainYearMonth.Calendar);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.era */
|
||||
function PlainYearMonthProto_eraGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).Era);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.erayear */
|
||||
function PlainYearMonthProto_eraYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
const result = CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).EraYear;
|
||||
return result === undefined ? Value.undefined : F(result);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.year */
|
||||
function PlainYearMonthProto_yearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).Year);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.month */
|
||||
function PlainYearMonthProto_monthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).Month);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.monthcode */
|
||||
function PlainYearMonthProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).MonthCode);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.daysinyear */
|
||||
function PlainYearMonthProto_daysInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).DaysInYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.daysinmonth */
|
||||
function PlainYearMonthProto_daysInMonthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).DaysInMonth);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.monthsinyear */
|
||||
function PlainYearMonthProto_monthsInYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return F(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).MonthsInYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.plainyearmonth.prototype.inleapyear */
|
||||
function PlainYearMonthProto_inLeapYearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Value(CalendarISOToDate(plainYearMonth.Calendar, plainYearMonth.ISODate).InLeapYear);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.with */
|
||||
function* PlainYearMonthProto_with([temporalYearMonthLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
if (!Q(yield* IsPartialTemporalObject(temporalYearMonthLike))) {
|
||||
return Throw.TypeError('$1 is not a partial Temporal object', temporalYearMonthLike);
|
||||
}
|
||||
const calendar = plainYearMonth.Calendar;
|
||||
let fields = ISODateToFields(calendar, plainYearMonth.ISODate, 'year-month');
|
||||
const partialYearMonth = Q(yield* PrepareCalendarFields(calendar, temporalYearMonthLike as ObjectValue, ['year', 'month', 'month-code'], [], 'partial'));
|
||||
fields = CalendarMergeFields(calendar, fields, partialYearMonth);
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions));
|
||||
const isoDate = Q(yield* CalendarYearMonthFromFields(calendar, fields, overflow));
|
||||
return X(CreateTemporalYearMonth(isoDate, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.add */
|
||||
function* PlainYearMonthProto_add([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Q(yield* AddDurationToYearMonth('add', plainYearMonth, temporalDurationLike, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.subtract */
|
||||
function* PlainYearMonthProto_subtract([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Q(yield* AddDurationToYearMonth('subtract', plainYearMonth, temporalDurationLike, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.until */
|
||||
function* PlainYearMonthProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalPlainYearMonth('until', plainYearMonth, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.since */
|
||||
function* PlainYearMonthProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalPlainYearMonth('since', plainYearMonth, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.equals */
|
||||
function* PlainYearMonthProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
const other = Q(yield* ToTemporalYearMonth(_other));
|
||||
if (CompareISODate(plainYearMonth.ISODate, other.ISODate) !== 0) {
|
||||
return Value.false;
|
||||
}
|
||||
return Value(CalendarEquals(plainYearMonth.Calendar, other.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tostring */
|
||||
function* PlainYearMonthProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions));
|
||||
return Value(TemporalYearMonthToString(plainYearMonth, showCalendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tolocalestring */
|
||||
function PlainYearMonthProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Value(TemporalYearMonthToString(plainYearMonth, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tojson */
|
||||
function PlainYearMonthProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Value(TemporalYearMonthToString(plainYearMonth, 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.valueof */
|
||||
function PlainYearMonthProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
Q(thisTemporalYearMonthValue(thisValue));
|
||||
return Throw.TypeError('Temporal.PlainYearMonth cannot be converted to primitive value. If you are comparing two Temporal.PlainYearMonth objects with > or <, use Temporal.PlainYearMonth.compare() instead.');
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.toplaindate */
|
||||
function* PlainYearMonthProto_toPlainDate([item = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const plainYearMonth = Q(thisTemporalYearMonthValue(thisValue));
|
||||
if (!(item instanceof ObjectValue)) {
|
||||
return Throw.TypeError('$1 is not an object', item);
|
||||
}
|
||||
const calendar = plainYearMonth.Calendar;
|
||||
const fields = ISODateToFields(calendar, plainYearMonth.ISODate, 'year-month');
|
||||
const inputFields = Q(yield* PrepareCalendarFields(calendar, item, ['day'], [], []));
|
||||
const mergedFields = CalendarMergeFields(calendar, fields, inputFields);
|
||||
const isoDate = Q(yield* CalendarDateFromFields(calendar, mergedFields, 'constrain'));
|
||||
return X(CreateTemporalDate(isoDate, calendar));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalPlainYearMonthPrototype(realmRec: Realm) {
|
||||
const prototype = bootstrapPrototype(realmRec, [
|
||||
['calendarId', [PlainYearMonthProto_calendarIdGetter]],
|
||||
['era', [PlainYearMonthProto_eraGetter]],
|
||||
['eraYear', [PlainYearMonthProto_eraYearGetter]],
|
||||
['year', [PlainYearMonthProto_yearGetter]],
|
||||
['month', [PlainYearMonthProto_monthGetter]],
|
||||
['monthCode', [PlainYearMonthProto_monthCodeGetter]],
|
||||
['daysInYear', [PlainYearMonthProto_daysInYearGetter]],
|
||||
['daysInMonth', [PlainYearMonthProto_daysInMonthGetter]],
|
||||
['monthsInYear', [PlainYearMonthProto_monthsInYearGetter]],
|
||||
['inLeapYear', [PlainYearMonthProto_inLeapYearGetter]],
|
||||
['with', PlainYearMonthProto_with, 1],
|
||||
['add', PlainYearMonthProto_add, 1],
|
||||
['subtract', PlainYearMonthProto_subtract, 1],
|
||||
['until', PlainYearMonthProto_until, 1],
|
||||
['since', PlainYearMonthProto_since, 1],
|
||||
['equals', PlainYearMonthProto_equals, 1],
|
||||
['toString', PlainYearMonthProto_toString, 0],
|
||||
['toLocaleString', PlainYearMonthProto_toLocaleString, 0],
|
||||
['toJSON', PlainYearMonthProto_toJSON, 0],
|
||||
['valueOf', PlainYearMonthProto_valueOf, 0],
|
||||
['toPlainDate', PlainYearMonthProto_toPlainDate, 1],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.PlainYearMonth');
|
||||
realmRec.Intrinsics['%Temporal.PlainYearMonth.prototype%'] = prototype;
|
||||
return prototype;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Using spec: 87c74ec2ae7d55cf10c58674478784d09f323ff1
|
||||
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import { bootstrapTemporalDuration } from './Duration.mts';
|
||||
import { bootstrapTemporalInstant } from './Instant.mts';
|
||||
import { bootstrapTemporalPlainDate } from './PlainDate.mts';
|
||||
import { bootstrapTemporalPlainDateTime } from './PlainDateTime.mts';
|
||||
import { bootstrapTemporalPlainMonthDay } from './PlainMonthDay.mts';
|
||||
import { bootstrapTemporalPlainTime } from './PlainTime.mts';
|
||||
import { bootstrapTemporalPlainYearMonth } from './PlainYearMonth.mts';
|
||||
import { bootstrapTemporalNow } from './Now.mts';
|
||||
import { bootstrapTemporalZonedDateTime } from './ZonedDateTime.mts';
|
||||
import { type Realm } from '#self';
|
||||
|
||||
export function bootstrapTemporal(realmRec: Realm) {
|
||||
const TemporalObject = bootstrapPrototype(realmRec, [
|
||||
['Duration', bootstrapTemporalDuration(realmRec)],
|
||||
['Instant', bootstrapTemporalInstant(realmRec)],
|
||||
['PlainDateTime', bootstrapTemporalPlainDateTime(realmRec)],
|
||||
['Now', bootstrapTemporalNow(realmRec)],
|
||||
['PlainDate', bootstrapTemporalPlainDate(realmRec)],
|
||||
['PlainTime', bootstrapTemporalPlainTime(realmRec)],
|
||||
['PlainYearMonth', bootstrapTemporalPlainYearMonth(realmRec)],
|
||||
['PlainMonthDay', bootstrapTemporalPlainMonthDay(realmRec)],
|
||||
['ZonedDateTime', bootstrapTemporalZonedDateTime(realmRec)],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal');
|
||||
|
||||
realmRec.Intrinsics['%Temporal%'] = TemporalObject;
|
||||
return TemporalObject;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { bootstrapConstructor } from '../bootstrap.mts';
|
||||
import {
|
||||
type TimeZoneIdentifier,
|
||||
} from '../../abstract-ops/temporal/addition.mts';
|
||||
import {
|
||||
FormatOffsetTimeZoneIdentifier,
|
||||
GetAvailableNamedTimeZoneIdentifier,
|
||||
} from '../../abstract-ops/temporal/time-zone.mts';
|
||||
import {
|
||||
CanonicalizeCalendar,
|
||||
type CalendarType,
|
||||
} from '../../abstract-ops/temporal/calendar.mts';
|
||||
import { CreateTemporalZonedDateTime, ToTemporalZonedDateTime } from '../../abstract-ops/temporal/zoned-datetime.mts';
|
||||
import { ParseTimeZoneIdentifier } from '../../parser/TemporalParser.mts';
|
||||
import { bootstrapTemporalZonedDateTimePrototype } from './ZonedDateTimePrototype.mts';
|
||||
import {
|
||||
JSStringValue,
|
||||
Value,
|
||||
Q,
|
||||
type OrdinaryObject,
|
||||
type ValueEvaluator,
|
||||
Throw,
|
||||
type Realm,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
UndefinedValue,
|
||||
F,
|
||||
ToBigInt,
|
||||
R,
|
||||
CompareEpochNanoseconds,
|
||||
IsValidEpochNanoseconds,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-properties-of-temporal-zoneddatetime-instances */
|
||||
export interface TemporalZonedDateTimeObject extends OrdinaryObject {
|
||||
readonly InitializedTemporalZonedDateTime: never;
|
||||
readonly EpochNanoseconds: bigint;
|
||||
readonly TimeZone: TimeZoneIdentifier;
|
||||
readonly Calendar: CalendarType;
|
||||
}
|
||||
export function isTemporalZonedDateTimeObject(o: Value): o is TemporalZonedDateTimeObject {
|
||||
return 'InitializedTemporalZonedDateTime' in o;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime */
|
||||
function* ZonedDateTimeConstructor([
|
||||
_epochNanoseconds = Value.undefined,
|
||||
_timeZone = Value.undefined,
|
||||
_calendar = Value.undefined,
|
||||
]: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return Throw.TypeError('Temporal.ZonedDateTime cannot be called without new');
|
||||
}
|
||||
const epochNanoseconds = R(Q(yield* ToBigInt(_epochNanoseconds)));
|
||||
if (!IsValidEpochNanoseconds(epochNanoseconds)) {
|
||||
return Throw.RangeError('$1 is not a valid epoch nanoseconds', epochNanoseconds);
|
||||
}
|
||||
if (!(_timeZone instanceof JSStringValue)) {
|
||||
return Throw.TypeError('timeZone is not a string');
|
||||
}
|
||||
const timeZoneParse = Q(ParseTimeZoneIdentifier(_timeZone.stringValue()));
|
||||
let timeZone;
|
||||
if (timeZoneParse.OffsetMinutes === undefined) {
|
||||
const identifierRecord = GetAvailableNamedTimeZoneIdentifier((timeZoneParse.Name || '') as TimeZoneIdentifier);
|
||||
if (identifierRecord === undefined) {
|
||||
return Throw.RangeError('invalid time zone identifier: $1', timeZoneParse.Name || '');
|
||||
}
|
||||
timeZone = identifierRecord.Identifier;
|
||||
} else {
|
||||
timeZone = FormatOffsetTimeZoneIdentifier(timeZoneParse.OffsetMinutes);
|
||||
}
|
||||
if (_calendar instanceof UndefinedValue) {
|
||||
_calendar = Value('iso8601');
|
||||
}
|
||||
if (!(_calendar instanceof JSStringValue)) {
|
||||
return Throw.TypeError('calendar is not a string');
|
||||
}
|
||||
const calendar = Q(CanonicalizeCalendar(_calendar.stringValue()));
|
||||
return Q(yield* CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar, NewTarget));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.from */
|
||||
function* ZonedDateTime_from([item = Value.undefined, options = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* ToTemporalZonedDateTime(item, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.compare */
|
||||
function* ZonedDateTime_compare([_one = Value.undefined, _two = Value.undefined]: Arguments): ValueEvaluator {
|
||||
const one = Q(yield* ToTemporalZonedDateTime(_one));
|
||||
const two = Q(yield* ToTemporalZonedDateTime(_two));
|
||||
return F(CompareEpochNanoseconds(one.EpochNanoseconds, two.EpochNanoseconds));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalZonedDateTime(realmRec: Realm) {
|
||||
const prototype = bootstrapTemporalZonedDateTimePrototype(realmRec);
|
||||
|
||||
const constructor = bootstrapConstructor(realmRec, ZonedDateTimeConstructor, 'ZonedDateTime', 2, prototype, [
|
||||
['from', ZonedDateTime_from, 1],
|
||||
['compare', ZonedDateTime_compare, 2],
|
||||
]);
|
||||
realmRec.Intrinsics['%Temporal.ZonedDateTime%'] = constructor;
|
||||
return constructor;
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
import { bootstrapPrototype } from '../bootstrap.mts';
|
||||
import {
|
||||
GetOptionsObject,
|
||||
GetRoundingIncrementOption,
|
||||
GetRoundingModeOption,
|
||||
IsOffsetTimeZoneIdentifier,
|
||||
RoundingMode,
|
||||
} from '../../abstract-ops/temporal/addition.mts';
|
||||
import {
|
||||
GetTemporalFractionalSecondDigitsOption,
|
||||
GetDirectionOption,
|
||||
GetTemporalDisambiguationOption,
|
||||
GetTemporalOffsetOption,
|
||||
GetTemporalOverflowOption,
|
||||
IsPartialTemporalObject,
|
||||
GetTemporalShowCalendarNameOption,
|
||||
GetTemporalShowOffsetOption,
|
||||
GetTemporalShowTimeZoneNameOption,
|
||||
GetTemporalUnitValuedOption,
|
||||
ISODateToFields,
|
||||
MaximumTemporalDurationRoundingIncrement,
|
||||
TemporalUnit,
|
||||
ToSecondsStringPrecisionRecord,
|
||||
ValidateTemporalRoundingIncrement,
|
||||
ValidateTemporalUnitValue,
|
||||
type TimeUnit,
|
||||
} from '../../abstract-ops/temporal/temporal.mts';
|
||||
import {
|
||||
CalendarEquals,
|
||||
CalendarISOToDate,
|
||||
CalendarMergeFields,
|
||||
PrepareCalendarFields,
|
||||
ToTemporalCalendarIdentifier,
|
||||
} from '../../abstract-ops/temporal/calendar.mts';
|
||||
import {
|
||||
AddDurationToZonedDateTime,
|
||||
CreateTemporalZonedDateTime,
|
||||
DifferenceTemporalZonedDateTime,
|
||||
InterpretISODateTimeOffset,
|
||||
TemporalZonedDateTimeToString,
|
||||
ToTemporalZonedDateTime,
|
||||
} from '../../abstract-ops/temporal/zoned-datetime.mts';
|
||||
import {
|
||||
GetISODateTimeFor,
|
||||
GetEpochNanosecondsFor,
|
||||
GetOffsetNanosecondsFor,
|
||||
GetNamedTimeZoneNextTransition,
|
||||
GetNamedTimeZonePreviousTransition,
|
||||
FormatUTCOffsetNanoseconds,
|
||||
TimeZoneEquals,
|
||||
ToTemporalTimeZoneIdentifier,
|
||||
GetStartOfDay,
|
||||
} from '../../abstract-ops/temporal/time-zone.mts';
|
||||
import { AddDaysToISODate, CreateTemporalDate } from '../../abstract-ops/temporal/plain-date.mts';
|
||||
import { ParseDateTimeUTCOffset } from '../../parser/TemporalParser.mts';
|
||||
import { CreateTemporalTime, ToTemporalTime } from '../../abstract-ops/temporal/plain-time.mts';
|
||||
import {
|
||||
CombineISODateAndTimeRecord,
|
||||
CreateTemporalDateTime,
|
||||
InterpretTemporalDateTimeFields,
|
||||
RoundISODateTime,
|
||||
} from '../../abstract-ops/temporal/plain-date-time.mts';
|
||||
import { CreateTemporalInstant } from '../../abstract-ops/temporal/instant.mts';
|
||||
import { __ts_cast__ } from '../../helpers.mts';
|
||||
import type { TemporalZonedDateTimeObject } from './ZonedDateTime.mts';
|
||||
import {
|
||||
AddTimeDurationToEpochNanoseconds,
|
||||
Assert,
|
||||
CreateDataPropertyOrThrow,
|
||||
F,
|
||||
JSStringValue,
|
||||
OrdinaryObjectCreate,
|
||||
type ObjectValue,
|
||||
Q,
|
||||
RequireInternalSlot,
|
||||
RoundTimeDurationToIncrement,
|
||||
Throw,
|
||||
TimeDurationFromEpochNanosecondsDifference,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
X,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type PlainCompletion,
|
||||
type Realm,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
function thisTemporalZonedDateTimeValue(value: Value): PlainCompletion<TemporalZonedDateTimeObject> {
|
||||
Q(RequireInternalSlot(value, 'InitializedTemporalZonedDateTime'));
|
||||
return value as TemporalZonedDateTimeObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.calendarid */
|
||||
function ZonedDateTimeProto_calendarIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
return Value(Q(thisTemporalZonedDateTimeValue(thisValue)).Calendar);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.timezoneid */
|
||||
function ZonedDateTimeProto_timeZoneIdGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
return Value(Q(thisTemporalZonedDateTimeValue(thisValue)).TimeZone);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.year */
|
||||
function ZonedDateTimeProto_yearGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds);
|
||||
return F(CalendarISOToDate(zonedDateTime.Calendar, isoDateTime.ISODate).Year);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.month */
|
||||
function ZonedDateTimeProto_monthGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds);
|
||||
return F(CalendarISOToDate(zonedDateTime.Calendar, isoDateTime.ISODate).Month);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.monthcode */
|
||||
function ZonedDateTimeProto_monthCodeGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds);
|
||||
return Value(CalendarISOToDate(zonedDateTime.Calendar, isoDateTime.ISODate).MonthCode);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.day */
|
||||
function ZonedDateTimeProto_dayGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds);
|
||||
return F(CalendarISOToDate(zonedDateTime.Calendar, isoDateTime.ISODate).Day);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.hour */
|
||||
function ZonedDateTimeProto_hourGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Hour);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.minute */
|
||||
function ZonedDateTimeProto_minuteGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Minute);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.second */
|
||||
function ZonedDateTimeProto_secondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Second);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.millisecond */
|
||||
function ZonedDateTimeProto_millisecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Millisecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.microsecond */
|
||||
function ZonedDateTimeProto_microsecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Microsecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.nanosecond */
|
||||
function ZonedDateTimeProto_nanosecondGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return F(GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds).Time.Nanosecond);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.epochmilliseconds */
|
||||
function ZonedDateTimeProto_epochMillisecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const ns = Q(thisTemporalZonedDateTimeValue(thisValue)).EpochNanoseconds;
|
||||
return F(Number(ns / 1_000_000n));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.epochnanoseconds */
|
||||
function ZonedDateTimeProto_epochNanosecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
return Value(Q(thisTemporalZonedDateTimeValue(thisValue)).EpochNanoseconds);
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.offsetnanoseconds */
|
||||
function ZonedDateTimeProto_offsetNanosecondsGetter(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return F(GetOffsetNanosecondsFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.with */
|
||||
function* ZonedDateTimeProto_with([temporalZonedDateTimeLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
if (!Q(yield* IsPartialTemporalObject(temporalZonedDateTimeLike))) {
|
||||
return Throw.TypeError('$1 is not a partial Temporal object', temporalZonedDateTimeLike);
|
||||
}
|
||||
const epochNs = zonedDateTime.EpochNanoseconds;
|
||||
const timeZone = zonedDateTime.TimeZone;
|
||||
const calendar = zonedDateTime.Calendar;
|
||||
const offsetNanoseconds = GetOffsetNanosecondsFor(timeZone, epochNs);
|
||||
const isoDateTime = GetISODateTimeFor(timeZone, epochNs);
|
||||
let fields = ISODateToFields(calendar, isoDateTime.ISODate, 'date');
|
||||
fields.Hour = isoDateTime.Time.Hour;
|
||||
fields.Minute = isoDateTime.Time.Minute;
|
||||
fields.Second = isoDateTime.Time.Second;
|
||||
fields.Millisecond = isoDateTime.Time.Millisecond;
|
||||
fields.Microsecond = isoDateTime.Time.Microsecond;
|
||||
fields.Nanosecond = isoDateTime.Time.Nanosecond;
|
||||
fields.OffsetString = FormatUTCOffsetNanoseconds(offsetNanoseconds);
|
||||
const partialZonedDateTime = Q(yield* PrepareCalendarFields(calendar, temporalZonedDateTimeLike as ObjectValue, ['year', 'month', 'month-code', 'day'], ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'offset'], 'partial'));
|
||||
fields = CalendarMergeFields(calendar, fields, partialZonedDateTime);
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const disambiguation = Q(yield* GetTemporalDisambiguationOption(resolvedOptions));
|
||||
const offset = Q(yield* GetTemporalOffsetOption(resolvedOptions, 'prefer'));
|
||||
const overflow = Q(yield* GetTemporalOverflowOption(resolvedOptions));
|
||||
const dateTimeResult = Q(yield* InterpretTemporalDateTimeFields(calendar, fields, overflow));
|
||||
const offsetString = fields.OffsetString!;
|
||||
const newOffsetNanoseconds = X(ParseDateTimeUTCOffset(offsetString));
|
||||
const epochNanoseconds = Q(InterpretISODateTimeOffset(dateTimeResult.ISODate, dateTimeResult.Time, 'option', newOffsetNanoseconds, timeZone, disambiguation, offset, 'match-exactly'));
|
||||
return X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withplaintime */
|
||||
function* ZonedDateTimeProto_withPlainTime([plainTimeLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const timeZone = zonedDateTime.TimeZone;
|
||||
const calendar = zonedDateTime.Calendar;
|
||||
const isoDateTime = GetISODateTimeFor(timeZone, zonedDateTime.EpochNanoseconds);
|
||||
let epochNs;
|
||||
if (plainTimeLike instanceof UndefinedValue) {
|
||||
epochNs = Q(GetStartOfDay(timeZone, isoDateTime.ISODate));
|
||||
} else {
|
||||
const plainTime = Q(yield* ToTemporalTime(plainTimeLike));
|
||||
const resultISODateTime = CombineISODateAndTimeRecord(isoDateTime.ISODate, plainTime.Time);
|
||||
epochNs = Q(GetEpochNanosecondsFor(timeZone, resultISODateTime, 'compatible'));
|
||||
}
|
||||
return X(CreateTemporalZonedDateTime(epochNs, timeZone, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withtimezone */
|
||||
function* ZonedDateTimeProto_withTimeZone([timeZoneLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const timeZone = Q(ToTemporalTimeZoneIdentifier(timeZoneLike));
|
||||
return X(CreateTemporalZonedDateTime(zonedDateTime.EpochNanoseconds, timeZone, zonedDateTime.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withcalendar */
|
||||
function ZonedDateTimeProto_withCalendar([calendarLike = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const calendar = Q(ToTemporalCalendarIdentifier(calendarLike));
|
||||
return X(CreateTemporalZonedDateTime(zonedDateTime.EpochNanoseconds, zonedDateTime.TimeZone, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.add */
|
||||
function* ZonedDateTimeProto_add([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return Q(yield* AddDurationToZonedDateTime('add', zonedDateTime, temporalDurationLike, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.subtract */
|
||||
function* ZonedDateTimeProto_subtract([temporalDurationLike = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return Q(yield* AddDurationToZonedDateTime('subtract', zonedDateTime, temporalDurationLike, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.until */
|
||||
function* ZonedDateTimeProto_until([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalZonedDateTime('until', zonedDateTime, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.since */
|
||||
function* ZonedDateTimeProto_since([other = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return Q(yield* DifferenceTemporalZonedDateTime('since', zonedDateTime, other, options));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.round */
|
||||
function* ZonedDateTimeProto_round([roundTo = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
if (roundTo instanceof UndefinedValue) {
|
||||
return Throw.TypeError('roundTo is required');
|
||||
}
|
||||
if (roundTo instanceof JSStringValue) {
|
||||
const paramString = roundTo;
|
||||
roundTo = OrdinaryObjectCreate(Value.null);
|
||||
X(CreateDataPropertyOrThrow(roundTo, Value('smallestUnit'), paramString));
|
||||
} else {
|
||||
roundTo = Q(GetOptionsObject(roundTo));
|
||||
}
|
||||
const roundingIncrement = Q(yield* GetRoundingIncrementOption(roundTo));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(roundTo, RoundingMode.HalfExpand));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(roundTo, 'smallestUnit', 'required'));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time', [TemporalUnit.Day]));
|
||||
let maximum;
|
||||
let inclusive;
|
||||
if (smallestUnit === TemporalUnit.Day) {
|
||||
maximum = 1;
|
||||
inclusive = true;
|
||||
} else {
|
||||
maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit as TemporalUnit);
|
||||
Assert(maximum !== 'unset');
|
||||
inclusive = false;
|
||||
}
|
||||
Q(ValidateTemporalRoundingIncrement(roundingIncrement, maximum, inclusive));
|
||||
if (smallestUnit === TemporalUnit.Nanosecond && roundingIncrement === 1) {
|
||||
return X(CreateTemporalZonedDateTime(zonedDateTime.EpochNanoseconds, zonedDateTime.TimeZone, zonedDateTime.Calendar));
|
||||
}
|
||||
const thisNs = zonedDateTime.EpochNanoseconds;
|
||||
const timeZone = zonedDateTime.TimeZone;
|
||||
const calendar = zonedDateTime.Calendar;
|
||||
const isoDateTime = GetISODateTimeFor(timeZone, thisNs);
|
||||
let epochNanoseconds;
|
||||
if (smallestUnit === TemporalUnit.Day) {
|
||||
const dateStart = isoDateTime.ISODate;
|
||||
const dateEnd = AddDaysToISODate(dateStart, 1);
|
||||
const startNs = Q(GetStartOfDay(timeZone, dateStart));
|
||||
Assert(thisNs >= startNs);
|
||||
const endNs = Q(GetStartOfDay(timeZone, dateEnd));
|
||||
Assert(thisNs < endNs);
|
||||
const dayLengthNs = endNs - startNs;
|
||||
const dayProgressNs = TimeDurationFromEpochNanosecondsDifference(thisNs, startNs);
|
||||
const roundedDayNs = X(RoundTimeDurationToIncrement(dayProgressNs, Number(dayLengthNs), roundingMode));
|
||||
epochNanoseconds = AddTimeDurationToEpochNanoseconds(roundedDayNs, startNs);
|
||||
} else {
|
||||
const roundResult = RoundISODateTime(isoDateTime, roundingIncrement, smallestUnit as TimeUnit | TemporalUnit.Day, roundingMode);
|
||||
const offsetNanoseconds = GetOffsetNanosecondsFor(timeZone, thisNs);
|
||||
epochNanoseconds = Q(InterpretISODateTimeOffset(roundResult.ISODate, roundResult.Time, 'option', offsetNanoseconds, timeZone, 'compatible', 'prefer', 'match-exactly'));
|
||||
}
|
||||
return X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.equals */
|
||||
function* ZonedDateTimeProto_equals([_other = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const other = Q(yield* ToTemporalZonedDateTime(_other));
|
||||
if (zonedDateTime.EpochNanoseconds !== other.EpochNanoseconds) {
|
||||
return Value.false;
|
||||
}
|
||||
if (!TimeZoneEquals(zonedDateTime.TimeZone, other.TimeZone)) {
|
||||
return Value.false;
|
||||
}
|
||||
return Value(CalendarEquals(zonedDateTime.Calendar, other.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tostring */
|
||||
function* ZonedDateTimeProto_toString([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const resolvedOptions = Q(GetOptionsObject(options));
|
||||
const showCalendar = Q(yield* GetTemporalShowCalendarNameOption(resolvedOptions));
|
||||
const digits = Q(yield* GetTemporalFractionalSecondDigitsOption(resolvedOptions));
|
||||
const showOffset = Q(yield* GetTemporalShowOffsetOption(resolvedOptions));
|
||||
const roundingMode = Q(yield* GetRoundingModeOption(resolvedOptions, 3));
|
||||
const smallestUnit = Q(yield* GetTemporalUnitValuedOption(resolvedOptions, 'smallestUnit', 'unset'));
|
||||
const showTimeZone = Q(yield* GetTemporalShowTimeZoneNameOption(resolvedOptions));
|
||||
Q(ValidateTemporalUnitValue(smallestUnit, 'time'));
|
||||
if (smallestUnit === TemporalUnit.Hour) {
|
||||
return Throw.RangeError('smallestUnit cannot be hour');
|
||||
}
|
||||
const precision = ToSecondsStringPrecisionRecord(
|
||||
smallestUnit as Exclude<TimeUnit, TemporalUnit.Hour> | 'unset',
|
||||
digits,
|
||||
);
|
||||
return Value(TemporalZonedDateTimeToString(zonedDateTime, precision.Precision, showCalendar, showTimeZone, showOffset, precision.Increment, precision.Unit, roundingMode));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tolocalestring */
|
||||
function ZonedDateTimeProto_toLocaleString(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return Value(TemporalZonedDateTimeToString(zonedDateTime, 'auto', 'auto', 'auto', 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tojson */
|
||||
function ZonedDateTimeProto_toJSON(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return Value(TemporalZonedDateTimeToString(zonedDateTime, 'auto', 'auto', 'auto', 'auto'));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.valueof */
|
||||
function ZonedDateTimeProto_valueOf(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return Throw.TypeError('Temporal.ZonedDateTime cannot be converted to primitive value. If you are comparing two Temporal.ZonedDateTime objects with > or <, use Temporal.ZonedDateTime.compare() instead.');
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.startofday */
|
||||
function ZonedDateTimeProto_startOfDay(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const timeZone = zonedDateTime.TimeZone;
|
||||
const calendar = zonedDateTime.Calendar;
|
||||
const isoDateTime = GetISODateTimeFor(timeZone, zonedDateTime.EpochNanoseconds).ISODate;
|
||||
const epochNanoseconds = Q(GetStartOfDay(timeZone, isoDateTime));
|
||||
return X(CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.gettimezonetransition */
|
||||
function* ZonedDateTimeProto_getTimeZoneTransition([directionParam = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const timeZone = zonedDateTime.TimeZone;
|
||||
if (directionParam instanceof UndefinedValue) {
|
||||
return Throw.TypeError('directionParam is required');
|
||||
}
|
||||
if (directionParam instanceof JSStringValue) {
|
||||
const paramString = directionParam;
|
||||
directionParam = OrdinaryObjectCreate(Value.null);
|
||||
X(CreateDataPropertyOrThrow(directionParam, Value('direction'), paramString));
|
||||
} else {
|
||||
directionParam = Q(GetOptionsObject(directionParam));
|
||||
}
|
||||
const direction = Q(yield* GetDirectionOption(directionParam));
|
||||
if (IsOffsetTimeZoneIdentifier(timeZone)) {
|
||||
return Value.null;
|
||||
}
|
||||
let transition;
|
||||
if (direction === 'next') {
|
||||
transition = GetNamedTimeZoneNextTransition(timeZone, zonedDateTime.EpochNanoseconds);
|
||||
} else {
|
||||
Assert(direction === 'previous');
|
||||
transition = GetNamedTimeZonePreviousTransition(timeZone, zonedDateTime.EpochNanoseconds);
|
||||
}
|
||||
if (transition === null) return Value.null;
|
||||
return X(CreateTemporalZonedDateTime(transition, timeZone, zonedDateTime.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toinstant */
|
||||
function ZonedDateTimeProto_toInstant(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
return X(CreateTemporalInstant(zonedDateTime.EpochNanoseconds));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaindate */
|
||||
function ZonedDateTimeProto_toPlainDate(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds);
|
||||
return X(CreateTemporalDate(isoDateTime.ISODate, zonedDateTime.Calendar));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaintime */
|
||||
function ZonedDateTimeProto_toPlainTime(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds);
|
||||
return X(CreateTemporalTime(isoDateTime.Time));
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaindatetime */
|
||||
function ZonedDateTimeProto_toPlainDateTime(_args: Arguments, { thisValue }: FunctionCallContext): PlainCompletion<Value> {
|
||||
const zonedDateTime = Q(thisTemporalZonedDateTimeValue(thisValue));
|
||||
const isoDateTime = GetISODateTimeFor(zonedDateTime.TimeZone, zonedDateTime.EpochNanoseconds);
|
||||
return X(CreateTemporalDateTime(isoDateTime, zonedDateTime.Calendar));
|
||||
}
|
||||
|
||||
export function bootstrapTemporalZonedDateTimePrototype(realmRec: Realm) {
|
||||
const prototype = bootstrapPrototype(realmRec, [
|
||||
['calendarId', [ZonedDateTimeProto_calendarIdGetter]],
|
||||
['timeZoneId', [ZonedDateTimeProto_timeZoneIdGetter]],
|
||||
['year', [ZonedDateTimeProto_yearGetter]],
|
||||
['month', [ZonedDateTimeProto_monthGetter]],
|
||||
['monthCode', [ZonedDateTimeProto_monthCodeGetter]],
|
||||
['day', [ZonedDateTimeProto_dayGetter]],
|
||||
['hour', [ZonedDateTimeProto_hourGetter]],
|
||||
['minute', [ZonedDateTimeProto_minuteGetter]],
|
||||
['second', [ZonedDateTimeProto_secondGetter]],
|
||||
['millisecond', [ZonedDateTimeProto_millisecondGetter]],
|
||||
['microsecond', [ZonedDateTimeProto_microsecondGetter]],
|
||||
['nanosecond', [ZonedDateTimeProto_nanosecondGetter]],
|
||||
['epochMilliseconds', [ZonedDateTimeProto_epochMillisecondsGetter]],
|
||||
['epochNanoseconds', [ZonedDateTimeProto_epochNanosecondsGetter]],
|
||||
['offsetNanoseconds', [ZonedDateTimeProto_offsetNanosecondsGetter]],
|
||||
['with', ZonedDateTimeProto_with, 1],
|
||||
['withTimeZone', ZonedDateTimeProto_withTimeZone, 1],
|
||||
['withCalendar', ZonedDateTimeProto_withCalendar, 1],
|
||||
['withPlainTime', ZonedDateTimeProto_withPlainTime, 0],
|
||||
['add', ZonedDateTimeProto_add, 1],
|
||||
['subtract', ZonedDateTimeProto_subtract, 1],
|
||||
['until', ZonedDateTimeProto_until, 1],
|
||||
['since', ZonedDateTimeProto_since, 1],
|
||||
['round', ZonedDateTimeProto_round, 1],
|
||||
['equals', ZonedDateTimeProto_equals, 1],
|
||||
['toString', ZonedDateTimeProto_toString, 0],
|
||||
['toLocaleString', ZonedDateTimeProto_toLocaleString, 0],
|
||||
['toJSON', ZonedDateTimeProto_toJSON, 0],
|
||||
['valueOf', ZonedDateTimeProto_valueOf, 0],
|
||||
['startOfDay', ZonedDateTimeProto_startOfDay, 0],
|
||||
['getTimeZoneTransition', ZonedDateTimeProto_getTimeZoneTransition, 1],
|
||||
['toInstant', [ZonedDateTimeProto_toInstant]],
|
||||
['toPlainDate', [ZonedDateTimeProto_toPlainDate]],
|
||||
['toPlainTime', [ZonedDateTimeProto_toPlainTime]],
|
||||
['toPlainDateTime', [ZonedDateTimeProto_toPlainDateTime]],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'Temporal.ZonedDateTime');
|
||||
realmRec.Intrinsics['%Temporal.ZonedDateTime.prototype%'] = prototype;
|
||||
return prototype;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { Value } from '../value.mts';
|
||||
import { X } from '../completion.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateBuiltinFunction,
|
||||
Realm,
|
||||
SetIntegrityLevel,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%throwtypeerror% */
|
||||
function ThrowTypeError() {
|
||||
// 1. Throw a TypeError exception.
|
||||
return surroundingAgent.Throw('TypeError', 'StrictPoisonPill');
|
||||
}
|
||||
|
||||
export function bootstrapThrowTypeError(realmRec: Realm) {
|
||||
const f = X(CreateBuiltinFunction(ThrowTypeError, 0, Value(''), [], realmRec));
|
||||
Assert(X(SetIntegrityLevel(f, 'frozen')) === Value.true);
|
||||
realmRec.Intrinsics['%ThrowTypeError%'] = f;
|
||||
}
|
||||
@@ -0,0 +1,576 @@
|
||||
import {
|
||||
Q, X, type PlainCompletion,
|
||||
} from '../completion.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
BigIntValue,
|
||||
BooleanValue,
|
||||
JSStringValue,
|
||||
NullValue,
|
||||
NumberValue,
|
||||
ObjectValue,
|
||||
UndefinedValue,
|
||||
Value, wellKnownSymbols, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { type Mutable, __ts_cast__ } from '../helpers.mts';
|
||||
import type { PlainEvaluator, ValueEvaluator } from '../evaluator.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
Get,
|
||||
GetMethod,
|
||||
IsCallable,
|
||||
IsConstructor,
|
||||
IteratorToList,
|
||||
Set,
|
||||
LengthOfArrayLike,
|
||||
ToObject,
|
||||
ToString,
|
||||
F,
|
||||
Realm,
|
||||
type BuiltinFunctionObject,
|
||||
type FunctionObject,
|
||||
GetIteratorFromMethod,
|
||||
AllocateArrayBuffer,
|
||||
Construct,
|
||||
GetPrototypeFromConstructor,
|
||||
isNonNegativeInteger,
|
||||
IsTypedArrayOutOfBounds,
|
||||
MakeTypedArrayWithBufferWitnessRecord,
|
||||
R,
|
||||
RequireInternalSlot,
|
||||
SpeciesConstructor,
|
||||
ToBigInt64,
|
||||
ToBigUint64,
|
||||
ToInt16,
|
||||
ToInt32,
|
||||
ToInt8,
|
||||
ToNumber,
|
||||
ToUint16,
|
||||
ToUint32,
|
||||
ToUint8,
|
||||
ToUint8Clamp,
|
||||
TypedArrayCreate,
|
||||
TypedArrayLength,
|
||||
type ArrayBufferObject,
|
||||
type ExoticObject,
|
||||
type Intrinsics,
|
||||
type TypedArrayWithBufferWitnessRecord,
|
||||
CloneArrayBuffer,
|
||||
GetValueFromBuffer,
|
||||
SetValueInBuffer,
|
||||
ToIndex,
|
||||
IsFixedLengthArrayBuffer,
|
||||
IsDetachedBuffer,
|
||||
ArrayBufferByteLength,
|
||||
} from '#self';
|
||||
|
||||
export const typedArrayInfoByName = {
|
||||
Int8Array: {
|
||||
IntrinsicName: '%Int8Array%',
|
||||
ElementType: 'Int8',
|
||||
ElementSize: 1,
|
||||
ConversionOperation: ToInt8,
|
||||
},
|
||||
Uint8Array: {
|
||||
IntrinsicName: '%Uint8Array%',
|
||||
ElementType: 'Uint8',
|
||||
ElementSize: 1,
|
||||
ConversionOperation: ToUint8,
|
||||
},
|
||||
Uint8ClampedArray: {
|
||||
IntrinsicName: '%Uint8ClampedArray%',
|
||||
ElementType: 'Uint8C',
|
||||
ElementSize: 1,
|
||||
ConversionOperation: ToUint8Clamp,
|
||||
},
|
||||
Int16Array: {
|
||||
IntrinsicName: '%Int16Array%',
|
||||
ElementType: 'Int16',
|
||||
ElementSize: 2,
|
||||
ConversionOperation: ToInt16,
|
||||
},
|
||||
Uint16Array: {
|
||||
IntrinsicName: '%Uint16Array%',
|
||||
ElementType: 'Uint16',
|
||||
ElementSize: 2,
|
||||
ConversionOperation: ToUint16,
|
||||
},
|
||||
Int32Array: {
|
||||
IntrinsicName: '%Int32Array%',
|
||||
ElementType: 'Int32',
|
||||
ElementSize: 4,
|
||||
ConversionOperation: ToInt32,
|
||||
},
|
||||
Uint32Array: {
|
||||
IntrinsicName: '%Uint32Array%',
|
||||
ElementType: 'Uint32',
|
||||
ElementSize: 4,
|
||||
ConversionOperation: ToUint32,
|
||||
},
|
||||
BigInt64Array: {
|
||||
IntrinsicName: '%BigInt64Array%',
|
||||
ElementType: 'BigInt64',
|
||||
ElementSize: 8,
|
||||
ConversionOperation: ToBigInt64,
|
||||
},
|
||||
BigUint64Array: {
|
||||
IntrinsicName: '%BigUint64Array%',
|
||||
ElementType: 'BigUint64',
|
||||
ElementSize: 8,
|
||||
ConversionOperation: ToBigUint64,
|
||||
},
|
||||
Float32Array: {
|
||||
IntrinsicName: '%Float32Array%',
|
||||
ElementType: 'Float32',
|
||||
ElementSize: 4,
|
||||
ConversionOperation: undefined,
|
||||
},
|
||||
Float64Array: {
|
||||
IntrinsicName: '%Float64Array%',
|
||||
ElementType: 'Float64',
|
||||
ElementSize: 8,
|
||||
ConversionOperation: undefined,
|
||||
},
|
||||
} as const;
|
||||
export type TypedArrayConstructorNames = keyof typeof typedArrayInfoByName;
|
||||
|
||||
export const typedArrayInfoByType = {
|
||||
Int8: typedArrayInfoByName.Int8Array,
|
||||
Uint8: typedArrayInfoByName.Uint8Array,
|
||||
Uint8C: typedArrayInfoByName.Uint8ClampedArray,
|
||||
Int16: typedArrayInfoByName.Int16Array,
|
||||
Uint16: typedArrayInfoByName.Uint16Array,
|
||||
Int32: typedArrayInfoByName.Int32Array,
|
||||
Uint32: typedArrayInfoByName.Uint32Array,
|
||||
BigInt64: typedArrayInfoByName.BigInt64Array,
|
||||
BigUint64: typedArrayInfoByName.BigUint64Array,
|
||||
Float32: typedArrayInfoByName.Float32Array,
|
||||
Float64: typedArrayInfoByName.Float64Array,
|
||||
} as const;
|
||||
export type TypedArrayTypes = keyof typeof typedArrayInfoByType;
|
||||
|
||||
export interface TypedArrayObject extends ExoticObject {
|
||||
readonly Prototype: ObjectValue | NullValue;
|
||||
readonly Extensible: BooleanValue<false>;
|
||||
|
||||
ViewedArrayBuffer: ArrayBufferObject | UndefinedValue;
|
||||
readonly ArrayLength: number | 'auto';
|
||||
readonly ByteOffset: number;
|
||||
readonly ContentType: 'BigInt' | 'Number';
|
||||
readonly TypedArrayName: JSStringValue;
|
||||
readonly ByteLength: number | 'auto';
|
||||
}
|
||||
export function isTypedArrayObject(value: Value): value is TypedArrayObject {
|
||||
return 'TypedArrayName' in value;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#typedarray-species-create */
|
||||
export function* TypedArraySpeciesCreate(exemplar: TypedArrayObject, argumentList: Arguments): ValueEvaluator<TypedArrayObject> {
|
||||
// 1. Assert: exemplar is an Object that has [[TypedArrayName]] and [[ContentType]] internal slots.
|
||||
Assert(exemplar instanceof ObjectValue
|
||||
&& 'TypedArrayName' in exemplar
|
||||
&& 'ContentType' in exemplar);
|
||||
// 2. Let defaultConstructor be the intrinsic object listed in column one of Table 61 for exemplar.[[TypedArrayName]].
|
||||
const defaultConstructor = surroundingAgent.intrinsic(typedArrayInfoByName[exemplar.TypedArrayName.stringValue() as TypedArrayConstructorNames].IntrinsicName);
|
||||
// 3. Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor).
|
||||
const constructor = Q(yield* SpeciesConstructor(exemplar, defaultConstructor));
|
||||
// 4. Let result be ? TypedArrayCreate(constructor, argumentList).
|
||||
const result = Q(yield* TypedArrayCreateFromConstructor(constructor, argumentList));
|
||||
// 5. Assert: result has [[TypedArrayName]] and [[ContentType]] internal slots.
|
||||
Assert('TypedArrayName' in result && 'ContentType' in result);
|
||||
// 6. If result.[[ContentType]] is not equal to exemplar.[[ContentType]], throw a TypeError exception.
|
||||
if (result.ContentType !== exemplar.ContentType) {
|
||||
return surroundingAgent.Throw('TypeError', 'BufferContentTypeMismatch');
|
||||
}
|
||||
// 7. Return result.
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-typedarraycreatefromconstructor */
|
||||
export function* TypedArrayCreateFromConstructor(constructor: FunctionObject, argumentList: Arguments): ValueEvaluator<TypedArrayObject> {
|
||||
const newTypedArray = Q(yield* Construct(constructor, argumentList)) as TypedArrayObject;
|
||||
const taRecord = Q(ValidateTypedArray(newTypedArray, 'seq-cst'));
|
||||
if (argumentList.length === 1 && argumentList[0] instanceof NumberValue) {
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
// TODO: error message
|
||||
return surroundingAgent.Throw('TypeError', 'Raw', 'TypedArrayCreateFromConstructor:IsTypedArrayOutOfBounds');
|
||||
}
|
||||
const length = TypedArrayLength(taRecord);
|
||||
if (length < R(argumentList[0])) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayTooSmall');
|
||||
}
|
||||
}
|
||||
return newTypedArray;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-typedarray-create-same-type */
|
||||
export function* TypedArrayCreateSameType(exemplar: TypedArrayObject, length: number): ValueEvaluator<TypedArrayObject> {
|
||||
const constructor = surroundingAgent.intrinsic(typedArrayInfoByName[exemplar.TypedArrayName.stringValue() as TypedArrayConstructorNames].IntrinsicName);
|
||||
const result = Q(yield* TypedArrayCreateFromConstructor(constructor, [Value(length)]));
|
||||
Assert('TypedArrayName' in result && 'ContentType' in result);
|
||||
Assert(result.ContentType === exemplar.ContentType);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-validatetypedarray */
|
||||
export function ValidateTypedArray(O: Value, order: 'seq-cst' | 'unordered'): PlainCompletion<TypedArrayWithBufferWitnessRecord> {
|
||||
Q(RequireInternalSlot(O, 'TypedArrayName'));
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
const taRecord = MakeTypedArrayWithBufferWitnessRecord(O as TypedArrayObject, order);
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds');
|
||||
}
|
||||
return taRecord;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-typedarrayelementsize */
|
||||
export function TypedArrayElementSize(O: TypedArrayObject): number {
|
||||
const type = O.TypedArrayName.stringValue() as TypedArrayConstructorNames;
|
||||
return typedArrayInfoByName[type].ElementSize;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-typedarrayelementtype */
|
||||
export function TypedArrayElementType(O: TypedArrayObject): TypedArrayTypes {
|
||||
const type = O.TypedArrayName.stringValue() as TypedArrayConstructorNames;
|
||||
return typedArrayInfoByName[type].ElementType;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-comparetypedarrayelements */
|
||||
export function* CompareTypedArrayElements(x: NumberValue | BigIntValue, y: NumberValue | BigIntValue, comparator: FunctionObject | UndefinedValue): ValueEvaluator<NumberValue> {
|
||||
Assert(
|
||||
(x instanceof NumberValue && y instanceof NumberValue)
|
||||
|| (x instanceof BigIntValue && y instanceof BigIntValue),
|
||||
);
|
||||
if (!(comparator instanceof UndefinedValue)) {
|
||||
const v = Q(yield* ToNumber(Q(yield* Call(comparator, Value.undefined, [x, y]))));
|
||||
if (v.isNaN()) {
|
||||
return F(0);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
if (x.isNaN() && y.isNaN()) {
|
||||
return F(0);
|
||||
}
|
||||
if (x.isNaN()) {
|
||||
return F(1);
|
||||
}
|
||||
if (y.isNaN()) {
|
||||
return F(-1);
|
||||
}
|
||||
if (x.value < y.value) {
|
||||
return F(-1);
|
||||
}
|
||||
if (x.value > y.value) {
|
||||
return F(1);
|
||||
}
|
||||
if (Object.is(-0, x.value) && Object.is(0, y.value)) {
|
||||
return F(-1);
|
||||
}
|
||||
if (Object.is(0, x.value) && Object.is(-0, y.value)) {
|
||||
return F(1);
|
||||
}
|
||||
return F(0);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%-intrinsic-object */
|
||||
function TypedArrayConstructor(this: BuiltinFunctionObject) {
|
||||
// 1. Throw a TypeError exception.
|
||||
return surroundingAgent.Throw('TypeError', 'NotAConstructor', this);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-allocatetypedarray */
|
||||
export function* AllocateTypedArray(constructorName: JSStringValue, newTarget: FunctionObject, defaultProto: keyof Intrinsics, length?: number): ValueEvaluator<Mutable<TypedArrayObject>> {
|
||||
// 1. Let proto be ? GetPrototypeFromConstructor(newTarget, defaultProto).
|
||||
const proto = Q(yield* GetPrototypeFromConstructor(newTarget, defaultProto));
|
||||
// 2. Let obj be TypedArrayCreate(proto).
|
||||
const obj = TypedArrayCreate(proto) as Mutable<TypedArrayObject>;
|
||||
// 3. Assert: obj.[[ViewedArrayBuffer]] is undefined.
|
||||
Assert(obj.ViewedArrayBuffer === Value.undefined);
|
||||
// 4. Set obj.[[TypedArrayName]] to constructorName.
|
||||
obj.TypedArrayName = constructorName;
|
||||
// 5. If constructorName is "BigInt64Array" or "BigUint64Array", set obj.[[ContentType]] to BigInt.
|
||||
// 6. Otherwise, set obj.[[ContentType]] to Number.
|
||||
if (constructorName.stringValue() === 'BigInt64Array' || constructorName.stringValue() === 'BigUint64Array') {
|
||||
obj.ContentType = 'BigInt';
|
||||
} else {
|
||||
obj.ContentType = 'Number';
|
||||
}
|
||||
// 7. If length is not present, then
|
||||
if (length === undefined) {
|
||||
// 1. Set obj.[[ByteLength]] to 0.
|
||||
obj.ByteLength = 0;
|
||||
// 1. Set obj.[[ByteOffset]] to 0.
|
||||
obj.ByteOffset = 0;
|
||||
// 1. Set obj.[[ArrayLength]] to 0.
|
||||
obj.ArrayLength = 0;
|
||||
} else {
|
||||
// a. Perform ? AllocateTypedArrayBuffer(obj, length).
|
||||
Q(yield* AllocateTypedArrayBuffer(obj, length));
|
||||
}
|
||||
// 9. Return obj.
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray */
|
||||
export function* InitializeTypedArrayFromTypedArray(O: Mutable<TypedArrayObject>, srcArray: TypedArrayObject): PlainEvaluator {
|
||||
const srcData = srcArray.ViewedArrayBuffer as ArrayBufferObject;
|
||||
const elementType = TypedArrayElementType(O);
|
||||
const elementSize = TypedArrayElementSize(O);
|
||||
const srcType = TypedArrayElementType(srcArray);
|
||||
const srcElementSize = TypedArrayElementSize(srcArray);
|
||||
const srcByteOffset = srcArray.ByteOffset;
|
||||
const srcRecord = MakeTypedArrayWithBufferWitnessRecord(srcArray, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(srcRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds');
|
||||
}
|
||||
const elementLength = TypedArrayLength(srcRecord);
|
||||
const byteLength = elementSize * elementLength;
|
||||
let data;
|
||||
if (elementType === srcType) {
|
||||
data = Q(yield* CloneArrayBuffer(srcData, srcByteOffset, byteLength));
|
||||
} else {
|
||||
data = Q(yield* AllocateArrayBuffer(surroundingAgent.intrinsic('%ArrayBuffer%'), byteLength));
|
||||
if (srcArray.ContentType !== O.ContentType) {
|
||||
return surroundingAgent.Throw('TypeError', 'BufferContentTypeMismatch');
|
||||
}
|
||||
let srcByteIndex = srcByteOffset;
|
||||
let targetByteIndex = 0;
|
||||
let count = elementLength;
|
||||
while (count > 0) {
|
||||
const value = GetValueFromBuffer(srcData, srcByteIndex, srcType, true, 'unordered');
|
||||
Q(yield* SetValueInBuffer(data, targetByteIndex, elementType, value, true, 'unordered'));
|
||||
srcByteIndex += srcElementSize;
|
||||
targetByteIndex += elementSize;
|
||||
count -= 1;
|
||||
}
|
||||
}
|
||||
O.ViewedArrayBuffer = data;
|
||||
O.ByteLength = byteLength;
|
||||
O.ByteOffset = 0;
|
||||
O.ArrayLength = elementLength;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer */
|
||||
export function* InitializeTypedArrayFromArrayBuffer(O: Mutable<TypedArrayObject>, buffer: ArrayBufferObject, byteOffset: Value, length: Value): PlainEvaluator {
|
||||
const elementSize = TypedArrayElementSize(O);
|
||||
const offset = Q(yield* ToIndex(byteOffset));
|
||||
if (offset % elementSize !== 0) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayOffsetAlignment', offset, elementSize);
|
||||
}
|
||||
const bufferIsFixedLength = IsFixedLengthArrayBuffer(buffer);
|
||||
let newLength;
|
||||
if (length !== Value.undefined) {
|
||||
newLength = Q(yield* ToIndex(length));
|
||||
}
|
||||
if (IsDetachedBuffer(buffer)) {
|
||||
return surroundingAgent.Throw('TypeError', 'ArrayBufferDetached');
|
||||
}
|
||||
const bufferByteLength = ArrayBufferByteLength(buffer, 'seq-cst');
|
||||
if (length === Value.undefined && !bufferIsFixedLength) {
|
||||
if (offset > bufferByteLength) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayCreationOOB');
|
||||
}
|
||||
O.ByteLength = 'auto';
|
||||
O.ArrayLength = 'auto';
|
||||
} else {
|
||||
let newByteLength;
|
||||
if (length === Value.undefined) {
|
||||
if (bufferByteLength % elementSize !== 0) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayLengthAlignment', bufferByteLength, elementSize);
|
||||
}
|
||||
newByteLength = bufferByteLength - offset;
|
||||
if (newByteLength < 0) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayCreationOOB');
|
||||
}
|
||||
} else {
|
||||
Assert(newLength !== undefined);
|
||||
newByteLength = newLength * elementSize;
|
||||
if (offset + newByteLength > bufferByteLength) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayCreationOOB');
|
||||
}
|
||||
}
|
||||
O.ByteLength = newByteLength;
|
||||
O.ArrayLength = newByteLength / elementSize;
|
||||
}
|
||||
O.ViewedArrayBuffer = buffer;
|
||||
O.ByteOffset = offset;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-initializetypedarrayfromlist */
|
||||
export function* InitializeTypedArrayFromList(O: Mutable<TypedArrayObject>, value: Value[]): PlainEvaluator {
|
||||
const len = value.length;
|
||||
Q(yield* AllocateTypedArrayBuffer(O, len));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = value.shift()!;
|
||||
Q(yield* Set(O, Pk, kValue, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
Assert(value.length === 0);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike */
|
||||
export function* InitializeTypedArrayFromArrayLike(O: Mutable<TypedArrayObject>, arrayLike: ObjectValue): PlainEvaluator {
|
||||
const len = Q(yield* LengthOfArrayLike(arrayLike));
|
||||
Q(yield* AllocateTypedArrayBuffer(O, len));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = Q(yield* Get(arrayLike, Pk));
|
||||
Q(yield* Set(O, Pk, kValue, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-allocatetypedarraybuffer */
|
||||
export function* AllocateTypedArrayBuffer(O: TypedArrayObject, length: number): ValueEvaluator<TypedArrayObject> {
|
||||
// 1. Assert: O is an Object that has a [[ViewedArrayBuffer]] internal slot.
|
||||
Assert(O instanceof ObjectValue && 'ViewedArrayBuffer' in O);
|
||||
// 2. Assert: O.[[ViewedArrayBuffer]] is undefined.
|
||||
Assert(O.ViewedArrayBuffer === Value.undefined);
|
||||
// 3. Assert: length is a non-negative integer.
|
||||
Assert(isNonNegativeInteger(length));
|
||||
// 4. Let constructorName be the String value of O.[[TypedArrayName]].
|
||||
const constructorName = O.TypedArrayName.stringValue() as TypedArrayConstructorNames;
|
||||
// 5. Let elementSize be the Element Size value specified in Table 61 for constructorName.
|
||||
const elementSize = typedArrayInfoByName[constructorName].ElementSize;
|
||||
// 6. Let byteLength be elementSize × length.
|
||||
const byteLength = elementSize * length;
|
||||
// 7. Let data be ? AllocateArrayBuffer(%ArrayBuffer%, byteLength).
|
||||
const data = Q(yield* AllocateArrayBuffer(surroundingAgent.intrinsic('%ArrayBuffer%'), byteLength));
|
||||
// 8. Set O.[[ViewedArrayBuffer]] to data.
|
||||
O.ViewedArrayBuffer = data;
|
||||
// 9. Set O.[[ByteLength]] to byteLength.
|
||||
__ts_cast__<Mutable<TypedArrayObject>>(O);
|
||||
O.ByteLength = byteLength;
|
||||
// 10. Set O.[[ByteOffset]] to 0.
|
||||
O.ByteOffset = 0;
|
||||
// 11. Set O.[[ArrayLength]] to length.
|
||||
O.ArrayLength = length;
|
||||
// 12. Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.from */
|
||||
function* TypedArray_from([source = Value.undefined, mapper = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 2. If IsConstructor(C) is false, throw a TypeError exception.
|
||||
if (!IsConstructor(C)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAConstructor', C);
|
||||
}
|
||||
// 3. If mapfn is undefined, let mapping be false.
|
||||
let mapping;
|
||||
if (mapper === Value.undefined) {
|
||||
mapping = false;
|
||||
} else {
|
||||
// a. If IsCallable(mapfn) is false, throw a TypeError exception.
|
||||
if (!IsCallable(mapper)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', mapper);
|
||||
}
|
||||
// b. Let mapping be true.
|
||||
mapping = true;
|
||||
}
|
||||
// 5. Let usingIterator be ? GetMethod(source, @@iterator).
|
||||
const usingIterator = Q(yield* GetMethod(source, wellKnownSymbols.iterator));
|
||||
// 6. If usingIterator is not undefined, then
|
||||
if (!(usingIterator instanceof UndefinedValue)) {
|
||||
const values = Q(yield* IteratorToList(Q(yield* GetIteratorFromMethod(source, usingIterator))));
|
||||
const len = values.length;
|
||||
const targetObj = Q(yield* TypedArrayCreateFromConstructor(C, [F(len)]));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = values.shift()!;
|
||||
let mappedValue;
|
||||
if (mapping) {
|
||||
mappedValue = Q(yield* Call(mapper, thisArg, [kValue, F(k)]));
|
||||
} else {
|
||||
mappedValue = kValue;
|
||||
}
|
||||
Q(yield* Set(targetObj, Pk, mappedValue, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
Assert(values.length === 0);
|
||||
return targetObj;
|
||||
}
|
||||
// 7. NOTE: source is not an Iterable so assume it is already an array-like object.
|
||||
// 8. Let arrayLike be ! ToObject(source).
|
||||
const arrayLike = X(ToObject(source));
|
||||
// 9. Let len be ? LengthOfArrayLike(arrayLike).
|
||||
const len = Q(yield* LengthOfArrayLike(arrayLike));
|
||||
// 10. Let targetObj be ? TypedArrayCreate(C, « 𝔽(len) »).
|
||||
const targetObj = Q(yield* TypedArrayCreateFromConstructor(C, [F(len)]));
|
||||
// 11. Let k be 0.
|
||||
let k = 0;
|
||||
// 12. Repeat, while k < len
|
||||
while (k < len) {
|
||||
// a. Let Pk be ! ToString(𝔽(k)).
|
||||
const Pk = X(ToString(F(k)));
|
||||
// b. Let kValue be ? Get(arrayLike, Pk).
|
||||
const kValue = Q(yield* Get(arrayLike, Pk));
|
||||
let mappedValue;
|
||||
// c. If mapping is true, then
|
||||
if (mapping) {
|
||||
// i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, 𝔽(k) »).
|
||||
mappedValue = Q(yield* Call(mapper, thisArg, [kValue, F(k)]));
|
||||
} else {
|
||||
// d. Else, let mappedValue be kValue.
|
||||
mappedValue = kValue;
|
||||
}
|
||||
// e. Perform ? Set(targetObj, Pk, mappedValue, true).
|
||||
Q(yield* Set(targetObj, Pk, mappedValue, Value.true));
|
||||
// f. Set k to k + 1.
|
||||
k += 1;
|
||||
}
|
||||
// 13. Return targetObj.
|
||||
return targetObj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.of */
|
||||
function* TypedArray_of(items: Arguments, { thisValue }: FunctionCallContext) {
|
||||
// 1. Let len be the actual number of arguments passed to this function.
|
||||
// 2. Let items be the List of arguments passed to this function.
|
||||
const len = items.length;
|
||||
// 3. Let C be the this value.
|
||||
const C = thisValue;
|
||||
// 4. If IsConstructor(C) is false, throw a TypeError exception.
|
||||
if (!IsConstructor(C)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAConstructor', C);
|
||||
}
|
||||
// 5. Let newObj be ? TypedArrayCreate(C, « 𝔽(len) »).
|
||||
const newObj = Q(yield* TypedArrayCreateFromConstructor(C, [F(len)]));
|
||||
// 6. Let k be 0.
|
||||
let k = 0;
|
||||
// 7. Repeat, while k < len
|
||||
while (k < len) {
|
||||
// a. Let kValue be items[k].
|
||||
const kValue = items[k];
|
||||
// b. Let Pk be ! ToString(𝔽(k)).
|
||||
const Pk = X(ToString(F(k)));
|
||||
// c. Perform ? Set(newObj, Pk, kValue, true).
|
||||
Q(yield* Set(newObj, Pk, kValue!, Value.true));
|
||||
// d. Set k to k + 1.
|
||||
k += 1;
|
||||
}
|
||||
// 8. Return newObj.
|
||||
return newObj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-%typedarray%-@@species */
|
||||
function TypedArray_speciesGetter(_args: Arguments, { thisValue }: FunctionCallContext) {
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
export function bootstrapTypedArray(realmRec: Realm) {
|
||||
const typedArrayConstructor = bootstrapConstructor(realmRec, TypedArrayConstructor, 'TypedArray', 0, realmRec.Intrinsics['%TypedArray.prototype%'], [
|
||||
['from', TypedArray_from, 1],
|
||||
['of', TypedArray_of, 0],
|
||||
[wellKnownSymbols.species, [TypedArray_speciesGetter]],
|
||||
]);
|
||||
|
||||
realmRec.Intrinsics['%TypedArray%'] = typedArrayConstructor;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue, UndefinedValue, Value, wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
AllocateTypedArray, InitializeTypedArrayFromArrayBuffer, InitializeTypedArrayFromArrayLike, InitializeTypedArrayFromList, InitializeTypedArrayFromTypedArray, isTypedArrayObject, typedArrayInfoByName, type TypedArrayConstructorNames,
|
||||
} from './TypedArray.mts';
|
||||
import {
|
||||
Assert,
|
||||
GetMethod,
|
||||
IteratorToList,
|
||||
ToIndex,
|
||||
F,
|
||||
Realm,
|
||||
GetIteratorFromMethod,
|
||||
isArrayBufferObject,
|
||||
} from '#self';
|
||||
|
||||
export function bootstrapTypedArrayConstructors(realmRec: Realm) {
|
||||
Object.entries(typedArrayInfoByName).forEach(([TypedArray, info]) => {
|
||||
/** https://tc39.es/ecma262/#sec-typedarray-constructors */
|
||||
function* TypedArrayConstructor(this: Value, args: Arguments, { NewTarget }: FunctionCallContext): ValueEvaluator {
|
||||
__ts_cast__<TypedArrayConstructorNames>(TypedArray);
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
const constructorName = Value(TypedArray);
|
||||
const proto = `%${TypedArray}.prototype%` as const;
|
||||
const numberOfArgs = args.length;
|
||||
if (numberOfArgs === 0) {
|
||||
return yield* AllocateTypedArray(constructorName, NewTarget, proto, 0);
|
||||
} else {
|
||||
const firstArgument = args[0]!;
|
||||
if (firstArgument instanceof ObjectValue) {
|
||||
const O = Q(yield* AllocateTypedArray(constructorName, NewTarget, proto));
|
||||
if (isTypedArrayObject(firstArgument)) {
|
||||
Q(yield* InitializeTypedArrayFromTypedArray(O, firstArgument));
|
||||
} else if (isArrayBufferObject(firstArgument)) {
|
||||
let byteOffset;
|
||||
let length;
|
||||
if (numberOfArgs > 1) {
|
||||
byteOffset = args[1]!;
|
||||
} else {
|
||||
byteOffset = Value.undefined;
|
||||
}
|
||||
if (numberOfArgs > 2) {
|
||||
length = args[2]!;
|
||||
} else {
|
||||
length = Value.undefined;
|
||||
}
|
||||
Q(yield* InitializeTypedArrayFromArrayBuffer(O, firstArgument, byteOffset, length));
|
||||
} else {
|
||||
Assert(firstArgument instanceof ObjectValue && !isTypedArrayObject(firstArgument) && !isArrayBufferObject(firstArgument));
|
||||
const usingIterator = Q(yield* GetMethod(firstArgument, wellKnownSymbols.iterator));
|
||||
if (!(usingIterator instanceof UndefinedValue)) {
|
||||
const values = Q(yield* IteratorToList(Q(yield* GetIteratorFromMethod(firstArgument, usingIterator))));
|
||||
Q(yield* InitializeTypedArrayFromList(O, values));
|
||||
} else {
|
||||
Q(yield* InitializeTypedArrayFromArrayLike(O, firstArgument));
|
||||
}
|
||||
}
|
||||
return O;
|
||||
} else {
|
||||
Assert(!(firstArgument instanceof ObjectValue));
|
||||
const elementLength = Q(yield* ToIndex(firstArgument));
|
||||
return yield* AllocateTypedArray(constructorName, NewTarget, proto, elementLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const taConstructor = bootstrapConstructor(realmRec, TypedArrayConstructor, TypedArray, 3, realmRec.Intrinsics[`%${TypedArray as TypedArrayConstructorNames}.prototype%`], [
|
||||
['BYTES_PER_ELEMENT', F(info.ElementSize), undefined, {
|
||||
Writable: Value.false,
|
||||
Configurable: Value.false,
|
||||
}],
|
||||
]);
|
||||
X(taConstructor.SetPrototypeOf(realmRec.Intrinsics['%TypedArray%']));
|
||||
realmRec.Intrinsics[`%${TypedArray as TypedArrayConstructorNames}%`] = taConstructor;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,722 @@
|
||||
import {
|
||||
Q, X, type ValueEvaluator,
|
||||
type ValueCompletion,
|
||||
} from '../completion.mts';
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
BigIntValue,
|
||||
Descriptor, JSStringValue, NumberValue, ObjectValue, Value, wellKnownSymbols,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { bootstrapArrayPrototypeShared, SortIndexedProperties } from './ArrayPrototypeShared.mts';
|
||||
import {
|
||||
CompareTypedArrayElements,
|
||||
TypedArrayCreateSameType,
|
||||
TypedArrayElementSize,
|
||||
TypedArrayElementType,
|
||||
TypedArraySpeciesCreate, ValidateTypedArray, type TypedArrayObject,
|
||||
} from './TypedArray.mts';
|
||||
import {
|
||||
Assert,
|
||||
Call,
|
||||
CloneArrayBuffer,
|
||||
CreateArrayIterator,
|
||||
Get,
|
||||
GetValueFromBuffer,
|
||||
TypedArraySetElement,
|
||||
IsCallable,
|
||||
IsSharedArrayBuffer,
|
||||
SameValue,
|
||||
Set,
|
||||
SetValueInBuffer,
|
||||
LengthOfArrayLike,
|
||||
ToBoolean,
|
||||
ToBigInt,
|
||||
ToIntegerOrInfinity,
|
||||
ToNumber,
|
||||
ToObject,
|
||||
ToString,
|
||||
RequireInternalSlot,
|
||||
F,
|
||||
Realm,
|
||||
type ArrayBufferObject,
|
||||
MakeTypedArrayWithBufferWitnessRecord,
|
||||
TypedArrayByteLength,
|
||||
IsTypedArrayOutOfBounds,
|
||||
TypedArrayLength,
|
||||
IsValidIntegerIndex,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.buffer */
|
||||
function TypedArrayProto_buffer(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as TypedArrayObject;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
|
||||
Q(RequireInternalSlot(O, 'TypedArrayName'));
|
||||
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
// 4. Let buffer be O.[[ViewedArrayBuffer]].
|
||||
const buffer = O.ViewedArrayBuffer;
|
||||
// 5. Return buffer.
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.bytelength */
|
||||
function TypedArrayProto_byteLength(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as TypedArrayObject;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
|
||||
Q(RequireInternalSlot(O, 'TypedArrayName'));
|
||||
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return F(0);
|
||||
}
|
||||
const size = TypedArrayByteLength(taRecord);
|
||||
return F(size);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.byteoffset */
|
||||
function TypedArrayProto_byteOffset(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as TypedArrayObject;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
|
||||
Q(RequireInternalSlot(O, 'TypedArrayName'));
|
||||
// 3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return F(0);
|
||||
}
|
||||
const offset = O.ByteOffset;
|
||||
return F(offset);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin */
|
||||
function* TypedArrayProto_copyWithin([target = Value.undefined, start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
let taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
let len = TypedArrayLength(taRecord);
|
||||
const relativeTarget = Q(yield* ToIntegerOrInfinity(target));
|
||||
let targetIndex;
|
||||
if (relativeTarget === -Infinity) {
|
||||
targetIndex = 0;
|
||||
} else if (relativeTarget < 0) {
|
||||
targetIndex = Math.max(len + relativeTarget, 0);
|
||||
} else {
|
||||
targetIndex = Math.min(relativeTarget, len);
|
||||
}
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let startIndex;
|
||||
if (relativeStart === -Infinity) {
|
||||
startIndex = 0;
|
||||
} else if (relativeStart < 0) {
|
||||
startIndex = Math.max(len + relativeStart, 0);
|
||||
} else {
|
||||
startIndex = Math.min(relativeStart, len);
|
||||
}
|
||||
let relativeEnd;
|
||||
if (end === Value.undefined) {
|
||||
relativeEnd = len;
|
||||
} else {
|
||||
relativeEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let endIndex;
|
||||
if (relativeEnd === -Infinity) {
|
||||
endIndex = 0;
|
||||
} else if (relativeEnd < 0) {
|
||||
endIndex = Math.max(len + relativeEnd, 0);
|
||||
} else {
|
||||
endIndex = Math.min(relativeEnd, len);
|
||||
}
|
||||
let count = Math.min(endIndex - startIndex, len - targetIndex);
|
||||
if (count > 0) {
|
||||
const buffer = O.ViewedArrayBuffer as ArrayBufferObject;
|
||||
taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOOB');
|
||||
}
|
||||
len = TypedArrayLength(taRecord);
|
||||
count = Math.min(count, len - startIndex, len - targetIndex);
|
||||
const elementSize = TypedArrayElementSize(O);
|
||||
const byteOffset = O.ByteOffset;
|
||||
let toByteIndex = (targetIndex * elementSize) + byteOffset;
|
||||
let fromByteIndex = (startIndex * elementSize) + byteOffset;
|
||||
let countBytes = count * elementSize;
|
||||
let direction;
|
||||
if (fromByteIndex < toByteIndex && toByteIndex < fromByteIndex + countBytes) {
|
||||
direction = -1;
|
||||
fromByteIndex = fromByteIndex + countBytes - 1;
|
||||
toByteIndex = toByteIndex + countBytes - 1;
|
||||
} else {
|
||||
direction = 1;
|
||||
}
|
||||
while (countBytes > 0) {
|
||||
const value = GetValueFromBuffer(buffer, fromByteIndex, 'Uint8', true, 'unordered');
|
||||
Q(yield* SetValueInBuffer(buffer, toByteIndex, 'Uint8', value, true, 'unordered'));
|
||||
fromByteIndex += direction;
|
||||
toByteIndex += direction;
|
||||
countBytes -= 1;
|
||||
}
|
||||
}
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.entries */
|
||||
function TypedArrayProto_entries(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as TypedArrayObject;
|
||||
// 2. Perform ? ValidateTypedArray(O).
|
||||
Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
// 3. Return CreateArrayIterator(O, key+value).
|
||||
return CreateArrayIterator(O, 'key+value');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.fill */
|
||||
function* TypedArrayProto_fill([value = Value.undefined, start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
let taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
let len = TypedArrayLength(taRecord);
|
||||
if (O.ContentType === 'BigInt') {
|
||||
value = Q(yield* ToBigInt(value));
|
||||
} else {
|
||||
value = Q(yield* ToNumber(value));
|
||||
}
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let startIndex;
|
||||
if (relativeStart === -Infinity) {
|
||||
startIndex = 0;
|
||||
} else if (relativeStart < 0) {
|
||||
startIndex = Math.max(len + relativeStart, 0);
|
||||
} else {
|
||||
startIndex = Math.min(relativeStart, len);
|
||||
}
|
||||
let relativeEnd;
|
||||
if (end === Value.undefined) {
|
||||
relativeEnd = len;
|
||||
} else {
|
||||
relativeEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let endIndex;
|
||||
if (relativeEnd === -Infinity) {
|
||||
endIndex = 0;
|
||||
} else if (relativeEnd < 0) {
|
||||
endIndex = Math.max(len + relativeEnd, 0);
|
||||
} else {
|
||||
endIndex = Math.min(relativeEnd, len);
|
||||
}
|
||||
taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOOB');
|
||||
}
|
||||
len = TypedArrayLength(taRecord);
|
||||
endIndex = Math.min(endIndex, len);
|
||||
let k = startIndex;
|
||||
while (k < endIndex) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
X(Set(O, Pk, value, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
return O;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter */
|
||||
function* TypedArrayProto_filter([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
const taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
const len = TypedArrayLength(taRecord);
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
const kept = [];
|
||||
let captured = 0;
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = X(Get(O, Pk));
|
||||
const selected = ToBoolean(Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O])));
|
||||
if (selected === Value.true) {
|
||||
kept.push(kValue);
|
||||
captured += 1;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
const A = Q(yield* TypedArraySpeciesCreate(O, [F(captured)]));
|
||||
let n = 0;
|
||||
for (const e of kept) {
|
||||
X(Set(A, X(ToString(F(n))), e, Value.true));
|
||||
n += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys */
|
||||
function TypedArrayProto_keys(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as TypedArrayObject;
|
||||
// 2. Perform ? ValidateTypedArray(O).
|
||||
Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
// 3. Return CreateArrayIterator(O, key).
|
||||
return CreateArrayIterator(O, 'key');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length */
|
||||
function TypedArrayProto_length(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
Q(RequireInternalSlot(O, 'TypedArrayName'));
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
const taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return F(0);
|
||||
}
|
||||
const length = TypedArrayLength(taRecord);
|
||||
return F(length);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.map */
|
||||
function* TypedArrayProto_map([callbackfn = Value.undefined, thisArg = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
const taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
const len = TypedArrayLength(taRecord);
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
const A = Q(yield* TypedArraySpeciesCreate(O, [F(len)]));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = X(Get(O, Pk));
|
||||
const mappedValue = Q(yield* Call(callbackfn, thisArg, [kValue, F(k), O]));
|
||||
X(Set(A, Pk, mappedValue, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-settypedarrayfromtypedarray */
|
||||
function* SetTypedArrayFromTypedArray(target: TypedArrayObject, targetOffset: number, source: TypedArrayObject) {
|
||||
const targetBuffer = target.ViewedArrayBuffer as ArrayBufferObject;
|
||||
const targetRecord = MakeTypedArrayWithBufferWitnessRecord(target, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(targetRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOOB');
|
||||
}
|
||||
const targetLength = TypedArrayLength(targetRecord);
|
||||
let srcBuffer = source.ViewedArrayBuffer as ArrayBufferObject;
|
||||
const srcRecord = MakeTypedArrayWithBufferWitnessRecord(source, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(srcRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOOB');
|
||||
}
|
||||
const srcLength = TypedArrayLength(srcRecord);
|
||||
const targetType = TypedArrayElementType(target);
|
||||
const targetElementSize = TypedArrayElementSize(target);
|
||||
const targetByteOffset = target.ByteOffset;
|
||||
const srcType = TypedArrayElementType(source);
|
||||
const srcElementSize = TypedArrayElementSize(source);
|
||||
const srcByteOffset = source.ByteOffset;
|
||||
if (targetOffset === +Infinity) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayOOB');
|
||||
}
|
||||
if (srcLength + targetOffset > targetLength) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayOOB');
|
||||
}
|
||||
if (target.ContentType !== source.ContentType) {
|
||||
return surroundingAgent.Throw('TypeError', 'BufferContentTypeMismatch');
|
||||
}
|
||||
let sameSharedArrayBuffer;
|
||||
if (IsSharedArrayBuffer(srcBuffer) && IsSharedArrayBuffer(targetBuffer) && srcBuffer.ArrayBufferData === targetBuffer.ArrayBufferData) {
|
||||
sameSharedArrayBuffer = true;
|
||||
} else {
|
||||
sameSharedArrayBuffer = false;
|
||||
}
|
||||
let srcByteIndex;
|
||||
if (SameValue(srcBuffer, targetBuffer) === Value.true || sameSharedArrayBuffer) {
|
||||
const srcByteLength = TypedArrayByteLength(srcRecord);
|
||||
srcBuffer = Q(yield* CloneArrayBuffer(srcBuffer, srcByteOffset, srcByteLength));
|
||||
srcByteIndex = 0;
|
||||
} else {
|
||||
srcByteIndex = srcByteOffset;
|
||||
}
|
||||
let targetByteIndex = (targetOffset * targetElementSize) + targetByteOffset;
|
||||
const limit = targetByteIndex + (targetElementSize * srcLength);
|
||||
if (srcType === targetType) {
|
||||
while (targetByteIndex < limit) {
|
||||
const value = GetValueFromBuffer(srcBuffer, srcByteIndex, 'Uint8', true, 'unordered');
|
||||
Q(yield* SetValueInBuffer(targetBuffer, targetByteIndex, 'Uint8', value, true, 'unordered'));
|
||||
srcByteIndex += 1;
|
||||
targetByteIndex += 1;
|
||||
}
|
||||
} else {
|
||||
while (targetByteIndex < limit) {
|
||||
const value = GetValueFromBuffer(srcBuffer, srcByteIndex, srcType, true, 'unordered');
|
||||
Q(yield* SetValueInBuffer(targetBuffer, targetByteIndex, targetType, value, true, 'unordered'));
|
||||
srcByteIndex += srcElementSize;
|
||||
targetByteIndex += targetElementSize;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-settypedarrayfromarraylike */
|
||||
function* SetTypedArrayFromArrayLike(target: TypedArrayObject, targetOffset: number, source: Value) {
|
||||
const targetRecord = MakeTypedArrayWithBufferWitnessRecord(target, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(targetRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOOB');
|
||||
}
|
||||
const targetLength = TypedArrayLength(targetRecord);
|
||||
const src = Q(ToObject(source));
|
||||
const srcLength = Q(yield* LengthOfArrayLike(src));
|
||||
if (targetOffset === +Infinity) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayOOB');
|
||||
}
|
||||
if (srcLength + targetOffset > targetLength) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayOOB');
|
||||
}
|
||||
let k = 0;
|
||||
while (k < srcLength) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const value = Q(yield* Get(src, Pk));
|
||||
const targetIndex = F(targetOffset + k);
|
||||
Q(yield* TypedArraySetElement(target, targetIndex, value));
|
||||
k += 1;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.set-overloaded-offset */
|
||||
function* TypedArrayProto_set([source = Value.undefined, offset = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let target be the this value.
|
||||
const target = thisValue as TypedArrayObject;
|
||||
// 2. Perform ? RequireInternalSlot(target, [[TypedArrayName]]).
|
||||
Q(RequireInternalSlot(target, 'TypedArrayName'));
|
||||
// 3. Assert: target has a [[ViewedArrayBuffer]] internal slot.
|
||||
Assert('ViewedArrayBuffer' in target);
|
||||
// 4. Let targetOffset be ? ToIntegerOrInfinity(offset).
|
||||
const targetOffset = Q(yield* ToIntegerOrInfinity(offset));
|
||||
// 5. If targetOffset < 0, throw a RangeError exception.
|
||||
if (targetOffset < 0) {
|
||||
return surroundingAgent.Throw('RangeError', 'NegativeIndex', 'Offset');
|
||||
}
|
||||
// 6. If source is an Object that has a [[TypedArrayName]] internal slot, then
|
||||
if (source instanceof ObjectValue && 'TypedArrayName' in source) {
|
||||
// a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source).
|
||||
Q(yield* SetTypedArrayFromTypedArray(target, targetOffset, source as TypedArrayObject));
|
||||
} else { // 7. Else,
|
||||
// a. Perform ? SetTypedArrayFromArrayLike(target, targetOffset, source).
|
||||
Q(yield* SetTypedArrayFromArrayLike(target, targetOffset, source));
|
||||
}
|
||||
// 8. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.slice */
|
||||
function* TypedArrayProto_slice([start = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
let taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
const srcArrayLength = TypedArrayLength(taRecord);
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(start));
|
||||
let startIndex;
|
||||
if (relativeStart === -Infinity) {
|
||||
startIndex = 0;
|
||||
} else if (relativeStart < 0) {
|
||||
startIndex = Math.max(srcArrayLength + relativeStart, 0);
|
||||
} else {
|
||||
startIndex = Math.min(relativeStart, srcArrayLength);
|
||||
}
|
||||
let relativeEnd;
|
||||
if (end === Value.undefined) {
|
||||
relativeEnd = srcArrayLength;
|
||||
} else {
|
||||
relativeEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let endIndex;
|
||||
if (relativeEnd === -Infinity) {
|
||||
endIndex = 0;
|
||||
} else if (relativeEnd < 0) {
|
||||
endIndex = Math.max(srcArrayLength + relativeEnd, 0);
|
||||
} else {
|
||||
endIndex = Math.min(relativeEnd, srcArrayLength);
|
||||
}
|
||||
let countBytes = Math.max(endIndex - startIndex, 0);
|
||||
const A = Q(yield* TypedArraySpeciesCreate(O, [F(countBytes)]));
|
||||
if (countBytes > 0) {
|
||||
taRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOOB');
|
||||
}
|
||||
endIndex = Math.min(endIndex, TypedArrayLength(taRecord));
|
||||
countBytes = Math.max(endIndex - startIndex, 0);
|
||||
const srcType = TypedArrayElementType(O);
|
||||
const targetType = TypedArrayElementType(A);
|
||||
if (srcType === targetType) {
|
||||
const srcBuffer = O.ViewedArrayBuffer as ArrayBufferObject;
|
||||
const targetBuffer = A.ViewedArrayBuffer as ArrayBufferObject;
|
||||
const elementSize = TypedArrayElementSize(O);
|
||||
const srcByteOffset = O.ByteOffset;
|
||||
let srcByteIndex = (startIndex * elementSize) + srcByteOffset;
|
||||
let targetByteIndex = A.ByteOffset;
|
||||
const endByteIndex = targetByteIndex + (countBytes * elementSize);
|
||||
while (targetByteIndex < endByteIndex) {
|
||||
const value = GetValueFromBuffer(srcBuffer, srcByteIndex, 'Uint8', true, 'unordered');
|
||||
Q(yield* SetValueInBuffer(targetBuffer, targetByteIndex, 'Uint8', value, true, 'unordered'));
|
||||
srcByteIndex += 1;
|
||||
targetByteIndex += 1;
|
||||
}
|
||||
} else {
|
||||
let n = 0;
|
||||
let k = startIndex;
|
||||
while (k < endIndex) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
const kValue = X(Get(O, Pk));
|
||||
X(Set(A, X(ToString(F(n))), kValue, Value.true));
|
||||
k += 1;
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort */
|
||||
function* TypedArrayProto_sort([comparator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
if (comparator !== Value.undefined && !IsCallable(comparator)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', comparator);
|
||||
}
|
||||
const obj = thisValue as TypedArrayObject;
|
||||
const taRecord = Q(ValidateTypedArray(obj, 'seq-cst'));
|
||||
const len = TypedArrayLength(taRecord);
|
||||
const SortCompare = function* SortCompare(x: Value, y: Value): ValueEvaluator<NumberValue> {
|
||||
Assert(x instanceof NumberValue || x instanceof BigIntValue);
|
||||
Assert(y instanceof NumberValue || y instanceof BigIntValue);
|
||||
return yield* CompareTypedArrayElements(x, y, comparator);
|
||||
};
|
||||
const sortedList = Q(yield* SortIndexedProperties(obj, len, SortCompare, 'read-through-holes'));
|
||||
let j = 0;
|
||||
while (j < len) {
|
||||
X(Set(obj, X(ToString(F(j))), sortedList[j], Value.true));
|
||||
j += 1;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.tosorted */
|
||||
function* TypedArrayProto_toSorted([comparator = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
if (comparator !== Value.undefined && !IsCallable(comparator)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', comparator);
|
||||
}
|
||||
const O = thisValue as TypedArrayObject;
|
||||
const taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
const len = TypedArrayLength(taRecord);
|
||||
const A = Q(yield* TypedArrayCreateSameType(O, len));
|
||||
const SortCompare = function* SortCompare(x: Value, y: Value): ValueEvaluator<NumberValue> {
|
||||
Assert(x instanceof NumberValue || x instanceof BigIntValue);
|
||||
Assert(y instanceof NumberValue || y instanceof BigIntValue);
|
||||
return yield* CompareTypedArrayElements(x, y, comparator);
|
||||
};
|
||||
const sortedList = Q(yield* SortIndexedProperties(O, len, SortCompare, 'read-through-holes'));
|
||||
let j = 0;
|
||||
while (j < len) {
|
||||
X(Set(A, X(ToString(F(j))), sortedList[j], Value.true));
|
||||
j += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.subarray */
|
||||
function* TypedArrayProto_subarray([begin = Value.undefined, end = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
Q(RequireInternalSlot(O, 'TypedArrayName'));
|
||||
Assert('ViewedArrayBuffer' in O);
|
||||
const buffer = O.ViewedArrayBuffer as ArrayBufferObject;
|
||||
const srcRecord = MakeTypedArrayWithBufferWitnessRecord(O, 'seq-cst');
|
||||
let srcLength;
|
||||
if (IsTypedArrayOutOfBounds(srcRecord)) {
|
||||
srcLength = 0;
|
||||
} else {
|
||||
srcLength = TypedArrayLength(srcRecord);
|
||||
}
|
||||
const relativeStart = Q(yield* ToIntegerOrInfinity(begin));
|
||||
let startIndex;
|
||||
if (relativeStart === -Infinity) {
|
||||
startIndex = 0;
|
||||
} else if (relativeStart < 0) {
|
||||
startIndex = Math.max(srcLength + relativeStart, 0);
|
||||
} else {
|
||||
startIndex = Math.min(relativeStart, srcLength);
|
||||
}
|
||||
const elementSize = TypedArrayElementSize(O);
|
||||
const srcByteOffset = O.ByteOffset;
|
||||
const beginByteOffset = srcByteOffset + (startIndex * elementSize);
|
||||
let argumentsList;
|
||||
if (O.ArrayLength === 'auto' && end === Value.undefined) {
|
||||
argumentsList = [buffer, F(beginByteOffset)];
|
||||
} else {
|
||||
let relativeEnd;
|
||||
if (end === Value.undefined) {
|
||||
relativeEnd = srcLength;
|
||||
} else {
|
||||
relativeEnd = Q(yield* ToIntegerOrInfinity(end));
|
||||
}
|
||||
let endIndex;
|
||||
if (relativeEnd === -Infinity) {
|
||||
endIndex = 0;
|
||||
} else if (relativeEnd < 0) {
|
||||
endIndex = Math.max(srcLength + relativeEnd, 0);
|
||||
} else {
|
||||
endIndex = Math.min(relativeEnd, srcLength);
|
||||
}
|
||||
const newLength = Math.max(endIndex - startIndex, 0);
|
||||
argumentsList = [buffer, F(beginByteOffset), F(newLength)];
|
||||
}
|
||||
return Q(yield* TypedArraySpeciesCreate(O, argumentsList));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.values */
|
||||
function TypedArrayProto_values(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let o be the this value.
|
||||
const O = thisValue as TypedArrayObject;
|
||||
// 2. Perform ? ValidateTypedArray(O).
|
||||
Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
// Return CreateArrayIterator(O, value).
|
||||
return CreateArrayIterator(O, 'value');
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-get-%typedarray%.prototype-@@tostringtag */
|
||||
function TypedArrayProto_toStringTag(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let O be the this value.
|
||||
const O = thisValue as TypedArrayObject;
|
||||
// 2. If Type(O) is not Object, return undefined.
|
||||
if (!(O instanceof ObjectValue)) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 3. If O does not have a [[TypedArrayName]] internal slot, return undefined.
|
||||
if (!('TypedArrayName' in O)) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 4. Let name be O.[[TypedArrayName]].
|
||||
const name = O.TypedArrayName;
|
||||
// 5. Assert: Type(name) is String.
|
||||
Assert(name instanceof JSStringValue);
|
||||
// 6. Return name.
|
||||
return name;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.at */
|
||||
function* TypedArrayProto_at([index = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
const taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
const len = TypedArrayLength(taRecord);
|
||||
const relativeIndex = Q(yield* ToIntegerOrInfinity(index));
|
||||
let k;
|
||||
if (relativeIndex >= 0) {
|
||||
k = relativeIndex;
|
||||
} else {
|
||||
k = len + relativeIndex;
|
||||
}
|
||||
if (k < 0 || k >= len) {
|
||||
return Value.undefined;
|
||||
}
|
||||
return X(Get(O, X(ToString(F(k)))));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.with */
|
||||
function* TypedArrayProto_with([index = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue;
|
||||
const taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
__ts_cast__<TypedArrayObject>(O);
|
||||
const len = TypedArrayLength(taRecord);
|
||||
const relativeIndex = Q(yield* ToIntegerOrInfinity(index));
|
||||
let actualIndex;
|
||||
if (relativeIndex >= 0) {
|
||||
actualIndex = relativeIndex;
|
||||
} else {
|
||||
actualIndex = len + relativeIndex;
|
||||
}
|
||||
let numericValue;
|
||||
if (O.ContentType === 'BigInt') {
|
||||
numericValue = Q(yield* ToBigInt(value));
|
||||
} else {
|
||||
numericValue = Q(yield* ToNumber(value));
|
||||
}
|
||||
if (IsValidIntegerIndex(O, F(actualIndex)) === Value.false) {
|
||||
return surroundingAgent.Throw('RangeError', 'TypedArrayOOB');
|
||||
}
|
||||
const A = Q(yield* TypedArrayCreateSameType(O, len));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const Pk = X(ToString(F(k)));
|
||||
let fromValue;
|
||||
if (k === actualIndex) {
|
||||
fromValue = numericValue;
|
||||
} else {
|
||||
fromValue = X(Get(O, Pk));
|
||||
}
|
||||
X(Set(A, Pk, fromValue, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype.toreversed */
|
||||
function* TypedArrayProto_toReversed(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
const O = thisValue as TypedArrayObject;
|
||||
const taRecord = Q(ValidateTypedArray(O, 'seq-cst'));
|
||||
const len = TypedArrayLength(taRecord);
|
||||
const A = Q(yield* TypedArrayCreateSameType(O, len));
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
const from = X(ToString(F(len - k - 1)));
|
||||
const Pk = X(ToString(F(k)));
|
||||
const fromValue = X(Get(O, from));
|
||||
X(Set(A, Pk, fromValue, Value.true));
|
||||
k += 1;
|
||||
}
|
||||
return A;
|
||||
}
|
||||
|
||||
export function bootstrapTypedArrayPrototype(realmRec: Realm) {
|
||||
const ArrayProto_toString = X(Get(realmRec.Intrinsics['%Array.prototype%'], Value('toString')));
|
||||
Assert(ArrayProto_toString instanceof ObjectValue);
|
||||
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['buffer', [TypedArrayProto_buffer]],
|
||||
['byteLength', [TypedArrayProto_byteLength]],
|
||||
['byteOffset', [TypedArrayProto_byteOffset]],
|
||||
['copyWithin', TypedArrayProto_copyWithin, 2],
|
||||
['entries', TypedArrayProto_entries, 0],
|
||||
['fill', TypedArrayProto_fill, 1],
|
||||
['filter', TypedArrayProto_filter, 1],
|
||||
['at', TypedArrayProto_at, 1],
|
||||
['keys', TypedArrayProto_keys, 0],
|
||||
['length', [TypedArrayProto_length]],
|
||||
['map', TypedArrayProto_map, 1],
|
||||
['set', TypedArrayProto_set, 1],
|
||||
['slice', TypedArrayProto_slice, 2],
|
||||
['sort', TypedArrayProto_sort, 1],
|
||||
['toSorted', TypedArrayProto_toSorted, 1],
|
||||
['subarray', TypedArrayProto_subarray, 2],
|
||||
['values', TypedArrayProto_values, 0],
|
||||
['with', TypedArrayProto_with, 2],
|
||||
['toReversed', TypedArrayProto_toReversed, 0],
|
||||
['toString', ArrayProto_toString],
|
||||
[wellKnownSymbols.toStringTag, [TypedArrayProto_toStringTag]],
|
||||
], realmRec.Intrinsics['%Object.prototype%']);
|
||||
|
||||
bootstrapArrayPrototypeShared(realmRec, proto, 'TypedArray');
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%typedarray%.prototype-@@iterator */
|
||||
{
|
||||
const fn = X(Get(proto, Value('values')));
|
||||
X(proto.DefineOwnProperty(wellKnownSymbols.iterator, Descriptor({
|
||||
Value: fn,
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
}
|
||||
|
||||
realmRec.Intrinsics['%TypedArray.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Value } from '../value.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import { typedArrayInfoByName, type TypedArrayConstructorNames } from './TypedArray.mts';
|
||||
import { F, Realm } from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-properties-of-typedarray-prototype-objects */
|
||||
export function bootstrapTypedArrayPrototypes(realmRec: Realm) {
|
||||
Object.entries(typedArrayInfoByName).forEach(([TypedArray, info]) => {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['BYTES_PER_ELEMENT', F(info.ElementSize), undefined, {
|
||||
Writable: Value.false,
|
||||
Configurable: Value.false,
|
||||
}],
|
||||
], realmRec.Intrinsics['%TypedArray.prototype%']);
|
||||
realmRec.Intrinsics[`%${TypedArray as TypedArrayConstructorNames}.prototype%`] = proto;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { Value, type Arguments, type FunctionCallContext } from '../value.mts';
|
||||
import {
|
||||
AllocateTypedArray, type TypedArrayObject,
|
||||
} from './TypedArray.mts';
|
||||
import { assignProps } from './bootstrap.mts';
|
||||
import { F } from '#self';
|
||||
import {
|
||||
Assert, CodePointsToString, CreateDataPropertyOrThrow, EnsureCompletion, Get, GetValueFromBuffer, IsTypedArrayOutOfBounds, JSStringValue, MakeTypedArrayWithBufferWitnessRecord, NumberValue, ObjectValue, OrdinaryObjectCreate, Q, R, Realm, RequireInternalSlot, SetValueInBuffer, StringPad, surroundingAgent, ThrowCompletion, ToBoolean, TypedArrayLength, UndefinedValue, X, type ArrayBufferObject, type PlainCompletion, type ValueCompletion,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-uint8array.prototype.tobase64 */
|
||||
function* Uint8Array_prototype_toBase64([options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
const O = thisValue;
|
||||
Q(ValidateUint8Array(O));
|
||||
__ts_cast__<TypedArrayObject>(O);
|
||||
const opts = Q(GetOptionsObject(options));
|
||||
let alphabet = Q(yield* Get(opts, Value('alphabet')));
|
||||
if (alphabet instanceof UndefinedValue) {
|
||||
alphabet = Value('base64');
|
||||
}
|
||||
if (!(alphabet instanceof JSStringValue) || (alphabet.stringValue() !== 'base64' && alphabet.stringValue() !== 'base64url')) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidAlphabet');
|
||||
}
|
||||
const omitPadding = ToBoolean(Q(yield* Get(opts, Value('omitPadding'))));
|
||||
const toEncode = Q(GetUint8ArrayBytes(O));
|
||||
let outAscii: string;
|
||||
if (alphabet.stringValue() === 'base64') {
|
||||
// Let outAscii be the sequence of code points which results from encoding toEncode according to the base64 encoding specified in section 4 of RFC 4648. Padding is included if and only if omitPadding is false.
|
||||
outAscii = btoa(String.fromCharCode(...toEncode));
|
||||
if (omitPadding !== Value.false) {
|
||||
outAscii = outAscii.replace(/=/g, '');
|
||||
}
|
||||
} else {
|
||||
Assert(alphabet.stringValue() === 'base64url');
|
||||
// Let outAscii be the sequence of code points which results from encoding toEncode according to the base64url encoding specified in section 5 of RFC 4648. Padding is included if and only if omitPadding is false.
|
||||
outAscii = btoa(String.fromCharCode(...toEncode)).replace(/\+/g, '-').replace(/\//g, '_');
|
||||
if (omitPadding !== Value.false) {
|
||||
outAscii = outAscii.replace(/=/g, '');
|
||||
}
|
||||
}
|
||||
return Value(CodePointsToString(outAscii));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-uint8array.prototype.tohex */
|
||||
function Uint8Array_prototype_toHex(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
const O = thisValue;
|
||||
Q(ValidateUint8Array(O));
|
||||
__ts_cast__<TypedArrayObject>(O);
|
||||
const toEncode = Q(GetUint8ArrayBytes(O));
|
||||
let out = '';
|
||||
for (const byte of toEncode) {
|
||||
let hex = NumberValue.toString(F(byte), 16);
|
||||
hex = X(StringPad(hex, Value(2), Value('0'), 'start'));
|
||||
out += hex.stringValue();
|
||||
}
|
||||
return Value(out);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-uint8array.frombase64 */
|
||||
function* Uint8Array_fromBase64([string = Value.undefined, options = Value.undefined]: Arguments) {
|
||||
if (!(string instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAString', string);
|
||||
}
|
||||
const opts = Q(GetOptionsObject(options));
|
||||
let alphabet = Q(yield* Get(opts, Value('alphabet')));
|
||||
if (alphabet instanceof UndefinedValue) {
|
||||
alphabet = Value('base64');
|
||||
}
|
||||
if (!(alphabet instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidAlphabet');
|
||||
}
|
||||
const alphabetStr = alphabet.stringValue();
|
||||
if (alphabetStr !== 'base64' && alphabetStr !== 'base64url') {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidAlphabet');
|
||||
}
|
||||
let lastChunkHandling = Q(yield* Get(opts, Value('lastChunkHandling')));
|
||||
if (lastChunkHandling instanceof UndefinedValue) {
|
||||
lastChunkHandling = Value('loose');
|
||||
}
|
||||
if (!(lastChunkHandling instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidLastChunkHandling');
|
||||
}
|
||||
const lastChunkHandlingStr = lastChunkHandling.stringValue();
|
||||
if ((lastChunkHandlingStr !== 'loose' && lastChunkHandlingStr !== 'strict' && lastChunkHandlingStr !== 'stop-before-partial')) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidLastChunkHandling');
|
||||
}
|
||||
const result = FromBase64(string.stringValue(), alphabetStr, lastChunkHandlingStr);
|
||||
if (result.Error) {
|
||||
return ThrowCompletion(result.Error);
|
||||
}
|
||||
const resultLength = result.Bytes.length;
|
||||
const ta = Q(yield* AllocateTypedArray(Value('Uint8Array'), surroundingAgent.intrinsic('%Uint8Array%'), '%Uint8Array.prototype%', resultLength));
|
||||
|
||||
// TODO: Assert: ta.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is the number of elements in result.[[Bytes]].
|
||||
|
||||
// Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of result.[[Bytes]].
|
||||
for (let i = 0; i < resultLength; i += 1) {
|
||||
const byte = result.Bytes[i];
|
||||
yield* SetValueInBuffer(ta.ViewedArrayBuffer as ArrayBufferObject, ta.ByteOffset + i, 'Uint8', F(byte), true, 'unordered');
|
||||
}
|
||||
return ta;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-uint8array.prototype.setfrombase64 */
|
||||
function* Uint8Array_prototype_setFromBase64([string = Value.undefined, options = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
const into = thisValue;
|
||||
Q(ValidateUint8Array(into));
|
||||
__ts_cast__<TypedArrayObject>(into);
|
||||
if (!(string instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAString', string);
|
||||
}
|
||||
const opts = Q(GetOptionsObject(options));
|
||||
let alphabet = Q(yield* Get(opts, Value('alphabet')));
|
||||
if (alphabet instanceof UndefinedValue) {
|
||||
alphabet = Value('base64');
|
||||
}
|
||||
if (!(alphabet instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidAlphabet');
|
||||
}
|
||||
const alphabetStr = alphabet.stringValue();
|
||||
if (alphabetStr !== 'base64' && alphabetStr !== 'base64url') {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidAlphabet');
|
||||
}
|
||||
let lastChunkHandling = Q(yield* Get(opts, Value('lastChunkHandling')));
|
||||
if (lastChunkHandling instanceof UndefinedValue) {
|
||||
lastChunkHandling = Value('loose');
|
||||
}
|
||||
if (!(lastChunkHandling instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidLastChunkHandling');
|
||||
}
|
||||
const lastChunkHandlingStr = lastChunkHandling.stringValue();
|
||||
if ((lastChunkHandlingStr !== 'loose' && lastChunkHandlingStr !== 'strict' && lastChunkHandlingStr !== 'stop-before-partial')) {
|
||||
return surroundingAgent.Throw('TypeError', 'InvalidLastChunkHandling');
|
||||
}
|
||||
const taRecord = MakeTypedArrayWithBufferWitnessRecord(into, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds');
|
||||
}
|
||||
const byteLength = TypedArrayLength(taRecord);
|
||||
const result = FromBase64(string.stringValue(), alphabetStr, lastChunkHandlingStr, byteLength);
|
||||
const bytes = result.Bytes;
|
||||
const written = bytes.length;
|
||||
Assert(written <= byteLength);
|
||||
yield* SetUint8ArrayBytes(into, bytes);
|
||||
if (result.Error) {
|
||||
return ThrowCompletion(result.Error);
|
||||
}
|
||||
const resultObject = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
X(CreateDataPropertyOrThrow(resultObject, Value('read'), F(result.Read)));
|
||||
X(CreateDataPropertyOrThrow(resultObject, Value('written'), F(written)));
|
||||
return resultObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-uint8array.fromhex */
|
||||
function* Uint8Array_fromHex([string = Value.undefined]: Arguments) {
|
||||
if (!(string instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAString', string);
|
||||
}
|
||||
const result = FromHex(string.stringValue());
|
||||
if (result.Error) {
|
||||
return ThrowCompletion(result.Error);
|
||||
}
|
||||
const resultLength = result.Bytes.length;
|
||||
const ta = Q(yield* AllocateTypedArray(Value('Uint8Array'), surroundingAgent.intrinsic('%Uint8Array%'), '%Uint8Array.prototype%', resultLength));
|
||||
// TODO Assert: ta.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is the number of elements in result.[[Bytes]].
|
||||
|
||||
// Set the value at each index of ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the corresponding index of result.[[Bytes]].
|
||||
for (let i = 0; i < resultLength; i += 1) {
|
||||
const byte = result.Bytes[i];
|
||||
yield* SetValueInBuffer(ta.ViewedArrayBuffer as ArrayBufferObject, ta.ByteOffset + i, 'Uint8', F(byte), true, 'unordered');
|
||||
}
|
||||
return ta;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-uint8array.prototype.setfromhex */
|
||||
function* Uint8Array_prototype_setFromHex([string = Value.undefined]: Arguments, { thisValue }: FunctionCallContext) {
|
||||
const into = thisValue;
|
||||
Q(ValidateUint8Array(into));
|
||||
__ts_cast__<TypedArrayObject>(into);
|
||||
if (!(string instanceof JSStringValue)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAString', string);
|
||||
}
|
||||
const taRecord = MakeTypedArrayWithBufferWitnessRecord(into, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds');
|
||||
}
|
||||
const byteLength = TypedArrayLength(taRecord);
|
||||
const result = FromHex(string.stringValue(), byteLength);
|
||||
const bytes = result.Bytes;
|
||||
const written = bytes.length;
|
||||
Assert(written <= byteLength);
|
||||
yield* SetUint8ArrayBytes(into, bytes);
|
||||
if (result.Error) {
|
||||
return ThrowCompletion(result.Error);
|
||||
}
|
||||
const resultObject = OrdinaryObjectCreate(surroundingAgent.intrinsic('%Object.prototype%'));
|
||||
X(CreateDataPropertyOrThrow(resultObject, Value('read'), F(result.Read)));
|
||||
X(CreateDataPropertyOrThrow(resultObject, Value('written'), F(written)));
|
||||
return resultObject;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-validateuint8array */
|
||||
function ValidateUint8Array(ta: Value) {
|
||||
Q(RequireInternalSlot(ta, 'TypedArrayName'));
|
||||
__ts_cast__<TypedArrayObject>(ta);
|
||||
if (ta.TypedArrayName.stringValue() !== 'Uint8Array') {
|
||||
return surroundingAgent.Throw('TypeError', 'NotUint8Array');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-getuint8arraybytes */
|
||||
function GetUint8ArrayBytes(ta: TypedArrayObject): PlainCompletion<number[]> {
|
||||
const buffer = ta.ViewedArrayBuffer;
|
||||
const taRecord = MakeTypedArrayWithBufferWitnessRecord(ta, 'seq-cst');
|
||||
if (IsTypedArrayOutOfBounds(taRecord)) {
|
||||
return surroundingAgent.Throw('TypeError', 'TypedArrayOutOfBounds');
|
||||
}
|
||||
const len = TypedArrayLength(taRecord);
|
||||
const byteOffset = ta.ByteOffset;
|
||||
const bytes = [];
|
||||
let index = 0;
|
||||
while (index < len) {
|
||||
const byteIndex = byteOffset + index;
|
||||
const byte = R(GetValueFromBuffer(buffer as ArrayBufferObject, byteIndex, 'Uint8', true, 'unordered'));
|
||||
Assert(typeof byte === 'number');
|
||||
bytes.push(byte);
|
||||
index += 1;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-setuint8arraybytes */
|
||||
function* SetUint8ArrayBytes(into: TypedArrayObject, bytes: readonly number[]) {
|
||||
const offset = into.ByteOffset;
|
||||
const len = bytes.length;
|
||||
let index = 0;
|
||||
while (index < len) {
|
||||
const byte = bytes[index];
|
||||
const byteIndexInBuffer = index + offset;
|
||||
yield* SetValueInBuffer(into.ViewedArrayBuffer as ArrayBufferObject, byteIndexInBuffer, 'Uint8', F(byte), true, 'unordered');
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-skipasciiwhitespace */
|
||||
function SkipAsciiWhitespace(string: string, index: number) {
|
||||
const length = string.length;
|
||||
while (index < length) {
|
||||
const char = string.charCodeAt(index);
|
||||
if (char !== 0x09 && char !== 0x0A && char !== 0x0C && char !== 0x0D && char !== 0x20) {
|
||||
return index;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-decodefinalbase64chunk */
|
||||
function DecodeFinalBase64Chunk(chunk: string, throwOnExtraBits: boolean): PlainCompletion<number[]> {
|
||||
const chunkLength = chunk.length;
|
||||
if (chunkLength === 2) {
|
||||
chunk += 'AA';
|
||||
} else {
|
||||
Assert(chunkLength === 3);
|
||||
chunk += 'A';
|
||||
}
|
||||
const bytes = DecodeFullLengthBase64Chunk(chunk);
|
||||
if (chunkLength === 2) {
|
||||
if (throwOnExtraBits && bytes[1] !== 0) {
|
||||
return surroundingAgent.Throw('SyntaxError', 'InvalidBase64String');
|
||||
}
|
||||
return [bytes[0]];
|
||||
} else {
|
||||
if (throwOnExtraBits && (bytes[2] !== 0)) {
|
||||
return surroundingAgent.Throw('SyntaxError', 'InvalidBase64String');
|
||||
}
|
||||
return [bytes[0], bytes[1]];
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-decodefulllengthbase64chunk */
|
||||
function DecodeFullLengthBase64Chunk(chunk: string): number[] {
|
||||
// 1. Let byteSequence be the unique sequence of 3 bytes resulting from decoding chunk as base64 (i.e., the sequence such that applying the base64 encoding specified in section 4 of RFC 4648 to byteSequence would produce chunk).
|
||||
// 2. Return a List whose elements are the elements of byteSequence, in order.
|
||||
const byteSequence = [...atob(chunk)].map((c) => c.charCodeAt(0));
|
||||
return byteSequence;
|
||||
}
|
||||
|
||||
interface Record {
|
||||
Read: number;
|
||||
Bytes: number[];
|
||||
Error: undefined | Value;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-frombase64 */
|
||||
function FromBase64(string: string, alphabet: 'base64' | 'base64url', lastChunkHandling: 'loose' | 'strict' | 'stop-before-partial', maxLength = 2 ** 53 - 1): Record {
|
||||
if (maxLength === 0) {
|
||||
return { Read: 0, Bytes: [], Error: undefined };
|
||||
}
|
||||
let read = 0;
|
||||
const bytes: number[] = [];
|
||||
let chunk = '';
|
||||
let chunkLength = 0;
|
||||
let index = 0;
|
||||
const length = string.length;
|
||||
while (true) {
|
||||
index = SkipAsciiWhitespace(string, index);
|
||||
if (index === length) {
|
||||
if (chunkLength > 0) {
|
||||
if (lastChunkHandling === 'stop-before-partial') {
|
||||
return { Read: read, Bytes: bytes, Error: undefined };
|
||||
} else if (lastChunkHandling === 'loose') {
|
||||
if (chunkLength === 1) {
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
}
|
||||
bytes.push(...X(DecodeFinalBase64Chunk(chunk, false)));
|
||||
} else {
|
||||
Assert(lastChunkHandling === 'strict');
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
}
|
||||
}
|
||||
return { Read: length, Bytes: bytes, Error: undefined };
|
||||
}
|
||||
let char = string.substring(index, index + 1);
|
||||
index += 1;
|
||||
if (char === '=') {
|
||||
if (chunkLength < 2) {
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
}
|
||||
index = SkipAsciiWhitespace(string, index);
|
||||
if (chunkLength === 2) {
|
||||
if (index === length) {
|
||||
if (lastChunkHandling === 'stop-before-partial') {
|
||||
return { Read: read, Bytes: bytes, Error: undefined };
|
||||
}
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
}
|
||||
char = string.substring(index, index + 1);
|
||||
if (char === '=') {
|
||||
index = SkipAsciiWhitespace(string, index + 1);
|
||||
}
|
||||
}
|
||||
if (index < length) {
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
}
|
||||
let throwOnExtraBits;
|
||||
if (lastChunkHandling === 'strict') {
|
||||
throwOnExtraBits = true;
|
||||
} else {
|
||||
throwOnExtraBits = false;
|
||||
}
|
||||
const decodeResult = EnsureCompletion(DecodeFinalBase64Chunk(chunk, throwOnExtraBits));
|
||||
if (decodeResult instanceof ThrowCompletion) {
|
||||
return { Read: read, Bytes: bytes, Error: decodeResult.Value };
|
||||
}
|
||||
bytes.push(...X(decodeResult));
|
||||
return { Read: length, Bytes: bytes, Error: undefined };
|
||||
}
|
||||
if (alphabet === 'base64url') {
|
||||
if (char === '+' || char === '/') {
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
} else if (char === '-') {
|
||||
char = '+';
|
||||
} else if (char === '_') {
|
||||
char = '/';
|
||||
}
|
||||
}
|
||||
if (!/[A-Za-z0-9+/]/.test(char)) {
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidBase64String').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
}
|
||||
const remaining = maxLength - bytes.length;
|
||||
if ((remaining === 1 && chunkLength === 2) || (remaining === 2 && chunkLength === 3)) {
|
||||
return { Read: read, Bytes: bytes, Error: undefined };
|
||||
}
|
||||
chunk += char;
|
||||
chunkLength = chunk.length;
|
||||
if (chunkLength === 4) {
|
||||
bytes.push(...X(DecodeFullLengthBase64Chunk(chunk)));
|
||||
chunk = '';
|
||||
chunkLength = 0;
|
||||
read = index;
|
||||
if (bytes.length === maxLength) {
|
||||
return { Read: read, Bytes: bytes, Error: undefined };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-fromhex */
|
||||
function FromHex(string: string, maxLength = 2 ** 53 - 1): Record {
|
||||
const length = string.length;
|
||||
const bytes: number[] = [];
|
||||
let read = 0;
|
||||
if (length % 2 !== 0) {
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidHexString').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
}
|
||||
while (read < length && bytes.length < maxLength) {
|
||||
const hexits = string.substring(read, read + 2);
|
||||
if ([...hexits].some((c) => !/[0-9a-fA-F]/.test(c))) {
|
||||
const error = surroundingAgent.Throw('SyntaxError', 'InvalidHexString').Value;
|
||||
return { Read: read, Bytes: bytes, Error: error };
|
||||
}
|
||||
read += 2;
|
||||
const byte = parseInt(hexits, 16);
|
||||
bytes.push(byte);
|
||||
}
|
||||
return { Read: read, Bytes: bytes, Error: undefined };
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-getoptionsobject */
|
||||
function GetOptionsObject(options: Value) {
|
||||
if (options instanceof UndefinedValue) {
|
||||
return OrdinaryObjectCreate(Value.null);
|
||||
}
|
||||
if (options instanceof ObjectValue) {
|
||||
return options;
|
||||
}
|
||||
return surroundingAgent.Throw('TypeError', 'NotAnObject', options);
|
||||
}
|
||||
|
||||
export function bootstrapUint8Array(realmRec: Realm) {
|
||||
const proto = realmRec.Intrinsics['%Uint8Array.prototype%'];
|
||||
const constructor = realmRec.Intrinsics['%Uint8Array%'];
|
||||
assignProps(realmRec, proto, [
|
||||
['toBase64', Uint8Array_prototype_toBase64, 0],
|
||||
['setFromBase64', Uint8Array_prototype_setFromBase64, 1],
|
||||
['toHex', Uint8Array_prototype_toHex, 0],
|
||||
['setFromHex', Uint8Array_prototype_setFromHex, 1],
|
||||
]);
|
||||
assignProps(realmRec, constructor, [
|
||||
['fromBase64', Uint8Array_fromBase64, 1],
|
||||
['fromHex', Uint8Array_fromHex, 1],
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import { JSStringValue, Value, type Arguments } from '../value.mts';
|
||||
import { CodePointAt, UTF16EncodeCodePoint } from '../static-semantics/all.mts';
|
||||
import { Q, type ValueEvaluator } from '../completion.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateBuiltinFunction,
|
||||
Realm,
|
||||
ToString,
|
||||
} from '#self';
|
||||
import type { CodePoint } from '#self';
|
||||
|
||||
function utf8Encode(codepoint: CodePoint) {
|
||||
if (codepoint <= 0x7F) {
|
||||
return [codepoint];
|
||||
}
|
||||
if (codepoint <= 0x07FF) {
|
||||
return [
|
||||
(((codepoint >> 6) & 0x1F) | 0xC0),
|
||||
(((codepoint >> 0) & 0x3F) | 0x80),
|
||||
];
|
||||
}
|
||||
if (codepoint <= 0xFFFF) {
|
||||
return [
|
||||
(((codepoint >> 12) & 0x0F) | 0xE0),
|
||||
(((codepoint >> 6) & 0x3F) | 0x80),
|
||||
(((codepoint >> 0) & 0x3F) | 0x80),
|
||||
];
|
||||
}
|
||||
if (codepoint <= 0x10FFFF) {
|
||||
return [
|
||||
(((codepoint >> 18) & 0x07) | 0xF0),
|
||||
(((codepoint >> 12) & 0x3F) | 0x80),
|
||||
(((codepoint >> 6) & 0x3F) | 0x80),
|
||||
(((codepoint >> 0) & 0x3F) | 0x80),
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** https://encoding.spec.whatwg.org/#utf-8-decoder */
|
||||
function utf8Decode(bytes: readonly number[]): CodePoint | null {
|
||||
let codepoint = 0;
|
||||
let index = 0;
|
||||
let bytes_seen = 0;
|
||||
let bytes_needed = 0;
|
||||
let lower_boundary = 0x80;
|
||||
let upper_boundary = 0xBF;
|
||||
|
||||
while (true) {
|
||||
// If byte is end-of-queue and UTF-8 bytes needed is not 0, then set UTF-8 bytes needed to 0 and return error.
|
||||
// If byte is end-of-queue, then return finished.
|
||||
if (!bytes.length) {
|
||||
if (bytes_needed === 0) {
|
||||
return null;
|
||||
}
|
||||
return codepoint as CodePoint;
|
||||
}
|
||||
|
||||
const byte = bytes[index];
|
||||
if (bytes_needed === 0) {
|
||||
if (byte >= 0x00 && byte <= 0x7F) {
|
||||
return byte as CodePoint;
|
||||
} else if (byte >= 0xC2 && byte <= 0xDF) {
|
||||
bytes_needed = 1;
|
||||
codepoint = byte & 0x1F;
|
||||
} else if (byte >= 0xE0 && byte <= 0xEF) {
|
||||
if (byte === 0xE0) {
|
||||
lower_boundary = 0xA0;
|
||||
}
|
||||
if (byte === 0xED) {
|
||||
upper_boundary = 0x9F;
|
||||
}
|
||||
bytes_needed = 2;
|
||||
codepoint = byte & 0xF;
|
||||
} else if (byte >= 0xF0 && byte <= 0xF4) {
|
||||
if (byte === 0xF0) {
|
||||
lower_boundary = 0x90;
|
||||
}
|
||||
if (byte === 0xF4) {
|
||||
upper_boundary = 0x8F;
|
||||
}
|
||||
bytes_needed = 3;
|
||||
codepoint = byte & 0x7;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (byte < lower_boundary || byte > upper_boundary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
lower_boundary = 0x80;
|
||||
upper_boundary = 0xBF;
|
||||
|
||||
codepoint = (codepoint << 6) | (byte & 0x3F);
|
||||
bytes_seen += 1;
|
||||
index += 1;
|
||||
|
||||
if (bytes_seen === bytes_needed) {
|
||||
return codepoint as CodePoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-encode */
|
||||
function Encode(_string: JSStringValue, extraUnescaped: string) {
|
||||
const string = _string.stringValue();
|
||||
const len = string.length;
|
||||
let R = '';
|
||||
const alwaysUnescaped = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.!~*\'()';
|
||||
const unescapedSet = alwaysUnescaped + extraUnescaped;
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
// Let C be the code unit at index k within string.
|
||||
const C = string[k];
|
||||
if (unescapedSet.includes(C)) {
|
||||
k += 1;
|
||||
R += C;
|
||||
} else {
|
||||
const cp = CodePointAt(string, k);
|
||||
if (cp.IsUnpairedSurrogate) {
|
||||
return surroundingAgent.Throw('URIError', 'URIMalformed');
|
||||
}
|
||||
k += cp.CodeUnitCount;
|
||||
// Let Octets be the List of octets resulting by applying the UTF-8 transformation to cp.[[CodePoint]].
|
||||
const Octets = utf8Encode(cp.CodePoint)!;
|
||||
Octets.forEach((octet) => {
|
||||
const hex = octet.toString(16).toUpperCase().padStart(2, '0');
|
||||
R = `${R}%${hex}`;
|
||||
});
|
||||
}
|
||||
}
|
||||
return Value(R);
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-decode */
|
||||
function Decode(_string: JSStringValue, preserveEscapeSet: string) {
|
||||
const string = _string.stringValue();
|
||||
const len = string.length;
|
||||
let R = '';
|
||||
let k = 0;
|
||||
while (k < len) {
|
||||
// Let C be the code unit at index k within string.
|
||||
const C = string[k];
|
||||
let S = C;
|
||||
if (C === '\u{0025}') {
|
||||
if (k + 3 > len) {
|
||||
return surroundingAgent.Throw('URIError', 'URIMalformed');
|
||||
}
|
||||
const escape = string.substring(k, k + 3);
|
||||
const B = ParseHexOctet(string, k + 1);
|
||||
if (typeof B !== 'number') {
|
||||
return surroundingAgent.Throw('URIError', 'URIMalformed');
|
||||
}
|
||||
k += 2;
|
||||
// Let n be the number of leading 1 bits in B.
|
||||
const n = B.toString(2).padStart(8, '0').match(/^1+/)?.[0].length || 0;
|
||||
if (n === 0) {
|
||||
// Let asciiChar be the code unit whose numeric value is B.
|
||||
const asciiChar = String.fromCharCode(B);
|
||||
if (preserveEscapeSet.includes(asciiChar)) {
|
||||
S = escape;
|
||||
} else {
|
||||
S = asciiChar;
|
||||
}
|
||||
} else {
|
||||
if (n === 1 || n > 4) {
|
||||
return surroundingAgent.Throw('URIError', 'URIMalformed');
|
||||
}
|
||||
const Octets = [B];
|
||||
let j = 1;
|
||||
while (j < n) {
|
||||
k += 1;
|
||||
if (k + 3 > len) {
|
||||
return surroundingAgent.Throw('URIError', 'URIMalformed');
|
||||
}
|
||||
// If the code unit at index k within string is not U+0025 PERCENT SIGN (%),
|
||||
if (string[k] !== '\u{0025}') {
|
||||
return surroundingAgent.Throw('URIError', 'URIMalformed');
|
||||
}
|
||||
const continuationByte = ParseHexOctet(string, k + 1);
|
||||
if (typeof continuationByte !== 'number') {
|
||||
return surroundingAgent.Throw('URIError', 'URIMalformed');
|
||||
}
|
||||
Octets.push(continuationByte);
|
||||
k += 2;
|
||||
j += 1;
|
||||
}
|
||||
Assert(Octets.length === n);
|
||||
// If Octets does not contain a valid UTF-8 encoding of a Unicode code point, ...
|
||||
// Let V be the code point obtained by applying the UTF-8 transformation to Octets, that is, from a List of octets into a 21-bit value.
|
||||
const V = utf8Decode(Octets);
|
||||
if (V === null) {
|
||||
return surroundingAgent.Throw('URIError', 'URIMalformed');
|
||||
}
|
||||
S = UTF16EncodeCodePoint(V);
|
||||
}
|
||||
}
|
||||
R += S;
|
||||
k += 1;
|
||||
}
|
||||
return Value(R);
|
||||
}
|
||||
|
||||
function ParseHexOctet(string: string, position: number): number | string[] {
|
||||
const len = string.length;
|
||||
Assert(position + 2 <= len);
|
||||
const hexDigits = string.substring(position, position + 2);
|
||||
// Let parseResult be ParseText(hexDigits, HexDigits[~Sep]).
|
||||
// If parseResult is not a Parse Node, return parseResult.
|
||||
if (!/^[0-9A-Fa-f]{2}$/.test(hexDigits)) {
|
||||
return [];
|
||||
}
|
||||
const parseResult = parseInt(hexDigits, 16);
|
||||
if (Number.isNaN(parseResult)) {
|
||||
return [];
|
||||
}
|
||||
const n = parseResult;
|
||||
// eslint-disable-next-line yoda
|
||||
Assert(0 <= n && n <= 255);
|
||||
return n;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-decodeuri-encodeduri */
|
||||
function* decodeURI([encodedURI = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let uriString be ? ToString(encodedURI).
|
||||
const uriString = Q(yield* ToString(encodedURI));
|
||||
// 2. Let preserveEscapeSet be ";/?:@&=+$,#".
|
||||
const preserveEscapeSet = ';/?:@&=+$,#';
|
||||
// 3. Return ? Decode(uriString, reservedURISet).
|
||||
return Q(Decode(uriString, preserveEscapeSet));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-decodeuricomponent-encodeduricomponent */
|
||||
function* decodeURIComponent([encodedURIComponent = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let componentString be ? ToString(encodedURIComponent).
|
||||
const componentString = Q(yield* ToString(encodedURIComponent));
|
||||
// 2. Let preserveEscapeSet be the empty String.
|
||||
const preserveEscapeSet = '';
|
||||
// 3. Return ? Decode(componentString, reservedURIComponentSet).
|
||||
return Q(Decode(componentString, preserveEscapeSet));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-encodeuri-uri */
|
||||
function* encodeURI([uri = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let uriString be ? ToString(uri).
|
||||
const uriString = Q(yield* ToString(uri));
|
||||
// 2. Let extraUnescaped be ";/?:@&=+$,#".
|
||||
const extraUnescaped = ';/?:@&=+$,#';
|
||||
// 3. Return ? Encode(uriString, unescapedURISet).
|
||||
return Q(Encode(uriString, extraUnescaped));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-encodeuricomponent-uricomponent */
|
||||
function* encodeURIComponent([uriComponent = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let componentString be ? ToString(uriComponent).
|
||||
const componentString = Q(yield* ToString(uriComponent));
|
||||
// 2. Let extraUnescaped be the empty String.
|
||||
const extraUnescaped = '';
|
||||
// 3. Return ? Encode(componentString, unescapedURIComponentSet).
|
||||
return Q(Encode(componentString, extraUnescaped));
|
||||
}
|
||||
|
||||
export function bootstrapURIHandling(realmRec: Realm) {
|
||||
([
|
||||
['decodeURI', decodeURI, 1],
|
||||
['decodeURIComponent', decodeURIComponent, 1],
|
||||
['encodeURI', encodeURI, 1],
|
||||
['encodeURIComponent', encodeURIComponent, 1],
|
||||
] as const).forEach(([name, f, length]) => {
|
||||
realmRec.Intrinsics[`%${name}%`] = CreateBuiltinFunction(f, length, Value(name), [], realmRec);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
UndefinedValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import {
|
||||
Q,
|
||||
} from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { AddEntriesFromIterable } from './Map.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
Get,
|
||||
IsCallable,
|
||||
OrdinaryCreateFromConstructor,
|
||||
Realm,
|
||||
type FunctionObject,
|
||||
type OrdinaryObject,
|
||||
} from '#self';
|
||||
|
||||
export interface WeakMapObject extends OrdinaryObject {
|
||||
readonly WeakMapData: { Key: Value | undefined; Value: Value | undefined; }[];
|
||||
}
|
||||
export function isWeakMapObject(object: object): object is WeakMapObject {
|
||||
return 'WeakMapData' in object;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-weakmap-constructor */
|
||||
function* WeakMapConstructor(this: FunctionObject, [iterable = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakMap.prototype%", « [[WeakMapData]] »).
|
||||
const map = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%WeakMap.prototype%', ['WeakMapData'])) as Mutable<WeakMapObject>;
|
||||
// 3. Set map.[[WeakMapData]] to a new empty List.
|
||||
map.WeakMapData = [];
|
||||
// 4. If iterable is either undefined or null, return map.
|
||||
if (iterable === Value.undefined || iterable === Value.null) {
|
||||
return map;
|
||||
}
|
||||
// 5. Let adder be ? Get(map, "set").
|
||||
const adder = Q(yield* Get(map, Value('set')));
|
||||
if (!IsCallable(adder)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', adder);
|
||||
}
|
||||
// 6. Return ? AddEntriesFromIterable(map, iterable, adder).
|
||||
return Q(yield* AddEntriesFromIterable(map, iterable, adder));
|
||||
}
|
||||
|
||||
export function bootstrapWeakMap(realmRec: Realm) {
|
||||
const c = bootstrapConstructor(realmRec, WeakMapConstructor, 'WeakMap', 0, realmRec.Intrinsics['%WeakMap.prototype%'], []);
|
||||
|
||||
realmRec.Intrinsics['%WeakMap%'] = c;
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q, type ValueCompletion, type ValueEvaluator } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { WeakMapObject } from './WeakMap.mts';
|
||||
import {
|
||||
Call,
|
||||
IsCallable,
|
||||
SameValue,
|
||||
RequireInternalSlot,
|
||||
CanBeHeldWeakly,
|
||||
Realm,
|
||||
Throw,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weakmap.prototype.delete */
|
||||
function WeakMapProto_delete([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as WeakMapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
|
||||
Q(RequireInternalSlot(M, 'WeakMapData'));
|
||||
// 3. If CanBeHeldWeakly(key) is false, return false.
|
||||
if (!CanBeHeldWeakly(key)) {
|
||||
return Value.false;
|
||||
}
|
||||
// 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do
|
||||
const entries = M.WeakMapData;
|
||||
for (let i = 0; i < entries.length; i += 1) {
|
||||
const p = entries[i];
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then
|
||||
if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) {
|
||||
// i. Set p.[[Key]] to empty.
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(M));
|
||||
p.Key = undefined;
|
||||
// ii. Set p.[[Value]] to empty.
|
||||
p.Value = undefined;
|
||||
// iii. return true.
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
// 5. Return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weakmap.prototype.get */
|
||||
function WeakMapProto_get([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let m be the this value.
|
||||
const M = thisValue as WeakMapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
|
||||
Q(RequireInternalSlot(M, 'WeakMapData'));
|
||||
// 3. If CanBeHeldWeakly(key) is false, return false.
|
||||
if (!CanBeHeldWeakly(key)) {
|
||||
return Value.undefined;
|
||||
}
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do
|
||||
const entries = M.WeakMapData;
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
|
||||
if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) {
|
||||
return p.Value!;
|
||||
}
|
||||
}
|
||||
// 6. Return undefined.
|
||||
return Value.undefined;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-upsert/#sec-weakmap.prototype.getOrInsert */
|
||||
function WeakMapProto_getOrInsert([key = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let m be the this value.
|
||||
const M = thisValue as WeakMapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
|
||||
Q(RequireInternalSlot(M, 'WeakMapData'));
|
||||
// 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception.
|
||||
if (!CanBeHeldWeakly(key)) {
|
||||
return Throw.TypeError('$1 cannot be used as a WeakMap key', key);
|
||||
}
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do
|
||||
const entries = M.WeakMapData;
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
|
||||
if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) {
|
||||
return p.Value!;
|
||||
}
|
||||
}
|
||||
// 5. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
const p = { Key: key, Value: value };
|
||||
// 6. Append p to M.[[WeakMapData]].
|
||||
entries.push(p);
|
||||
// 7. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
/** https://tc39.es/proposal-upsert/#sec-weakmap.prototype.getOrInsertComputed */
|
||||
function* WeakMapProto_getOrInsertComputed([key = Value.undefined, callbackfn = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let m be the this value.
|
||||
const M = thisValue as WeakMapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
|
||||
Q(RequireInternalSlot(M, 'WeakMapData'));
|
||||
// 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception.
|
||||
if (!CanBeHeldWeakly(key)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAWeakKey', key);
|
||||
}
|
||||
// 4. If IsCallable(callbackfn) is false, throw a TypeError exception.
|
||||
if (!IsCallable(callbackfn)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', callbackfn);
|
||||
}
|
||||
// 5. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do
|
||||
const entries = M.WeakMapData;
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]].
|
||||
if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) {
|
||||
return p.Value!;
|
||||
}
|
||||
}
|
||||
// 6. Let value be ? Call(callbackfn, undefined, « key »).
|
||||
const value = Q(yield* Call(callbackfn, Value.undefined, [key]));
|
||||
// 7. NOTE: The Map may have been modified during execution of callbackfn.
|
||||
// 8. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then
|
||||
if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) {
|
||||
// i. Set p.[[Value]] to value.
|
||||
p.Value = value;
|
||||
// ii. Return value.
|
||||
return value;
|
||||
}
|
||||
}
|
||||
// 9. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
const p = { Key: key, Value: value };
|
||||
// 10. Append p to M.[[WeakMapData]].
|
||||
entries.push(p);
|
||||
// 11. Return value.
|
||||
return value;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weakmap.prototype.has */
|
||||
function WeakMapProto_has([key = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as WeakMapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
|
||||
Q(RequireInternalSlot(M, 'WeakMapData'));
|
||||
// 3. If CanBeHeldWeakly(key) is false, return false.
|
||||
if (!CanBeHeldWeakly(key)) {
|
||||
return Value.false;
|
||||
}
|
||||
// 4. For each Record { [[Key]], [[Value]] } p of M.[[WeakMapData]], do
|
||||
const entries = M.WeakMapData;
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return true.
|
||||
if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) {
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
// 6. Return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weakmap.prototype.set */
|
||||
function WeakMapProto_set([key = Value.undefined, value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let M be the this value.
|
||||
const M = thisValue as WeakMapObject;
|
||||
// 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]).
|
||||
Q(RequireInternalSlot(M, 'WeakMapData'));
|
||||
// 3. If CanBeHeldWeakly(key) is false, throw a TypeError exception.
|
||||
if (!CanBeHeldWeakly(key)) {
|
||||
return surroundingAgent.Throw('TypeError', 'WeakCollectionNotObject', key);
|
||||
}
|
||||
// 4. For each Record { [[Key]], [[Value]] } p that is an element of entries, do
|
||||
const entries = M.WeakMapData;
|
||||
for (const p of entries) {
|
||||
// a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, then
|
||||
if (p.Key !== undefined && SameValue(p.Key, key) === Value.true) {
|
||||
// i. Set p.[[Value]] to value.
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(M));
|
||||
p.Value = value;
|
||||
// ii. Return M.
|
||||
return M;
|
||||
}
|
||||
}
|
||||
// 5. Let p be the Record { [[Key]]: key, [[Value]]: value }.
|
||||
const p = { Key: key, Value: value };
|
||||
// 7. Append p as the last element of entries.
|
||||
entries.push(p);
|
||||
// 7. Return M.
|
||||
return M;
|
||||
}
|
||||
|
||||
export function bootstrapWeakMapPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['delete', WeakMapProto_delete, 1],
|
||||
['get', WeakMapProto_get, 1],
|
||||
['getOrInsert', WeakMapProto_getOrInsert, 2],
|
||||
['getOrInsertComputed', WeakMapProto_getOrInsertComputed, 2],
|
||||
['has', WeakMapProto_has, 1],
|
||||
['set', WeakMapProto_set, 2],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'WeakMap');
|
||||
|
||||
realmRec.Intrinsics['%WeakMap.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
ObjectValue,
|
||||
SymbolValue,
|
||||
UndefinedValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q } from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
AddToKeptObjects, CanBeHeldWeakly, OrdinaryCreateFromConstructor, Realm, type FunctionObject, type OrdinaryObject,
|
||||
} from '#self';
|
||||
|
||||
export interface WeakRefObject extends OrdinaryObject {
|
||||
WeakRefTarget: ObjectValue | SymbolValue | undefined;
|
||||
}
|
||||
export function isWeakRef(object: object): object is WeakRefObject {
|
||||
return 'WeakRefTarget' in object && !('HeldValue' in object);
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-weak-ref-target */
|
||||
function* WeakRefConstructor(this: FunctionObject, [target = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. If CanBeHeldWeakly(target) is false, throw a TypeError exception.
|
||||
if (!CanBeHeldWeakly(target)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAWeakKey', target);
|
||||
}
|
||||
// 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakRefPrototype%", « [[WeakRefTarget]] »).
|
||||
const weakRef = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%WeakRef.prototype%', ['WeakRefTarget'])) as Mutable<WeakRefObject>;
|
||||
// 4. Perform ! AddToKeptObjects(target).
|
||||
AddToKeptObjects(target);
|
||||
// 5. Set weakRef.[[WeakRefTarget]] to target.
|
||||
weakRef.WeakRefTarget = target;
|
||||
// 6. Return weakRef
|
||||
return weakRef;
|
||||
}
|
||||
|
||||
export function bootstrapWeakRef(realmRec: Realm) {
|
||||
const weakRefConstructor = bootstrapConstructor(realmRec, WeakRefConstructor, 'WeakRef', 1, realmRec.Intrinsics['%WeakRef.prototype%'], []);
|
||||
|
||||
realmRec.Intrinsics['%WeakRef%'] = weakRefConstructor;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Q, X } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { WeakRefObject } from './WeakRef.mts';
|
||||
import { Realm, RequireInternalSlot, WeakRefDeref } from '#self';
|
||||
import type { Arguments, ValueCompletion, FunctionCallContext } from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weak-ref.prototype.deref */
|
||||
function WeakRefProto_deref(_args: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let weakRef be the this value.
|
||||
const weakRef = thisValue as WeakRefObject;
|
||||
// 2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]).
|
||||
Q(RequireInternalSlot(weakRef, 'WeakRefTarget'));
|
||||
// 3. Return ! WeakRefDeref(weakRef).
|
||||
return X(WeakRefDeref(weakRef));
|
||||
}
|
||||
|
||||
export function bootstrapWeakRefPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['deref', WeakRefProto_deref, 0],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'WeakRef');
|
||||
|
||||
realmRec.Intrinsics['%WeakRef.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
UndefinedValue, Value, type Arguments, type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { IfAbruptCloseIterator, Q } from '../completion.mts';
|
||||
import type { Mutable } from '../helpers.mts';
|
||||
import { bootstrapConstructor } from './bootstrap.mts';
|
||||
import {
|
||||
IsCallable,
|
||||
OrdinaryCreateFromConstructor,
|
||||
Call,
|
||||
Get,
|
||||
GetIterator,
|
||||
type OrdinaryObject,
|
||||
Realm,
|
||||
type FunctionObject,
|
||||
IteratorStepValue,
|
||||
} from '#self';
|
||||
|
||||
export interface WeakSetObject extends OrdinaryObject {
|
||||
readonly WeakSetData: (Value | undefined)[];
|
||||
}
|
||||
export function isWeakSetObject(object: object): object is WeakSetObject {
|
||||
return 'WeakSetData' in object;
|
||||
}
|
||||
/** https://tc39.es/ecma262/#sec-weakset-iterable */
|
||||
function* WeakSetConstructor(this: FunctionObject, [iterable = Value.undefined]: Arguments, { NewTarget }: FunctionCallContext) {
|
||||
// 1. If NewTarget is undefined, throw a TypeError exception.
|
||||
if (NewTarget instanceof UndefinedValue) {
|
||||
return surroundingAgent.Throw('TypeError', 'ConstructorNonCallable', this);
|
||||
}
|
||||
// 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%WeakSet.prototype%", « [[WeakSetData]] »).
|
||||
const set = Q(yield* OrdinaryCreateFromConstructor(NewTarget, '%WeakSet.prototype%', ['WeakSetData'])) as Mutable<WeakSetObject>;
|
||||
// 3. Set set.[[WeakSetData]] to a new empty List.
|
||||
set.WeakSetData = [];
|
||||
// 4. If iterable is either undefined or null, return set.
|
||||
if (iterable === Value.undefined || iterable === Value.null) {
|
||||
return set;
|
||||
}
|
||||
// 5. Let adder be ? Get(set, "add").
|
||||
const adder = Q(yield* Get(set, Value('add')));
|
||||
// 6. If IsCallable(adder) is false, throw a TypeError exception.
|
||||
if (!IsCallable(adder)) {
|
||||
return surroundingAgent.Throw('TypeError', 'NotAFunction', adder);
|
||||
}
|
||||
// 7. Let iteratorRecord be ? GetIterator(iterable).
|
||||
const iteratorRecord = Q(yield* GetIterator(iterable, 'sync'));
|
||||
// 8. Repeat,
|
||||
while (true) {
|
||||
// a. Let next be ? IteratorStep(iteratorRecord).
|
||||
const next = Q(yield* IteratorStepValue(iteratorRecord));
|
||||
// b. If next is false, return set.
|
||||
if (next === 'done') {
|
||||
return set;
|
||||
}
|
||||
// d. Let status be Call(adder, set, « next »).
|
||||
const status = yield* Call(adder, set, [next]);
|
||||
// e. IfAbruptCloseIterator(status, iteratorRecord).
|
||||
IfAbruptCloseIterator(status, iteratorRecord);
|
||||
}
|
||||
}
|
||||
|
||||
export function bootstrapWeakSet(realmRec: Realm) {
|
||||
const c = bootstrapConstructor(realmRec, WeakSetConstructor, 'WeakSet', 0, realmRec.Intrinsics['%WeakSet.prototype%'], []);
|
||||
realmRec.Intrinsics['%WeakSet%'] = c;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { surroundingAgent } from '../host-defined/engine.mts';
|
||||
import {
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
} from '../value.mts';
|
||||
import { Q, type ValueCompletion } from '../completion.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import type { WeakSetObject } from './WeakSet.mts';
|
||||
import {
|
||||
SameValue,
|
||||
RequireInternalSlot,
|
||||
CanBeHeldWeakly,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weakset.prototype.add */
|
||||
function WeakSetProto_add([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be this value.
|
||||
const S = thisValue as WeakSetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]).
|
||||
Q(RequireInternalSlot(S, 'WeakSetData'));
|
||||
// 3. If CanBeHeldWeakly(value) is false, throw a TypeError exception.
|
||||
if (!CanBeHeldWeakly(value)) {
|
||||
return surroundingAgent.Throw('TypeError', 'WeakCollectionNotObject', value);
|
||||
}
|
||||
// 4. For each e that is an element of entries, do
|
||||
const entries = S.WeakSetData;
|
||||
for (const e of entries) {
|
||||
// a. If e is not empty and SameValue(e, value) is true, then
|
||||
if (e !== undefined && SameValue(e, value) === Value.true) {
|
||||
// i. Return S.
|
||||
return S;
|
||||
}
|
||||
}
|
||||
// 6. Append value as the last element of entries.
|
||||
entries.push(value);
|
||||
// 6. Return S.
|
||||
return S;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weakset.prototype.delete */
|
||||
function WeakSetProto_delete([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.`
|
||||
const S = thisValue as WeakSetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]).
|
||||
Q(RequireInternalSlot(S, 'WeakSetData'));
|
||||
// 3. If CanBeHeldWeakly(value) is false, return false.
|
||||
if (!CanBeHeldWeakly(value)) {
|
||||
return Value.false;
|
||||
}
|
||||
// 4. For each element e of S.[[WeakSetData]], do
|
||||
const entries = S.WeakSetData;
|
||||
for (let i = 0; i < entries.length; i += 1) {
|
||||
const e = entries[i];
|
||||
// i. If e is not empty and SameValue(e, value) is true, then
|
||||
if (e !== undefined && SameValue(e, value) === Value.true) {
|
||||
// i. Replace the element of entries whose value is e with an element whose value is empty.
|
||||
Q(surroundingAgent.debugger_tryTouchDuringPreview(S));
|
||||
entries[i] = undefined;
|
||||
// ii. Return true.
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
// 5. Return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-weakset.prototype.has */
|
||||
function WeakSetProto_has([value = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueCompletion {
|
||||
// 1. Let S be the this value.
|
||||
const S = thisValue as WeakSetObject;
|
||||
// 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]).
|
||||
Q(RequireInternalSlot(S, 'WeakSetData'));
|
||||
// 3. If CanBeHeldWeakly(value) is false, return false.
|
||||
if (!CanBeHeldWeakly(value)) {
|
||||
return Value.false;
|
||||
}
|
||||
// 4. For each element e of S.[[WeakSetData]], do
|
||||
const entries = S.WeakSetData;
|
||||
for (const e of entries) {
|
||||
// a. If e is not empty and SameValue(e, value) is true, return true.
|
||||
if (e !== undefined && SameValue(e, value) === Value.true) {
|
||||
return Value.true;
|
||||
}
|
||||
}
|
||||
// 5. Return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
export function bootstrapWeakSetPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['add', WeakSetProto_add, 1],
|
||||
['delete', WeakSetProto_delete, 1],
|
||||
['has', WeakSetProto_has, 1],
|
||||
], realmRec.Intrinsics['%Object.prototype%'], 'WeakSet');
|
||||
|
||||
realmRec.Intrinsics['%WeakSet.prototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { __ts_cast__ } from '../helpers.mts';
|
||||
import { bootstrapPrototype } from './bootstrap.mts';
|
||||
import {
|
||||
type IteratorObject,
|
||||
|
||||
Assert,
|
||||
Call,
|
||||
CreateIteratorResultObject,
|
||||
GetMethod,
|
||||
ObjectValue,
|
||||
Q,
|
||||
RequireInternalSlot,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
type Arguments,
|
||||
type FunctionCallContext,
|
||||
type IteratorRecord,
|
||||
type Realm,
|
||||
type ValueEvaluator,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%wrapforvaliditeratorprototype%.next */
|
||||
function* WrapForValidIteratorPrototype_next(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be this value.
|
||||
const O = thisValue;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[Iterated]]).
|
||||
Q(RequireInternalSlot(O, 'Iterated'));
|
||||
// 3. Let iteratorRecord be O.[[Iterated]].
|
||||
__ts_cast__<IteratorObject>(O);
|
||||
const iteratorRecord: IteratorRecord = O.Iterated;
|
||||
// 4. Return ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
|
||||
return Q(yield* Call(iteratorRecord.NextMethod, iteratorRecord.Iterator));
|
||||
}
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-%wrapforvaliditeratorprototype%.return */
|
||||
function* WrapForValidIteratorPrototype_return(_args: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator {
|
||||
// 1. Let O be this value.
|
||||
const O = thisValue;
|
||||
// 2. Perform ? RequireInternalSlot(O, [[Iterated]]).
|
||||
Q(RequireInternalSlot(O, 'Iterated'));
|
||||
// 3. Let iterator be O.[[Iterated]].[[Iterator]].
|
||||
__ts_cast__<IteratorObject>(O);
|
||||
const iteratorRecord: IteratorRecord = O.Iterated;
|
||||
const iterator = iteratorRecord.Iterator;
|
||||
// 4. Assert: iterator is an Object.
|
||||
Assert(iterator instanceof ObjectValue);
|
||||
// 5. Let returnMethod be ? GetMethod(iterator, "return").
|
||||
const returnMethod = Q(yield* GetMethod(iterator, Value('return')));
|
||||
// 6. If returnMethod is undefined, then
|
||||
if (returnMethod instanceof UndefinedValue) {
|
||||
// a. Return CreateIteratorResultObject(undefined, true).
|
||||
return CreateIteratorResultObject(Value.undefined, Value.true);
|
||||
}
|
||||
// 7. Return ? Call(returnMethod, iterator).
|
||||
return Q(yield* Call(returnMethod, iterator));
|
||||
}
|
||||
|
||||
export function bootstrapWrapForValidIteratorPrototype(realmRec: Realm) {
|
||||
const proto = bootstrapPrototype(realmRec, [
|
||||
['next', WrapForValidIteratorPrototype_next, 0],
|
||||
['return', WrapForValidIteratorPrototype_return, 0],
|
||||
], realmRec.Intrinsics['%Iterator.prototype%']);
|
||||
|
||||
realmRec.Intrinsics['%WrapForValidIteratorPrototype%'] = proto;
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import {
|
||||
Descriptor,
|
||||
JSStringValue,
|
||||
NullValue,
|
||||
ObjectValue,
|
||||
SymbolValue,
|
||||
UndefinedValue,
|
||||
Value,
|
||||
wellKnownSymbols,
|
||||
type DescriptorInit,
|
||||
type NativeSteps,
|
||||
} from '../value.mts';
|
||||
import { X } from '../completion.mts';
|
||||
import {
|
||||
Assert,
|
||||
CreateBuiltinFunction,
|
||||
markBuiltinFunctionAsConstructor,
|
||||
OrdinaryObjectCreate,
|
||||
Realm,
|
||||
type FunctionObject,
|
||||
} from '#self';
|
||||
|
||||
type Accessor = [
|
||||
getter: NativeSteps | UndefinedValue | FunctionObject,
|
||||
setter?: NativeSteps | UndefinedValue | FunctionObject,
|
||||
];
|
||||
|
||||
type Props = [
|
||||
name: string | JSStringValue | SymbolValue,
|
||||
value: Accessor | NativeSteps | Value,
|
||||
fnLength?: number,
|
||||
desc?: DescriptorInit,
|
||||
async?: boolean
|
||||
];
|
||||
/** https://tc39.es/ecma262/#sec-ecmascript-standard-built-in-objects */
|
||||
export function assignProps(realmRec: Realm, obj: ObjectValue, props: readonly (Props | undefined)[]) {
|
||||
for (const item of props) {
|
||||
if (item === undefined) {
|
||||
continue;
|
||||
}
|
||||
const [n, v, len, descriptor, async] = item;
|
||||
const name = n instanceof Value ? n : Value(n);
|
||||
if (Array.isArray(v)) {
|
||||
// Every accessor property described in clauses 18 through 26 and in
|
||||
// Annex B.2 has the attributes { [[Enumerable]]: false,
|
||||
// [[Configurable]]: true } unless otherwise specified. If only a get
|
||||
// accessor function is described, the set accessor function is the
|
||||
// default value, undefined. If only a set accessor is described the get
|
||||
// accessor is the default value, undefined.
|
||||
let [
|
||||
getter = Value.undefined,
|
||||
setter = Value.undefined,
|
||||
] = v;
|
||||
if (typeof getter === 'function') {
|
||||
getter = CreateBuiltinFunction(
|
||||
getter,
|
||||
0,
|
||||
name,
|
||||
[],
|
||||
realmRec,
|
||||
undefined,
|
||||
Value('get'),
|
||||
async,
|
||||
);
|
||||
}
|
||||
if (typeof setter === 'function') {
|
||||
setter = CreateBuiltinFunction(
|
||||
setter,
|
||||
1,
|
||||
name,
|
||||
[],
|
||||
realmRec,
|
||||
undefined,
|
||||
Value('set'),
|
||||
async,
|
||||
);
|
||||
}
|
||||
X(obj.DefineOwnProperty(name, Descriptor({
|
||||
Get: getter,
|
||||
Set: setter,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
...descriptor,
|
||||
})));
|
||||
} else {
|
||||
// Every other data property described in clauses 18 through 26 and in
|
||||
// Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]:
|
||||
// false, [[Configurable]]: true } unless otherwise specified.
|
||||
let value;
|
||||
if (typeof v === 'function') {
|
||||
Assert(typeof len === 'number');
|
||||
value = CreateBuiltinFunction(v, len, name, [], realmRec, undefined, undefined, async);
|
||||
} else {
|
||||
value = v;
|
||||
}
|
||||
obj.properties.set(name, Descriptor({
|
||||
Value: value,
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
...descriptor,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function bootstrapPrototype(realmRec: Realm, props: readonly (Props | undefined)[], Prototype: ObjectValue | NullValue, stringTag?: string) {
|
||||
Assert(Prototype !== undefined);
|
||||
const proto = OrdinaryObjectCreate(Prototype);
|
||||
|
||||
assignProps(realmRec, proto, props);
|
||||
|
||||
if (stringTag !== undefined) {
|
||||
X(proto.DefineOwnProperty(wellKnownSymbols.toStringTag, Descriptor({
|
||||
Value: Value(stringTag),
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
export function bootstrapConstructor(realmRec: Realm, Constructor: NativeSteps, name: string, length: number, Prototype: ObjectValue, props: readonly Props[] = []) {
|
||||
const cons = CreateBuiltinFunction(
|
||||
markBuiltinFunctionAsConstructor(Constructor),
|
||||
length,
|
||||
Value(name),
|
||||
[],
|
||||
realmRec,
|
||||
);
|
||||
|
||||
X(cons.DefineOwnProperty(Value('prototype'), Descriptor({
|
||||
Value: Prototype,
|
||||
Writable: Value.false,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.false,
|
||||
})));
|
||||
|
||||
if (!Prototype.properties.has('constructor')) {
|
||||
X(Prototype.DefineOwnProperty(Value('constructor'), Descriptor({
|
||||
Value: cons,
|
||||
Writable: Value.true,
|
||||
Enumerable: Value.false,
|
||||
Configurable: Value.true,
|
||||
})));
|
||||
}
|
||||
|
||||
assignProps(realmRec, cons, props);
|
||||
|
||||
return cons;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Q, type ValueEvaluator } from '../completion.mts';
|
||||
import { Value, type Arguments } from '../value.mts';
|
||||
import {
|
||||
CreateBuiltinFunction,
|
||||
PerformEval,
|
||||
} from '#self';
|
||||
import type { Realm } from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-eval-x */
|
||||
function* Eval([x = Value.undefined]: Arguments): ValueEvaluator {
|
||||
return Q(yield* PerformEval(x, false, false));
|
||||
}
|
||||
|
||||
export function bootstrapEval(realmRec: Realm) {
|
||||
realmRec.Intrinsics['%eval%'] = CreateBuiltinFunction(Eval, 1, Value('eval'), [], realmRec);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Value, type Arguments } from '../value.mts';
|
||||
import { Q, type ValueEvaluator } from '../completion.mts';
|
||||
import {
|
||||
ToNumber,
|
||||
CreateBuiltinFunction,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-isfinite-number */
|
||||
function* IsFinite([number = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let num be ? ToNumber(number).
|
||||
const num = Q(yield* ToNumber(number));
|
||||
// 2. If num is NaN, +∞, or -∞, return false.
|
||||
if (num.isNaN() || num.isInfinity()) {
|
||||
return Value.false;
|
||||
}
|
||||
// 3. Otherwise, return true.
|
||||
return Value.true;
|
||||
}
|
||||
|
||||
export function bootstrapIsFinite(realmRec: Realm) {
|
||||
realmRec.Intrinsics['%isFinite%'] = CreateBuiltinFunction(IsFinite, 1, Value('isFinite'), [], realmRec);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Value, type Arguments } from '../value.mts';
|
||||
import { Q, type ValueEvaluator } from '../completion.mts';
|
||||
import {
|
||||
ToNumber,
|
||||
CreateBuiltinFunction,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-isnan-number */
|
||||
function* IsNaN([number = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let num be ? ToNumber(number).
|
||||
const num = Q(yield* ToNumber(number));
|
||||
// 2. If num is NaN, return true.
|
||||
if (num.isNaN()) {
|
||||
return Value.true;
|
||||
}
|
||||
// 3. Otherwise, return false.
|
||||
return Value.false;
|
||||
}
|
||||
|
||||
export function bootstrapIsNaN(realmRec: Realm) {
|
||||
realmRec.Intrinsics['%isNaN%'] = CreateBuiltinFunction(IsNaN, 1, Value('isNaN'), [], realmRec);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Q, X, type ValueEvaluator } from '../completion.mts';
|
||||
import { Value, type Arguments } from '../value.mts';
|
||||
import {
|
||||
TrimString,
|
||||
} from '../runtime-semantics/all.mts';
|
||||
import {
|
||||
CreateBuiltinFunction,
|
||||
ToString,
|
||||
F,
|
||||
Realm,
|
||||
} from '#self';
|
||||
|
||||
/** https://tc39.es/ecma262/#sec-parsefloat-string */
|
||||
function* ParseFloat([string = Value.undefined]: Arguments): ValueEvaluator {
|
||||
// 1. Let inputString be ? ToString(string).
|
||||
const inputString = Q(yield* ToString(string));
|
||||
// 2. Let trimmedString be ! TrimString(inputString, start).
|
||||
const trimmedString = X(TrimString(inputString, 'start')).stringValue();
|
||||
// 3. If neither trimmedString nor any prefix of trimmedString satisfies the syntax of a StrDecimalLiteral (see 7.1.4.1), return NaN.
|
||||
// 4. Let numberString be the longest prefix of trimmedString, which might be trimmedString itself, that satisfies the syntax of a StrDecimalLiteral.
|
||||
// 5. Let mathFloat be MV of numberString.
|
||||
// 6. If mathFloat = 0ℝ, then
|
||||
// a. If the first code unit of trimmedString is the code unit 0x002D (HYPHEN-MINUS), return -0.
|
||||
// b. Return +0.
|
||||
// 7. Return the Number value for mathFloat.
|
||||
let numberString = trimmedString;
|
||||
if (/^[+-]/.test(numberString)) {
|
||||
numberString = numberString.slice(1);
|
||||
}
|
||||
const multiplier = trimmedString.startsWith('-') ? -1 : 1;
|
||||
if (numberString.startsWith('Infinity')) {
|
||||
return F(Infinity * multiplier);
|
||||
}
|
||||
let index = 0;
|
||||
done: { // eslint-disable-line no-labels
|
||||
// Eat leading zeros
|
||||
while (numberString[index] === '0') {
|
||||
index += 1;
|
||||
if (index === numberString.length) {
|
||||
return F(+0 * multiplier);
|
||||
}
|
||||
}
|
||||
// Eat integer part
|
||||
if (numberString[index] !== '.') {
|
||||
while (/[0-9]/.test(numberString[index])) {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
// Eat fractional part
|
||||
if (numberString[index] === '.') {
|
||||
if (!/[0-9eE]/.test(numberString[index + 1])) {
|
||||
break done; // eslint-disable-line no-labels
|
||||
}
|
||||
index += 1;
|
||||
while (/[0-9]/.test(numberString[index])) {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
// Eat exponent part
|
||||
if (numberString[index] === 'e' || numberString[index] === 'E') {
|
||||
if (!/[-+0-9]/.test(numberString[index + 1])) {
|
||||
break done; // eslint-disable-line no-labels
|
||||
}
|
||||
index += 1;
|
||||
if (numberString[index] === '-' || numberString[index] === '+') {
|
||||
index += 1;
|
||||
}
|
||||
while (/[0-9]/.test(numberString[index])) {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return F(parseFloat(numberString.slice(0, index)) * multiplier);
|
||||
}
|
||||
|
||||
export function bootstrapParseFloat(realmRec: Realm) {
|
||||
realmRec.Intrinsics['%parseFloat%'] = CreateBuiltinFunction(ParseFloat, 1, Value('parseFloat'), [], realmRec);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user