Merge commit '6e92d2345e8231a44556ce7d416451f5f3e6c398' as 'engine262'

This commit is contained in:
2026-03-27 10:07:29 +05:30
433 changed files with 90478 additions and 0 deletions
@@ -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;
}
+326
View File
@@ -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;
}
+49
View File
@@ -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;
}
+755
View File
@@ -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, *+&infin;*, or *-&infin;*.
// 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;
}
+69
View File
@@ -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;
}
+47
View File
@@ -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;
}
+82
View File
@@ -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;
}
+218
View File
@@ -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;
}
+788
View File
@@ -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;
}
+85
View File
@@ -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;
}
+113
View File
@@ -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;
}
+24
View File
@@ -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;
}
+122
View File
@@ -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;
}
+738
View File
@@ -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;
}
+128
View File
@@ -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;
}
+295
View File
@@ -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;
}
+288
View File
@@ -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;
}
+88
View File
@@ -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;
}
}
+135
View File
@@ -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;
}
+465
View File
@@ -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;
}
+604
View File
@@ -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;
}
+100
View File
@@ -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;
}
+211
View File
@@ -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;
}
+165
View File
@@ -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;
}
+75
View File
@@ -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;
}
+715
View File
@@ -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);
}
+72
View File
@@ -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;
}
+138
View File
@@ -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;
}
+106
View File
@@ -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;
}
+66
View File
@@ -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;
}
+576
View File
@@ -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],
]);
}
+277
View File
@@ -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);
});
}
+56
View File
@@ -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;
}
+44
View File
@@ -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;
}
+66
View File
@@ -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;
}
+153
View File
@@ -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;
}
+16
View File
@@ -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);
}
+23
View File
@@ -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);
}
+23
View File
@@ -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);
}
+78
View File
@@ -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