@opencode/codemode 2.0.16 → 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 +36 -2
- package/dist/interpreter/interpreter.js +212 -111
- 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 +23 -1
- package/dist/interpreter/objects.js +36 -1
- package/dist/stdlib/array.js +18 -13
- 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/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.js +3 -2
- 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
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);
|
|
@@ -845,20 +886,20 @@ class Frame {
|
|
|
845
886
|
return;
|
|
846
887
|
}
|
|
847
888
|
if (pattern.type === "ObjectPattern") {
|
|
848
|
-
if (
|
|
849
|
-
throw typeError(`
|
|
889
|
+
if (value === null || value === undefined) {
|
|
890
|
+
throw typeError(`Cannot destructure ${describeValue(value)} as it is ${value}.`, pattern);
|
|
850
891
|
}
|
|
851
892
|
const consumed = new Set();
|
|
852
893
|
for (const property of pattern.properties) {
|
|
853
894
|
if (property.type === "RestElement") {
|
|
854
895
|
const rest = new Obj(self.ctx.builtins.Object);
|
|
855
|
-
assign(rest, value, consumed);
|
|
896
|
+
assign(rest, enumerableSource(self.ctx, "Object destructuring", value, pattern), consumed);
|
|
856
897
|
yield* self.declarePattern(property.argument, rest, mutable, property, initialize);
|
|
857
898
|
continue;
|
|
858
899
|
}
|
|
859
900
|
const key = yield* self.destructuringPropertyKey(property);
|
|
860
901
|
consumed.add(typeof key === "symbol" ? key : String(key));
|
|
861
|
-
yield* self.declarePattern(property.value, self.
|
|
902
|
+
yield* self.declarePattern(property.value, self.destructuredProperty(value, key, property), mutable, property, initialize);
|
|
862
903
|
}
|
|
863
904
|
return;
|
|
864
905
|
}
|
|
@@ -885,20 +926,20 @@ class Frame {
|
|
|
885
926
|
return;
|
|
886
927
|
}
|
|
887
928
|
if (pattern.type === "ObjectPattern") {
|
|
888
|
-
if (
|
|
889
|
-
throw
|
|
929
|
+
if (value === null || value === undefined) {
|
|
930
|
+
throw typeError(`Cannot destructure ${describeValue(value)} as it is ${value}.`, pattern);
|
|
890
931
|
}
|
|
891
932
|
const consumed = new Set();
|
|
892
933
|
for (const property of pattern.properties) {
|
|
893
934
|
if (property.type === "RestElement") {
|
|
894
935
|
const rest = new Obj(self.ctx.builtins.Object);
|
|
895
|
-
assign(rest, value, consumed);
|
|
936
|
+
assign(rest, enumerableSource(self.ctx, "Object destructuring", value, pattern), consumed);
|
|
896
937
|
yield* self.assignPattern(property.argument, rest, property);
|
|
897
938
|
continue;
|
|
898
939
|
}
|
|
899
940
|
const key = yield* self.destructuringPropertyKey(property);
|
|
900
941
|
consumed.add(typeof key === "symbol" ? key : String(key));
|
|
901
|
-
yield* self.assignPattern(property.value, self.
|
|
942
|
+
yield* self.assignPattern(property.value, self.destructuredProperty(value, key, property), property);
|
|
902
943
|
}
|
|
903
944
|
return;
|
|
904
945
|
}
|
|
@@ -960,7 +1001,7 @@ class Frame {
|
|
|
960
1001
|
}
|
|
961
1002
|
const keyNode = property.key;
|
|
962
1003
|
if (property.computed) {
|
|
963
|
-
return Effect.
|
|
1004
|
+
return Effect.flatMap(this.evaluateExpression(keyNode), (value) => this.toPropertyKey(value, keyNode));
|
|
964
1005
|
}
|
|
965
1006
|
if (keyNode.type === "Identifier")
|
|
966
1007
|
return Effect.succeed(keyNode.name);
|
|
@@ -978,6 +1019,8 @@ class Frame {
|
|
|
978
1019
|
}
|
|
979
1020
|
case "Identifier":
|
|
980
1021
|
return Effect.sync(() => this.scopes.get(node.name, node));
|
|
1022
|
+
case "ThisExpression":
|
|
1023
|
+
return Effect.sync(() => this.scopes.get("this", node));
|
|
981
1024
|
case "BinaryExpression":
|
|
982
1025
|
return this.evaluateBinaryExpression(node);
|
|
983
1026
|
case "LogicalExpression":
|
|
@@ -1062,9 +1105,40 @@ class Frame {
|
|
|
1062
1105
|
const rhs = yield* self.evaluateExpression(node.right);
|
|
1063
1106
|
if (operator === "instanceof")
|
|
1064
1107
|
return instanceofValue(lhs, rhs, node);
|
|
1108
|
+
if (lhs instanceof Obj || rhs instanceof Obj)
|
|
1109
|
+
return yield* self.applyOperator(operator, lhs, rhs, node);
|
|
1065
1110
|
return self.applyBinaryOperator(operator, lhs, rhs, node);
|
|
1066
1111
|
});
|
|
1067
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
|
+
}
|
|
1068
1142
|
applyBinaryOperator(operator, lhs, rhs, node) {
|
|
1069
1143
|
if (operator === "===")
|
|
1070
1144
|
return lhs === rhs;
|
|
@@ -1074,78 +1148,67 @@ class Frame {
|
|
|
1074
1148
|
return this.looselyEqual(lhs, rhs, node);
|
|
1075
1149
|
if (operator === "!=")
|
|
1076
1150
|
return !this.looselyEqual(lhs, rhs, node);
|
|
1077
|
-
if (operator === "in" && rhs instanceof Obj && !isOpaque(lhs))
|
|
1078
|
-
return has(rhs,
|
|
1079
|
-
}
|
|
1151
|
+
if (operator === "in" && rhs instanceof Obj && !isOpaque(lhs))
|
|
1152
|
+
return has(rhs, propertyKey(lhs));
|
|
1080
1153
|
if (isOpaque(lhs) || isOpaque(rhs)) {
|
|
1081
1154
|
throw invalidData("Binary operators require data values.", node);
|
|
1082
1155
|
}
|
|
1083
|
-
//
|
|
1084
|
-
const hint = operator === "+" ? "default" : "number";
|
|
1085
|
-
const coerceOperand = (operand) => (operand instanceof Obj ? operand.toPrimitive(hint) : operand);
|
|
1086
|
-
const l = coerceOperand(lhs);
|
|
1087
|
-
const r = coerceOperand(rhs);
|
|
1156
|
+
// Object operands were already converted by applyOperator; only primitives reach the arithmetic below.
|
|
1088
1157
|
switch (operator) {
|
|
1089
1158
|
case "+": {
|
|
1090
|
-
const sum =
|
|
1159
|
+
const sum = lhs + rhs;
|
|
1091
1160
|
if (typeof sum === "string")
|
|
1092
1161
|
checkStringLength(sum.length);
|
|
1093
1162
|
return sum;
|
|
1094
1163
|
}
|
|
1095
1164
|
case "-":
|
|
1096
|
-
return
|
|
1165
|
+
return lhs - rhs;
|
|
1097
1166
|
case "*":
|
|
1098
|
-
return
|
|
1167
|
+
return lhs * rhs;
|
|
1099
1168
|
case "/":
|
|
1100
|
-
return
|
|
1169
|
+
return lhs / rhs;
|
|
1101
1170
|
case "%":
|
|
1102
|
-
return
|
|
1171
|
+
return lhs % rhs;
|
|
1103
1172
|
case "**":
|
|
1104
|
-
return
|
|
1173
|
+
return lhs ** rhs;
|
|
1105
1174
|
case "<":
|
|
1106
|
-
return
|
|
1175
|
+
return lhs < rhs;
|
|
1107
1176
|
case "<=":
|
|
1108
|
-
return
|
|
1177
|
+
return lhs <= rhs;
|
|
1109
1178
|
case ">":
|
|
1110
|
-
return
|
|
1179
|
+
return lhs > rhs;
|
|
1111
1180
|
case ">=":
|
|
1112
|
-
return
|
|
1181
|
+
return lhs >= rhs;
|
|
1113
1182
|
case "&":
|
|
1114
|
-
return
|
|
1183
|
+
return lhs & rhs;
|
|
1115
1184
|
case "|":
|
|
1116
|
-
return
|
|
1185
|
+
return lhs | rhs;
|
|
1117
1186
|
case "^":
|
|
1118
|
-
return
|
|
1187
|
+
return lhs ^ rhs;
|
|
1119
1188
|
case "<<":
|
|
1120
|
-
return
|
|
1189
|
+
return lhs << rhs;
|
|
1121
1190
|
case ">>":
|
|
1122
|
-
return
|
|
1191
|
+
return lhs >> rhs;
|
|
1123
1192
|
case ">>>":
|
|
1124
|
-
return
|
|
1193
|
+
return lhs >>> rhs;
|
|
1125
1194
|
case "in":
|
|
1126
|
-
|
|
1127
|
-
throw typeError("The 'in' operator requires a data object on the right-hand side.", node);
|
|
1128
|
-
}
|
|
1129
|
-
return has(rhs, coerceOperand(lhs));
|
|
1195
|
+
throw typeError("The 'in' operator requires a data object on the right-hand side.", node);
|
|
1130
1196
|
default:
|
|
1131
1197
|
throw typeError(`Unsupported binary operator '${operator}'.`, node);
|
|
1132
1198
|
}
|
|
1133
1199
|
}
|
|
1134
|
-
// IsLooselyEqual: objects (including functions and tool references) compare by identity, and
|
|
1135
|
-
//
|
|
1200
|
+
// IsLooselyEqual: objects (including functions and tool references) compare by identity, and a nullish
|
|
1201
|
+
// primitive never equals an object.
|
|
1136
1202
|
looselyEqual(lhs, rhs, node) {
|
|
1137
1203
|
const lhsObject = lhs !== null && typeof lhs === "object";
|
|
1138
1204
|
const rhsObject = rhs !== null && typeof rhs === "object";
|
|
1139
1205
|
if (lhsObject === rhsObject)
|
|
1140
1206
|
return lhsObject ? lhs === rhs : lhs == rhs;
|
|
1141
|
-
const object = lhsObject ? lhs : rhs;
|
|
1142
1207
|
const primitive = lhsObject ? rhs : lhs;
|
|
1143
1208
|
if (primitive === null || primitive === undefined)
|
|
1144
1209
|
return false;
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
return object.toPrimitive("default") == primitive;
|
|
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);
|
|
1149
1212
|
}
|
|
1150
1213
|
evaluateLogicalExpression(node) {
|
|
1151
1214
|
const operator = node.operator;
|
|
@@ -1168,17 +1231,19 @@ class Frame {
|
|
|
1168
1231
|
if (operator === "typeof" && argument.type === "Identifier" && !this.scopes.resolve(argument.name)) {
|
|
1169
1232
|
return Effect.succeed("undefined");
|
|
1170
1233
|
}
|
|
1171
|
-
|
|
1234
|
+
const self = this;
|
|
1235
|
+
return Effect.gen(function* () {
|
|
1236
|
+
const value = yield* self.evaluateExpression(argument);
|
|
1172
1237
|
if (operator === "typeof")
|
|
1173
1238
|
return typeofValue(value);
|
|
1174
1239
|
if (operator === "!")
|
|
1175
1240
|
return !value;
|
|
1176
1241
|
if (operator === "void")
|
|
1177
1242
|
return undefined;
|
|
1178
|
-
|
|
1243
|
+
const operand = yield* self.toPrimitive(value, "number", node);
|
|
1244
|
+
if (isOpaque(operand)) {
|
|
1179
1245
|
throw invalidData("Unary operators require data values.", node);
|
|
1180
1246
|
}
|
|
1181
|
-
const operand = value instanceof Obj ? value.toPrimitive("number") : value;
|
|
1182
1247
|
let result;
|
|
1183
1248
|
switch (operator) {
|
|
1184
1249
|
case "+":
|
|
@@ -1199,11 +1264,16 @@ class Frame {
|
|
|
1199
1264
|
evaluateAssignmentExpression(node) {
|
|
1200
1265
|
const left = node.left;
|
|
1201
1266
|
const operator = node.operator;
|
|
1267
|
+
// The binary operator a compound assignment applies: `+=` is `+`.
|
|
1268
|
+
const binary = operator.slice(0, -1);
|
|
1202
1269
|
const self = this;
|
|
1203
1270
|
return Effect.gen(function* () {
|
|
1204
1271
|
if (operator === "??=" || operator === "||=" || operator === "&&=") {
|
|
1205
1272
|
return yield* self.evaluateLogicalAssignment(node, left, operator);
|
|
1206
1273
|
}
|
|
1274
|
+
if (operator !== "=" && !compoundOperators.has(operator)) {
|
|
1275
|
+
throw typeError(`Unsupported assignment operator '${operator}'.`, node);
|
|
1276
|
+
}
|
|
1207
1277
|
if (operator === "=" && (left.type === "ObjectPattern" || left.type === "ArrayPattern")) {
|
|
1208
1278
|
const rightValue = yield* self.evaluateExpression(node.right);
|
|
1209
1279
|
yield* self.assignPattern(left, rightValue, node);
|
|
@@ -1214,17 +1284,23 @@ class Frame {
|
|
|
1214
1284
|
if (operator !== "=") {
|
|
1215
1285
|
const current = self.scopes.get(name, left);
|
|
1216
1286
|
const rightValue = yield* self.evaluateExpression(node.right);
|
|
1217
|
-
|
|
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);
|
|
1218
1291
|
}
|
|
1219
1292
|
const rightValue = yield* self.evaluateNamed(node.right, name);
|
|
1220
1293
|
return self.scopes.set(name, rightValue, left);
|
|
1221
1294
|
}
|
|
1222
1295
|
if (left.type === "MemberExpression") {
|
|
1223
|
-
return yield* self.modifyMember(left, (current) => Effect.
|
|
1296
|
+
return yield* self.modifyMember(left, (current) => Effect.flatMap(self.evaluateExpression(node.right), (rightValue) => {
|
|
1224
1297
|
if (operator === "=")
|
|
1225
|
-
return { write: true, next: rightValue, result: rightValue };
|
|
1226
|
-
|
|
1227
|
-
|
|
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
|
+
}));
|
|
1228
1304
|
}));
|
|
1229
1305
|
}
|
|
1230
1306
|
throw typeError("Assignment target must be an Identifier or MemberExpression.", left);
|
|
@@ -1262,8 +1338,7 @@ class Frame {
|
|
|
1262
1338
|
if (increment === undefined) {
|
|
1263
1339
|
throw typeError(`Unsupported update operator '${operator}'.`, node);
|
|
1264
1340
|
}
|
|
1265
|
-
// CodeMode numeric coercion, not host Number()
|
|
1266
|
-
// 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.
|
|
1267
1342
|
const operand = (current) => {
|
|
1268
1343
|
if (isOpaque(current)) {
|
|
1269
1344
|
throw invalidData(`'${operator}' requires a data value.`, argument);
|
|
@@ -1271,20 +1346,24 @@ class Frame {
|
|
|
1271
1346
|
return coerceToNumber(current);
|
|
1272
1347
|
};
|
|
1273
1348
|
if (argument.type === "Identifier") {
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
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;
|
|
1278
1354
|
this.scopes.set(name, next, argument);
|
|
1279
|
-
return prefix ? next :
|
|
1280
|
-
}
|
|
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);
|
|
1281
1360
|
}
|
|
1282
1361
|
if (argument.type === "MemberExpression") {
|
|
1283
|
-
return this.modifyMember(argument, (current) => {
|
|
1284
|
-
const value = operand(
|
|
1362
|
+
return this.modifyMember(argument, (current) => Effect.map(this.toPrimitive(current, "number", argument), (primitive) => {
|
|
1363
|
+
const value = operand(primitive);
|
|
1285
1364
|
const next = value + increment;
|
|
1286
|
-
return
|
|
1287
|
-
});
|
|
1365
|
+
return { write: true, next, result: prefix ? next : value };
|
|
1366
|
+
}));
|
|
1288
1367
|
}
|
|
1289
1368
|
throw typeError("Update target must be an Identifier or MemberExpression.", argument);
|
|
1290
1369
|
}
|
|
@@ -1328,7 +1407,7 @@ class Frame {
|
|
|
1328
1407
|
return yield* self.createToolCallPromise(callable.path, args);
|
|
1329
1408
|
}
|
|
1330
1409
|
if (callable instanceof Fn)
|
|
1331
|
-
return yield* self.invokeFunction(callable, args, node);
|
|
1410
|
+
return yield* self.invokeFunction(callable, thisValue, args, node);
|
|
1332
1411
|
if (callable instanceof Native) {
|
|
1333
1412
|
return yield* self.native(() => callable.call(thisValue, args), node);
|
|
1334
1413
|
}
|
|
@@ -1364,15 +1443,26 @@ class Frame {
|
|
|
1364
1443
|
});
|
|
1365
1444
|
}
|
|
1366
1445
|
// A callback invoked by a built-in runs below the call that invoked the built-in, so the deeper of the two counts.
|
|
1367
|
-
invokeFunction(fn, args, node) {
|
|
1446
|
+
invokeFunction(fn, thisValue, args, node) {
|
|
1368
1447
|
const self = this;
|
|
1369
1448
|
return Effect.flatMap(CallSite, (site) => {
|
|
1370
1449
|
const depth = Math.max(self.depth, site.depth) + 1;
|
|
1371
1450
|
if (depth > MAX_CALL_DEPTH)
|
|
1372
1451
|
throw rangeError("Maximum call stack size exceeded", node);
|
|
1373
1452
|
const invocation = new Frame(this.ctx, new ScopeStack([...fn.capturedScopes, new Map()]), depth);
|
|
1374
|
-
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
|
|
1375
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.
|
|
1376
1466
|
for (const parameter of fn.parameters) {
|
|
1377
1467
|
for (const name of collectPatternNames(parameter)) {
|
|
1378
1468
|
paramScope.set(name, { mutable: true, value: undefined, initialized: false });
|
|
@@ -1624,13 +1714,13 @@ class Frame {
|
|
|
1624
1714
|
const keyNode = property.key;
|
|
1625
1715
|
let key;
|
|
1626
1716
|
if (property.computed) {
|
|
1627
|
-
key = self.toPropertyKey(yield* self.evaluateExpression(keyNode));
|
|
1717
|
+
key = yield* self.toPropertyKey(yield* self.evaluateExpression(keyNode), keyNode);
|
|
1628
1718
|
}
|
|
1629
1719
|
else if (keyNode.type === "Identifier") {
|
|
1630
1720
|
key = keyNode.name;
|
|
1631
1721
|
}
|
|
1632
1722
|
else if (keyNode.type === "Literal") {
|
|
1633
|
-
key =
|
|
1723
|
+
key = propertyKey(literal(keyNode));
|
|
1634
1724
|
}
|
|
1635
1725
|
else {
|
|
1636
1726
|
throw typeError("Unsupported object property key shape.", keyNode);
|
|
@@ -1690,7 +1780,7 @@ class Frame {
|
|
|
1690
1780
|
checkStringLength(output.length);
|
|
1691
1781
|
if (index < expressions.length) {
|
|
1692
1782
|
const raw = yield* self.evaluateExpression(expressions[index]);
|
|
1693
|
-
output += coerceToString(raw);
|
|
1783
|
+
output += coerceToString(yield* self.toPrimitive(raw, "string", expressions[index]));
|
|
1694
1784
|
checkStringLength(output.length);
|
|
1695
1785
|
}
|
|
1696
1786
|
}
|
|
@@ -1721,12 +1811,6 @@ class Frame {
|
|
|
1721
1811
|
evaluateConditionalExpression(node) {
|
|
1722
1812
|
return Effect.flatMap(this.evaluateExpression(node.test), (test) => this.evaluateExpression(test ? node.consequent : node.alternate));
|
|
1723
1813
|
}
|
|
1724
|
-
applyCompoundAssignment(operator, current, incoming, node) {
|
|
1725
|
-
if (!compoundOperators.has(operator)) {
|
|
1726
|
-
throw typeError(`Unsupported assignment operator '${operator}'.`, node);
|
|
1727
|
-
}
|
|
1728
|
-
return this.applyBinaryOperator(operator.slice(0, -1), current, incoming, node);
|
|
1729
|
-
}
|
|
1730
1814
|
getMemberReference(node) {
|
|
1731
1815
|
const objectNode = node.object;
|
|
1732
1816
|
const propertyNode = node.property;
|
|
@@ -1741,36 +1825,52 @@ class Frame {
|
|
|
1741
1825
|
return OptionalShortCircuit;
|
|
1742
1826
|
if ((objectValue === null || objectValue === undefined) && node.optional)
|
|
1743
1827
|
return OptionalShortCircuit;
|
|
1744
|
-
const
|
|
1745
|
-
?
|
|
1746
|
-
: propertyNode
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
if (objectValue instanceof ToolReference) {
|
|
1750
|
-
if (typeof key !== "string") {
|
|
1751
|
-
throw typeError("Tool paths must use string property names.", propertyNode);
|
|
1752
|
-
}
|
|
1753
|
-
return new ToolReference([...objectValue.path, key]);
|
|
1754
|
-
}
|
|
1755
|
-
if (objectValue instanceof Obj)
|
|
1756
|
-
return { target: objectValue, key, receiver: objectValue };
|
|
1757
|
-
// Strings own length and indexes; every other primitive property reads through the wrapper prototype.
|
|
1758
|
-
if (typeof objectValue === "string") {
|
|
1759
|
-
if (key === "length")
|
|
1760
|
-
return { value: objectValue.length };
|
|
1761
|
-
const index = typeof key === "symbol" ? undefined : parseArrayIndex(key);
|
|
1762
|
-
if (index !== undefined)
|
|
1763
|
-
return { value: objectValue[index] };
|
|
1764
|
-
}
|
|
1765
|
-
const proto = primitivePrototype(self.ctx.builtins, objectValue);
|
|
1766
|
-
if (proto !== undefined)
|
|
1767
|
-
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.
|
|
1768
1833
|
if (objectValue === null || objectValue === undefined) {
|
|
1769
|
-
throw typeError(`Cannot read properties of ${objectValue} (reading '${
|
|
1834
|
+
throw typeError(`Cannot read properties of ${objectValue} (reading '${coerceToString(keyValue)}').`, objectNode);
|
|
1770
1835
|
}
|
|
1771
|
-
|
|
1836
|
+
const key = yield* self.toPropertyKey(keyValue, propertyNode);
|
|
1837
|
+
return self.resolveProperty(objectValue, key, objectNode, propertyNode);
|
|
1772
1838
|
});
|
|
1773
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
|
+
}
|
|
1774
1874
|
readReference(reference, node) {
|
|
1775
1875
|
// Reject unknown promise properties so a missing await cannot hide.
|
|
1776
1876
|
if (reference.target instanceof PromiseObj && !has(reference.target, reference.key)) {
|
|
@@ -1851,10 +1951,11 @@ class Frame {
|
|
|
1851
1951
|
return;
|
|
1852
1952
|
throw typeError(`Cannot assign to read only property '${String(key)}'.`, node);
|
|
1853
1953
|
}
|
|
1854
|
-
// ToPropertyKey:
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
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);
|
|
1859
1960
|
}
|
|
1860
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;
|
|
@@ -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,
|
|
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, 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) => ({
|