@opencode/codemode 2.0.15 → 2.0.17
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/data.d.ts +2 -2
- package/dist/data.js +1 -1
- package/dist/interpreter/callback.d.ts +10 -3
- package/dist/interpreter/callback.js +17 -4
- package/dist/interpreter/errors.js +8 -4
- package/dist/interpreter/globals.d.ts +1 -1
- package/dist/interpreter/globals.js +38 -3
- package/dist/interpreter/interpreter.js +229 -122
- package/dist/interpreter/intrinsics.d.ts +1 -1
- package/dist/interpreter/intrinsics.js +4 -0
- package/dist/interpreter/model.d.ts +1 -1
- package/dist/interpreter/model.js +1 -1
- package/dist/interpreter/objects.d.ts +29 -1
- package/dist/interpreter/objects.js +68 -9
- package/dist/stdlib/array.d.ts +7 -1
- package/dist/stdlib/array.js +93 -54
- package/dist/stdlib/bytes.js +19 -1
- package/dist/stdlib/collections.d.ts +2 -0
- package/dist/stdlib/collections.js +130 -3
- package/dist/stdlib/date.js +10 -3
- package/dist/stdlib/headers.js +1 -1
- package/dist/stdlib/json.js +1 -1
- package/dist/stdlib/math.js +18 -14
- package/dist/stdlib/number.js +23 -34
- package/dist/stdlib/object.js +64 -10
- package/dist/stdlib/string.js +98 -72
- package/dist/stdlib/url.js +1 -1
- package/dist/stdlib/value.d.ts +2 -1
- package/dist/stdlib/value.js +14 -11
- package/dist/stdlib/web.d.ts +1 -0
- package/dist/stdlib/web.js +63 -1
- package/package.json +1 -1
|
@@ -6,14 +6,35 @@ 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, frozen, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
|
|
10
|
-
import { preserveConsumerError } from "./callback.js";
|
|
9
|
+
import { assign, Callable, define, frozen, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arguments, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
|
|
10
|
+
import { preserveConsumerError, toPrimitive } 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";
|
|
16
16
|
import { compoundOperators } from "../stdlib/value.js";
|
|
17
|
+
/** The binary operators that convert object operands through ToPrimitive before acting on primitives. */
|
|
18
|
+
const primitiveOperators = new Set([
|
|
19
|
+
"+",
|
|
20
|
+
"-",
|
|
21
|
+
"*",
|
|
22
|
+
"/",
|
|
23
|
+
"%",
|
|
24
|
+
"**",
|
|
25
|
+
"<",
|
|
26
|
+
"<=",
|
|
27
|
+
">",
|
|
28
|
+
">=",
|
|
29
|
+
"&",
|
|
30
|
+
"|",
|
|
31
|
+
"^",
|
|
32
|
+
"<<",
|
|
33
|
+
">>",
|
|
34
|
+
">>>",
|
|
35
|
+
]);
|
|
36
|
+
/** ToPropertyKey on a primitive (or an opaque value, which keeps its built-in string form). */
|
|
37
|
+
const propertyKey = (value) => typeof value === "string" || typeof value === "number" || typeof value === "symbol" ? value : coerceToString(value);
|
|
17
38
|
// What a loop does with its body's result: exit with a StatementResult, or undefined to keep iterating.
|
|
18
39
|
// Unlabelled break ends this loop; a label the loop does not carry propagates outward.
|
|
19
40
|
const loopExit = (result, labels) => {
|
|
@@ -94,6 +115,25 @@ const collectPatternNames = (pattern, out = []) => {
|
|
|
94
115
|
}
|
|
95
116
|
return out;
|
|
96
117
|
};
|
|
118
|
+
// Whether a function body (or a parameter default) reads `arguments`, looking through arrows but not nested
|
|
119
|
+
// functions, which own theirs. Memoized so the object is only built for calls that can observe it.
|
|
120
|
+
const argumentsUse = new WeakMap();
|
|
121
|
+
const usesArguments = (fn) => {
|
|
122
|
+
const cached = argumentsUse.get(fn.body);
|
|
123
|
+
if (cached !== undefined)
|
|
124
|
+
return cached;
|
|
125
|
+
const found = [...fn.parameters, fn.body].some(function visit(node) {
|
|
126
|
+
if (node === null || typeof node !== "object")
|
|
127
|
+
return false;
|
|
128
|
+
if (node.type === "Identifier")
|
|
129
|
+
return node.name === "arguments";
|
|
130
|
+
if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression")
|
|
131
|
+
return false;
|
|
132
|
+
return Object.values(node).some((child) => Array.isArray(child) ? child.some((item) => visit(item)) : visit(child));
|
|
133
|
+
});
|
|
134
|
+
argumentsUse.set(fn.body, found);
|
|
135
|
+
return found;
|
|
136
|
+
};
|
|
97
137
|
// `var` names declared anywhere in a function body except inside nested functions, which own theirs.
|
|
98
138
|
// Memoized per body: a function's var names never change, and hoisting runs on every call.
|
|
99
139
|
const varNames = new WeakMap();
|
|
@@ -171,7 +211,8 @@ export class Interpreter {
|
|
|
171
211
|
this.pending = options.pending;
|
|
172
212
|
this.builtins = options.builtins;
|
|
173
213
|
this.logs = options.logs ?? [];
|
|
174
|
-
|
|
214
|
+
// Program code has no receiver: top-level `this` is undefined, as in a module.
|
|
215
|
+
const globalScope = new Map([["this", { mutable: false, value: undefined }]]);
|
|
175
216
|
// Calling back into the program never reads frame state, so any frame serves; the root is always alive.
|
|
176
217
|
this.root = new Frame(this, new ScopeStack([globalScope]));
|
|
177
218
|
for (const [name, value] of [...globals(this), ...(options.globals?.(this) ?? [])]) {
|
|
@@ -321,7 +362,7 @@ class Frame {
|
|
|
321
362
|
}
|
|
322
363
|
createFunction(node, name = node.type === "ArrowFunctionExpression" ? "" : (node.id?.name ?? "")) {
|
|
323
364
|
const builtins = this.ctx.builtins;
|
|
324
|
-
const fn = new Fn(builtins.Function, name, node.params, node.body, this.scopes.capture(), node.async, node.generator);
|
|
365
|
+
const fn = new Fn(builtins.Function, name, node.params, node.body, this.scopes.capture(), node.async, node.generator, node.type === "ArrowFunctionExpression");
|
|
325
366
|
// Each generator function gets its own prototype, so `g() instanceof g` holds as in JS.
|
|
326
367
|
if (node.generator)
|
|
327
368
|
define(fn, "prototype", new Obj(node.async ? builtins.AsyncGenerator : builtins.Generator), hidden);
|
|
@@ -388,9 +429,6 @@ class Frame {
|
|
|
388
429
|
const self = this;
|
|
389
430
|
return Effect.gen(function* () {
|
|
390
431
|
const discriminant = yield* self.evaluateExpression(node.discriminant);
|
|
391
|
-
if (containsOpaqueReference(discriminant)) {
|
|
392
|
-
throw invalidData("Switch discriminants must be data values.", node);
|
|
393
|
-
}
|
|
394
432
|
self.scopes.push();
|
|
395
433
|
return yield* Effect.gen(function* () {
|
|
396
434
|
const cases = node.cases;
|
|
@@ -405,11 +443,7 @@ class Frame {
|
|
|
405
443
|
defaultIndex = index;
|
|
406
444
|
continue;
|
|
407
445
|
}
|
|
408
|
-
|
|
409
|
-
if (containsOpaqueReference(candidate)) {
|
|
410
|
-
throw invalidData("Switch case values must be data values.", test);
|
|
411
|
-
}
|
|
412
|
-
if (candidate === discriminant) {
|
|
446
|
+
if ((yield* self.evaluateExpression(test)) === discriminant) {
|
|
413
447
|
selected = index;
|
|
414
448
|
break;
|
|
415
449
|
}
|
|
@@ -852,20 +886,20 @@ class Frame {
|
|
|
852
886
|
return;
|
|
853
887
|
}
|
|
854
888
|
if (pattern.type === "ObjectPattern") {
|
|
855
|
-
if (
|
|
856
|
-
throw typeError(`
|
|
889
|
+
if (value === null || value === undefined) {
|
|
890
|
+
throw typeError(`Cannot destructure ${describeValue(value)} as it is ${value}.`, pattern);
|
|
857
891
|
}
|
|
858
892
|
const consumed = new Set();
|
|
859
893
|
for (const property of pattern.properties) {
|
|
860
894
|
if (property.type === "RestElement") {
|
|
861
895
|
const rest = new Obj(self.ctx.builtins.Object);
|
|
862
|
-
assign(rest, value, consumed);
|
|
896
|
+
assign(rest, enumerableSource(self.ctx, "Object destructuring", value, pattern), consumed);
|
|
863
897
|
yield* self.declarePattern(property.argument, rest, mutable, property, initialize);
|
|
864
898
|
continue;
|
|
865
899
|
}
|
|
866
900
|
const key = yield* self.destructuringPropertyKey(property);
|
|
867
901
|
consumed.add(typeof key === "symbol" ? key : String(key));
|
|
868
|
-
yield* self.declarePattern(property.value, self.
|
|
902
|
+
yield* self.declarePattern(property.value, self.destructuredProperty(value, key, property), mutable, property, initialize);
|
|
869
903
|
}
|
|
870
904
|
return;
|
|
871
905
|
}
|
|
@@ -892,20 +926,20 @@ class Frame {
|
|
|
892
926
|
return;
|
|
893
927
|
}
|
|
894
928
|
if (pattern.type === "ObjectPattern") {
|
|
895
|
-
if (
|
|
896
|
-
throw
|
|
929
|
+
if (value === null || value === undefined) {
|
|
930
|
+
throw typeError(`Cannot destructure ${describeValue(value)} as it is ${value}.`, pattern);
|
|
897
931
|
}
|
|
898
932
|
const consumed = new Set();
|
|
899
933
|
for (const property of pattern.properties) {
|
|
900
934
|
if (property.type === "RestElement") {
|
|
901
935
|
const rest = new Obj(self.ctx.builtins.Object);
|
|
902
|
-
assign(rest, value, consumed);
|
|
936
|
+
assign(rest, enumerableSource(self.ctx, "Object destructuring", value, pattern), consumed);
|
|
903
937
|
yield* self.assignPattern(property.argument, rest, property);
|
|
904
938
|
continue;
|
|
905
939
|
}
|
|
906
940
|
const key = yield* self.destructuringPropertyKey(property);
|
|
907
941
|
consumed.add(typeof key === "symbol" ? key : String(key));
|
|
908
|
-
yield* self.assignPattern(property.value, self.
|
|
942
|
+
yield* self.assignPattern(property.value, self.destructuredProperty(value, key, property), property);
|
|
909
943
|
}
|
|
910
944
|
return;
|
|
911
945
|
}
|
|
@@ -967,7 +1001,7 @@ class Frame {
|
|
|
967
1001
|
}
|
|
968
1002
|
const keyNode = property.key;
|
|
969
1003
|
if (property.computed) {
|
|
970
|
-
return Effect.
|
|
1004
|
+
return Effect.flatMap(this.evaluateExpression(keyNode), (value) => this.toPropertyKey(value, keyNode));
|
|
971
1005
|
}
|
|
972
1006
|
if (keyNode.type === "Identifier")
|
|
973
1007
|
return Effect.succeed(keyNode.name);
|
|
@@ -985,6 +1019,8 @@ class Frame {
|
|
|
985
1019
|
}
|
|
986
1020
|
case "Identifier":
|
|
987
1021
|
return Effect.sync(() => this.scopes.get(node.name, node));
|
|
1022
|
+
case "ThisExpression":
|
|
1023
|
+
return Effect.sync(() => this.scopes.get("this", node));
|
|
988
1024
|
case "BinaryExpression":
|
|
989
1025
|
return this.evaluateBinaryExpression(node);
|
|
990
1026
|
case "LogicalExpression":
|
|
@@ -1069,76 +1105,111 @@ class Frame {
|
|
|
1069
1105
|
const rhs = yield* self.evaluateExpression(node.right);
|
|
1070
1106
|
if (operator === "instanceof")
|
|
1071
1107
|
return instanceofValue(lhs, rhs, node);
|
|
1108
|
+
if (lhs instanceof Obj || rhs instanceof Obj)
|
|
1109
|
+
return yield* self.applyOperator(operator, lhs, rhs, node);
|
|
1072
1110
|
return self.applyBinaryOperator(operator, lhs, rhs, node);
|
|
1073
1111
|
});
|
|
1074
1112
|
}
|
|
1113
|
+
/** ToPrimitive for an operand: data objects run their own methods; opaque values stay for the data gates below. */
|
|
1114
|
+
toPrimitive(value, hint, node) {
|
|
1115
|
+
return this.native(() => toPrimitive(this.ctx, value, hint), node);
|
|
1116
|
+
}
|
|
1117
|
+
// Arithmetic, relational, and bitwise operators convert both operands first, left then right, so a `valueOf`
|
|
1118
|
+
// runs (and throws) in spec order; `+` asks for the default hint and the rest for a number.
|
|
1119
|
+
applyOperator(operator, lhs, rhs, node) {
|
|
1120
|
+
if (!(lhs instanceof Obj || rhs instanceof Obj))
|
|
1121
|
+
return Effect.succeed(this.applyBinaryOperator(operator, lhs, rhs, node));
|
|
1122
|
+
// IsLooselyEqual converts only an object facing a non-nullish primitive; two objects (including tool
|
|
1123
|
+
// references, which are not Obj) compare by identity.
|
|
1124
|
+
const equality = operator === "==" || operator === "!=";
|
|
1125
|
+
// `in` checks the right operand before ToPropertyKey on the left, so a bad right side wins over a bad key.
|
|
1126
|
+
if (operator === "in" && lhs instanceof Obj && !isOpaque(lhs) && rhs instanceof Obj) {
|
|
1127
|
+
return Effect.map(this.toPropertyKey(lhs, node), (key) => has(rhs, key));
|
|
1128
|
+
}
|
|
1129
|
+
const other = lhs instanceof Obj ? rhs : lhs;
|
|
1130
|
+
const converts = primitiveOperators.has(operator) ||
|
|
1131
|
+
(equality && other !== null && other !== undefined && typeof other !== "object");
|
|
1132
|
+
if (!converts)
|
|
1133
|
+
return Effect.succeed(this.applyBinaryOperator(operator, lhs, rhs, node));
|
|
1134
|
+
const hint = operator === "+" || equality ? "default" : "number";
|
|
1135
|
+
const self = this;
|
|
1136
|
+
return Effect.gen(function* () {
|
|
1137
|
+
const l = yield* self.toPrimitive(lhs, hint, node);
|
|
1138
|
+
const r = yield* self.toPrimitive(rhs, hint, node);
|
|
1139
|
+
return self.applyBinaryOperator(operator, l, r, node);
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1075
1142
|
applyBinaryOperator(operator, lhs, rhs, node) {
|
|
1076
1143
|
if (operator === "===")
|
|
1077
1144
|
return lhs === rhs;
|
|
1078
1145
|
if (operator === "!==")
|
|
1079
1146
|
return lhs !== rhs;
|
|
1080
|
-
if (operator === "
|
|
1081
|
-
return
|
|
1082
|
-
|
|
1083
|
-
|
|
1147
|
+
if (operator === "==")
|
|
1148
|
+
return this.looselyEqual(lhs, rhs, node);
|
|
1149
|
+
if (operator === "!=")
|
|
1150
|
+
return !this.looselyEqual(lhs, rhs, node);
|
|
1151
|
+
if (operator === "in" && rhs instanceof Obj && !isOpaque(lhs))
|
|
1152
|
+
return has(rhs, propertyKey(lhs));
|
|
1153
|
+
if (isOpaque(lhs) || isOpaque(rhs)) {
|
|
1084
1154
|
throw invalidData("Binary operators require data values.", node);
|
|
1085
1155
|
}
|
|
1086
|
-
//
|
|
1087
|
-
const hint = operator === "+" || operator === "==" || operator === "!=" ? "default" : "number";
|
|
1088
|
-
const coerceOperand = (operand) => (operand instanceof Obj ? operand.toPrimitive(hint) : operand);
|
|
1089
|
-
const bothObjects = lhs !== null && typeof lhs === "object" && rhs !== null && typeof rhs === "object";
|
|
1090
|
-
const l = coerceOperand(lhs);
|
|
1091
|
-
const r = coerceOperand(rhs);
|
|
1156
|
+
// Object operands were already converted by applyOperator; only primitives reach the arithmetic below.
|
|
1092
1157
|
switch (operator) {
|
|
1093
1158
|
case "+": {
|
|
1094
|
-
const sum =
|
|
1159
|
+
const sum = lhs + rhs;
|
|
1095
1160
|
if (typeof sum === "string")
|
|
1096
1161
|
checkStringLength(sum.length);
|
|
1097
1162
|
return sum;
|
|
1098
1163
|
}
|
|
1099
1164
|
case "-":
|
|
1100
|
-
return
|
|
1165
|
+
return lhs - rhs;
|
|
1101
1166
|
case "*":
|
|
1102
|
-
return
|
|
1167
|
+
return lhs * rhs;
|
|
1103
1168
|
case "/":
|
|
1104
|
-
return
|
|
1169
|
+
return lhs / rhs;
|
|
1105
1170
|
case "%":
|
|
1106
|
-
return
|
|
1171
|
+
return lhs % rhs;
|
|
1107
1172
|
case "**":
|
|
1108
|
-
return
|
|
1109
|
-
case "==":
|
|
1110
|
-
return bothObjects ? lhs === rhs : l == r;
|
|
1111
|
-
case "!=":
|
|
1112
|
-
return bothObjects ? lhs !== rhs : l != r;
|
|
1173
|
+
return lhs ** rhs;
|
|
1113
1174
|
case "<":
|
|
1114
|
-
return
|
|
1175
|
+
return lhs < rhs;
|
|
1115
1176
|
case "<=":
|
|
1116
|
-
return
|
|
1177
|
+
return lhs <= rhs;
|
|
1117
1178
|
case ">":
|
|
1118
|
-
return
|
|
1179
|
+
return lhs > rhs;
|
|
1119
1180
|
case ">=":
|
|
1120
|
-
return
|
|
1181
|
+
return lhs >= rhs;
|
|
1121
1182
|
case "&":
|
|
1122
|
-
return
|
|
1183
|
+
return lhs & rhs;
|
|
1123
1184
|
case "|":
|
|
1124
|
-
return
|
|
1185
|
+
return lhs | rhs;
|
|
1125
1186
|
case "^":
|
|
1126
|
-
return
|
|
1187
|
+
return lhs ^ rhs;
|
|
1127
1188
|
case "<<":
|
|
1128
|
-
return
|
|
1189
|
+
return lhs << rhs;
|
|
1129
1190
|
case ">>":
|
|
1130
|
-
return
|
|
1191
|
+
return lhs >> rhs;
|
|
1131
1192
|
case ">>>":
|
|
1132
|
-
return
|
|
1193
|
+
return lhs >>> rhs;
|
|
1133
1194
|
case "in":
|
|
1134
|
-
|
|
1135
|
-
throw typeError("The 'in' operator requires a data object on the right-hand side.", node);
|
|
1136
|
-
}
|
|
1137
|
-
return has(rhs, coerceOperand(lhs));
|
|
1195
|
+
throw typeError("The 'in' operator requires a data object on the right-hand side.", node);
|
|
1138
1196
|
default:
|
|
1139
1197
|
throw typeError(`Unsupported binary operator '${operator}'.`, node);
|
|
1140
1198
|
}
|
|
1141
1199
|
}
|
|
1200
|
+
// IsLooselyEqual: objects (including functions and tool references) compare by identity, and a nullish
|
|
1201
|
+
// primitive never equals an object.
|
|
1202
|
+
looselyEqual(lhs, rhs, node) {
|
|
1203
|
+
const lhsObject = lhs !== null && typeof lhs === "object";
|
|
1204
|
+
const rhsObject = rhs !== null && typeof rhs === "object";
|
|
1205
|
+
if (lhsObject === rhsObject)
|
|
1206
|
+
return lhsObject ? lhs === rhs : lhs == rhs;
|
|
1207
|
+
const primitive = lhsObject ? rhs : lhs;
|
|
1208
|
+
if (primitive === null || primitive === undefined)
|
|
1209
|
+
return false;
|
|
1210
|
+
// Data objects were converted by applyOperator, so only an opaque reference facing a primitive gets here.
|
|
1211
|
+
throw invalidData("Binary operators require data values.", node);
|
|
1212
|
+
}
|
|
1142
1213
|
evaluateLogicalExpression(node) {
|
|
1143
1214
|
const operator = node.operator;
|
|
1144
1215
|
return Effect.flatMap(this.evaluateExpression(node.left), (left) => {
|
|
@@ -1160,17 +1231,19 @@ class Frame {
|
|
|
1160
1231
|
if (operator === "typeof" && argument.type === "Identifier" && !this.scopes.resolve(argument.name)) {
|
|
1161
1232
|
return Effect.succeed("undefined");
|
|
1162
1233
|
}
|
|
1163
|
-
|
|
1234
|
+
const self = this;
|
|
1235
|
+
return Effect.gen(function* () {
|
|
1236
|
+
const value = yield* self.evaluateExpression(argument);
|
|
1164
1237
|
if (operator === "typeof")
|
|
1165
1238
|
return typeofValue(value);
|
|
1166
1239
|
if (operator === "!")
|
|
1167
1240
|
return !value;
|
|
1168
1241
|
if (operator === "void")
|
|
1169
1242
|
return undefined;
|
|
1170
|
-
|
|
1243
|
+
const operand = yield* self.toPrimitive(value, "number", node);
|
|
1244
|
+
if (isOpaque(operand)) {
|
|
1171
1245
|
throw invalidData("Unary operators require data values.", node);
|
|
1172
1246
|
}
|
|
1173
|
-
const operand = value instanceof Obj ? value.toPrimitive("number") : value;
|
|
1174
1247
|
let result;
|
|
1175
1248
|
switch (operator) {
|
|
1176
1249
|
case "+":
|
|
@@ -1191,11 +1264,16 @@ class Frame {
|
|
|
1191
1264
|
evaluateAssignmentExpression(node) {
|
|
1192
1265
|
const left = node.left;
|
|
1193
1266
|
const operator = node.operator;
|
|
1267
|
+
// The binary operator a compound assignment applies: `+=` is `+`.
|
|
1268
|
+
const binary = operator.slice(0, -1);
|
|
1194
1269
|
const self = this;
|
|
1195
1270
|
return Effect.gen(function* () {
|
|
1196
1271
|
if (operator === "??=" || operator === "||=" || operator === "&&=") {
|
|
1197
1272
|
return yield* self.evaluateLogicalAssignment(node, left, operator);
|
|
1198
1273
|
}
|
|
1274
|
+
if (operator !== "=" && !compoundOperators.has(operator)) {
|
|
1275
|
+
throw typeError(`Unsupported assignment operator '${operator}'.`, node);
|
|
1276
|
+
}
|
|
1199
1277
|
if (operator === "=" && (left.type === "ObjectPattern" || left.type === "ArrayPattern")) {
|
|
1200
1278
|
const rightValue = yield* self.evaluateExpression(node.right);
|
|
1201
1279
|
yield* self.assignPattern(left, rightValue, node);
|
|
@@ -1206,17 +1284,23 @@ class Frame {
|
|
|
1206
1284
|
if (operator !== "=") {
|
|
1207
1285
|
const current = self.scopes.get(name, left);
|
|
1208
1286
|
const rightValue = yield* self.evaluateExpression(node.right);
|
|
1209
|
-
|
|
1287
|
+
const next = current instanceof Obj || rightValue instanceof Obj
|
|
1288
|
+
? yield* self.applyOperator(binary, current, rightValue, node)
|
|
1289
|
+
: self.applyBinaryOperator(binary, current, rightValue, node);
|
|
1290
|
+
return self.scopes.set(name, next, left);
|
|
1210
1291
|
}
|
|
1211
1292
|
const rightValue = yield* self.evaluateNamed(node.right, name);
|
|
1212
1293
|
return self.scopes.set(name, rightValue, left);
|
|
1213
1294
|
}
|
|
1214
1295
|
if (left.type === "MemberExpression") {
|
|
1215
|
-
return yield* self.modifyMember(left, (current) => Effect.
|
|
1296
|
+
return yield* self.modifyMember(left, (current) => Effect.flatMap(self.evaluateExpression(node.right), (rightValue) => {
|
|
1216
1297
|
if (operator === "=")
|
|
1217
|
-
return { write: true, next: rightValue, result: rightValue };
|
|
1218
|
-
|
|
1219
|
-
|
|
1298
|
+
return Effect.succeed({ write: true, next: rightValue, result: rightValue });
|
|
1299
|
+
return Effect.map(self.applyOperator(binary, current, rightValue, node), (next) => ({
|
|
1300
|
+
write: true,
|
|
1301
|
+
next,
|
|
1302
|
+
result: next,
|
|
1303
|
+
}));
|
|
1220
1304
|
}));
|
|
1221
1305
|
}
|
|
1222
1306
|
throw typeError("Assignment target must be an Identifier or MemberExpression.", left);
|
|
@@ -1254,29 +1338,32 @@ class Frame {
|
|
|
1254
1338
|
if (increment === undefined) {
|
|
1255
1339
|
throw typeError(`Unsupported update operator '${operator}'.`, node);
|
|
1256
1340
|
}
|
|
1257
|
-
// CodeMode numeric coercion, not host Number()
|
|
1258
|
-
// the host throw during ToPrimitive, and opaque runtime references must reject clearly.
|
|
1341
|
+
// CodeMode numeric coercion, not host Number(), so opaque runtime references reject clearly.
|
|
1259
1342
|
const operand = (current) => {
|
|
1260
|
-
if (
|
|
1343
|
+
if (isOpaque(current)) {
|
|
1261
1344
|
throw invalidData(`'${operator}' requires a data value.`, argument);
|
|
1262
1345
|
}
|
|
1263
1346
|
return coerceToNumber(current);
|
|
1264
1347
|
};
|
|
1265
1348
|
if (argument.type === "Identifier") {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
const
|
|
1349
|
+
const name = argument.name;
|
|
1350
|
+
const current = this.scopes.get(name, argument);
|
|
1351
|
+
const update = (value) => {
|
|
1352
|
+
const before = operand(value);
|
|
1353
|
+
const next = before + increment;
|
|
1270
1354
|
this.scopes.set(name, next, argument);
|
|
1271
|
-
return prefix ? next :
|
|
1272
|
-
}
|
|
1355
|
+
return prefix ? next : before;
|
|
1356
|
+
};
|
|
1357
|
+
if (!(current instanceof Obj))
|
|
1358
|
+
return Effect.sync(() => update(current));
|
|
1359
|
+
return Effect.map(this.toPrimitive(current, "number", argument), update);
|
|
1273
1360
|
}
|
|
1274
1361
|
if (argument.type === "MemberExpression") {
|
|
1275
|
-
return this.modifyMember(argument, (current) => {
|
|
1276
|
-
const value = operand(
|
|
1362
|
+
return this.modifyMember(argument, (current) => Effect.map(this.toPrimitive(current, "number", argument), (primitive) => {
|
|
1363
|
+
const value = operand(primitive);
|
|
1277
1364
|
const next = value + increment;
|
|
1278
|
-
return
|
|
1279
|
-
});
|
|
1365
|
+
return { write: true, next, result: prefix ? next : value };
|
|
1366
|
+
}));
|
|
1280
1367
|
}
|
|
1281
1368
|
throw typeError("Update target must be an Identifier or MemberExpression.", argument);
|
|
1282
1369
|
}
|
|
@@ -1320,7 +1407,7 @@ class Frame {
|
|
|
1320
1407
|
return yield* self.createToolCallPromise(callable.path, args);
|
|
1321
1408
|
}
|
|
1322
1409
|
if (callable instanceof Fn)
|
|
1323
|
-
return yield* self.invokeFunction(callable, args, node);
|
|
1410
|
+
return yield* self.invokeFunction(callable, thisValue, args, node);
|
|
1324
1411
|
if (callable instanceof Native) {
|
|
1325
1412
|
return yield* self.native(() => callable.call(thisValue, args), node);
|
|
1326
1413
|
}
|
|
@@ -1356,15 +1443,26 @@ class Frame {
|
|
|
1356
1443
|
});
|
|
1357
1444
|
}
|
|
1358
1445
|
// A callback invoked by a built-in runs below the call that invoked the built-in, so the deeper of the two counts.
|
|
1359
|
-
invokeFunction(fn, args, node) {
|
|
1446
|
+
invokeFunction(fn, thisValue, args, node) {
|
|
1360
1447
|
const self = this;
|
|
1361
1448
|
return Effect.flatMap(CallSite, (site) => {
|
|
1362
1449
|
const depth = Math.max(self.depth, site.depth) + 1;
|
|
1363
1450
|
if (depth > MAX_CALL_DEPTH)
|
|
1364
1451
|
throw rangeError("Maximum call stack size exceeded", node);
|
|
1365
1452
|
const invocation = new Frame(this.ctx, new ScopeStack([...fn.capturedScopes, new Map()]), depth);
|
|
1366
|
-
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
|
|
1367
1453
|
const paramScope = invocation.scopes.current();
|
|
1454
|
+
// `this` and `arguments` are scope bindings so arrows resolve them lexically; a parameter named
|
|
1455
|
+
// `arguments` shadows the object, as in JS.
|
|
1456
|
+
if (!fn.arrow)
|
|
1457
|
+
paramScope.set("this", { mutable: false, value: thisValue, initialized: true });
|
|
1458
|
+
if (!fn.arrow && usesArguments(fn)) {
|
|
1459
|
+
paramScope.set("arguments", {
|
|
1460
|
+
mutable: true,
|
|
1461
|
+
value: new Arguments(self.ctx.builtins.Object, args),
|
|
1462
|
+
initialized: true,
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
|
|
1368
1466
|
for (const parameter of fn.parameters) {
|
|
1369
1467
|
for (const name of collectPatternNames(parameter)) {
|
|
1370
1468
|
paramScope.set(name, { mutable: true, value: undefined, initialized: false });
|
|
@@ -1616,13 +1714,13 @@ class Frame {
|
|
|
1616
1714
|
const keyNode = property.key;
|
|
1617
1715
|
let key;
|
|
1618
1716
|
if (property.computed) {
|
|
1619
|
-
key = self.toPropertyKey(yield* self.evaluateExpression(keyNode));
|
|
1717
|
+
key = yield* self.toPropertyKey(yield* self.evaluateExpression(keyNode), keyNode);
|
|
1620
1718
|
}
|
|
1621
1719
|
else if (keyNode.type === "Identifier") {
|
|
1622
1720
|
key = keyNode.name;
|
|
1623
1721
|
}
|
|
1624
1722
|
else if (keyNode.type === "Literal") {
|
|
1625
|
-
key =
|
|
1723
|
+
key = propertyKey(literal(keyNode));
|
|
1626
1724
|
}
|
|
1627
1725
|
else {
|
|
1628
1726
|
throw typeError("Unsupported object property key shape.", keyNode);
|
|
@@ -1682,7 +1780,7 @@ class Frame {
|
|
|
1682
1780
|
checkStringLength(output.length);
|
|
1683
1781
|
if (index < expressions.length) {
|
|
1684
1782
|
const raw = yield* self.evaluateExpression(expressions[index]);
|
|
1685
|
-
output += coerceToString(raw);
|
|
1783
|
+
output += coerceToString(yield* self.toPrimitive(raw, "string", expressions[index]));
|
|
1686
1784
|
checkStringLength(output.length);
|
|
1687
1785
|
}
|
|
1688
1786
|
}
|
|
@@ -1713,12 +1811,6 @@ class Frame {
|
|
|
1713
1811
|
evaluateConditionalExpression(node) {
|
|
1714
1812
|
return Effect.flatMap(this.evaluateExpression(node.test), (test) => this.evaluateExpression(test ? node.consequent : node.alternate));
|
|
1715
1813
|
}
|
|
1716
|
-
applyCompoundAssignment(operator, current, incoming, node) {
|
|
1717
|
-
if (!compoundOperators.has(operator)) {
|
|
1718
|
-
throw typeError(`Unsupported assignment operator '${operator}'.`, node);
|
|
1719
|
-
}
|
|
1720
|
-
return this.applyBinaryOperator(operator.slice(0, -1), current, incoming, node);
|
|
1721
|
-
}
|
|
1722
1814
|
getMemberReference(node) {
|
|
1723
1815
|
const objectNode = node.object;
|
|
1724
1816
|
const propertyNode = node.property;
|
|
@@ -1733,36 +1825,52 @@ class Frame {
|
|
|
1733
1825
|
return OptionalShortCircuit;
|
|
1734
1826
|
if ((objectValue === null || objectValue === undefined) && node.optional)
|
|
1735
1827
|
return OptionalShortCircuit;
|
|
1736
|
-
const
|
|
1737
|
-
?
|
|
1738
|
-
: propertyNode
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
if (objectValue instanceof ToolReference) {
|
|
1742
|
-
if (typeof key !== "string") {
|
|
1743
|
-
throw typeError("Tool paths must use string property names.", propertyNode);
|
|
1744
|
-
}
|
|
1745
|
-
return new ToolReference([...objectValue.path, key]);
|
|
1746
|
-
}
|
|
1747
|
-
if (objectValue instanceof Obj)
|
|
1748
|
-
return { target: objectValue, key, receiver: objectValue };
|
|
1749
|
-
// Strings own length and indexes; every other primitive property reads through the wrapper prototype.
|
|
1750
|
-
if (typeof objectValue === "string") {
|
|
1751
|
-
if (key === "length")
|
|
1752
|
-
return { value: objectValue.length };
|
|
1753
|
-
const index = typeof key === "symbol" ? undefined : parseArrayIndex(key);
|
|
1754
|
-
if (index !== undefined)
|
|
1755
|
-
return { value: objectValue[index] };
|
|
1756
|
-
}
|
|
1757
|
-
const proto = primitivePrototype(self.ctx.builtins, objectValue);
|
|
1758
|
-
if (proto !== undefined)
|
|
1759
|
-
return { target: proto, key, receiver: objectValue };
|
|
1828
|
+
const keyValue = !node.computed && propertyNode.type === "Identifier"
|
|
1829
|
+
? propertyNode.name
|
|
1830
|
+
: yield* self.evaluateExpression(propertyNode);
|
|
1831
|
+
// GetValue applies ToObject to the base before ToPropertyKey, so a nullish base throws before the key's own
|
|
1832
|
+
// toString runs.
|
|
1760
1833
|
if (objectValue === null || objectValue === undefined) {
|
|
1761
|
-
throw typeError(`Cannot read properties of ${objectValue} (reading '${
|
|
1834
|
+
throw typeError(`Cannot read properties of ${objectValue} (reading '${coerceToString(keyValue)}').`, objectNode);
|
|
1762
1835
|
}
|
|
1763
|
-
|
|
1836
|
+
const key = yield* self.toPropertyKey(keyValue, propertyNode);
|
|
1837
|
+
return self.resolveProperty(objectValue, key, objectNode, propertyNode);
|
|
1764
1838
|
});
|
|
1765
1839
|
}
|
|
1840
|
+
resolveProperty(objectValue, key, objectNode, propertyNode) {
|
|
1841
|
+
if (objectValue instanceof ToolReference) {
|
|
1842
|
+
if (typeof key !== "string") {
|
|
1843
|
+
throw typeError("Tool paths must use string property names.", propertyNode);
|
|
1844
|
+
}
|
|
1845
|
+
return new ToolReference([...objectValue.path, key]);
|
|
1846
|
+
}
|
|
1847
|
+
if (objectValue instanceof Obj)
|
|
1848
|
+
return { target: objectValue, key, receiver: objectValue };
|
|
1849
|
+
// Strings own length and indexes; every other primitive property reads through the wrapper prototype.
|
|
1850
|
+
if (typeof objectValue === "string") {
|
|
1851
|
+
if (key === "length")
|
|
1852
|
+
return { value: objectValue.length };
|
|
1853
|
+
const index = typeof key === "symbol" ? undefined : parseArrayIndex(key);
|
|
1854
|
+
if (index !== undefined)
|
|
1855
|
+
return { value: objectValue[index] };
|
|
1856
|
+
}
|
|
1857
|
+
const proto = primitivePrototype(this.ctx.builtins, objectValue);
|
|
1858
|
+
if (proto !== undefined)
|
|
1859
|
+
return { target: proto, key, receiver: objectValue };
|
|
1860
|
+
if (objectValue === null || objectValue === undefined) {
|
|
1861
|
+
throw typeError(`Cannot read properties of ${objectValue} (reading '${String(key)}').`, objectNode);
|
|
1862
|
+
}
|
|
1863
|
+
throw typeError("Cannot access a property on a non-object value.", objectNode);
|
|
1864
|
+
}
|
|
1865
|
+
// One destructured property, read the way a member expression would read it (primitives use their prototype).
|
|
1866
|
+
destructuredProperty(source, key, node) {
|
|
1867
|
+
const reference = this.resolveProperty(source, key, node, node);
|
|
1868
|
+
if (reference instanceof ToolReference)
|
|
1869
|
+
return reference;
|
|
1870
|
+
if ("value" in reference)
|
|
1871
|
+
return reference.value;
|
|
1872
|
+
return this.readProperty(reference.target, reference.key, node, reference.receiver);
|
|
1873
|
+
}
|
|
1766
1874
|
readReference(reference, node) {
|
|
1767
1875
|
// Reject unknown promise properties so a missing await cannot hide.
|
|
1768
1876
|
if (reference.target instanceof PromiseObj && !has(reference.target, reference.key)) {
|
|
@@ -1841,14 +1949,13 @@ class Frame {
|
|
|
1841
1949
|
})();
|
|
1842
1950
|
if (written)
|
|
1843
1951
|
return;
|
|
1844
|
-
if (target instanceof Arr && key === "length")
|
|
1845
|
-
throw rangeError("Invalid array length", node);
|
|
1846
1952
|
throw typeError(`Cannot assign to read only property '${String(key)}'.`, node);
|
|
1847
1953
|
}
|
|
1848
|
-
// ToPropertyKey:
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1954
|
+
// ToPropertyKey: a data object converts through its own `toString`/`valueOf` first; anything else becomes its
|
|
1955
|
+
// string form synchronously, so `counts[row.category]` works when the field is null.
|
|
1956
|
+
toPropertyKey(value, node) {
|
|
1957
|
+
if (!(value instanceof Obj))
|
|
1958
|
+
return Effect.succeed(propertyKey(value));
|
|
1959
|
+
return Effect.map(this.toPrimitive(value, "string", node), propertyKey);
|
|
1853
1960
|
}
|
|
1854
1961
|
}
|
|
@@ -2,7 +2,7 @@ import { ErrorObj, Obj, type Value } from "./objects.js";
|
|
|
2
2
|
export declare const errorTypes: readonly ["Error", "TypeError", "RangeError", "SyntaxError", "ReferenceError", "EvalError", "URIError", "AggregateError"];
|
|
3
3
|
export type ErrorType = (typeof errorTypes)[number];
|
|
4
4
|
export declare const isErrorType: (name: string) => name is ErrorType;
|
|
5
|
-
declare const builtins: readonly ["Object", "Function", "Array", "String", "Number", "Boolean", "Date", "RegExp", "Map", "Set", "URL", "URLSearchParams", "Headers", "Uint8Array", "TextEncoder", "TextDecoder", "Promise", "Iterator", "IteratorHelper", "AsyncIterator", "Generator", "AsyncGenerator"];
|
|
5
|
+
declare const builtins: readonly ["Object", "Function", "Array", "String", "Number", "Boolean", "Date", "RegExp", "Map", "Set", "WeakMap", "WeakSet", "URL", "URLSearchParams", "Headers", "Uint8Array", "TextEncoder", "TextDecoder", "Promise", "Iterator", "IteratorHelper", "AsyncIterator", "Generator", "AsyncGenerator"];
|
|
6
6
|
/**
|
|
7
7
|
* The built-in prototype objects of one runtime, allocated empty in dependency order. The globals populate them
|
|
8
8
|
* and attach their constructors when the runtime is built.
|
|
@@ -22,6 +22,8 @@ const builtins = [
|
|
|
22
22
|
"RegExp",
|
|
23
23
|
"Map",
|
|
24
24
|
"Set",
|
|
25
|
+
"WeakMap",
|
|
26
|
+
"WeakSet",
|
|
25
27
|
"URL",
|
|
26
28
|
"URLSearchParams",
|
|
27
29
|
"Headers",
|
|
@@ -78,6 +80,8 @@ export const createBuiltins = () => {
|
|
|
78
80
|
RegExp: plain(),
|
|
79
81
|
Map: plain(),
|
|
80
82
|
Set: plain(),
|
|
83
|
+
WeakMap: plain(),
|
|
84
|
+
WeakSet: plain(),
|
|
81
85
|
URL: plain(),
|
|
82
86
|
URLSearchParams: plain(),
|
|
83
87
|
Headers: plain(),
|
|
@@ -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,
|
|
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, 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;
|