@opencode/codemode 2.0.14 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/interpreter/globals.js +2 -1
- package/dist/interpreter/interpreter.d.ts +4 -2
- package/dist/interpreter/interpreter.js +63 -28
- package/dist/interpreter/model.d.ts +1 -1
- package/dist/interpreter/model.js +1 -1
- package/dist/interpreter/objects.d.ts +6 -0
- package/dist/interpreter/objects.js +32 -8
- package/dist/stdlib/array.d.ts +7 -1
- package/dist/stdlib/array.js +77 -43
- package/dist/stdlib/bytes.js +19 -1
- package/dist/stdlib/object.js +64 -10
- package/dist/stdlib/regexp.js +7 -11
- package/dist/stdlib/string.js +21 -2
- package/dist/stdlib/web.d.ts +1 -0
- package/dist/stdlib/web.js +62 -1
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import { uriGlobal, urlGlobal, urlSearchParamsGlobal } from "../stdlib/url.js";
|
|
|
14
14
|
import { headersGlobal } from "../stdlib/headers.js";
|
|
15
15
|
import { iteratorGlobals } from "../stdlib/iterator.js";
|
|
16
16
|
import { coercion } from "../stdlib/value.js";
|
|
17
|
-
import { base64Global, cryptoGlobal } from "../stdlib/web.js";
|
|
17
|
+
import { base64Global, cryptoGlobal, structuredCloneGlobal } from "../stdlib/web.js";
|
|
18
18
|
import { ToolReference } from "../tool-runtime.js";
|
|
19
19
|
import { errorGlobal } from "./errors.js";
|
|
20
20
|
import { errorTypes } from "./intrinsics.js";
|
|
@@ -85,6 +85,7 @@ const table = {
|
|
|
85
85
|
atob: (ctx) => base64Global(ctx, "atob"),
|
|
86
86
|
btoa: (ctx) => base64Global(ctx, "btoa"),
|
|
87
87
|
crypto: (ctx) => cryptoGlobal(ctx),
|
|
88
|
+
structuredClone: (ctx) => structuredCloneGlobal(ctx),
|
|
88
89
|
...Object.fromEntries(errorTypes.map((type) => [type, (ctx) => errorGlobal(type, ctx)])),
|
|
89
90
|
};
|
|
90
91
|
/** Names bound in every program before extensions apply. */
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { Program } from "acorn";
|
|
1
|
+
import type { Program, TaggedTemplateExpression } from "acorn";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { type Json } from "../data.js";
|
|
4
4
|
import { type ToolRuntime } from "../tool-runtime.js";
|
|
5
5
|
import { type Builtins } from "./intrinsics.js";
|
|
6
|
-
import { type Cursor, PromiseObj, type Value } from "./objects.js";
|
|
6
|
+
import { type Cursor, Arr, PromiseObj, type Value } from "./objects.js";
|
|
7
7
|
import { Pending } from "./promises.js";
|
|
8
8
|
/** One program execution: the tool bridge, promise scheduler, captured logs, and the global scope built once. */
|
|
9
9
|
export declare class Interpreter<R> {
|
|
@@ -11,6 +11,8 @@ export declare class Interpreter<R> {
|
|
|
11
11
|
readonly pending: Pending<R>;
|
|
12
12
|
readonly builtins: Builtins;
|
|
13
13
|
readonly logs: Array<string>;
|
|
14
|
+
/** Template objects by site: a tag sees the same `strings` array every time its literal is evaluated, as in JS. */
|
|
15
|
+
readonly templates: WeakMap<TaggedTemplateExpression, Arr>;
|
|
14
16
|
private readonly root;
|
|
15
17
|
constructor(options: {
|
|
16
18
|
readonly tools: ToolRuntime<R>;
|
|
@@ -6,10 +6,10 @@ import { checkStringLength } from "./limits.js";
|
|
|
6
6
|
import { locate, materialize } from "./errors.js";
|
|
7
7
|
import { primitivePrototype } from "./intrinsics.js";
|
|
8
8
|
import { globals } from "./globals.js";
|
|
9
|
-
import { assign, Callable, define, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
|
|
9
|
+
import { assign, Callable, define, frozen, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
|
|
10
10
|
import { preserveConsumerError } from "./callback.js";
|
|
11
11
|
import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js";
|
|
12
|
-
import {
|
|
12
|
+
import { describeValue, isOpaque, rejectCircularInsertion, typeofValue } from "./references.js";
|
|
13
13
|
import { ScopeStack } from "./scope.js";
|
|
14
14
|
import { constructRegExp } from "../stdlib/regexp.js";
|
|
15
15
|
import { enumerableSource } from "../stdlib/object.js";
|
|
@@ -163,6 +163,8 @@ export class Interpreter {
|
|
|
163
163
|
pending;
|
|
164
164
|
builtins;
|
|
165
165
|
logs;
|
|
166
|
+
/** Template objects by site: a tag sees the same `strings` array every time its literal is evaluated, as in JS. */
|
|
167
|
+
templates = new WeakMap();
|
|
166
168
|
root;
|
|
167
169
|
constructor(options) {
|
|
168
170
|
this.tools = options.tools;
|
|
@@ -386,9 +388,6 @@ class Frame {
|
|
|
386
388
|
const self = this;
|
|
387
389
|
return Effect.gen(function* () {
|
|
388
390
|
const discriminant = yield* self.evaluateExpression(node.discriminant);
|
|
389
|
-
if (containsOpaqueReference(discriminant)) {
|
|
390
|
-
throw invalidData("Switch discriminants must be data values.", node);
|
|
391
|
-
}
|
|
392
391
|
self.scopes.push();
|
|
393
392
|
return yield* Effect.gen(function* () {
|
|
394
393
|
const cases = node.cases;
|
|
@@ -403,11 +402,7 @@ class Frame {
|
|
|
403
402
|
defaultIndex = index;
|
|
404
403
|
continue;
|
|
405
404
|
}
|
|
406
|
-
|
|
407
|
-
if (containsOpaqueReference(candidate)) {
|
|
408
|
-
throw invalidData("Switch case values must be data values.", test);
|
|
409
|
-
}
|
|
410
|
-
if (candidate === discriminant) {
|
|
405
|
+
if ((yield* self.evaluateExpression(test)) === discriminant) {
|
|
411
406
|
selected = index;
|
|
412
407
|
break;
|
|
413
408
|
}
|
|
@@ -715,10 +710,10 @@ class Frame {
|
|
|
715
710
|
self.predeclarePattern(declared.pattern, declared.mutable, left);
|
|
716
711
|
const right = yield* self.evaluateExpression(node.right);
|
|
717
712
|
const keys = self.enumerableKeys(right, node.right);
|
|
718
|
-
if (left.type
|
|
713
|
+
if (left.type === "RestElement" || left.type === "AssignmentPattern") {
|
|
719
714
|
throw typeError("Unsupported for...in binding.", left);
|
|
720
715
|
}
|
|
721
|
-
const
|
|
716
|
+
const assignment = left.type === "VariableDeclaration" ? undefined : left;
|
|
722
717
|
for (const key of keys) {
|
|
723
718
|
const result = yield* Effect.gen(function* () {
|
|
724
719
|
if (declared?.lexical) {
|
|
@@ -729,8 +724,8 @@ class Frame {
|
|
|
729
724
|
else if (declared) {
|
|
730
725
|
yield* self.assignPattern(declared.pattern, key, left);
|
|
731
726
|
}
|
|
732
|
-
else if (
|
|
733
|
-
self.
|
|
727
|
+
else if (assignment) {
|
|
728
|
+
yield* self.assignPattern(assignment, key, left);
|
|
734
729
|
}
|
|
735
730
|
return yield* self.evaluateStatement(node.body);
|
|
736
731
|
}).pipe(Effect.ensuring(Effect.sync(() => {
|
|
@@ -1016,6 +1011,8 @@ class Frame {
|
|
|
1016
1011
|
return this.evaluateArrayExpression(node);
|
|
1017
1012
|
case "TemplateLiteral":
|
|
1018
1013
|
return this.evaluateTemplateLiteral(node);
|
|
1014
|
+
case "TaggedTemplateExpression":
|
|
1015
|
+
return this.evaluateTaggedTemplate(node);
|
|
1019
1016
|
case "ConditionalExpression":
|
|
1020
1017
|
return this.evaluateConditionalExpression(node);
|
|
1021
1018
|
case "UpdateExpression":
|
|
@@ -1073,16 +1070,19 @@ class Frame {
|
|
|
1073
1070
|
return lhs === rhs;
|
|
1074
1071
|
if (operator === "!==")
|
|
1075
1072
|
return lhs !== rhs;
|
|
1076
|
-
if (operator === "
|
|
1073
|
+
if (operator === "==")
|
|
1074
|
+
return this.looselyEqual(lhs, rhs, node);
|
|
1075
|
+
if (operator === "!=")
|
|
1076
|
+
return !this.looselyEqual(lhs, rhs, node);
|
|
1077
|
+
if (operator === "in" && rhs instanceof Obj && !isOpaque(lhs)) {
|
|
1077
1078
|
return has(rhs, lhs !== null && typeof lhs === "object" ? coerceToString(lhs) : lhs);
|
|
1078
1079
|
}
|
|
1079
|
-
if (
|
|
1080
|
+
if (isOpaque(lhs) || isOpaque(rhs)) {
|
|
1080
1081
|
throw invalidData("Binary operators require data values.", node);
|
|
1081
1082
|
}
|
|
1082
|
-
// Addition
|
|
1083
|
-
const hint = operator === "+"
|
|
1083
|
+
// Addition uses the default hint; every other operator asks for a number.
|
|
1084
|
+
const hint = operator === "+" ? "default" : "number";
|
|
1084
1085
|
const coerceOperand = (operand) => (operand instanceof Obj ? operand.toPrimitive(hint) : operand);
|
|
1085
|
-
const bothObjects = lhs !== null && typeof lhs === "object" && rhs !== null && typeof rhs === "object";
|
|
1086
1086
|
const l = coerceOperand(lhs);
|
|
1087
1087
|
const r = coerceOperand(rhs);
|
|
1088
1088
|
switch (operator) {
|
|
@@ -1102,10 +1102,6 @@ class Frame {
|
|
|
1102
1102
|
return l % r;
|
|
1103
1103
|
case "**":
|
|
1104
1104
|
return l ** r;
|
|
1105
|
-
case "==":
|
|
1106
|
-
return bothObjects ? lhs === rhs : l == r;
|
|
1107
|
-
case "!=":
|
|
1108
|
-
return bothObjects ? lhs !== rhs : l != r;
|
|
1109
1105
|
case "<":
|
|
1110
1106
|
return l < r;
|
|
1111
1107
|
case "<=":
|
|
@@ -1135,6 +1131,22 @@ class Frame {
|
|
|
1135
1131
|
throw typeError(`Unsupported binary operator '${operator}'.`, node);
|
|
1136
1132
|
}
|
|
1137
1133
|
}
|
|
1134
|
+
// IsLooselyEqual: objects (including functions and tool references) compare by identity, and only a
|
|
1135
|
+
// data object facing a non-nullish primitive needs to coerce, so an opaque value is rejected only there.
|
|
1136
|
+
looselyEqual(lhs, rhs, node) {
|
|
1137
|
+
const lhsObject = lhs !== null && typeof lhs === "object";
|
|
1138
|
+
const rhsObject = rhs !== null && typeof rhs === "object";
|
|
1139
|
+
if (lhsObject === rhsObject)
|
|
1140
|
+
return lhsObject ? lhs === rhs : lhs == rhs;
|
|
1141
|
+
const object = lhsObject ? lhs : rhs;
|
|
1142
|
+
const primitive = lhsObject ? rhs : lhs;
|
|
1143
|
+
if (primitive === null || primitive === undefined)
|
|
1144
|
+
return false;
|
|
1145
|
+
if (!(object instanceof Obj) || isOpaque(object)) {
|
|
1146
|
+
throw invalidData("Binary operators require data values.", node);
|
|
1147
|
+
}
|
|
1148
|
+
return object.toPrimitive("default") == primitive;
|
|
1149
|
+
}
|
|
1138
1150
|
evaluateLogicalExpression(node) {
|
|
1139
1151
|
const operator = node.operator;
|
|
1140
1152
|
return Effect.flatMap(this.evaluateExpression(node.left), (left) => {
|
|
@@ -1163,7 +1175,7 @@ class Frame {
|
|
|
1163
1175
|
return !value;
|
|
1164
1176
|
if (operator === "void")
|
|
1165
1177
|
return undefined;
|
|
1166
|
-
if (
|
|
1178
|
+
if (isOpaque(value)) {
|
|
1167
1179
|
throw invalidData("Unary operators require data values.", node);
|
|
1168
1180
|
}
|
|
1169
1181
|
const operand = value instanceof Obj ? value.toPrimitive("number") : value;
|
|
@@ -1253,7 +1265,7 @@ class Frame {
|
|
|
1253
1265
|
// CodeMode numeric coercion, not host Number(): null-prototype data objects would make
|
|
1254
1266
|
// the host throw during ToPrimitive, and opaque runtime references must reject clearly.
|
|
1255
1267
|
const operand = (current) => {
|
|
1256
|
-
if (
|
|
1268
|
+
if (isOpaque(current)) {
|
|
1257
1269
|
throw invalidData(`'${operator}' requires a data value.`, argument);
|
|
1258
1270
|
}
|
|
1259
1271
|
return coerceToNumber(current);
|
|
@@ -1366,12 +1378,16 @@ class Frame {
|
|
|
1366
1378
|
paramScope.set(name, { mutable: true, value: undefined, initialized: false });
|
|
1367
1379
|
}
|
|
1368
1380
|
}
|
|
1381
|
+
const parameters = fn.parameters.map((parameter) => parameter.type === "Identifier" ? parameter.name : undefined);
|
|
1369
1382
|
const bind = Effect.gen(function* () {
|
|
1370
1383
|
for (const [index, parameter] of fn.parameters.entries()) {
|
|
1371
1384
|
if (parameter.type === "RestElement") {
|
|
1372
1385
|
yield* invocation.declarePattern(parameter.argument, new Arr(self.ctx.builtins.Array, args.slice(index)), true, parameter, true);
|
|
1373
1386
|
break;
|
|
1374
1387
|
}
|
|
1388
|
+
// A sloppy simple parameter list may repeat a name; the last occurrence wins, as in JS.
|
|
1389
|
+
if (parameter.type === "Identifier" && parameters.lastIndexOf(parameter.name) !== index)
|
|
1390
|
+
continue;
|
|
1375
1391
|
yield* invocation.declarePattern(parameter, args[index], true, parameter, true);
|
|
1376
1392
|
}
|
|
1377
1393
|
});
|
|
@@ -1666,7 +1682,7 @@ class Frame {
|
|
|
1666
1682
|
return Effect.gen(function* () {
|
|
1667
1683
|
for (let index = 0; index < quasis.length; index += 1) {
|
|
1668
1684
|
const quasi = quasis[index];
|
|
1669
|
-
// acorn only omits `cooked` for invalid escapes
|
|
1685
|
+
// acorn only omits `cooked` for invalid escapes, which are a parse error outside a tagged template.
|
|
1670
1686
|
if (typeof quasi.value.cooked !== "string") {
|
|
1671
1687
|
throw typeError("Invalid template literal quasi.", quasi);
|
|
1672
1688
|
}
|
|
@@ -1681,6 +1697,27 @@ class Frame {
|
|
|
1681
1697
|
return output;
|
|
1682
1698
|
});
|
|
1683
1699
|
}
|
|
1700
|
+
// `tag\`a${x}b\`` is `tag(strings, x)`: the tag is read like a callee (a member keeps its receiver), the
|
|
1701
|
+
// substitutions are evaluated in order, and `strings` carries the escaped source as `strings.raw`.
|
|
1702
|
+
evaluateTaggedTemplate(node) {
|
|
1703
|
+
const self = this;
|
|
1704
|
+
return Effect.gen(function* () {
|
|
1705
|
+
const { callable, thisValue } = node.tag.type === "MemberExpression"
|
|
1706
|
+
? yield* self.readMethod(node.tag)
|
|
1707
|
+
: { callable: yield* self.evaluateExpression(node.tag), thisValue: undefined };
|
|
1708
|
+
const strings = self.ctx.templates.get(node) ?? self.createTemplateObject(node);
|
|
1709
|
+
const values = yield* Effect.forEach(node.quasi.expressions, (expression) => self.evaluateExpression(expression));
|
|
1710
|
+
return yield* self.call(callable, thisValue, [strings, ...values], node, node.tag);
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
createTemplateObject(node) {
|
|
1714
|
+
const array = this.ctx.builtins.Array;
|
|
1715
|
+
// An invalid escape such as `\unicode` cooks to `undefined` and survives only in `raw`.
|
|
1716
|
+
const strings = new Arr(array, node.quasi.quasis.map((quasi) => quasi.value.cooked ?? undefined));
|
|
1717
|
+
define(strings, "raw", new Arr(array, node.quasi.quasis.map((quasi) => quasi.value.raw)), frozen);
|
|
1718
|
+
this.ctx.templates.set(node, strings);
|
|
1719
|
+
return strings;
|
|
1720
|
+
}
|
|
1684
1721
|
evaluateConditionalExpression(node) {
|
|
1685
1722
|
return Effect.flatMap(this.evaluateExpression(node.test), (test) => this.evaluateExpression(test ? node.consequent : node.alternate));
|
|
1686
1723
|
}
|
|
@@ -1812,8 +1849,6 @@ class Frame {
|
|
|
1812
1849
|
})();
|
|
1813
1850
|
if (written)
|
|
1814
1851
|
return;
|
|
1815
|
-
if (target instanceof Arr && key === "length")
|
|
1816
|
-
throw rangeError("Invalid array length", node);
|
|
1817
1852
|
throw typeError(`Cannot assign to read only property '${String(key)}'.`, node);
|
|
1818
1853
|
}
|
|
1819
1854
|
// ToPropertyKey: anything else becomes its string form, so `counts[row.category]` works when the field is null.
|
|
@@ -62,7 +62,7 @@ export declare const rangeError: (message: string, node?: AstNode) => PendingThr
|
|
|
62
62
|
export declare const referenceError: (message: string, node?: AstNode) => PendingThrow;
|
|
63
63
|
export declare const syntaxError: (message: string, node?: AstNode) => PendingThrow;
|
|
64
64
|
export declare const uriError: (message: string, node?: AstNode) => PendingThrow;
|
|
65
|
-
export declare const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters,
|
|
65
|
+
export declare const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, BigInt, and custom Symbols. Use plain functions and data objects instead.";
|
|
66
66
|
export declare const unsupportedSyntax: (kind: string, node: AstNode) => PendingThrow;
|
|
67
67
|
export declare const sourceLocation: (node: AstNode) => {
|
|
68
68
|
readonly line: number;
|
|
@@ -49,7 +49,7 @@ export const referenceError = failure("ReferenceError");
|
|
|
49
49
|
export const syntaxError = failure("SyntaxError");
|
|
50
50
|
export const uriError = failure("URIError");
|
|
51
51
|
// Orient the agent rather than enumerate JavaScript; interpreter-support.md is the full matrix.
|
|
52
|
-
export const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters,
|
|
52
|
+
export const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, BigInt, and custom Symbols. Use plain functions and data objects instead.";
|
|
53
53
|
export const unsupportedSyntax = (kind, node) => new PendingThrow("SyntaxError", `Syntax '${kind}' is not supported. ${supportedSyntaxMessage}`, node, "UnsupportedSyntax", [supportedSyntaxMessage]);
|
|
54
54
|
// Acorn lines are 1-based and its columns are 0-based. Diagnostics use 1-based columns of the submitted source.
|
|
55
55
|
export const sourceLocation = (node) => ({
|
|
@@ -38,6 +38,8 @@ export declare const frozen: Attributes;
|
|
|
38
38
|
export declare class Obj {
|
|
39
39
|
proto: Obj | null;
|
|
40
40
|
readonly props: Map<string | symbol, Slot>;
|
|
41
|
+
/** [[Extensible]]: cleared by `Object.preventExtensions`, `seal`, and `freeze`. */
|
|
42
|
+
extensible: boolean;
|
|
41
43
|
constructor(proto: Obj | null);
|
|
42
44
|
/** The class name `Object.prototype.toString` reports: `[object Map]`. */
|
|
43
45
|
readonly tag: string;
|
|
@@ -59,6 +61,8 @@ export declare class Obj {
|
|
|
59
61
|
export declare class Arr extends Obj {
|
|
60
62
|
readonly items: Array<Value>;
|
|
61
63
|
readonly tag = "Array";
|
|
64
|
+
/** The attributes every live element shares; `seal` and `freeze` narrow them since elements have no slots. */
|
|
65
|
+
elements: Attributes;
|
|
62
66
|
constructor(proto: Obj, items?: Array<Value>);
|
|
63
67
|
get describe(): string;
|
|
64
68
|
toString(): string;
|
|
@@ -232,6 +236,8 @@ export declare const has: (target: Obj, key: PropertyKey) => boolean;
|
|
|
232
236
|
export declare const hasPrototype: (value: Value, proto: Obj) => boolean;
|
|
233
237
|
/** [[Set]]: an inherited setter or read-only property decides before an own data property is created. */
|
|
234
238
|
export declare const set: (target: Obj, key: PropertyKey, value: Value) => boolean;
|
|
239
|
+
/** Creating a property on a non-extensible object is the one [[Set]] failure with its own message. */
|
|
240
|
+
export declare const rejectAddition: (target: Obj, key: string | symbol | number) => void;
|
|
235
241
|
/** [[DefineOwnProperty]] for a data property, ignoring the chain. */
|
|
236
242
|
export declare const define: (target: Obj, key: PropertyKey, value: Value, attrs?: Attributes) => void;
|
|
237
243
|
export declare const defineAccessor: (target: Obj, key: PropertyKey, get: Getter | undefined, set?: Setter) => void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { ToolReference } from "../tool-runtime.js";
|
|
3
3
|
import { checkArrayLength } from "./limits.js";
|
|
4
|
-
import { AsyncIteratorSymbol, IteratorSymbol, } from "./model.js";
|
|
4
|
+
import { AsyncIteratorSymbol, IteratorSymbol, rangeError, typeError, } from "./model.js";
|
|
5
5
|
/** Ordinary assignment: writable, enumerable, configurable. */
|
|
6
6
|
export const data = { writable: true, enumerable: true, configurable: true };
|
|
7
7
|
/** Built-in methods and `constructor`: writable and configurable but hidden from enumeration. */
|
|
@@ -17,6 +17,8 @@ export const frozen = { writable: false, enumerable: false, configurable: false
|
|
|
17
17
|
export class Obj {
|
|
18
18
|
proto;
|
|
19
19
|
props = new Map();
|
|
20
|
+
/** [[Extensible]]: cleared by `Object.preventExtensions`, `seal`, and `freeze`. */
|
|
21
|
+
extensible = true;
|
|
20
22
|
constructor(proto) {
|
|
21
23
|
this.proto = proto;
|
|
22
24
|
}
|
|
@@ -60,6 +62,8 @@ export class Obj {
|
|
|
60
62
|
export class Arr extends Obj {
|
|
61
63
|
items;
|
|
62
64
|
tag = "Array";
|
|
65
|
+
/** The attributes every live element shares; `seal` and `freeze` narrow them since elements have no slots. */
|
|
66
|
+
elements = data;
|
|
63
67
|
constructor(proto, items = []) {
|
|
64
68
|
super(proto);
|
|
65
69
|
this.items = items;
|
|
@@ -363,10 +367,12 @@ export const own = (target, key) => {
|
|
|
363
367
|
const at = index(target, name);
|
|
364
368
|
if (at !== undefined) {
|
|
365
369
|
const items = elements(target);
|
|
366
|
-
|
|
370
|
+
if (!(at in items))
|
|
371
|
+
return undefined;
|
|
372
|
+
return { value: items[at], ...(target instanceof Arr ? target.elements : data) };
|
|
367
373
|
}
|
|
368
374
|
if (target instanceof Arr && name === "length") {
|
|
369
|
-
return { value: target.items.length, writable:
|
|
375
|
+
return { value: target.items.length, writable: target.elements.writable, enumerable: false, configurable: false };
|
|
370
376
|
}
|
|
371
377
|
}
|
|
372
378
|
return target.props.get(name);
|
|
@@ -403,16 +409,22 @@ export const hasPrototype = (value, proto) => {
|
|
|
403
409
|
const writeElement = (target, name, value) => {
|
|
404
410
|
const at = index(target, name);
|
|
405
411
|
if (at !== undefined) {
|
|
406
|
-
if (target instanceof Bytes)
|
|
412
|
+
if (target instanceof Bytes) {
|
|
407
413
|
target.bytes[at] = typeof value === "number" ? value : Number(value);
|
|
408
|
-
|
|
409
|
-
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
if (!(at in target.items))
|
|
417
|
+
rejectAddition(target, at);
|
|
418
|
+
target.items[at] = value;
|
|
410
419
|
return true;
|
|
411
420
|
}
|
|
412
421
|
if (!(target instanceof Arr) || name !== "length")
|
|
413
422
|
return undefined;
|
|
414
423
|
const length = typeof value === "number" ? value : Number(value);
|
|
415
424
|
if (!Number.isInteger(length) || length < 0)
|
|
425
|
+
throw rangeError("Invalid array length");
|
|
426
|
+
// Shrinking deletes elements, which a sealed array forbids.
|
|
427
|
+
if (length < target.items.length && !target.elements.configurable)
|
|
416
428
|
return false;
|
|
417
429
|
checkArrayLength(length);
|
|
418
430
|
target.items.length = length;
|
|
@@ -448,9 +460,16 @@ export const set = (target, key, value) => {
|
|
|
448
460
|
if (written !== undefined)
|
|
449
461
|
return written;
|
|
450
462
|
}
|
|
463
|
+
rejectAddition(target, name);
|
|
451
464
|
target.props.set(name, { value, ...data });
|
|
452
465
|
return true;
|
|
453
466
|
};
|
|
467
|
+
/** Creating a property on a non-extensible object is the one [[Set]] failure with its own message. */
|
|
468
|
+
export const rejectAddition = (target, key) => {
|
|
469
|
+
if (target.extensible)
|
|
470
|
+
return;
|
|
471
|
+
throw typeError(`Cannot add property ${String(key)}, object is not extensible.`);
|
|
472
|
+
};
|
|
454
473
|
/** [[DefineOwnProperty]] for a data property, ignoring the chain. */
|
|
455
474
|
export const define = (target, key, value, attrs = data) => {
|
|
456
475
|
const name = canonical(key);
|
|
@@ -465,8 +484,13 @@ export const remove = (target, key) => {
|
|
|
465
484
|
const name = canonical(key);
|
|
466
485
|
if (isIndexed(target)) {
|
|
467
486
|
const at = index(target, name);
|
|
468
|
-
if (at !== undefined)
|
|
469
|
-
|
|
487
|
+
if (at !== undefined) {
|
|
488
|
+
if (target instanceof Bytes)
|
|
489
|
+
return !(at in target.bytes);
|
|
490
|
+
if (at in target.items && !target.elements.configurable)
|
|
491
|
+
return false;
|
|
492
|
+
return delete target.items[at];
|
|
493
|
+
}
|
|
470
494
|
if (target instanceof Arr && name === "length")
|
|
471
495
|
return false;
|
|
472
496
|
}
|
package/dist/stdlib/array.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
|
-
import { type
|
|
2
|
+
import { type Method } from "../interpreter/native.js";
|
|
3
|
+
import { Obj, type Value } from "../interpreter/objects.js";
|
|
3
4
|
import type { Interpreter } from "../interpreter/interpreter.js";
|
|
4
5
|
export declare const sortArray: <R>(ctx: Interpreter<R>, target: Array<Value>, comparator: Value, name: string) => Effect.Effect<Array<Value>, unknown, R>;
|
|
5
6
|
export declare const arrayGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
|
|
7
|
+
/**
|
|
8
|
+
* The callback methods Array and Uint8Array share. They fix the iteration length while reading existing elements
|
|
9
|
+
* live; `wrap` builds the collection `map` and `filter` return.
|
|
10
|
+
*/
|
|
11
|
+
export declare const callbackMethods: <R, T extends Obj>(ctx: Interpreter<R>, label: string, self: (thisValue: Value, name: string) => T, elements: (target: T) => ArrayLike<Value>, wrap: (values: Array<Value>) => Value) => Array<Method>;
|
package/dist/stdlib/array.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Effect } from "effect";
|
|
|
2
2
|
import { constructor, methods, prototypeFrom, receiver } from "../interpreter/native.js";
|
|
3
3
|
import { checkArrayLength, checkStringLength, MAX_ARRAY_LENGTH } from "../interpreter/limits.js";
|
|
4
4
|
import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
|
|
5
|
-
import { define, get, hidden, Arr, GeneratorObj, hostIterator, Obj, PromiseObj, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
|
|
5
|
+
import { define, get, hidden, Arr, GeneratorObj, hostIterator, Obj, PromiseObj, coerceToInteger, coerceToNumber, coerceToString, rejectAddition, } from "../interpreter/objects.js";
|
|
6
6
|
import { describeValue, rejectCircularInsertion } from "../interpreter/references.js";
|
|
7
7
|
import { applyCollectionCallback, invoke, preserveConsumerError } from "../interpreter/callback.js";
|
|
8
8
|
import { compareText } from "../tool-runtime.js";
|
|
@@ -102,16 +102,22 @@ export const arrayGlobal = (ctx) => {
|
|
|
102
102
|
["from", 1, (_, args) => arrayFrom(ctx, args)],
|
|
103
103
|
]);
|
|
104
104
|
const self = (thisValue, name) => receiver(Arr, thisValue, `Array.prototype.${name}`);
|
|
105
|
+
// A mutating method fails where its first element write, delete, or `length` write would on a frozen, sealed, or
|
|
106
|
+
// non-extensible array. `growth` is given by the methods that always write `length`, even when it is 0; holes a
|
|
107
|
+
// method would fill on a non-extensible array are not checked.
|
|
108
|
+
const mutable = (target, growth) => {
|
|
109
|
+
const length = target.items.length;
|
|
110
|
+
if ((growth ?? 0) > 0)
|
|
111
|
+
rejectAddition(target, length);
|
|
112
|
+
if ((growth ?? 0) < 0 && length > 0 && !target.elements.configurable) {
|
|
113
|
+
throw typeError(`Cannot delete property '${length - 1}'.`);
|
|
114
|
+
}
|
|
115
|
+
if (!target.elements.writable && (length > 0 || growth !== undefined)) {
|
|
116
|
+
throw typeError(`Cannot assign to read only property '${length > 0 ? 0 : "length"}'.`);
|
|
117
|
+
}
|
|
118
|
+
return target;
|
|
119
|
+
};
|
|
105
120
|
const optNumber = (value) => (value === undefined ? undefined : coerceToInteger(value));
|
|
106
|
-
// Callback methods fix the iteration length while reading existing elements live.
|
|
107
|
-
const iterate = (name, length, body) => [
|
|
108
|
-
name,
|
|
109
|
-
length,
|
|
110
|
-
(thisValue, args) => {
|
|
111
|
-
const target = self(thisValue, name);
|
|
112
|
-
return body(target.items, target, applyCollectionCallback(ctx, args[0], `Array.${name}`), args);
|
|
113
|
-
},
|
|
114
|
-
];
|
|
115
121
|
methods(builtins, proto, [
|
|
116
122
|
[
|
|
117
123
|
"join",
|
|
@@ -144,7 +150,8 @@ export const arrayGlobal = (ctx) => {
|
|
|
144
150
|
1,
|
|
145
151
|
(thisValue, args) => {
|
|
146
152
|
const target = self(thisValue, "lastIndexOf").items;
|
|
147
|
-
|
|
153
|
+
// An explicit undefined is a fromIndex of 0, unlike omitting it.
|
|
154
|
+
return args.length < 2 ? target.lastIndexOf(args[0]) : target.lastIndexOf(args[0], optNumber(args[1]));
|
|
148
155
|
},
|
|
149
156
|
],
|
|
150
157
|
["at", 1, (thisValue, args) => self(thisValue, "at").items.at(optNumber(args[0]) ?? 0)],
|
|
@@ -177,6 +184,9 @@ export const arrayGlobal = (ctx) => {
|
|
|
177
184
|
0,
|
|
178
185
|
(thisValue) => {
|
|
179
186
|
const target = self(thisValue, "reverse");
|
|
187
|
+
// Fewer than two elements means no writes at all, so a frozen one-element array reverses fine.
|
|
188
|
+
if (target.items.length > 1)
|
|
189
|
+
mutable(target);
|
|
180
190
|
target.items.reverse();
|
|
181
191
|
return target;
|
|
182
192
|
},
|
|
@@ -185,7 +195,7 @@ export const arrayGlobal = (ctx) => {
|
|
|
185
195
|
"sort",
|
|
186
196
|
1,
|
|
187
197
|
(thisValue, args) => {
|
|
188
|
-
const target = self(thisValue, "sort");
|
|
198
|
+
const target = mutable(self(thisValue, "sort"));
|
|
189
199
|
const items = target.items;
|
|
190
200
|
const length = items.length;
|
|
191
201
|
const holeCount = Array.from({ length }, (_, index) => Object.hasOwn(items, index)).filter((o) => !o).length;
|
|
@@ -225,7 +235,7 @@ export const arrayGlobal = (ctx) => {
|
|
|
225
235
|
"push",
|
|
226
236
|
1,
|
|
227
237
|
(thisValue, args) => {
|
|
228
|
-
const target = self(thisValue, "push");
|
|
238
|
+
const target = mutable(self(thisValue, "push"), args.length);
|
|
229
239
|
// Validate all insertions before mutating to avoid partial cyclic updates.
|
|
230
240
|
for (const item of args)
|
|
231
241
|
rejectCircularInsertion(target, item, "Array.push result");
|
|
@@ -236,29 +246,28 @@ export const arrayGlobal = (ctx) => {
|
|
|
236
246
|
"unshift",
|
|
237
247
|
1,
|
|
238
248
|
(thisValue, args) => {
|
|
239
|
-
const target = self(thisValue, "unshift");
|
|
249
|
+
const target = mutable(self(thisValue, "unshift"), args.length);
|
|
240
250
|
for (const item of args)
|
|
241
251
|
rejectCircularInsertion(target, item, "Array.unshift result");
|
|
242
252
|
return target.items.unshift(...args);
|
|
243
253
|
},
|
|
244
254
|
],
|
|
245
|
-
["pop", 0, (thisValue) => self(thisValue, "pop").items.pop()],
|
|
246
|
-
["shift", 0, (thisValue) => self(thisValue, "shift").items.shift()],
|
|
255
|
+
["pop", 0, (thisValue) => mutable(self(thisValue, "pop"), -1).items.pop()],
|
|
256
|
+
["shift", 0, (thisValue) => mutable(self(thisValue, "shift"), -1).items.shift()],
|
|
247
257
|
[
|
|
248
258
|
"splice",
|
|
249
259
|
2,
|
|
250
260
|
(thisValue, args) => {
|
|
251
261
|
const target = self(thisValue, "splice");
|
|
252
|
-
|
|
253
|
-
return wrap(target.items.splice(0, 0));
|
|
262
|
+
const length = target.items.length;
|
|
254
263
|
const start = optNumber(args[0]) ?? 0;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const deleteCount = optNumber(args[1]) ?? 0;
|
|
264
|
+
const from = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
|
|
265
|
+
const deleteCount = args.length === 1 ? length - from : Math.min(Math.max(optNumber(args[1]) ?? 0, 0), length - from);
|
|
258
266
|
const inserted = args.slice(2);
|
|
259
267
|
for (const item of inserted)
|
|
260
268
|
rejectCircularInsertion(target, item, "Array.splice result");
|
|
261
|
-
|
|
269
|
+
mutable(target, inserted.length - deleteCount);
|
|
270
|
+
return wrap(target.items.splice(from, deleteCount, ...inserted));
|
|
262
271
|
},
|
|
263
272
|
],
|
|
264
273
|
[
|
|
@@ -280,7 +289,7 @@ export const arrayGlobal = (ctx) => {
|
|
|
280
289
|
"fill",
|
|
281
290
|
1,
|
|
282
291
|
(thisValue, args) => {
|
|
283
|
-
const target = self(thisValue, "fill");
|
|
292
|
+
const target = mutable(self(thisValue, "fill"));
|
|
284
293
|
rejectCircularInsertion(target, args[0], "Array.fill result");
|
|
285
294
|
target.items.fill(args[0], optNumber(args[1]), optNumber(args[2]));
|
|
286
295
|
return target;
|
|
@@ -290,7 +299,7 @@ export const arrayGlobal = (ctx) => {
|
|
|
290
299
|
"copyWithin",
|
|
291
300
|
2,
|
|
292
301
|
(thisValue, args) => {
|
|
293
|
-
const target = self(thisValue, "copyWithin");
|
|
302
|
+
const target = mutable(self(thisValue, "copyWithin"));
|
|
294
303
|
target.items.copyWithin(optNumber(args[0]) ?? 0, optNumber(args[1]) ?? 0, optNumber(args[2]));
|
|
295
304
|
return target;
|
|
296
305
|
},
|
|
@@ -311,6 +320,47 @@ export const arrayGlobal = (ctx) => {
|
|
|
311
320
|
.items.entries()
|
|
312
321
|
.map(([index, item]) => wrap([index, item]))),
|
|
313
322
|
],
|
|
323
|
+
[
|
|
324
|
+
"flatMap",
|
|
325
|
+
1,
|
|
326
|
+
(thisValue, args) => {
|
|
327
|
+
const target = self(thisValue, "flatMap");
|
|
328
|
+
const apply = applyCollectionCallback(ctx, args[0], "Array.flatMap");
|
|
329
|
+
return Effect.gen(function* () {
|
|
330
|
+
const length = target.items.length;
|
|
331
|
+
const values = [];
|
|
332
|
+
for (let index = 0; index < length; index += 1) {
|
|
333
|
+
if (!(index in target.items))
|
|
334
|
+
continue;
|
|
335
|
+
const mapped = yield* apply([target.items[index], index, target]);
|
|
336
|
+
if (mapped instanceof Arr)
|
|
337
|
+
values.push(...mapped.items);
|
|
338
|
+
else
|
|
339
|
+
values.push(mapped);
|
|
340
|
+
}
|
|
341
|
+
return wrap(values);
|
|
342
|
+
});
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
...callbackMethods(ctx, "Array", self, (target) => target.items, wrap),
|
|
346
|
+
]);
|
|
347
|
+
define(proto, IteratorSymbol, get(proto, "values"), hidden);
|
|
348
|
+
return array;
|
|
349
|
+
};
|
|
350
|
+
/**
|
|
351
|
+
* The callback methods Array and Uint8Array share. They fix the iteration length while reading existing elements
|
|
352
|
+
* live; `wrap` builds the collection `map` and `filter` return.
|
|
353
|
+
*/
|
|
354
|
+
export const callbackMethods = (ctx, label, self, elements, wrap) => {
|
|
355
|
+
const iterate = (name, length, body) => [
|
|
356
|
+
name,
|
|
357
|
+
length,
|
|
358
|
+
(thisValue, args) => {
|
|
359
|
+
const target = self(thisValue, name);
|
|
360
|
+
return body(elements(target), target, applyCollectionCallback(ctx, args[0], `${label}.${name}`), args);
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
return [
|
|
314
364
|
iterate("map", 1, (target, receiver, apply) => Effect.gen(function* () {
|
|
315
365
|
const length = target.length;
|
|
316
366
|
const values = [];
|
|
@@ -322,20 +372,6 @@ export const arrayGlobal = (ctx) => {
|
|
|
322
372
|
}
|
|
323
373
|
return wrap(values);
|
|
324
374
|
})),
|
|
325
|
-
iterate("flatMap", 1, (target, receiver, apply) => Effect.gen(function* () {
|
|
326
|
-
const length = target.length;
|
|
327
|
-
const values = [];
|
|
328
|
-
for (let index = 0; index < length; index += 1) {
|
|
329
|
-
if (!(index in target))
|
|
330
|
-
continue;
|
|
331
|
-
const mapped = yield* apply([target[index], index, receiver]);
|
|
332
|
-
if (mapped instanceof Arr)
|
|
333
|
-
values.push(...mapped.items);
|
|
334
|
-
else
|
|
335
|
-
values.push(mapped);
|
|
336
|
-
}
|
|
337
|
-
return wrap(values);
|
|
338
|
-
})),
|
|
339
375
|
iterate("filter", 1, (target, receiver, apply) => Effect.gen(function* () {
|
|
340
376
|
const length = target.length;
|
|
341
377
|
const values = [];
|
|
@@ -416,7 +452,7 @@ export const arrayGlobal = (ctx) => {
|
|
|
416
452
|
while (start < length && !(start in target))
|
|
417
453
|
start += 1;
|
|
418
454
|
if (start === length) {
|
|
419
|
-
throw typeError(
|
|
455
|
+
throw typeError(`${label}.reduce of an empty array with no initial value.`);
|
|
420
456
|
}
|
|
421
457
|
accumulator = target[start];
|
|
422
458
|
start += 1;
|
|
@@ -435,7 +471,7 @@ export const arrayGlobal = (ctx) => {
|
|
|
435
471
|
while (start >= 0 && !(start in target))
|
|
436
472
|
start -= 1;
|
|
437
473
|
if (start < 0) {
|
|
438
|
-
throw typeError(
|
|
474
|
+
throw typeError(`${label}.reduceRight of an empty array with no initial value.`);
|
|
439
475
|
}
|
|
440
476
|
accumulator = target[start];
|
|
441
477
|
start -= 1;
|
|
@@ -447,7 +483,5 @@ export const arrayGlobal = (ctx) => {
|
|
|
447
483
|
}
|
|
448
484
|
return accumulator;
|
|
449
485
|
})),
|
|
450
|
-
]
|
|
451
|
-
define(proto, IteratorSymbol, get(proto, "values"), hidden);
|
|
452
|
-
return array;
|
|
486
|
+
];
|
|
453
487
|
};
|
package/dist/stdlib/bytes.js
CHANGED
|
@@ -4,6 +4,7 @@ import { constructor, methods, prototypeFrom, receiver, requiresNew } from "../i
|
|
|
4
4
|
import { IteratorSymbol, rangeError, syntaxError, typeError } from "../interpreter/model.js";
|
|
5
5
|
import { define, defineAccessor, get, hidden, Arr, Bytes, hostIterator, Obj, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
|
|
6
6
|
import { describeValue } from "../interpreter/references.js";
|
|
7
|
+
import { callbackMethods, sortArray } from "./array.js";
|
|
7
8
|
/** The bytes a Uint8Array, array, or other iterable of numbers describes; the host array clamps each value. */
|
|
8
9
|
const collectBytes = (ctx, source, name) => {
|
|
9
10
|
if (source instanceof Bytes)
|
|
@@ -110,6 +111,22 @@ export const uint8ArrayGlobal = (ctx) => {
|
|
|
110
111
|
return target;
|
|
111
112
|
},
|
|
112
113
|
],
|
|
114
|
+
[
|
|
115
|
+
"sort",
|
|
116
|
+
1,
|
|
117
|
+
(thisValue, args) => {
|
|
118
|
+
const target = self(thisValue, "sort");
|
|
119
|
+
// Without a comparator, typed arrays sort numerically rather than by string.
|
|
120
|
+
if (args[0] === undefined) {
|
|
121
|
+
target.bytes.sort();
|
|
122
|
+
return target;
|
|
123
|
+
}
|
|
124
|
+
return Effect.map(sortArray(ctx, [...target.bytes], args[0], "Uint8Array.sort"), (sorted) => {
|
|
125
|
+
target.bytes.set(Uint8Array.from(sorted, coerceToNumber));
|
|
126
|
+
return target;
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
],
|
|
113
130
|
[
|
|
114
131
|
"indexOf",
|
|
115
132
|
1,
|
|
@@ -120,7 +137,7 @@ export const uint8ArrayGlobal = (ctx) => {
|
|
|
120
137
|
1,
|
|
121
138
|
(thisValue, args) => {
|
|
122
139
|
const target = self(thisValue, "lastIndexOf").bytes;
|
|
123
|
-
return args
|
|
140
|
+
return args.length < 2
|
|
124
141
|
? target.lastIndexOf(coerceToNumber(args[0]))
|
|
125
142
|
: target.lastIndexOf(coerceToNumber(args[0]), optNumber(args[1]));
|
|
126
143
|
},
|
|
@@ -151,6 +168,7 @@ export const uint8ArrayGlobal = (ctx) => {
|
|
|
151
168
|
.bytes.entries()
|
|
152
169
|
.map(([index, byte]) => wrapAll([index, byte]))),
|
|
153
170
|
],
|
|
171
|
+
...callbackMethods(ctx, "Uint8Array", self, (target) => target.bytes, (values) => wrap(Uint8Array.from(values, coerceToNumber))),
|
|
154
172
|
]);
|
|
155
173
|
define(proto, IteratorSymbol, get(proto, "values"), hidden);
|
|
156
174
|
return uint8Array;
|
package/dist/stdlib/object.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { constructor, methods, receiver } from "../interpreter/native.js";
|
|
3
|
-
import { AsyncIteratorSymbol, invalidData, IteratorSymbol,
|
|
4
|
-
import { define, entries, enumerableKeys, getOwn, hasOwn, hasPrototype, hidden, keys, own, Arr, Obj, PromiseObj, set, coerceToString, } from "../interpreter/objects.js";
|
|
3
|
+
import { AsyncIteratorSymbol, invalidData, IteratorSymbol, typeError } from "../interpreter/model.js";
|
|
4
|
+
import { define, entries, enumerableKeys, getOwn, hasOwn, hasPrototype, hidden, keys, own, ownKeys, Arr, Bytes, Obj, PromiseObj, set, coerceToString, } from "../interpreter/objects.js";
|
|
5
|
+
import { primitivePrototype } from "../interpreter/intrinsics.js";
|
|
5
6
|
import { containsOpaqueReference, describeValue, rejectCircularInsertion } from "../interpreter/references.js";
|
|
6
7
|
import { invoke, preserveConsumerError } from "../interpreter/callback.js";
|
|
7
8
|
import { ToolReference } from "../tool-runtime.js";
|
|
@@ -37,8 +38,6 @@ export const objectAssign = (ctx, args) => {
|
|
|
37
38
|
for (const key of enumerableKeys(from)) {
|
|
38
39
|
rejectCircularInsertion(target, getOwn(from, key), "Object.assign result", seen);
|
|
39
40
|
if (!set(target, key, getOwn(from, key))) {
|
|
40
|
-
if (target instanceof Arr && key === "length")
|
|
41
|
-
throw rangeError("Invalid array length");
|
|
42
41
|
throw typeError(`Cannot assign to read only property '${String(key)}'.`);
|
|
43
42
|
}
|
|
44
43
|
}
|
|
@@ -81,6 +80,38 @@ const classTag = (value) => {
|
|
|
81
80
|
return "Object";
|
|
82
81
|
};
|
|
83
82
|
const propertyKey = (value) => value === AsyncIteratorSymbol || value === IteratorSymbol ? value : coerceToString(value);
|
|
83
|
+
// SetIntegrityLevel: primitives pass through. A typed array's bytes cannot carry attributes, so JS throws after
|
|
84
|
+
// already making it non-extensible.
|
|
85
|
+
const restrict = (level, value) => {
|
|
86
|
+
if (!(value instanceof Obj))
|
|
87
|
+
return value;
|
|
88
|
+
value.extensible = false;
|
|
89
|
+
if (level === "preventExtensions")
|
|
90
|
+
return value;
|
|
91
|
+
if (value instanceof Bytes && value.bytes.length > 0) {
|
|
92
|
+
throw typeError(`Cannot ${level} array buffer views with elements.`);
|
|
93
|
+
}
|
|
94
|
+
for (const slot of value.props.values()) {
|
|
95
|
+
slot.configurable = false;
|
|
96
|
+
if (level === "freeze" && "value" in slot)
|
|
97
|
+
slot.writable = false;
|
|
98
|
+
}
|
|
99
|
+
if (value instanceof Arr)
|
|
100
|
+
value.elements = { writable: level === "seal", enumerable: true, configurable: false };
|
|
101
|
+
return value;
|
|
102
|
+
};
|
|
103
|
+
// TestIntegrityLevel: array elements and `length` answer through `own`, so a non-extensible empty array is sealed
|
|
104
|
+
// but not frozen, as in JS.
|
|
105
|
+
const integrity = (value, frozen) => {
|
|
106
|
+
if (!(value instanceof Obj))
|
|
107
|
+
return true;
|
|
108
|
+
if (value.extensible)
|
|
109
|
+
return false;
|
|
110
|
+
return ownKeys(value).every((key) => {
|
|
111
|
+
const slot = own(value, key);
|
|
112
|
+
return slot !== undefined && !slot.configurable && !(frozen && "value" in slot && slot.writable);
|
|
113
|
+
});
|
|
114
|
+
};
|
|
84
115
|
// Object constructs identically with or without new, like JS. Only `keys` copies its result into the
|
|
85
116
|
// program; `values`, `entries`, `assign`, and `fromEntries` hand back the program's own values.
|
|
86
117
|
export const objectGlobal = (ctx) => {
|
|
@@ -118,18 +149,41 @@ export const objectGlobal = (ctx) => {
|
|
|
118
149
|
(_, args) => new Arr(builtins.Array, entries(enumerableSource(ctx, "Object.entries(...)", args[0])).map((entry) => new Arr(builtins.Array, entry))),
|
|
119
150
|
],
|
|
120
151
|
["hasOwn", 2, (_, args) => hasOwn(enumerableSource(ctx, "Object.hasOwn(...)", args[0]), propertyKey(args[1]))],
|
|
152
|
+
["is", 2, (_, args) => Object.is(args[0], args[1])],
|
|
153
|
+
["assign", 2, (_, args) => objectAssign(ctx, args)],
|
|
154
|
+
["fromEntries", 1, (_, args) => objectFromEntries(ctx, args[0])],
|
|
155
|
+
["freeze", 1, (_, args) => restrict("freeze", args[0])],
|
|
156
|
+
["seal", 1, (_, args) => restrict("seal", args[0])],
|
|
157
|
+
["preventExtensions", 1, (_, args) => restrict("preventExtensions", args[0])],
|
|
158
|
+
["isFrozen", 1, (_, args) => integrity(args[0], true)],
|
|
159
|
+
["isSealed", 1, (_, args) => integrity(args[0], false)],
|
|
160
|
+
["isExtensible", 1, (_, args) => args[0] instanceof Obj && args[0].extensible],
|
|
121
161
|
[
|
|
122
|
-
"
|
|
162
|
+
"getPrototypeOf",
|
|
163
|
+
1,
|
|
164
|
+
(_, args) => {
|
|
165
|
+
if (args[0] instanceof Obj)
|
|
166
|
+
return args[0].proto;
|
|
167
|
+
const proto = primitivePrototype(builtins, args[0]);
|
|
168
|
+
if (proto === undefined) {
|
|
169
|
+
throw typeError(`Object.getPrototypeOf cannot convert ${describeValue(args[0])} to an object.`);
|
|
170
|
+
}
|
|
171
|
+
return proto;
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
[
|
|
175
|
+
"create",
|
|
123
176
|
2,
|
|
124
177
|
(_, args) => {
|
|
125
|
-
if (
|
|
126
|
-
throw
|
|
178
|
+
if (args[0] !== null && !(args[0] instanceof Obj)) {
|
|
179
|
+
throw typeError("Object prototype may only be an Object or null.");
|
|
180
|
+
}
|
|
181
|
+
if (args[1] !== undefined) {
|
|
182
|
+
throw typeError("Object.create property descriptors are not supported; assign the fields after creating the object.");
|
|
127
183
|
}
|
|
128
|
-
return
|
|
184
|
+
return new Obj(args[0]);
|
|
129
185
|
},
|
|
130
186
|
],
|
|
131
|
-
["assign", 2, (_, args) => objectAssign(ctx, args)],
|
|
132
|
-
["fromEntries", 1, (_, args) => objectFromEntries(ctx, args[0])],
|
|
133
187
|
]);
|
|
134
188
|
define(object, "groupBy", groupBy(ctx, "Object"), hidden);
|
|
135
189
|
methods(builtins, builtins.Object, [
|
package/dist/stdlib/regexp.js
CHANGED
|
@@ -15,20 +15,16 @@ const flagProperties = [
|
|
|
15
15
|
const regexFailureReason = (error) => (error instanceof Error ? error.message : String(error)).replace(/^Invalid regular expression:\s*/i, "");
|
|
16
16
|
const escapeRegexHint = 'To match special characters like ( ) [ ] { } + * ? . literally, escape them with a backslash (e.g. "\\\\(") or test for them with String.includes instead.';
|
|
17
17
|
export const toHostRegex = (arg, method, extraFlags = "") => {
|
|
18
|
-
// Native parity: an undefined pattern behaves as an empty pattern.
|
|
19
|
-
if (arg === undefined)
|
|
20
|
-
return new RegExp("", extraFlags);
|
|
21
18
|
if (arg instanceof RegExpObj)
|
|
22
19
|
return arg.regex;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
20
|
+
// Anything else is a pattern string, as `new RegExp(arg)` would read it: undefined is the empty pattern.
|
|
21
|
+
const source = arg === undefined ? "" : coerceToString(arg);
|
|
22
|
+
try {
|
|
23
|
+
return new RegExp(source, extraFlags);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
throw syntaxError(`String.${method} received the string ${JSON.stringify(source)}, which is not a valid regular expression pattern (${regexFailureReason(error)}). ${escapeRegexHint}`);
|
|
30
27
|
}
|
|
31
|
-
throw typeError(`String.${method} expects a regular expression (a /pattern/flags literal or new RegExp(...)) or a string pattern, not ${arg === null ? "null" : typeof arg}.`);
|
|
32
28
|
};
|
|
33
29
|
export const matchToValue = (builtins, match) => {
|
|
34
30
|
const result = new Arr(builtins.Array, Array.from(match, (group) => group));
|
package/dist/stdlib/string.js
CHANGED
|
@@ -2,9 +2,9 @@ import { Effect } from "effect";
|
|
|
2
2
|
import { constructor, fn, methods } from "../interpreter/native.js";
|
|
3
3
|
import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
|
|
4
4
|
import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
|
|
5
|
-
import { define, hidden, Arr, hostIterator, RegExpObj, record, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
|
|
5
|
+
import { define, get, hidden, Arr, hostIterator, Obj, RegExpObj, record, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
|
|
6
6
|
import { containsOpaqueReference, typeofValue } from "../interpreter/references.js";
|
|
7
|
-
import { applyCollectionCallback, isSupportedCallback } from "../interpreter/callback.js";
|
|
7
|
+
import { applyCollectionCallback, isSupportedCallback, toPrimitiveString } from "../interpreter/callback.js";
|
|
8
8
|
import { matchToValue, toHostRegex } from "./regexp.js";
|
|
9
9
|
import { coercion } from "./value.js";
|
|
10
10
|
// console is intercepted by the interpreter before reaching here.
|
|
@@ -78,6 +78,25 @@ export const stringGlobal = (ctx) => {
|
|
|
78
78
|
methods(builtins, string, [
|
|
79
79
|
codeUnits("fromCharCode", String.fromCharCode),
|
|
80
80
|
codeUnits("fromCodePoint", String.fromCodePoint),
|
|
81
|
+
[
|
|
82
|
+
"raw",
|
|
83
|
+
1,
|
|
84
|
+
(_, args) => {
|
|
85
|
+
const template = args[0];
|
|
86
|
+
const raw = template instanceof Obj ? get(template, "raw") : undefined;
|
|
87
|
+
if (!(raw instanceof Obj))
|
|
88
|
+
throw typeError("String.raw expects a template object with a raw array.");
|
|
89
|
+
const count = Math.max(0, coerceToInteger(get(raw, "length")));
|
|
90
|
+
checkArrayLength(count);
|
|
91
|
+
// Each literal is followed by its substitution, except the last literal, or when substitutions run out.
|
|
92
|
+
const parts = Array.from({ length: count }, (_, index) => index + 1 < count && index + 1 < args.length ? [get(raw, index), args[index + 1]] : [get(raw, index)]).flat();
|
|
93
|
+
return Effect.map(Effect.forEach(parts, (part) => toPrimitiveString(ctx, part)), (strings) => {
|
|
94
|
+
const output = strings.join("");
|
|
95
|
+
checkStringLength(output.length);
|
|
96
|
+
return output;
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
],
|
|
81
100
|
]);
|
|
82
101
|
const self = (thisValue, name) => {
|
|
83
102
|
if (typeof thisValue === "string")
|
package/dist/stdlib/web.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Obj } from "../interpreter/objects.js";
|
|
2
2
|
import type { Interpreter } from "../interpreter/interpreter.js";
|
|
3
3
|
export declare const base64Global: <R>(ctx: Interpreter<R>, name: "atob" | "btoa") => import("../interpreter/objects.js").Native<R>;
|
|
4
|
+
export declare const structuredCloneGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
|
|
4
5
|
export declare const cryptoGlobal: <R>(ctx: Interpreter<R>) => Obj;
|
package/dist/stdlib/web.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { fn, methods } from "../interpreter/native.js";
|
|
2
2
|
import { typeError } from "../interpreter/model.js";
|
|
3
|
-
import { Bytes, Obj, coerceToString } from "../interpreter/objects.js";
|
|
3
|
+
import { define, entries, get, getOwn, hasOwn, hidden, isRuntimeReference, Arr, Bytes, DateObj, ErrorObj, MapObj, Obj, RegExpObj, SetObj, coerceToString, } from "../interpreter/objects.js";
|
|
4
|
+
import { createErrorValue, isErrorType } from "../interpreter/intrinsics.js";
|
|
4
5
|
import { describeValue } from "../interpreter/references.js";
|
|
6
|
+
import { ToolReference } from "../tool-runtime.js";
|
|
5
7
|
// WebIDL DOMString conversion: a missing argument is a TypeError, anything else stringifies. Invalid input is a
|
|
6
8
|
// TypeError as well; browsers throw a DOMException named InvalidCharacterError, which CodeMode does not have.
|
|
7
9
|
export const base64Global = (ctx, name) => fn(ctx.builtins, name, 1, (_, args) => {
|
|
@@ -15,6 +17,65 @@ export const base64Global = (ctx, name) => fn(ctx.builtins, name, 1, (_, args) =
|
|
|
15
17
|
throw typeError("The string contains invalid characters.");
|
|
16
18
|
}
|
|
17
19
|
});
|
|
20
|
+
// HTML structured clone over CodeMode's data kinds. Shared references survive through the memo; prototypes and
|
|
21
|
+
// frozen state do not, as in JS. Without a DOMException, DataCloneError is a TypeError carrying that prefix.
|
|
22
|
+
export const structuredCloneGlobal = (ctx) => fn(ctx.builtins, "structuredClone", 1, (_, args) => {
|
|
23
|
+
if (args.length === 0)
|
|
24
|
+
throw typeError("structuredClone requires 1 argument");
|
|
25
|
+
const builtins = ctx.builtins;
|
|
26
|
+
const memo = new Map();
|
|
27
|
+
const clone = (value) => {
|
|
28
|
+
if (typeof value === "symbol" || value instanceof ToolReference) {
|
|
29
|
+
throw typeError(`DataCloneError: ${describeValue(value)} could not be cloned.`);
|
|
30
|
+
}
|
|
31
|
+
if (!(value instanceof Obj))
|
|
32
|
+
return value;
|
|
33
|
+
const seen = memo.get(value);
|
|
34
|
+
if (seen !== undefined)
|
|
35
|
+
return seen;
|
|
36
|
+
const remember = (copy) => {
|
|
37
|
+
memo.set(value, copy);
|
|
38
|
+
return copy;
|
|
39
|
+
};
|
|
40
|
+
if (value instanceof MapObj) {
|
|
41
|
+
const copy = remember(new MapObj(builtins.Map));
|
|
42
|
+
for (const [key, item] of value.map)
|
|
43
|
+
copy.map.set(clone(key), clone(item));
|
|
44
|
+
return copy;
|
|
45
|
+
}
|
|
46
|
+
if (value instanceof SetObj) {
|
|
47
|
+
const copy = remember(new SetObj(builtins.Set));
|
|
48
|
+
for (const item of value.set)
|
|
49
|
+
copy.set.add(clone(item));
|
|
50
|
+
return copy;
|
|
51
|
+
}
|
|
52
|
+
if (value instanceof DateObj)
|
|
53
|
+
return remember(new DateObj(builtins.Date, value.time));
|
|
54
|
+
if (value instanceof RegExpObj) {
|
|
55
|
+
return remember(new RegExpObj(builtins.RegExp, value.regex.source, value.regex.flags));
|
|
56
|
+
}
|
|
57
|
+
if (value instanceof Bytes)
|
|
58
|
+
return remember(new Bytes(builtins.Uint8Array, new Uint8Array(value.bytes)));
|
|
59
|
+
if (value instanceof ErrorObj) {
|
|
60
|
+
const name = get(value, "name");
|
|
61
|
+
const message = get(value, "message");
|
|
62
|
+
const copy = remember(createErrorValue(builtins[typeof name === "string" && isErrorType(name) ? name : "Error"], message === undefined ? undefined : coerceToString(message)));
|
|
63
|
+
const stack = getOwn(value, "stack");
|
|
64
|
+
if (typeof stack === "string")
|
|
65
|
+
define(copy, "stack", stack, hidden);
|
|
66
|
+
if (hasOwn(value, "cause"))
|
|
67
|
+
define(copy, "cause", clone(getOwn(value, "cause")), hidden);
|
|
68
|
+
return copy;
|
|
69
|
+
}
|
|
70
|
+
if (isRuntimeReference(value))
|
|
71
|
+
throw typeError(`DataCloneError: ${describeValue(value)} could not be cloned.`);
|
|
72
|
+
const copy = remember(value instanceof Arr ? new Arr(builtins.Array, new Array(value.items.length)) : new Obj(builtins.Object));
|
|
73
|
+
for (const [key, item] of entries(value))
|
|
74
|
+
define(copy, key, clone(item));
|
|
75
|
+
return copy;
|
|
76
|
+
};
|
|
77
|
+
return clone(args[0]);
|
|
78
|
+
});
|
|
18
79
|
export const cryptoGlobal = (ctx) => {
|
|
19
80
|
const object = new Obj(ctx.builtins.Object);
|
|
20
81
|
methods(ctx.builtins, object, [
|
package/package.json
CHANGED