UNPKG

linvail

Version:

Provenancial Equality for JavaScript

701 lines (665 loc) 18.6 kB
import { LinvailExecError, LinvailTypeError } from "../../error.mjs"; import { resolveNaN, toInteger } from "../../util/number.mjs"; import { isDataDescriptor } from "../domain.mjs"; import { applyInternal, constructInternal, defineInternalPropertyDescriptor, getInternalPropertyValue, hasInternalProperty, isInternalArray, } from "../reflect.mjs"; import { wrapStandardPrimitive, wrapReference, wrapValue, } from "../region/core.mjs"; import { createEmptyArray, createFullObject } from "../region/create.mjs"; const { Reflect: { defineProperty }, Array: { from: toArray }, } = globalThis; export const void_oracle = { apply: null, construct: null }; ////////////////// // Access Input // ////////////////// /** * @type {( * region: import("../region.d.ts").Region, * array: import("../domain.d.ts").Wrapper[], * index: number, * ) => import("../domain.d.ts").Wrapper} */ export const atInternal = (region, array, index) => index < array.length ? array[index] : wrapStandardPrimitive(region, region["global.undefined"]); /** * @type {( * region: import("../region.d.ts").Region, * array: import("../domain.d.ts").Wrapper[], * index: number, * ) => import("../domain.d.ts").Value} */ export const atExternal = (region, array, index) => index < array.length ? array[index].inner : region["global.undefined"]; ////////////////////////// // Convert to Reference // ////////////////////////// /** * @type {( * region: import("../region.d.ts").Region, * value: import("../domain.d.ts").Wrapper, * ) => null | import("../domain.d.ts").ReferenceWrapper} */ export const toMaybeInternalReferenceSloppy = (region, value) => { switch (value.type) { case "primitive": { const { "global.Object": toObject } = region; return value.inner == null ? null : wrapReference(region, toObject(value.inner)); } case "guest": { return value; } case "host": { return value; } default: { throw new LinvailTypeError(value); } } }; /** * @type {( * region: import("../region.d.ts").Region, * value: import("../domain.d.ts").Wrapper, * ) => import("../domain.d.ts").ReferenceWrapper} */ export const toInternalReferenceSloppy = (region, value) => { switch (value.type) { case "primitive": { if (value.inner == null) { throw new region["global.TypeError"]( "Cannot convert nullish to reference", ); } else { const { "global.Object": toObject } = region; return wrapReference(region, toObject(value.inner)); } } case "guest": { return value; } case "host": { return value; } default: { throw new LinvailTypeError(value); } } }; /** * @type {( * _region: import("../region.d.ts").Region, * value: import("../domain.d.ts").Wrapper, * ) => null | import("../domain.d.ts").ReferenceWrapper} */ export const toMaybeInternalReferenceStrict = (_region, value) => { switch (value.type) { case "primitive": { return null; } case "guest": { return value; } case "host": { return value; } default: { throw new LinvailTypeError(value); } } }; /** * @type {( * region: import("../region.d.ts").Region, * value: import("../domain.d.ts").Wrapper, * ) => import("../domain.d.ts").ReferenceWrapper} */ export const toInternalReferenceStrict = (region, value) => { switch (value.type) { case "primitive": { throw new region["global.TypeError"]( "Cannot convert primitive to reference", ); } case "guest": { return value; } case "host": { return value; } default: { throw new LinvailTypeError(value); } } }; /////////// // Query // /////////// /** * @type {( * region: import("../region.d.ts").Region, * target: import("../domain.d.ts").ReferenceWrapper, * ) => boolean} */ export const isConcatSpreadable = (region, target) => { const { "global.Symbol.isConcatSpreadable": is_concat_spreadable_symbol, "global.undefined": undefined, } = region; const spreadable = getInternalPropertyValue( region, target, is_concat_spreadable_symbol, target, ).inner; if (spreadable === undefined) { return isInternalArray(region, target); } else { return !!spreadable; } }; ////////////////// // Create Array // ////////////////// /** * @type {( * region: import("../region.d.ts").Region, * target: import("../domain.d.ts").ReferenceWrapper, * length: number, * ) => import("../domain.d.ts").ReferenceWrapper} */ export const createSpecieArray = (region, target, length) => { const { "global.undefined": undefined, "global.Symbol.species": species_symbol, "global.TypeError": TypeError, } = region; if (isInternalArray(region, target)) { let constructor = getInternalPropertyValue( region, target, "constructor", target, ); if (constructor.type !== "primitive") { constructor = getInternalPropertyValue( region, constructor, species_symbol, constructor, ); if (constructor.inner === null) { constructor = wrapStandardPrimitive(region, undefined); } } if (constructor.inner === undefined) { // BREAKING: It should be using Array.prototype fromm the target's realm return createEmptyArray(region, length); } if (constructor.type === "primitive") { throw new TypeError("Invalid array species constructor: primitive"); } return constructInternal( region, constructor, [wrapStandardPrimitive(region, length)], constructor, ); } else { return createEmptyArray(region, length); } }; //////////////////////// // Closure Convertion // //////////////////////// /** * @type {( * region: import("../region.d.ts").Region, * value: import("../domain.d.ts").Wrapper, * ) => import("../domain.d.ts").ReferenceWrapper} */ export const toInternalClosureStrict = (region, value) => { if (value.type === "primitive") { throw new region["global.TypeError"]( "Cannot convert primitive value to function", ); } else { if (typeof value.inner === "function") { return value; } else { throw new region["global.TypeError"]( "Cannot convert object value to function", ); } } }; /** * @type {( * region: import("../region.d.ts").Region, * value: import("../domain.d.ts").Wrapper, * ) => null | import("../domain.d.ts").ReferenceWrapper} */ export const toInternalClosureSloppy = (region, value) => value.inner === region["global.undefined"] ? null : toInternalClosureStrict(region, value); ////////////////////////// // Prototype Convertion // ////////////////////////// /** * @type {( * region: import("../region.d.ts").Region, * value: import("../domain.d.ts").Wrapper, * ) => null | import("../domain.d.ts").Reference} */ export const toPrototype = (region, value) => { if (value.type === "primitive") { if (value.inner === null) { return null; } throw new region["global.TypeError"]( "Cannot convert primitive to prototype", ); } else if (value.type === "guest" || value.type === "host") { return value.inner; } else { throw new LinvailTypeError(value); } }; /** * @type {( * region: import("../region.d.ts").Region, * value: null | import("../domain.d.ts").Reference, * ) => import("../domain.d.ts").Wrapper} */ export const fromPrototype = (region, prototype) => prototype === null ? wrapStandardPrimitive(region, null) : wrapReference(region, prototype); /////////////////////////// // Descriptor Convertion // /////////////////////////// /** @type {["writable", "enumerable", "configurable"]} */ const flags = ["writable", "enumerable", "configurable"]; /** * @type {( * region: import("../region.d.ts").Region, * value: import("../domain.d.ts").Wrapper, * ) => import("../domain.d.ts").HostDefineDescriptor} */ export const toInternalDefineDescriptor = (region, value) => { if (value.type === "primitive") { throw new region["global.TypeError"]( "Cannot convert primitive to descriptor", ); } /** @type {import("../domain.d.ts").HostDefineDescriptor} */ const descriptor = { __proto__: null }; if (hasInternalProperty(region, value, "value")) { descriptor.value = getInternalPropertyValue(region, value, "value", value); } if (hasInternalProperty(region, value, "get")) { descriptor.get = getInternalPropertyValue( region, value, "get", value, ).inner; } if (hasInternalProperty(region, value, "set")) { descriptor.set = getInternalPropertyValue( region, value, "set", value, ).inner; } for (let index = 0; index < flags.length; index++) { const key = flags[index]; if (hasInternalProperty(region, value, key)) { descriptor[key] = !!getInternalPropertyValue(region, value, key, value) .inner; } } return descriptor; }; /** * @type {( * region: import("../region.d.ts").Region, * descriptor: import("../domain.d.ts").HostDescriptor, * ) => import("../domain.d.ts").HostReferenceWrapper<"object">} */ export const fromInternalDescriptor = (region, descriptor) => { const { "global.Object.prototype": prototype } = region; if (isDataDescriptor(descriptor)) { return createFullObject(region, { __proto__: prototype, value: descriptor.value, writable: wrapStandardPrimitive(region, descriptor.writable), enumerable: wrapStandardPrimitive(region, descriptor.enumerable), configurable: wrapStandardPrimitive(region, descriptor.configurable), }); } else { return createFullObject(region, { __proto__: prototype, get: wrapValue(region, descriptor.get), set: wrapValue(region, descriptor.set), enumerable: wrapStandardPrimitive(region, descriptor.enumerable), configurable: wrapStandardPrimitive(region, descriptor.configurable), }); } }; /** * @type {( * region: import("../region.d.ts").Region, * descriptor: import("../domain.d.ts").HostDescriptor, * receiver: import("../domain.d.ts").Wrapper, * ) => import("../domain.d.ts").Wrapper} */ export const getInternalDescriptorValue = (region, descriptor, receiver) => { const { "global.undefined": undefined } = region; if (isDataDescriptor(descriptor)) { return descriptor.value; } else { const { get } = descriptor; if (get) { return applyInternal(region, wrapValue(region, get), receiver, []); } else { return wrapStandardPrimitive(region, undefined); } } }; /////////// // Other // /////////// /** * @type {( * region: import("../region.d.ts").Region, * iterator: import("../domain.d.ts").ReferenceWrapper, * ) => void} */ export const closeIterator = (region, iterator) => { const close = getInternalPropertyValue(region, iterator, "return", iterator); if (typeof close.inner === "function") { try { applyInternal(region, close, iterator, []); } catch { /* noop */ } } }; /** * @type {( * region: import("../region.d.ts").Region, * target: import("../domain.d.ts").ReferenceWrapper, * ) => number} */ export const getLength = (region, target) => { const { "global.Number": Number } = region; const length = toInteger( Number(getInternalPropertyValue(region, target, "length", target).inner), 0, ); return length < 0 ? 0 : length; }; /** * @type {( * region: import("../region.d.ts").Region, * values: import("../domain.d.ts").Wrapper, * ) => import("../domain.d.ts").Wrapper[]} */ export const toInternalValueArray = (region, value) => { const { "global.Number": Number, "global.TypeError": TypeError } = region; if (value.type === "primitive") { throw new TypeError("Cannot convert prmitive to array"); } return toArray( { length: resolveNaN( Number(getInternalPropertyValue(region, value, "length", value).inner), 0, ), }, (_, index) => getInternalPropertyValue(region, value, index, value), ); }; /** * @type {( * region: import("../region.d.ts").Region, * key: import("../domain.d.ts").Value, * ) => PropertyKey} */ export const toPropertyKey = (region, value) => { const { "global.String": String } = region; return typeof value === "symbol" ? value : String(value); }; /** * @type {( * region: import("../region.d.ts").Region, * compare: null | import("../domain.d.ts").ReferenceWrapper, * value1: import("../domain.d.ts").Wrapper, * value2: import("../domain.d.ts").Wrapper, * ) => boolean} */ export const isStrictlyGreater = (region, compare, value1, value2) => { const { "global.String": String, "global.Number": Number, "global.undefined": undefined, } = region; if (value2.inner === undefined) { return false; } if (value1.inner === undefined) { return true; } if (compare) { return ( Number( applyInternal( region, compare, wrapStandardPrimitive(region, undefined), [value1, value2], ).inner, ) > 0 ); } else { return String(value1.inner) > String(value2.inner); } }; /** * @type {( * region: import("../region.d.ts").Region, * depth: number, * item: import("../domain.d.ts").Wrapper, * items: import("../domain.d.ts").ReferenceWrapper, * length: number, * ) => number} */ export const flatenArray = (region, depth, item, items, length) => { if (depth === 0 || !isInternalArray(region, item)) { if ( !defineInternalPropertyDescriptor(region, items, length, { __proto__: null, value: item, writable: true, enumerable: true, configurable: true, }) ) { throw new region["global.TypeError"]( "Cannot define array index at Array.prototype.flat", ); } return length + 1; } else { const item_length = getLength(region, item); for (let index = 0; index < item_length; index++) { length = flatenArray( region, depth - 1, getInternalPropertyValue(region, item, index, item), items, length, ); } return length; } }; /** * @type {( * region: import("../region.d.ts").Region, * target: import("../domain.d.ts").ReferenceWrapper, * transform: null | import("../domain.d.ts").ReferenceWrapper, * this_arg: import("../domain.d.ts").Wrapper, * ) => import("../domain.d.ts").HostReferenceWrapper<"array">} */ export const convertArrayLike = (region, target, transform, this_arg) => { const { "global.Reflect.defineProperty": defineProperty } = region; const initial_length = getLength(region, target); const array = createEmptyArray(region, initial_length); let index = 0; let length = initial_length; while (index < length) { const item = getInternalPropertyValue(region, target, index, target); defineProperty(array.plain, index, { __proto__: null, value: transform ? applyInternal(region, transform, this_arg, [ item, wrapStandardPrimitive(region, index), ]) : item, writable: true, enumerable: true, configurable: true, }); index++; length = getLength(region, target); } if (length < initial_length) { defineProperty(array.plain, "length", { __proto__: null, value: length, writable: true, enumerable: false, configurable: false, }); } return array; }; /** * @type {( * region: import("../region.d.ts").Region, * iterator: import("../domain.d.ts").ReferenceWrapper, * transform: null | import("../domain.d.ts").ReferenceWrapper, * this_arg: import("../domain.d.ts").Wrapper, * ) => import("../domain.d.ts").HostReferenceWrapper<"array">} */ export const convertIterator = (region, iterator, transform, this_arg) => { const { "global.Reflect.defineProperty": defineProperty } = region; let done = false; const next = toInternalReferenceStrict( region, getInternalPropertyValue(region, iterator, "next", iterator), ); const array = createEmptyArray(region, 0); let index = 0; while (!done) { const step = toInternalReferenceStrict( region, applyInternal(region, next, iterator, []), ); done = !!getInternalPropertyValue(region, step, "done", iterator).inner; if (!done) { let value = getInternalPropertyValue(region, step, "value", iterator); if (transform) { try { value = applyInternal(region, transform, this_arg, [ value, wrapStandardPrimitive(region, index), ]); } catch (error) { closeIterator(region, iterator); throw error; } } defineProperty(array.plain, index, { __proto__: null, value, writable: true, enumerable: true, configurable: true, }); index++; } } return array; }; /** * @type {( * region: import("../region.d.ts").Region, * target: import("../domain.d.ts").ReferenceWrapper, * length: number, * compare: null | import("../domain.d.ts").ReferenceWrapper, * hole: "skip" | "read", * ) => import("../domain.d.ts").Wrapper[]} */ export const sortInternalValueArray = ( region, target, target_length, compare, hole, ) => { /** @type {import("../domain.d.ts").Wrapper[]} */ const copy = []; let copy_length = 0; for (let index = 0; index < target_length; index++) { if (hole === "read" || hasInternalProperty(region, target, index)) { const descriptor = { __proto__: null, value: getInternalPropertyValue(region, target, index, target), writable: true, enumerable: true, configurable: true, }; if (!defineProperty(copy, copy_length++, descriptor)) { throw new LinvailExecError( "Cannot define array index at Array.prototype.sort", { copy, copy_length, descriptor }, ); } } } while (true) { let fix = true; for (let index = 0; index < copy_length - 1; index++) { const curr_item = copy[index]; const next_item = copy[index + 1]; if (isStrictlyGreater(region, compare, curr_item, next_item)) { fix = false; copy[index] = next_item; copy[index + 1] = curr_item; } } if (fix) { return copy; } } };