mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
1017 lines
34 KiB
TypeScript
1017 lines
34 KiB
TypeScript
import { type GCMarker, surroundingAgent } from './host-defined/engine.mts';
|
||
import {
|
||
Q, X, type ValueEvaluator, type PlainCompletion,
|
||
} from './completion.mts';
|
||
import {
|
||
PropertyKeyMap, OutOfRange, callable,
|
||
} from './helpers.mts';
|
||
import type { PrivateElementRecord } from './runtime-semantics/MethodDefinitionEvaluation.mts';
|
||
import type { PlainEvaluator } from './evaluator.mts';
|
||
import {
|
||
Assert,
|
||
OrdinaryDefineOwnProperty,
|
||
OrdinaryDelete,
|
||
OrdinaryGet,
|
||
OrdinaryGetOwnProperty,
|
||
OrdinaryGetPrototypeOf,
|
||
OrdinaryHasProperty,
|
||
OrdinaryIsExtensible,
|
||
OrdinaryOwnPropertyKeys,
|
||
OrdinaryPreventExtensions,
|
||
OrdinarySet,
|
||
OrdinarySetPrototypeOf,
|
||
ToInt32,
|
||
ToUint32,
|
||
Z,
|
||
F, R, type OrdinaryObject, type FunctionObject,
|
||
type BuiltinFunctionObject,
|
||
type ECMAScriptFunctionObject,
|
||
type DefaultConstructorBuiltinFunction, EnvironmentRecord,
|
||
Throw,
|
||
} from '#self';
|
||
|
||
let createStringValue: (value: string) => JSStringValue; // set by static block in StringValue for privileged access to constructor
|
||
let createNumberValue: (value: number) => NumberValue; // set by static block in NumberValue for privileged access to constructor
|
||
let createBigIntValue: (value: bigint) => BigIntValue; // set by static block in BigIntValue for privileged access to constructor
|
||
|
||
abstract class BaseValue {
|
||
static declare readonly null: NullValue; // defined in static block of NullValue
|
||
|
||
static declare readonly undefined: UndefinedValue; // defined in static block of UndefinedValue
|
||
|
||
static declare readonly true: BooleanValue<true>; // defined in static block of BooleanValue
|
||
|
||
static declare readonly false: BooleanValue<false>; // defined in static block of BooleanValue
|
||
|
||
abstract type: Value['type']; // ensures new `Value` subtypes must be added to `Value` union
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is Value; // no need to actually declare it.
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types */
|
||
export type Value =
|
||
| UndefinedValue
|
||
| NullValue
|
||
| BooleanValue
|
||
| JSStringValue
|
||
| SymbolValue
|
||
| NumberValue
|
||
| BigIntValue
|
||
| ObjectValue;
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types */
|
||
export const Value = (() => {
|
||
// NOTE: Using IIFE so that the class does not conflict with the type of the same name
|
||
@callable((_target, _thisArg, [value]) => {
|
||
if (value === null) {
|
||
return Value.null;
|
||
} else if (value === undefined) {
|
||
return Value.undefined;
|
||
} else if (value === true) {
|
||
return Value.true;
|
||
} else if (value === false) {
|
||
return Value.false;
|
||
}
|
||
switch (typeof value) {
|
||
case 'string':
|
||
return createStringValue(value);
|
||
case 'number':
|
||
return createNumberValue(value);
|
||
case 'bigint':
|
||
return createBigIntValue(value);
|
||
default:
|
||
throw new OutOfRange('new Value', value);
|
||
}
|
||
})
|
||
abstract class Value extends BaseValue {
|
||
}
|
||
return Value;
|
||
})() as typeof BaseValue & {
|
||
<T extends null | undefined | boolean | string | number | bigint>(value: T):
|
||
T extends null ? NullValue :
|
||
T extends undefined ? UndefinedValue :
|
||
T extends boolean ? BooleanValue<T> :
|
||
T extends string ? JSStringValue :
|
||
T extends number ? NumberValue :
|
||
T extends bigint ? BigIntValue :
|
||
never;
|
||
};
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types */
|
||
export type PropertyKeyValue =
|
||
| JSStringValue
|
||
| SymbolValue;
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types */
|
||
export type PrimitiveValue =
|
||
| UndefinedValue
|
||
| NullValue
|
||
| BooleanValue
|
||
| JSStringValue
|
||
| SymbolValue
|
||
| NumberValue
|
||
| BigIntValue;
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types */
|
||
export const PrimitiveValue = (() => {
|
||
type PrimValue = PrimitiveValue;
|
||
return (() => {
|
||
// NOTE: Using nested IIFE so that the class does not conflict with the type of the same name
|
||
// NOTE: Only using IIFE because TypeScript errors when `abstract` is used on class expressions
|
||
abstract class PrimitiveValue extends Value {
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is PrimValue;
|
||
}
|
||
return PrimitiveValue;
|
||
})();
|
||
})();
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types-undefined-type */
|
||
export class UndefinedValue extends PrimitiveValue {
|
||
declare readonly type: 'Undefined'; // defined on prototype by static block
|
||
|
||
declare readonly value: undefined; // defined on prototype by static block
|
||
|
||
private constructor() { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor
|
||
super();
|
||
}
|
||
|
||
static {
|
||
Object.defineProperty(this.prototype, 'type', { value: 'Undefined' });
|
||
Object.defineProperty(this.prototype, 'value', { value: undefined });
|
||
Object.defineProperty(Value, 'undefined', { value: new this() });
|
||
}
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is UndefinedValue;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types-null-type */
|
||
export class NullValue extends PrimitiveValue {
|
||
declare readonly type: 'Null'; // defined on prototype by static block
|
||
|
||
declare readonly value: null; // defined on prototype by static block
|
||
|
||
private constructor() { // eslint-disable-line no-useless-constructor -- Sets privacy for constructor
|
||
super();
|
||
}
|
||
|
||
static {
|
||
Object.defineProperty(this.prototype, 'type', { value: 'Null' });
|
||
Object.defineProperty(this.prototype, 'value', { value: null });
|
||
Object.defineProperty(Value, 'null', { value: new this() });
|
||
}
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is NullValue;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types-boolean-type */
|
||
export class BooleanValue<T extends boolean = boolean> extends PrimitiveValue {
|
||
declare readonly type: 'Boolean'; // defined on prototype by static block
|
||
|
||
readonly value: T;
|
||
|
||
private constructor(value: T) {
|
||
super();
|
||
this.value = value;
|
||
}
|
||
|
||
booleanValue() {
|
||
return this.value;
|
||
}
|
||
|
||
[Symbol.for('nodejs.util.inspect.custom')]() {
|
||
return `Boolean { ${this.value} }`;
|
||
}
|
||
|
||
static {
|
||
Object.defineProperty(this.prototype, 'type', { value: 'Boolean' });
|
||
Object.defineProperty(Value, 'true', { value: new this(true) });
|
||
Object.defineProperty(Value, 'false', { value: new this(false) });
|
||
}
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is BooleanValue;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type */
|
||
export class JSStringValue extends PrimitiveValue {
|
||
declare readonly type: 'String'; // defined on prototype by static block
|
||
|
||
readonly value: string;
|
||
|
||
private constructor(value: string) {
|
||
super();
|
||
this.value = value;
|
||
}
|
||
|
||
stringValue() {
|
||
return this.value;
|
||
}
|
||
|
||
static {
|
||
Object.defineProperty(this.prototype, 'type', { value: 'String' });
|
||
createStringValue = (value) => new this(value);
|
||
}
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is JSStringValue;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type */
|
||
export class SymbolValue extends PrimitiveValue {
|
||
declare readonly type: 'Symbol'; // defined on prototype by static block
|
||
|
||
readonly Description: JSStringValue | UndefinedValue;
|
||
|
||
constructor(Description: JSStringValue | UndefinedValue) {
|
||
super();
|
||
this.Description = Description;
|
||
}
|
||
|
||
static {
|
||
Object.defineProperty(this.prototype, 'type', { value: 'Symbol' });
|
||
}
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is SymbolValue;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type */
|
||
export const wellKnownSymbols = {
|
||
asyncIterator: new SymbolValue(Value('Symbol.asyncIterator')),
|
||
hasInstance: new SymbolValue(Value('Symbol.hasInstance')),
|
||
isConcatSpreadable: new SymbolValue(Value('Symbol.isConcatSpreadable')),
|
||
iterator: new SymbolValue(Value('Symbol.iterator')),
|
||
match: new SymbolValue(Value('Symbol.match')),
|
||
matchAll: new SymbolValue(Value('Symbol.matchAll')),
|
||
replace: new SymbolValue(Value('Symbol.replace')),
|
||
search: new SymbolValue(Value('Symbol.search')),
|
||
species: new SymbolValue(Value('Symbol.species')),
|
||
split: new SymbolValue(Value('Symbol.split')),
|
||
toPrimitive: new SymbolValue(Value('Symbol.toPrimitive')),
|
||
toStringTag: new SymbolValue(Value('Symbol.toStringTag')),
|
||
unscopables: new SymbolValue(Value('Symbol.unscopables')),
|
||
} as const;
|
||
Object.setPrototypeOf(wellKnownSymbols, null);
|
||
Object.freeze(wellKnownSymbols);
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type */
|
||
export class NumberValue extends PrimitiveValue {
|
||
declare readonly type: 'Number'; // defined on prototype by static block
|
||
|
||
readonly value: number;
|
||
|
||
private constructor(value: number) {
|
||
super();
|
||
this.value = value;
|
||
}
|
||
|
||
numberValue() {
|
||
return this.value;
|
||
}
|
||
|
||
isNaN() {
|
||
return Number.isNaN(this.value);
|
||
}
|
||
|
||
isInfinity() {
|
||
return !Number.isFinite(this.value) && !this.isNaN();
|
||
}
|
||
|
||
isFinite() {
|
||
return Number.isFinite(this.value);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-unaryMinus */
|
||
static unaryMinus(x: NumberValue) {
|
||
if (x.isNaN()) {
|
||
return F(NaN);
|
||
}
|
||
return F(-R(x));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseNOT */
|
||
static bitwiseNOT(x: NumberValue) {
|
||
// 1. Let oldValue be ! ToInt32(x).
|
||
const oldValue = X(ToInt32(x));
|
||
// 2. Return the result of applying bitwise complement to oldValue. The result is a signed 32-bit integer.
|
||
return F(~R(oldValue));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-exponentiate */
|
||
static exponentiate(base: NumberValue, exponent: NumberValue) {
|
||
return F(R(base) ** R(exponent));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-multiply */
|
||
static multiply(x: NumberValue, y: NumberValue) {
|
||
return F(R(x) * R(y));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-divide */
|
||
static divide(x: NumberValue, y: NumberValue) {
|
||
return F(R(x) / R(y));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-remainder */
|
||
static remainder(n: NumberValue, d: NumberValue) {
|
||
return F(R(n) % R(d));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-add */
|
||
static add(x: NumberValue, y: NumberValue) {
|
||
return F(R(x) + R(y));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-subtract */
|
||
static subtract(x: NumberValue, y: NumberValue) {
|
||
// The result of - operator is x + (-y).
|
||
return NumberValue.add(x, F(-R(y)));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-leftShift */
|
||
static leftShift(x: NumberValue, y: NumberValue) {
|
||
// 1. Let lnum be ! ToInt32(x).
|
||
const lnum = X(ToInt32(x));
|
||
// 2. Let rnum be ! ToUint32(y).
|
||
const rnum = X(ToUint32(y));
|
||
// 3. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F.
|
||
const shiftCount = R(rnum) & 0x1F; // eslint-disable-line no-bitwise
|
||
// 4. Return the result of left shifting lnum by shiftCount bits. The result is a signed 32-bit integer.
|
||
return F(R(lnum) << shiftCount); // eslint-disable-line no-bitwise
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-signedRightShift */
|
||
static signedRightShift(x: NumberValue, y: NumberValue) {
|
||
// 1. Let lnum be ! ToInt32(x).
|
||
const lnum = X(ToInt32(x));
|
||
// 2. Let rnum be ! ToUint32(y).
|
||
const rnum = X(ToUint32(y));
|
||
// 3. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F.
|
||
const shiftCount = R(rnum) & 0x1F; // eslint-disable-line no-bitwise
|
||
// 4. Return the result of performing a sign-extending right shift of lnum by shiftCount bits.
|
||
// The most significant bit is propagated. The result is a signed 32-bit integer.
|
||
return F(R(lnum) >> shiftCount); // eslint-disable-line no-bitwise
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-unsignedRightShift */
|
||
static unsignedRightShift(x: NumberValue, y: NumberValue) {
|
||
// 1. Let lnum be ! ToInt32(x).
|
||
const lnum = X(ToInt32(x));
|
||
// 2. Let rnum be ! ToUint32(y).
|
||
const rnum = X(ToUint32(y));
|
||
// 3. Let shiftCount be the result of masking out all but the least significant 5 bits of rnum, that is, compute rnum & 0x1F.
|
||
const shiftCount = R(rnum) & 0x1F; // eslint-disable-line no-bitwise
|
||
// 4. Return the result of performing a zero-filling right shift of lnum by shiftCount bits.
|
||
// Vacated bits are filled with zero. The result is an unsigned 32-bit integer.
|
||
return F(R(lnum) >>> shiftCount); // eslint-disable-line no-bitwise
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-lessThan */
|
||
static lessThan(x: NumberValue, y: NumberValue) {
|
||
if (x.isNaN()) {
|
||
return Value.undefined;
|
||
}
|
||
if (y.isNaN()) {
|
||
return Value.undefined;
|
||
}
|
||
// If nx and ny are the same Number value, return false.
|
||
// If nx is +0 and ny is -0, return false.
|
||
// If nx is -0 and ny is +0, return false.
|
||
if (R(x) === R(y)) {
|
||
return Value.false;
|
||
}
|
||
if (R(x) === +Infinity) {
|
||
return Value.false;
|
||
}
|
||
if (R(y) === +Infinity) {
|
||
return Value.true;
|
||
}
|
||
if (R(y) === -Infinity) {
|
||
return Value.false;
|
||
}
|
||
if (R(x) === -Infinity) {
|
||
return Value.true;
|
||
}
|
||
return R(x) < R(y) ? Value.true : Value.false;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-equal */
|
||
static equal(x: NumberValue, y: NumberValue) {
|
||
if (x.isNaN()) {
|
||
return Value.false;
|
||
}
|
||
if (y.isNaN()) {
|
||
return Value.false;
|
||
}
|
||
const xVal = R(x);
|
||
const yVal = R(y);
|
||
if (xVal === yVal) {
|
||
return Value.true;
|
||
}
|
||
if (Object.is(xVal, 0) && Object.is(yVal, -0)) {
|
||
return Value.true;
|
||
}
|
||
if (Object.is(xVal, -0) && Object.is(yVal, 0)) {
|
||
return Value.true;
|
||
}
|
||
return Value.false;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-sameValue */
|
||
static sameValue(x: NumberValue, y: NumberValue) {
|
||
if (x.isNaN() && y.isNaN()) {
|
||
return Value.true;
|
||
}
|
||
const xVal = R(x);
|
||
const yVal = R(y);
|
||
if (Object.is(xVal, 0) && Object.is(yVal, -0)) {
|
||
return Value.false;
|
||
}
|
||
if (Object.is(xVal, -0) && Object.is(yVal, 0)) {
|
||
return Value.false;
|
||
}
|
||
if (xVal === yVal) {
|
||
return Value.true;
|
||
}
|
||
return Value.false;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero */
|
||
static sameValueZero(x: NumberValue, y: NumberValue) {
|
||
if (x.isNaN() && y.isNaN()) {
|
||
return Value.true;
|
||
}
|
||
const xVal = R(x);
|
||
const yVal = R(y);
|
||
if (Object.is(xVal, 0) && Object.is(yVal, -0)) {
|
||
return Value.true;
|
||
}
|
||
if (Object.is(xVal, -0) && Object.is(yVal, 0)) {
|
||
return Value.true;
|
||
}
|
||
if (xVal === yVal) {
|
||
return Value.true;
|
||
}
|
||
return Value.false;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseAND */
|
||
static bitwiseAND(x: NumberValue, y: NumberValue) {
|
||
// 1. Return NumberBitwiseOp(&, x, y).
|
||
return NumberBitwiseOp('&', x, y);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseXOR */
|
||
static bitwiseXOR(x: NumberValue, y: NumberValue) {
|
||
// 1. Return NumberBitwiseOp(^, x, y).
|
||
return NumberBitwiseOp('^', x, y);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseOR */
|
||
static bitwiseOR(x: NumberValue, y: NumberValue) {
|
||
// 1. Return NumberBitwiseOp(|, x, y).
|
||
return NumberBitwiseOp('|', x, y);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-number-tostring */
|
||
static override toString(xV: NumberValue, radix: number): JSStringValue {
|
||
if (xV.isNaN()) {
|
||
return Value('NaN');
|
||
}
|
||
const x = R(xV);
|
||
if (Object.is(x, -0) || Object.is(x, 0)) {
|
||
return Value('0');
|
||
}
|
||
if (x < 0) {
|
||
return Value(`-${NumberValue.toString(F(-x), radix).stringValue()}`);
|
||
}
|
||
if (xV.isInfinity()) {
|
||
return Value('Infinity');
|
||
}
|
||
// TODO: implement properly, currently depends on host.
|
||
return Value(`${x.toString(radix)}`);
|
||
}
|
||
|
||
static readonly unit = new NumberValue(1);
|
||
|
||
static {
|
||
Object.defineProperty(this.prototype, 'type', { value: 'Number' });
|
||
createNumberValue = (value) => new NumberValue(value);
|
||
}
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is NumberValue;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numberbitwiseop */
|
||
function NumberBitwiseOp(op: '&' | '|' | '^', x: NumberValue, y: NumberValue) {
|
||
// 1. Let lnum be ! ToInt32(x).
|
||
const lnum = X(ToInt32(x));
|
||
// 2. Let rnum be ! ToUint32(y).
|
||
const rnum = X(ToUint32(y));
|
||
// 3. Return the result of applying the bitwise operator op to lnum and rnum. The result is a signed 32-bit integer.
|
||
switch (op) {
|
||
case '&':
|
||
return F(R(lnum) & R(rnum));
|
||
case '|':
|
||
return F(R(lnum) | R(rnum));
|
||
case '^':
|
||
return F(R(lnum) ^ R(rnum));
|
||
default:
|
||
throw new OutOfRange('NumberBitwiseOp', op);
|
||
}
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-ecmascript-language-types-bigint-type */
|
||
export class BigIntValue extends PrimitiveValue {
|
||
declare readonly type: 'BigInt'; // defined on prototype by static block
|
||
|
||
readonly value: bigint;
|
||
|
||
private constructor(value: bigint) {
|
||
super();
|
||
this.value = value;
|
||
}
|
||
|
||
bigintValue() {
|
||
return this.value;
|
||
}
|
||
|
||
isNaN() {
|
||
return false;
|
||
}
|
||
|
||
isFinite() {
|
||
return true;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-unaryMinus */
|
||
static unaryMinus(x: BigIntValue) {
|
||
if (R(x) === 0n) {
|
||
return Z(0n);
|
||
}
|
||
return Z(-R(x));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseNOT */
|
||
static bitwiseNOT(x: BigIntValue) {
|
||
return Z(-R(x) - 1n);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-exponentiate */
|
||
static exponentiate(base: BigIntValue, exponent: BigIntValue) {
|
||
// 1. If exponent < 0n, throw a RangeError exception.
|
||
if (R(exponent) < 0n) {
|
||
return Throw.RangeError('Exponent of bigint must be positive');
|
||
}
|
||
// 2. If base is 0n and exponent is 0n, return 1n.
|
||
if (R(base) === 0n && R(exponent) === 0n) {
|
||
return Z(1n);
|
||
}
|
||
// 3. Return the BigInt value that represents the mathematical value of base raised to the power exponent.
|
||
return Z(R(base) ** R(exponent));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-multiply */
|
||
static multiply(x: BigIntValue, y: BigIntValue) {
|
||
return Z(R(x) * R(y));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-divide */
|
||
static divide(x: BigIntValue, y: BigIntValue) {
|
||
// 1. If y is 0n, throw a RangeError exception.
|
||
if (R(y) === 0n) {
|
||
return Throw.RangeError('Cannot divide by zero');
|
||
}
|
||
// 2. Let quotient be the mathematical value of x divided by y.
|
||
const quotient = R(x) / R(y);
|
||
// 3. Return the BigInt value that represents quotient rounded towards 0 to the next integral value.
|
||
return Z(quotient);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-remainder */
|
||
static remainder(n: BigIntValue, d: BigIntValue) {
|
||
// 1. If d is 0n, throw a RangeError exception.
|
||
if (R(d) === 0n) {
|
||
return Throw.RangeError('Cannot divide by zero');
|
||
}
|
||
// 2. If n is 0n, return 0n.
|
||
if (R(n) === 0n) {
|
||
return Z(0n);
|
||
}
|
||
// 3. Let r be the BigInt defined by the mathematical relation r = n - (d × q)
|
||
// where q is a BigInt that is negative only if n/d is negative and positive
|
||
// only if n/d is positive, and whose magnitude is as large as possible without
|
||
// exceeding the magnitude of the true mathematical quotient of n and d.
|
||
const r = Z(R(n) % R(d));
|
||
// 4. Return r.
|
||
return r;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-add */
|
||
static add(x: BigIntValue, y: BigIntValue) {
|
||
return Z(R(x) + R(y));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-subtract */
|
||
static subtract(x: BigIntValue, y: BigIntValue) {
|
||
return Z(R(x) - R(y));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-leftShift */
|
||
static leftShift(x: BigIntValue, y: BigIntValue) {
|
||
return Z(R(x) << R(y)); // eslint-disable-line no-bitwise
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-signedRightShift */
|
||
static signedRightShift(x: BigIntValue, y: BigIntValue) {
|
||
// 1. Return BigInt::leftShift(x, -y).
|
||
return BigIntValue.leftShift(x, Z(-R(y)));
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-unsignedRightShift */
|
||
static unsignedRightShift(_x: BigIntValue, _y: BigIntValue) {
|
||
return Throw.TypeError('BigInt has no unsigned right shift, use >> instead');
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-lessThan */
|
||
static lessThan(x: BigIntValue, y: BigIntValue) {
|
||
return R(x) < R(y) ? Value.true : Value.false;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-equal */
|
||
static equal(x: BigIntValue, y: BigIntValue) {
|
||
// Return true if x and y have the same mathematical integer value and false otherwise.
|
||
return R(x) === R(y) ? Value.true : Value.false;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValue */
|
||
static sameValue(x: BigIntValue, y: BigIntValue) {
|
||
// 1. Return BigInt::equal(x, y).
|
||
return BigIntValue.equal(x, y);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-sameValueZero */
|
||
static sameValueZero(x: BigIntValue, y: BigIntValue) {
|
||
// 1. Return BigInt::equal(x, y).
|
||
return BigIntValue.equal(x, y);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseAND */
|
||
static bitwiseAND(x: BigIntValue, y: BigIntValue) {
|
||
// 1. Return BigIntBitwiseOp(&, x, y).
|
||
return BigIntBitwiseOp('&', x, y);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseXOR */
|
||
static bitwiseXOR(x: BigIntValue, y: BigIntValue) {
|
||
// 1. Return BigIntBitwiseOp(^, x, y).
|
||
return BigIntBitwiseOp('^', x, y);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-bitwiseOR */
|
||
static bitwiseOR(x: BigIntValue, y: BigIntValue) {
|
||
// 1. Return BigIntBitwiseOp(|, x, y);
|
||
return BigIntBitwiseOp('|', x, y);
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-numeric-types-bigint-tostring */
|
||
static override toString(x: BigIntValue, radix: number): JSStringValue {
|
||
// 1. If x is less than zero, return the string-concatenation of the String "-" and ! BigInt::toString(-x).
|
||
if (R(x) < 0n) {
|
||
const str = X(BigIntValue.toString(Z(-R(x)), radix)).stringValue();
|
||
return Value(`-${str}`);
|
||
}
|
||
// 2. Return the String value consisting of the code units of the digits of the decimal representation of x.
|
||
return Value(`${R(x).toString(radix)}`);
|
||
}
|
||
|
||
static readonly unit = new BigIntValue(1n);
|
||
|
||
static {
|
||
Object.defineProperty(this.prototype, 'type', { value: 'BigInt' });
|
||
createBigIntValue = (value) => new BigIntValue(value);
|
||
}
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is BigIntValue;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-bigintbitwiseop */
|
||
function BigIntBitwiseOp(op: '&' | '|' | '^', x: BigIntValue, y: BigIntValue) {
|
||
// TODO: figure out why this doesn't work, probably the modulo.
|
||
/*
|
||
// 1. Assert: op is "&", "|", or "^".
|
||
Assert(['&', '|', '^'].includes(op));
|
||
// 2. Let result be 0n.
|
||
let result = 0n;
|
||
// 3. Let shift be 0.
|
||
let shift = 0n;
|
||
// 4. Repeat, until (x = 0 or x = -1) and (y = 0 or y = -1),
|
||
while (!((x === 0n || x === -1n) && (y === 0n || y === -1n))) {
|
||
// a. Let xDigit be x modulo 2.
|
||
const xDigit = x % 2n;
|
||
// b. Let yDigit be y modulo 2.
|
||
const yDigit = y % 2n;
|
||
// c. If op is "&", set result to result + 2^shift × BinaryAnd(xDigit, yDigit).
|
||
if (op === '&') {
|
||
result += (2n ** shift) * BinaryAnd(xDigit, yDigit);
|
||
} else if (op === '|') {
|
||
// d. Else if op is "|", set result to result + 2shift × BinaryOr(xDigit, yDigit).
|
||
result += (2n ** shift) * BinaryXor(xDigit, yDigit);
|
||
} else {
|
||
// i. Assert: op is "^".
|
||
Assert(op === '^');
|
||
// ii. Set result to result + 2^shift × BinaryXor(xDigit, yDigit).
|
||
result += (2n ** shift) * BinaryXor(xDigit, yDigit);
|
||
}
|
||
// f. Set shift to shift + 1.
|
||
shift += 1n;
|
||
// g. Set x to (x - xDigit) / 2.
|
||
x = (x - xDigit) / 2n;
|
||
// h. Set y to (y - yDigit) / 2.
|
||
y = (y - yDigit) / 2n;
|
||
}
|
||
let tmp;
|
||
// 5. If op is "&", let tmp be BinaryAnd(x modulo 2, y modulo 2).
|
||
if (op === '&') {
|
||
tmp = BinaryAnd(x % 2n, y % 2n);
|
||
} else if (op === '|') {
|
||
// 6. Else if op is "|", let tmp be BinaryOr(x modulo 2, y modulo 2).
|
||
tmp = BinaryOr(x % 2n, y % 2n);
|
||
} else {
|
||
// a. Assert: op is "^".
|
||
Assert(op === '^');
|
||
// b. Let tmp be BinaryXor(x modulo 2, y modulo 2).
|
||
tmp = BinaryXor(x % 2n, y % 2n);
|
||
}
|
||
// 8. If tmp ≠ 0, then
|
||
if (tmp !== 0n) {
|
||
// a. Set result to result - 2^shift. NOTE: This extends the sign.
|
||
result -= 2n ** shift;
|
||
}
|
||
// 9. Return result.
|
||
return Z(result);
|
||
*/
|
||
switch (op) {
|
||
case '&':
|
||
return Z(R(x) & R(y));
|
||
case '|':
|
||
return Z(R(x) | R(y));
|
||
case '^':
|
||
return Z(R(x) ^ R(y));
|
||
default:
|
||
throw new OutOfRange('BigIntBitwiseOp', op);
|
||
}
|
||
}
|
||
|
||
export interface ObjectInternalMethods<Self> {
|
||
GetPrototypeOf(this: Self): ValueEvaluator<ObjectValue | NullValue>;
|
||
SetPrototypeOf(this: Self, V: ObjectValue | NullValue): ValueEvaluator<BooleanValue>;
|
||
IsExtensible(this: Self): ValueEvaluator<BooleanValue>;
|
||
PreventExtensions(this: Self): ValueEvaluator<BooleanValue>;
|
||
GetOwnProperty(this: Self, P: PropertyKeyValue): PlainEvaluator<Descriptor | UndefinedValue>;
|
||
DefineOwnProperty(this: Self, P: PropertyKeyValue, Desc: Descriptor): ValueEvaluator<BooleanValue>;
|
||
HasProperty(this: Self, P: PropertyKeyValue): ValueEvaluator<BooleanValue>;
|
||
Get(this: Self, P: PropertyKeyValue, Receiver: Value): ValueEvaluator;
|
||
Set(this: Self, P: PropertyKeyValue, V: Value, Receiver: Value): ValueEvaluator<BooleanValue>;
|
||
Delete(this: Self, P: PropertyKeyValue): ValueEvaluator<BooleanValue>;
|
||
OwnPropertyKeys(this: Self): PlainEvaluator<PropertyKeyValue[]>;
|
||
Call?(this: Self, thisArg: Value, args: Arguments): ValueEvaluator;
|
||
Construct?(this: Self, args: Arguments, newTarget: FunctionObject | UndefinedValue): ValueEvaluator<ObjectValue>;
|
||
}
|
||
|
||
type ObjectSlotReturn = {
|
||
[key in keyof ObjectInternalMethods<ObjectValue>]: ReturnType<NonNullable<ObjectInternalMethods<ObjectValue>[key]>>
|
||
};
|
||
/** https://tc39.es/ecma262/#sec-object-type */
|
||
export class ObjectValue extends Value implements ObjectInternalMethods<ObjectValue> {
|
||
declare readonly type: 'Object'; // defined on prototype by static block
|
||
|
||
readonly properties: PropertyKeyMap<Descriptor>;
|
||
|
||
readonly internalSlotsList: readonly string[];
|
||
|
||
readonly PrivateElements: PrivateElementRecord[];
|
||
|
||
// https://tc39.es/proposal-pattern-matching/#sec-object-internal-methods-and-internal-slots
|
||
readonly ConstructedBy: (ECMAScriptFunctionObject | DefaultConstructorBuiltinFunction)[];
|
||
|
||
constructor(internalSlotsList: readonly string[]) {
|
||
super();
|
||
|
||
this.PrivateElements = [];
|
||
this.ConstructedBy = [];
|
||
this.properties = new PropertyKeyMap();
|
||
this.internalSlotsList = internalSlotsList;
|
||
surroundingAgent.debugger_markObjectCreated(this);
|
||
}
|
||
|
||
// UNSAFE casts below. Methods below are expected to be rewritten when the object is not an OrdinaryObject. (an example is ArgumentExoticObject)
|
||
// If those methods aren't rewritten, it is an error.
|
||
// eslint-disable-next-line require-yield
|
||
* GetPrototypeOf(): ObjectSlotReturn['GetPrototypeOf'] {
|
||
return OrdinaryGetPrototypeOf(this as unknown as OrdinaryObject);
|
||
}
|
||
|
||
// eslint-disable-next-line require-yield
|
||
* SetPrototypeOf(V: ObjectValue | NullValue): ObjectSlotReturn['SetPrototypeOf'] {
|
||
Q(surroundingAgent.debugger_tryTouchDuringPreview(this));
|
||
return OrdinarySetPrototypeOf(this as unknown as OrdinaryObject, V);
|
||
}
|
||
|
||
// eslint-disable-next-line require-yield
|
||
* IsExtensible(): ObjectSlotReturn['IsExtensible'] {
|
||
return OrdinaryIsExtensible(this as unknown as OrdinaryObject);
|
||
}
|
||
|
||
// eslint-disable-next-line require-yield
|
||
* PreventExtensions(): ObjectSlotReturn['PreventExtensions'] {
|
||
Q(surroundingAgent.debugger_tryTouchDuringPreview(this));
|
||
return OrdinaryPreventExtensions(this as unknown as OrdinaryObject);
|
||
}
|
||
|
||
// eslint-disable-next-line require-yield
|
||
* GetOwnProperty(P: PropertyKeyValue): ObjectSlotReturn['GetOwnProperty'] {
|
||
return OrdinaryGetOwnProperty(this as unknown as OrdinaryObject, P);
|
||
}
|
||
|
||
* DefineOwnProperty(P: PropertyKeyValue, Desc: Descriptor): ObjectSlotReturn['DefineOwnProperty'] {
|
||
Q(surroundingAgent.debugger_tryTouchDuringPreview(this));
|
||
return yield* OrdinaryDefineOwnProperty(this as unknown as OrdinaryObject, P, Desc);
|
||
}
|
||
|
||
* HasProperty(P: PropertyKeyValue): ObjectSlotReturn['HasProperty'] {
|
||
return yield* OrdinaryHasProperty(this as unknown as OrdinaryObject, P);
|
||
}
|
||
|
||
* Get(P: PropertyKeyValue, Receiver: Value): ObjectSlotReturn['Get'] {
|
||
return yield* OrdinaryGet(this as unknown as OrdinaryObject, P, Receiver);
|
||
}
|
||
|
||
* Set(P: PropertyKeyValue, V: Value, Receiver: Value): ObjectSlotReturn['Set'] {
|
||
// TODO:
|
||
Q(surroundingAgent.debugger_tryTouchDuringPreview(Receiver as ObjectValue));
|
||
return yield* OrdinarySet(this as unknown as OrdinaryObject, P, V, Receiver);
|
||
}
|
||
|
||
* Delete(P: PropertyKeyValue): ObjectSlotReturn['Delete'] {
|
||
Q(surroundingAgent.debugger_tryTouchDuringPreview(this));
|
||
return yield* OrdinaryDelete(this as unknown as OrdinaryObject, P);
|
||
}
|
||
|
||
// eslint-disable-next-line require-yield
|
||
* OwnPropertyKeys(): ObjectSlotReturn['OwnPropertyKeys'] {
|
||
return OrdinaryOwnPropertyKeys(this as unknown as OrdinaryObject);
|
||
}
|
||
|
||
// NON-SPEC
|
||
mark(m: GCMarker) {
|
||
m(this.properties);
|
||
this.internalSlotsList.forEach((s) => {
|
||
// @ts-ignore
|
||
m(this[s]);
|
||
if (s === 'HostCapturedValues' && s in this && Array.isArray(this[s])) {
|
||
this[s].forEach(m);
|
||
}
|
||
});
|
||
}
|
||
|
||
static {
|
||
Object.defineProperty(this.prototype, 'type', { value: 'Object' });
|
||
}
|
||
|
||
declare static [Symbol.hasInstance]: (value: unknown) => value is ObjectValue;
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-private-names */
|
||
export class PrivateName {
|
||
// NOTE: The following declaration distinguishes `PrivateName` from `SymbolValue` so that type guards can properly
|
||
// remove it from unions with `SymbolValue` due to structural overlap.
|
||
declare private _: never;
|
||
|
||
readonly Description: JSStringValue;
|
||
|
||
constructor(description: JSStringValue) {
|
||
this.Description = description;
|
||
}
|
||
}
|
||
|
||
export class ReferenceRecord {
|
||
readonly Base: 'unresolvable' | Value | EnvironmentRecord;
|
||
|
||
ReferencedName: Value | PrivateName;
|
||
|
||
readonly Strict: BooleanValue;
|
||
|
||
readonly ThisValue: Value | undefined;
|
||
|
||
constructor({
|
||
Base,
|
||
ReferencedName,
|
||
Strict,
|
||
ThisValue,
|
||
}: Pick<ReferenceRecord, 'Base' | 'ReferencedName' | 'Strict' | 'ThisValue'>) {
|
||
this.Base = Base;
|
||
this.ReferencedName = ReferencedName;
|
||
this.Strict = Strict;
|
||
this.ThisValue = ThisValue;
|
||
}
|
||
|
||
// NON-SPEC
|
||
mark(m: GCMarker) {
|
||
m(this.Base);
|
||
m(this.ReferencedName);
|
||
m(this.ThisValue);
|
||
}
|
||
}
|
||
|
||
export type DescriptorInit = Pick<Descriptor, 'Configurable' | 'Enumerable' | 'Get' | 'Set' | 'Value' | 'Writable'>;
|
||
// @ts-expect-error
|
||
export function Descriptor(O: DescriptorInit): Descriptor // @ts-expect-error
|
||
export @callable() class Descriptor {
|
||
readonly Value?: Value;
|
||
|
||
readonly Get?: FunctionObject | UndefinedValue;
|
||
|
||
readonly Set?: FunctionObject | UndefinedValue;
|
||
|
||
readonly Writable?: BooleanValue;
|
||
|
||
readonly Enumerable?: BooleanValue;
|
||
|
||
readonly Configurable?: BooleanValue;
|
||
|
||
constructor(O: Pick<Descriptor, 'Configurable' | 'Enumerable' | 'Get' | 'Set' | 'Value' | 'Writable'>) {
|
||
this.Value = O.Value;
|
||
this.Get = O.Get;
|
||
this.Set = O.Set;
|
||
this.Writable = O.Writable;
|
||
this.Enumerable = O.Enumerable;
|
||
this.Configurable = O.Configurable;
|
||
}
|
||
|
||
everyFieldIsAbsent() {
|
||
return this.Value === undefined
|
||
&& this.Get === undefined
|
||
&& this.Set === undefined
|
||
&& this.Writable === undefined
|
||
&& this.Enumerable === undefined
|
||
&& this.Configurable === undefined;
|
||
}
|
||
|
||
// NON-SPEC
|
||
mark(m: GCMarker) {
|
||
m(this.Value);
|
||
m(this.Get);
|
||
m(this.Set);
|
||
}
|
||
}
|
||
|
||
export class DataBlock extends Uint8Array {
|
||
constructor(sizeOrBuffer: number | ArrayBuffer, byteOffset?: number, length?: number) {
|
||
if (sizeOrBuffer instanceof ArrayBuffer) {
|
||
super(sizeOrBuffer, byteOffset, length);
|
||
} else {
|
||
Assert(typeof sizeOrBuffer === 'number');
|
||
super(sizeOrBuffer);
|
||
}
|
||
}
|
||
}
|
||
|
||
/** https://tc39.es/ecma262/#sec-sametype */
|
||
export function SameType(x: Value, y: Value) {
|
||
switch (true) {
|
||
case x === Value.undefined && y === Value.undefined:
|
||
case x === Value.null && y === Value.null:
|
||
case x instanceof BooleanValue && y instanceof BooleanValue:
|
||
case x instanceof NumberValue && y instanceof NumberValue:
|
||
case x instanceof BigIntValue && y instanceof BigIntValue:
|
||
case x instanceof SymbolValue && y instanceof SymbolValue:
|
||
case x instanceof JSStringValue && y instanceof JSStringValue:
|
||
case x instanceof ObjectValue && y instanceof ObjectValue:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
type SafeAccessMethods = 'map' | 'values' | 'entries' | 'filter' | 'forEach' | 'find';
|
||
// function* myFunction([callback]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator
|
||
// ^^^^^^^^
|
||
// if user calls myFunction with no arguments, callback would be undefined, not Value.undefined
|
||
// the correct way is to type it as:
|
||
// function* myFunction([callback = Value.undefined]: Arguments, { thisValue }: FunctionCallContext): ValueEvaluator
|
||
//
|
||
// this type is to prevent such mistakes
|
||
export type Arguments =
|
||
Omit<readonly (Value | undefined)[], SafeAccessMethods> &
|
||
Pick<readonly Value[], SafeAccessMethods>;
|
||
export interface FunctionCallContext {
|
||
readonly thisValue: Value;
|
||
readonly NewTarget: FunctionObject | UndefinedValue;
|
||
}
|
||
export interface NativeSteps {
|
||
(this: BuiltinFunctionObject, args: Arguments, context: FunctionCallContext): PlainEvaluator<Value | void> | PlainCompletion<Value | void>;
|
||
section?: string;
|
||
isConstructor?: boolean;
|
||
}
|
||
export interface CanBeNativeSteps {
|
||
(...args: (Value | undefined)[]): PlainEvaluator<Value | void> | PlainCompletion<Value | void>;
|
||
}
|