@opencode-ai/codemode 0.0.0-dev-17471

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.
Files changed (72) hide show
  1. package/README.md +168 -0
  2. package/dist/codemode.d.ts +148 -0
  3. package/dist/codemode.js +70 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.js +5 -0
  6. package/dist/interpreter/errors.d.ts +9 -0
  7. package/dist/interpreter/errors.js +91 -0
  8. package/dist/interpreter/execute.d.ts +4 -0
  9. package/dist/interpreter/execute.js +178 -0
  10. package/dist/interpreter/iterator.d.ts +13 -0
  11. package/dist/interpreter/iterator.js +4 -0
  12. package/dist/interpreter/methods.d.ts +17 -0
  13. package/dist/interpreter/methods.js +1026 -0
  14. package/dist/interpreter/model.d.ts +151 -0
  15. package/dist/interpreter/model.js +186 -0
  16. package/dist/interpreter/promises.d.ts +29 -0
  17. package/dist/interpreter/promises.js +253 -0
  18. package/dist/interpreter/references.d.ts +6 -0
  19. package/dist/interpreter/references.js +114 -0
  20. package/dist/interpreter/runtime.d.ts +98 -0
  21. package/dist/interpreter/runtime.js +2351 -0
  22. package/dist/interpreter/scope.d.ts +15 -0
  23. package/dist/interpreter/scope.js +79 -0
  24. package/dist/interpreter/transpile.node.d.ts +5 -0
  25. package/dist/interpreter/transpile.node.js +19 -0
  26. package/dist/interpreter/transpile.workerd.d.ts +5 -0
  27. package/dist/interpreter/transpile.workerd.js +6 -0
  28. package/dist/openapi/index.d.ts +7 -0
  29. package/dist/openapi/index.js +101 -0
  30. package/dist/openapi/runtime.d.ts +4 -0
  31. package/dist/openapi/runtime.js +283 -0
  32. package/dist/openapi/spec.d.ts +20 -0
  33. package/dist/openapi/spec.js +588 -0
  34. package/dist/openapi/types.d.ts +122 -0
  35. package/dist/openapi/types.js +2 -0
  36. package/dist/stdlib/collections.d.ts +4 -0
  37. package/dist/stdlib/collections.js +57 -0
  38. package/dist/stdlib/console.d.ts +2 -0
  39. package/dist/stdlib/console.js +126 -0
  40. package/dist/stdlib/date.d.ts +7 -0
  41. package/dist/stdlib/date.js +186 -0
  42. package/dist/stdlib/json.d.ts +6 -0
  43. package/dist/stdlib/json.js +124 -0
  44. package/dist/stdlib/math.d.ts +12 -0
  45. package/dist/stdlib/math.js +157 -0
  46. package/dist/stdlib/number.d.ts +6 -0
  47. package/dist/stdlib/number.js +76 -0
  48. package/dist/stdlib/object.d.ts +7 -0
  49. package/dist/stdlib/object.js +100 -0
  50. package/dist/stdlib/promise.d.ts +2 -0
  51. package/dist/stdlib/promise.js +1 -0
  52. package/dist/stdlib/regexp.d.ts +11 -0
  53. package/dist/stdlib/regexp.js +106 -0
  54. package/dist/stdlib/string.d.ts +4 -0
  55. package/dist/stdlib/string.js +48 -0
  56. package/dist/stdlib/url.d.ts +12 -0
  57. package/dist/stdlib/url.js +84 -0
  58. package/dist/stdlib/value.d.ts +12 -0
  59. package/dist/stdlib/value.js +120 -0
  60. package/dist/tool-error.d.ts +11 -0
  61. package/dist/tool-error.js +9 -0
  62. package/dist/tool-runtime.d.ts +68 -0
  63. package/dist/tool-runtime.js +390 -0
  64. package/dist/tool-schema.d.ts +15 -0
  65. package/dist/tool-schema.js +213 -0
  66. package/dist/tool.d.ts +55 -0
  67. package/dist/tool.js +21 -0
  68. package/dist/tools.d.ts +4 -0
  69. package/dist/tools.js +1 -0
  70. package/dist/values.d.ts +31 -0
  71. package/dist/values.js +50 -0
  72. package/package.json +46 -0
@@ -0,0 +1,1026 @@
1
+ import { Effect } from "effect";
2
+ import { CodeModeFunction, CodeModeGenerator, CoercionFunction, ErrorConstructorReference, GlobalMethodReference, GlobalNamespace, IntrinsicReference, InterpreterRuntimeError, JsonMethodReference, PromiseCapabilityFunction, PromiseNamespace, UriFunction, } from "./model.js";
3
+ import { containsOpaqueReference, isRuntimeReference, rejectCircularInsertion, typeofValue } from "./references.js";
4
+ import { isBlockedMember } from "../tool-runtime.js";
5
+ import { CodeModeDate, CodeModeMap, CodeModePromise, CodeModeRegExp, CodeModeSet, CodeModeURL, CodeModeURLSearchParams, isCodeModeValue, } from "../values.js";
6
+ import { dateSetterArgumentCount, invokeDateMethod, invokeDateStatic } from "../stdlib/date.js";
7
+ import { invokeMathMethod } from "../stdlib/math.js";
8
+ import { invokeNumberMethod, invokeNumberStatic } from "../stdlib/number.js";
9
+ import { invokeObjectMethod } from "../stdlib/object.js";
10
+ import { invokeRegExpMethod, invokeRegExpStatic, matchToValue, toHostRegex } from "../stdlib/regexp.js";
11
+ import { invokeStringStatic } from "../stdlib/string.js";
12
+ import { invokeURLMethod, invokeURLStatic, uriArgument } from "../stdlib/url.js";
13
+ import { boundedData, coerceToNumber, coerceToString, errorBrandName } from "../stdlib/value.js";
14
+ import { preserveConsumerError } from "./iterator.js";
15
+ export const isSupportedCallback = (value) => value instanceof CodeModeFunction ||
16
+ value instanceof CoercionFunction ||
17
+ value instanceof UriFunction ||
18
+ value instanceof PromiseCapabilityFunction ||
19
+ value instanceof GlobalMethodReference ||
20
+ value instanceof JsonMethodReference ||
21
+ value instanceof IntrinsicReference ||
22
+ value instanceof ErrorConstructorReference ||
23
+ // Callable namespaces dispatch like JS: Array/Object/Date/RegExp construct,
24
+ // new-requiring constructors throw a TypeError. Math/JSON/console stay non-callable.
25
+ (value instanceof GlobalNamespace && typeofValue(value) === "function") ||
26
+ value instanceof PromiseNamespace;
27
+ export const invokeIntrinsic = (runner, ref, args, node) => {
28
+ if (typeof ref.receiver === "string") {
29
+ if (ref.name === "replace" || ref.name === "replaceAll") {
30
+ if (isSupportedCallback(args[1]))
31
+ return invokeStringReplacer(runner, ref.receiver, ref.name, args, node);
32
+ if (typeofValue(args[1]) === "function") {
33
+ throw new InterpreterRuntimeError(`String.${ref.name} cannot use this callable as a replacer; wrap it in an arrow function, e.g. (match) => tools.ns.tool(match).`, node);
34
+ }
35
+ }
36
+ return Effect.succeed(invokeStringMethod(ref.receiver, ref.name, args, node));
37
+ }
38
+ if (typeof ref.receiver === "number") {
39
+ return Effect.succeed(invokeNumberMethod(ref.receiver, ref.name, args, node));
40
+ }
41
+ if (Array.isArray(ref.receiver)) {
42
+ return invokeArrayMethod(runner, ref.receiver, ref.name, args, node);
43
+ }
44
+ if (ref.receiver instanceof CodeModeDate) {
45
+ const target = ref.receiver;
46
+ const argumentCount = dateSetterArgumentCount(ref.name);
47
+ if (argumentCount === undefined)
48
+ return Effect.succeed(invokeDateMethod(target, ref.name, [], node));
49
+ // Native setters read the current time before argument coercion, whose callbacks may mutate the Date.
50
+ const initialTime = target.time;
51
+ return Effect.map(Effect.forEach(args.slice(0, argumentCount), (arg) => coerceNumericArgument(runner, arg, node), {
52
+ concurrency: 1,
53
+ }), (values) => invokeDateMethod(target, ref.name, values, node, initialTime));
54
+ }
55
+ if (ref.receiver instanceof CodeModeRegExp) {
56
+ return Effect.succeed(invokeRegExpMethod(ref.receiver, ref.name, args, node));
57
+ }
58
+ if (ref.receiver instanceof CodeModeMap) {
59
+ return invokeMapMethod(runner, ref.receiver, ref.name, args, node);
60
+ }
61
+ if (ref.receiver instanceof CodeModeSet) {
62
+ return invokeSetMethod(runner, ref.receiver, ref.name, args, node);
63
+ }
64
+ if (ref.receiver instanceof CodeModeURL) {
65
+ return Effect.succeed(invokeURLMethod(ref.receiver, ref.name, node));
66
+ }
67
+ if (ref.receiver instanceof CodeModeURLSearchParams) {
68
+ return invokeURLSearchParamsMethod(runner, ref.receiver, ref.name, args, node);
69
+ }
70
+ throw new InterpreterRuntimeError(`Method '${ref.name}' is not available.`, node);
71
+ };
72
+ const coerceNumericArgument = (runner, value, node) => {
73
+ if (value === null || typeof value !== "object" || Array.isArray(value) || isCodeModeValue(value)) {
74
+ return Effect.succeed(coerceToNumber(value));
75
+ }
76
+ const object = value;
77
+ return Effect.gen(function* () {
78
+ if (Object.hasOwn(object, "valueOf") && typeofValue(object.valueOf) === "function") {
79
+ const result = yield* runner.invokeCallable(object.valueOf, [], node);
80
+ if (result === null || (typeof result !== "object" && typeof result !== "function")) {
81
+ return coerceToNumber(result);
82
+ }
83
+ }
84
+ if (!Object.hasOwn(object, "toString"))
85
+ return coerceToNumber(value);
86
+ if (typeofValue(object.toString) === "function") {
87
+ const result = yield* runner.invokeCallable(object.toString, [], node);
88
+ if (result === null || (typeof result !== "object" && typeof result !== "function")) {
89
+ return coerceToNumber(result);
90
+ }
91
+ }
92
+ throw new InterpreterRuntimeError("Cannot convert object to primitive value.", node).as("TypeError");
93
+ });
94
+ };
95
+ export const invokeGlobalMethod = (ref, args, node) => {
96
+ if (ref.namespace === "console")
97
+ throw new InterpreterRuntimeError(`console.${ref.name} is not available.`, node);
98
+ if (ref.namespace === "Object")
99
+ return invokeObjectMethod(ref.name, args, node);
100
+ if (ref.namespace === "Math")
101
+ return invokeMathMethod(ref.name, args, node);
102
+ if (ref.namespace === "Array")
103
+ return invokeArrayStatic(ref.name, args, node);
104
+ if (ref.namespace === "Number")
105
+ return invokeNumberStatic(ref.name, args, node);
106
+ if (ref.namespace === "String")
107
+ return invokeStringStatic(ref.name, args, node);
108
+ if (ref.namespace === "URL")
109
+ return invokeURLStatic(ref.name, args, node);
110
+ if (ref.namespace === "Date")
111
+ return invokeDateStatic(ref.name, args, node);
112
+ if (ref.namespace === "RegExp")
113
+ return invokeRegExpStatic(ref.name, args, node);
114
+ if (ref.namespace === "Map" || ref.namespace === "Set" || ref.namespace === "URLSearchParams") {
115
+ throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available.`, node);
116
+ }
117
+ throw new InterpreterRuntimeError(`${ref.namespace}.${ref.name} is not available.`, node);
118
+ };
119
+ const requireDataArgument = (name, index, arg, node) => {
120
+ if (containsOpaqueReference(arg)) {
121
+ throw new InterpreterRuntimeError(`String.${name} expects argument ${index + 1} to be a data value.`, node, "InvalidDataValue");
122
+ }
123
+ return arg;
124
+ };
125
+ const invokeStringMethod = (value, name, args, node) => {
126
+ // Coerce arguments like native JS; opaque runtime references still reject.
127
+ const str = (index) => coerceToString(requireDataArgument(name, index, args[index], node));
128
+ const num = (index) => coerceToNumber(requireDataArgument(name, index, args[index], node));
129
+ const optNum = (index) => (args[index] === undefined ? undefined : num(index));
130
+ const optStr = (index) => (args[index] === undefined ? undefined : str(index));
131
+ const rejectRegex = () => {
132
+ if (args[0] instanceof CodeModeRegExp) {
133
+ throw new InterpreterRuntimeError(`String.${name} cannot take a regular expression; use regex.test(string) or String.search instead.`, node).as("TypeError");
134
+ }
135
+ };
136
+ let result;
137
+ switch (name) {
138
+ case "toLowerCase":
139
+ result = value.toLowerCase();
140
+ break;
141
+ case "toUpperCase":
142
+ result = value.toUpperCase();
143
+ break;
144
+ case "trim":
145
+ result = value.trim();
146
+ break;
147
+ case "trimStart":
148
+ result = value.trimStart();
149
+ break;
150
+ case "trimEnd":
151
+ result = value.trimEnd();
152
+ break;
153
+ // Locale/options are deliberately unsupported; comparison uses the host default locale.
154
+ case "localeCompare":
155
+ result = value.localeCompare(str(0));
156
+ break;
157
+ case "normalize": {
158
+ const form = optStr(0);
159
+ try {
160
+ result = value.normalize(form);
161
+ }
162
+ catch {
163
+ throw new InterpreterRuntimeError(`String.normalize expects the form "NFC", "NFD", "NFKC", or "NFKD" (got ${JSON.stringify(form)}).`, node).as("RangeError");
164
+ }
165
+ break;
166
+ }
167
+ case "split": {
168
+ // Native: an undefined separator returns the whole string, not a split on "undefined",
169
+ // unless the limit truncates to zero.
170
+ if (args[0] === undefined) {
171
+ const requestedLimit = optNum(1);
172
+ result = requestedLimit !== undefined && requestedLimit >>> 0 === 0 ? [] : [value];
173
+ break;
174
+ }
175
+ if (args[0] instanceof CodeModeRegExp) {
176
+ result = value.split(args[0].regex, optNum(1));
177
+ break;
178
+ }
179
+ const requestedLimit = optNum(1);
180
+ result = value.split(str(0), requestedLimit === undefined ? undefined : requestedLimit >>> 0);
181
+ break;
182
+ }
183
+ case "slice":
184
+ result = value.slice(optNum(0), optNum(1));
185
+ break;
186
+ case "includes":
187
+ rejectRegex();
188
+ result = value.includes(str(0), optNum(1));
189
+ break;
190
+ case "startsWith":
191
+ rejectRegex();
192
+ result = value.startsWith(str(0), optNum(1));
193
+ break;
194
+ case "endsWith":
195
+ rejectRegex();
196
+ result = value.endsWith(str(0), optNum(1));
197
+ break;
198
+ case "indexOf":
199
+ result = value.indexOf(str(0), optNum(1));
200
+ break;
201
+ case "lastIndexOf":
202
+ result = value.lastIndexOf(str(0), optNum(1));
203
+ break;
204
+ case "replace":
205
+ case "replaceAll": {
206
+ if (args[0] instanceof CodeModeRegExp) {
207
+ const pattern = args[0].regex;
208
+ const replacement = str(1);
209
+ if (name === "replaceAll" && !pattern.global) {
210
+ throw new InterpreterRuntimeError(`String.replaceAll requires a regular expression with the global (g) flag: write /${pattern.source}/${pattern.flags}g, or use String.replace to replace only the first match.`, node);
211
+ }
212
+ result = name === "replace" ? value.replace(pattern, replacement) : value.replaceAll(pattern, replacement);
213
+ break;
214
+ }
215
+ if (name === "replace") {
216
+ result = value.replace(str(0), str(1));
217
+ break;
218
+ }
219
+ result = value.replaceAll(str(0), str(1));
220
+ break;
221
+ }
222
+ case "match": {
223
+ const pattern = toHostRegex(args[0], name, node);
224
+ const matched = value.match(pattern);
225
+ if (matched === null)
226
+ return null;
227
+ // Preserve the own `index` and `groups` properties on non-global matches.
228
+ if (pattern.global)
229
+ return boundedData(matched, "String.match result");
230
+ return matchToValue(matched);
231
+ }
232
+ case "matchAll": {
233
+ const pattern = toHostRegex(args[0], name, node, "g");
234
+ if (!pattern.global) {
235
+ throw new InterpreterRuntimeError(`String.matchAll requires a regular expression with the global (g) flag: write /${pattern.source}/${pattern.flags}g, or use String.match for a single match.`, node);
236
+ }
237
+ return Array.from(value.matchAll(pattern), matchToValue);
238
+ }
239
+ case "search": {
240
+ result = value.search(toHostRegex(args[0], name, node));
241
+ break;
242
+ }
243
+ case "repeat": {
244
+ const count = num(0);
245
+ if (!Number.isFinite(count) || count < 0)
246
+ throw new InterpreterRuntimeError("String.repeat expects a finite non-negative count.", node).as("RangeError");
247
+ result = value.repeat(count);
248
+ break;
249
+ }
250
+ case "padStart":
251
+ result = value.padStart(num(0), optStr(1));
252
+ break;
253
+ case "padEnd":
254
+ result = value.padEnd(num(0), optStr(1));
255
+ break;
256
+ case "charAt":
257
+ result = value.charAt(optNum(0) ?? 0);
258
+ break;
259
+ case "at":
260
+ result = value.at(optNum(0) ?? 0);
261
+ break;
262
+ case "substring":
263
+ result = value.substring(optNum(0) ?? 0, optNum(1));
264
+ break;
265
+ case "charCodeAt":
266
+ result = value.charCodeAt(optNum(0) ?? 0);
267
+ break;
268
+ case "codePointAt":
269
+ result = value.codePointAt(optNum(0) ?? 0);
270
+ break;
271
+ case "toString":
272
+ result = value;
273
+ break;
274
+ case "concat": {
275
+ result = value.concat(...args.map((_, index) => str(index)));
276
+ break;
277
+ }
278
+ default:
279
+ throw new InterpreterRuntimeError(`String method '${name}' is not available.`, node);
280
+ }
281
+ return boundedData(result, `String.${name} result`);
282
+ };
283
+ export const arrayStatics = new Set(["isArray", "of", "from"]);
284
+ const invokeArrayStatic = (name, args, node) => {
285
+ switch (name) {
286
+ case "isArray":
287
+ return Array.isArray(args[0]);
288
+ case "of":
289
+ return [...args];
290
+ default:
291
+ throw new InterpreterRuntimeError(`Array.${name} is not available.`, node);
292
+ }
293
+ };
294
+ const arrayLikeSource = (source, node) => {
295
+ if (source instanceof CodeModePromise) {
296
+ throw new InterpreterRuntimeError("Array.from received an un-awaited Promise; await it before creating the array.", node, "InvalidDataValue");
297
+ }
298
+ if (source !== null &&
299
+ typeof source === "object" &&
300
+ (Object.getPrototypeOf(source) === Object.prototype || Object.getPrototypeOf(source) === null) &&
301
+ typeof source.length === "number") {
302
+ const length = source.length;
303
+ const normalized = Number.isNaN(length) || length <= 0 ? 0 : Math.trunc(length);
304
+ if (normalized > 4_294_967_295)
305
+ throw new RangeError("Invalid array length");
306
+ return { length: normalized, source };
307
+ }
308
+ throw new InterpreterRuntimeError("Array.from expects an array, string, Map, Set, or array-like value.", node, "InvalidDataValue");
309
+ };
310
+ export const invokeArrayFrom = (runner, args, node) => {
311
+ const source = args[0];
312
+ const apply = args.length < 2 || args[1] === undefined ? undefined : applyCollectionCallback(runner, args[1], "Array.from", node);
313
+ return Effect.gen(function* () {
314
+ const cursor = yield* runner.syncIterator(source, node);
315
+ if (cursor === undefined) {
316
+ if (source instanceof CodeModeGenerator) {
317
+ throw new InterpreterRuntimeError("Array.from expects a synchronous iterable or array-like value.", node).as("TypeError");
318
+ }
319
+ const arrayLike = arrayLikeSource(source, node);
320
+ const values = [];
321
+ for (let index = 0; index < arrayLike.length; index += 1) {
322
+ const item = Reflect.get(arrayLike.source, index);
323
+ values.push(apply === undefined ? item : yield* apply([item, index]));
324
+ }
325
+ return values;
326
+ }
327
+ const values = [];
328
+ let index = 0;
329
+ while (true) {
330
+ const step = yield* cursor.next;
331
+ if (step.done)
332
+ return values;
333
+ values.push(apply === undefined ? step.value : yield* preserveConsumerError(cursor, apply([step.value, index])));
334
+ index += 1;
335
+ }
336
+ });
337
+ };
338
+ export const invokeGroupBy = (runner, namespace, args, node) => {
339
+ const source = args[0];
340
+ if (source === null || source === undefined) {
341
+ throw new InterpreterRuntimeError(`${namespace}.groupBy expects an iterable collection.`, node).as("TypeError");
342
+ }
343
+ const apply = applyCollectionCallback(runner, args[1], `${namespace}.groupBy`, node);
344
+ return Effect.gen(function* () {
345
+ const cursor = yield* runner.syncIterator(source, node);
346
+ if (cursor === undefined) {
347
+ throw new InterpreterRuntimeError(`${namespace}.groupBy expects an iterable collection.`, node).as("TypeError");
348
+ }
349
+ if (namespace === "Map") {
350
+ const result = new CodeModeMap();
351
+ let index = 0;
352
+ while (true) {
353
+ const step = yield* cursor.next;
354
+ if (step.done)
355
+ return result;
356
+ const item = step.value;
357
+ const key = yield* preserveConsumerError(cursor, apply([item, index]));
358
+ const group = result.map.get(key);
359
+ if (group === undefined)
360
+ result.map.set(key, [item]);
361
+ else
362
+ group.push(item);
363
+ index += 1;
364
+ }
365
+ }
366
+ const result = Object.create(null);
367
+ let index = 0;
368
+ while (true) {
369
+ const step = yield* cursor.next;
370
+ if (step.done)
371
+ return result;
372
+ const item = step.value;
373
+ const key = yield* preserveConsumerError(cursor, Effect.flatMap(apply([item, index]), (value) => coerceGroupByPropertyKey(runner, value, node)));
374
+ if (isBlockedMember(key)) {
375
+ return yield* preserveConsumerError(cursor, Effect.fail(new InterpreterRuntimeError(`Property '${key}' is not available.`, node)));
376
+ }
377
+ const group = result[key];
378
+ if (group === undefined)
379
+ result[key] = [item];
380
+ else
381
+ group.push(item);
382
+ index += 1;
383
+ }
384
+ });
385
+ };
386
+ const coerceGroupByPropertyKey = (runner, value, node) => {
387
+ if (value === null || typeof value !== "object" || Array.isArray(value) || isCodeModeValue(value)) {
388
+ return Effect.succeed(coerceToString(value));
389
+ }
390
+ if (value instanceof CodeModePromise)
391
+ return Effect.succeed("[object Promise]");
392
+ if (isRuntimeReference(value)) {
393
+ throw new InterpreterRuntimeError("Object.groupBy callback must return a data value.", node, "InvalidDataValue");
394
+ }
395
+ const object = value;
396
+ if (!Object.hasOwn(object, "toString"))
397
+ return Effect.succeed(coerceToString(value));
398
+ return Effect.gen(function* () {
399
+ if (typeofValue(object.toString) === "function") {
400
+ const result = yield* runner.invokeCallable(object.toString, [], node);
401
+ if (result === null || (typeof result !== "object" && typeof result !== "function")) {
402
+ return coerceToString(result);
403
+ }
404
+ }
405
+ if (Object.hasOwn(object, "valueOf") && typeofValue(object.valueOf) === "function") {
406
+ const result = yield* runner.invokeCallable(object.valueOf, [], node);
407
+ if (result === null || (typeof result !== "object" && typeof result !== "function")) {
408
+ return coerceToString(result);
409
+ }
410
+ }
411
+ throw new InterpreterRuntimeError("Cannot convert object to primitive value.", node).as("TypeError");
412
+ });
413
+ };
414
+ const invokeStringReplacer = (runner, value, name, args, node) => {
415
+ const apply = applyCollectionCallback(runner, args[1], `String.${name}`, node);
416
+ const matches = [];
417
+ const collect = (...callbackArgs) => {
418
+ const match = callbackArgs[0];
419
+ const groups = callbackArgs[callbackArgs.length - 1];
420
+ const hasGroups = groups !== null && typeof groups === "object";
421
+ const offset = callbackArgs[callbackArgs.length - (hasGroups ? 3 : 2)];
422
+ if (typeof match !== "string" || typeof offset !== "number") {
423
+ throw new InterpreterRuntimeError(`String.${name} produced an invalid replacement match.`, node);
424
+ }
425
+ if (hasGroups) {
426
+ const safeGroups = Object.create(null);
427
+ for (const [key, group] of Object.entries(groups)) {
428
+ if (!isBlockedMember(key))
429
+ safeGroups[key] = group;
430
+ }
431
+ callbackArgs[callbackArgs.length - 1] = safeGroups;
432
+ }
433
+ matches.push({ match, offset, args: callbackArgs });
434
+ return match;
435
+ };
436
+ const pattern = args[0];
437
+ if (pattern instanceof CodeModeRegExp) {
438
+ if (name === "replaceAll" && !pattern.regex.global) {
439
+ throw new InterpreterRuntimeError(`String.replaceAll requires a regular expression with the global (g) flag: write /${pattern.regex.source}/${pattern.regex.flags}g, or use String.replace to replace only the first match.`, node);
440
+ }
441
+ if (name === "replace")
442
+ value.replace(pattern.regex, collect);
443
+ else
444
+ value.replaceAll(pattern.regex, collect);
445
+ }
446
+ else {
447
+ const search = coerceToString(requireDataArgument(name, 0, pattern, node));
448
+ if (name === "replace")
449
+ value.replace(search, collect);
450
+ else
451
+ value.replaceAll(search, collect);
452
+ }
453
+ return Effect.gen(function* () {
454
+ const output = [];
455
+ let end = 0;
456
+ for (const match of matches) {
457
+ const replacement = yield* apply(match.args);
458
+ // Error values are branded plain objects; boundedData would strip the brand before coercion.
459
+ output.push(value.slice(end, match.offset), replacement instanceof CodeModePromise
460
+ ? "[object Promise]"
461
+ : errorBrandName(replacement)
462
+ ? coerceToString(replacement)
463
+ : coerceToString(boundedData(replacement, `String.${name} replacer result`)));
464
+ end = match.offset + match.match.length;
465
+ }
466
+ output.push(value.slice(end));
467
+ return boundedData(output.join(""), `String.${name} result`);
468
+ });
469
+ };
470
+ export const applyCollectionCallback = (runner, callback, name, node) => {
471
+ if (!isSupportedCallback(callback)) {
472
+ if (typeofValue(callback) === "function") {
473
+ throw new InterpreterRuntimeError(`${name} cannot use this callable as a callback; wrap it in an arrow function, e.g. (value) => tools.ns.tool(value).`, node);
474
+ }
475
+ throw new InterpreterRuntimeError(`${name} expects a function callback.`, node).as("TypeError");
476
+ }
477
+ return (callbackArgs) => runner.invokeCallable(callback, callbackArgs, node);
478
+ };
479
+ const invokeMapMethod = (runner, target, name, args, node) => {
480
+ switch (name) {
481
+ case "get":
482
+ return Effect.succeed(target.map.get(args[0]));
483
+ case "has":
484
+ return Effect.succeed(target.map.has(args[0]));
485
+ case "set":
486
+ return Effect.sync(() => {
487
+ target.map.set(args[0], args[1]);
488
+ return target;
489
+ });
490
+ case "delete":
491
+ return Effect.sync(() => target.map.delete(args[0]));
492
+ case "clear":
493
+ return Effect.sync(() => {
494
+ target.map.clear();
495
+ return undefined;
496
+ });
497
+ case "keys":
498
+ return Effect.sync(() => Array.from(target.map.keys()));
499
+ case "values":
500
+ return Effect.sync(() => Array.from(target.map.values()));
501
+ case "entries":
502
+ return Effect.sync(() => Array.from(target.map.entries(), ([key, item]) => [key, item]));
503
+ case "forEach": {
504
+ const apply = applyCollectionCallback(runner, args[0], "Map.forEach", node);
505
+ return Effect.gen(function* () {
506
+ for (const [key, item] of Array.from(target.map.entries()))
507
+ yield* apply([item, key, target]);
508
+ return undefined;
509
+ });
510
+ }
511
+ default:
512
+ throw new InterpreterRuntimeError(`Map method '${name}' is not available.`, node);
513
+ }
514
+ };
515
+ const invokeSetMethod = (runner, target, name, args, node) => {
516
+ switch (name) {
517
+ case "has":
518
+ return Effect.succeed(target.set.has(args[0]));
519
+ case "add":
520
+ return Effect.sync(() => {
521
+ target.set.add(args[0]);
522
+ return target;
523
+ });
524
+ case "delete":
525
+ return Effect.sync(() => target.set.delete(args[0]));
526
+ case "clear":
527
+ return Effect.sync(() => {
528
+ target.set.clear();
529
+ return undefined;
530
+ });
531
+ case "keys":
532
+ case "values":
533
+ return Effect.sync(() => Array.from(target.set.values()));
534
+ case "entries":
535
+ return Effect.sync(() => Array.from(target.set.values(), (item) => [item, item]));
536
+ case "forEach": {
537
+ const apply = applyCollectionCallback(runner, args[0], "Set.forEach", node);
538
+ return Effect.gen(function* () {
539
+ for (const item of Array.from(target.set.values()))
540
+ yield* apply([item, item, target]);
541
+ return undefined;
542
+ });
543
+ }
544
+ case "union":
545
+ case "intersection":
546
+ case "difference":
547
+ case "symmetricDifference":
548
+ case "isSubsetOf":
549
+ case "isSupersetOf":
550
+ case "isDisjointFrom":
551
+ return invokeSetOperation(runner, target, name, args[0], node);
552
+ default:
553
+ throw new InterpreterRuntimeError(`Set method '${name}' is not available.`, node);
554
+ }
555
+ };
556
+ const invokeSetOperation = (runner, target, name, source, node) => Effect.gen(function* () {
557
+ const other = yield* loadSetRecord(runner, source, name, node);
558
+ if (name === "union") {
559
+ const result = copySet(target);
560
+ for (const item of yield* other.keys())
561
+ result.set.add(item);
562
+ return result;
563
+ }
564
+ if (name === "intersection") {
565
+ const result = new CodeModeSet();
566
+ if (target.set.size <= other.size) {
567
+ for (const item of target.set.values()) {
568
+ if (yield* other.has(item))
569
+ result.set.add(item);
570
+ }
571
+ return result;
572
+ }
573
+ for (const item of yield* other.keys()) {
574
+ if (target.set.has(item))
575
+ result.set.add(item);
576
+ }
577
+ return result;
578
+ }
579
+ if (name === "difference") {
580
+ const result = copySet(target);
581
+ if (target.set.size <= other.size) {
582
+ for (const item of result.set.values()) {
583
+ if (yield* other.has(item))
584
+ result.set.delete(item);
585
+ }
586
+ return result;
587
+ }
588
+ for (const item of yield* other.keys())
589
+ result.set.delete(item);
590
+ return result;
591
+ }
592
+ if (name === "symmetricDifference") {
593
+ const result = copySet(target);
594
+ for (const item of yield* other.keys()) {
595
+ if (target.set.has(item))
596
+ result.set.delete(item);
597
+ else
598
+ result.set.add(item);
599
+ }
600
+ return result;
601
+ }
602
+ if (name === "isSubsetOf") {
603
+ if (target.set.size > other.size)
604
+ return false;
605
+ for (const item of target.set.values()) {
606
+ if (!(yield* other.has(item)))
607
+ return false;
608
+ }
609
+ return true;
610
+ }
611
+ if (name === "isSupersetOf") {
612
+ if (target.set.size < other.size)
613
+ return false;
614
+ for (const item of yield* other.keys()) {
615
+ if (!target.set.has(item))
616
+ return false;
617
+ }
618
+ return true;
619
+ }
620
+ if (target.set.size <= other.size) {
621
+ for (const item of target.set.values()) {
622
+ if (yield* other.has(item))
623
+ return false;
624
+ }
625
+ return true;
626
+ }
627
+ for (const item of yield* other.keys()) {
628
+ if (target.set.has(item))
629
+ return false;
630
+ }
631
+ return true;
632
+ });
633
+ const copySet = (source) => {
634
+ const result = new CodeModeSet();
635
+ for (const item of source.set.values())
636
+ result.set.add(item);
637
+ return result;
638
+ };
639
+ const loadSetRecord = (runner, source, name, node) => {
640
+ if (source instanceof CodeModeSet) {
641
+ return Effect.succeed({
642
+ size: source.set.size,
643
+ has: (item) => Effect.succeed(source.set.has(item)),
644
+ keys: () => Effect.succeed(source.set.values()),
645
+ });
646
+ }
647
+ if (source instanceof CodeModeMap) {
648
+ return Effect.succeed({
649
+ size: source.map.size,
650
+ has: (item) => Effect.succeed(source.map.has(item)),
651
+ keys: () => Effect.succeed(source.map.keys()),
652
+ });
653
+ }
654
+ if (source === null || typeof source !== "object" || isCodeModeValue(source)) {
655
+ throw new InterpreterRuntimeError(`Set.${name} expects a Set-like object.`, node).as("TypeError");
656
+ }
657
+ const object = source;
658
+ return Effect.gen(function* () {
659
+ const size = yield* coerceNumericArgument(runner, object.size, node);
660
+ if (Number.isNaN(size)) {
661
+ throw new InterpreterRuntimeError(`Set.${name} received a Set-like object with an invalid size.`, node).as("TypeError");
662
+ }
663
+ if (!isSupportedCallback(object.has) || !isSupportedCallback(object.keys)) {
664
+ throw new InterpreterRuntimeError(`Set.${name} expects callable 'has' and 'keys' methods.`, node).as("TypeError");
665
+ }
666
+ const has = object.has;
667
+ const keys = object.keys;
668
+ return {
669
+ size: Math.max(Math.trunc(size), 0),
670
+ has: (item) => Effect.map(runner.invokeCallable(has, [item], node), Boolean),
671
+ keys: () => Effect.flatMap(runner.invokeCallable(keys, [], node), (result) => {
672
+ if (Array.isArray(result))
673
+ return Effect.succeed(result);
674
+ throw new InterpreterRuntimeError(`Set.${name} expected 'keys' to return an iterator.`, node).as("TypeError");
675
+ }),
676
+ };
677
+ });
678
+ };
679
+ const invokeURLSearchParamsMethod = (runner, target, name, args, node) => {
680
+ const arg = (index) => uriArgument(args[index], `URLSearchParams.${name} argument ${index + 1}`);
681
+ const requireArgs = (count) => {
682
+ if (args.length < count) {
683
+ throw new InterpreterRuntimeError(`URLSearchParams.${name} requires ${count} argument${count === 1 ? "" : "s"}.`, node).as("TypeError");
684
+ }
685
+ };
686
+ switch (name) {
687
+ case "append": {
688
+ requireArgs(2);
689
+ return Effect.sync(() => {
690
+ target.params.append(arg(0), arg(1));
691
+ return undefined;
692
+ });
693
+ }
694
+ case "delete": {
695
+ requireArgs(1);
696
+ return Effect.sync(() => {
697
+ if (args[1] !== undefined)
698
+ target.params.delete(arg(0), arg(1));
699
+ else
700
+ target.params.delete(arg(0));
701
+ return undefined;
702
+ });
703
+ }
704
+ case "get":
705
+ requireArgs(1);
706
+ return Effect.sync(() => target.params.get(arg(0)));
707
+ case "getAll":
708
+ requireArgs(1);
709
+ return Effect.sync(() => target.params.getAll(arg(0)));
710
+ case "has":
711
+ requireArgs(1);
712
+ return Effect.sync(() => (args[1] !== undefined ? target.params.has(arg(0), arg(1)) : target.params.has(arg(0))));
713
+ case "set": {
714
+ requireArgs(2);
715
+ return Effect.sync(() => {
716
+ target.params.set(arg(0), arg(1));
717
+ return undefined;
718
+ });
719
+ }
720
+ case "sort":
721
+ return Effect.sync(() => {
722
+ target.params.sort();
723
+ return undefined;
724
+ });
725
+ case "keys":
726
+ return Effect.sync(() => Array.from(target.params.keys()));
727
+ case "values":
728
+ return Effect.sync(() => Array.from(target.params.values()));
729
+ case "entries":
730
+ return Effect.sync(() => Array.from(target.params.entries(), ([key, value]) => [key, value]));
731
+ case "toString":
732
+ return Effect.sync(() => target.params.toString());
733
+ case "forEach": {
734
+ requireArgs(1);
735
+ const apply = applyCollectionCallback(runner, args[0], "URLSearchParams.forEach", node);
736
+ return Effect.gen(function* () {
737
+ for (const [key, value] of Array.from(target.params.entries()))
738
+ yield* apply([value, key, target]);
739
+ return undefined;
740
+ });
741
+ }
742
+ default:
743
+ throw new InterpreterRuntimeError(`URLSearchParams method '${name}' is not available.`, node);
744
+ }
745
+ };
746
+ const invokeArrayMethod = (runner, target, name, args, node) => {
747
+ const optNumber = (value, label) => {
748
+ if (value === undefined)
749
+ return undefined;
750
+ if (typeof value !== "number")
751
+ throw new InterpreterRuntimeError(`Array.${name} expects ${label} to be a number.`, node);
752
+ return value;
753
+ };
754
+ switch (name) {
755
+ case "join": {
756
+ if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
757
+ throw new InterpreterRuntimeError("Array.join expects zero arguments or one string separator.", node);
758
+ }
759
+ const input = boundedData(target, "Array.join input");
760
+ return Effect.succeed(input.map((item) => coerceToString(item ?? "")).join(args.length === 0 ? "," : args[0]));
761
+ }
762
+ case "includes":
763
+ if (args.length === 0 || args.length > 2)
764
+ throw new InterpreterRuntimeError("Array.includes expects a value and optional start index.", node);
765
+ return Effect.succeed(target.includes(args[0], optNumber(args[1], "start index")));
766
+ case "indexOf":
767
+ return Effect.succeed(target.indexOf(args[0], optNumber(args[1], "start index")));
768
+ case "lastIndexOf":
769
+ return Effect.succeed(args[1] === undefined
770
+ ? target.lastIndexOf(args[0])
771
+ : target.lastIndexOf(args[0], optNumber(args[1], "start index")));
772
+ case "at":
773
+ return Effect.succeed(target.at(optNumber(args[0], "index") ?? 0));
774
+ case "slice":
775
+ return Effect.succeed(target.slice(optNumber(args[0], "start"), optNumber(args[1], "end")));
776
+ case "concat":
777
+ return Effect.succeed(target.concat(...args));
778
+ case "flat":
779
+ return Effect.succeed(target.flat(optNumber(args[0], "depth") ?? 1));
780
+ case "reverse":
781
+ return Effect.succeed(target.reverse());
782
+ case "sort": {
783
+ const length = target.length;
784
+ const holeCount = Array.from({ length }, (_, index) => Object.hasOwn(target, index)).filter((own) => !own).length;
785
+ const itemCount = length - holeCount;
786
+ return Effect.map(sortArray(runner, target, args[0], "Array.sort", node), (sorted) => {
787
+ sorted.slice(0, itemCount).forEach((item, index) => {
788
+ target[index] = item;
789
+ });
790
+ Array.from({ length: holeCount }, (_, index) => itemCount + index).forEach((index) => {
791
+ Reflect.deleteProperty(target, index);
792
+ });
793
+ return target;
794
+ });
795
+ }
796
+ case "toSorted":
797
+ return sortArray(runner, target, args[0], "Array.toSorted", node);
798
+ case "toReversed":
799
+ return Effect.succeed([...target].reverse());
800
+ case "with": {
801
+ const index = optNumber(args[0], "index") ?? 0;
802
+ const resolved = index < 0 ? target.length + index : index;
803
+ if (resolved < 0 || resolved >= target.length) {
804
+ throw new InterpreterRuntimeError("Array.with index is out of range.", node);
805
+ }
806
+ const copied = [...target];
807
+ copied[resolved] = args[1];
808
+ return Effect.succeed(copied);
809
+ }
810
+ case "push": {
811
+ // Validate all insertions before mutating to avoid partial cyclic updates.
812
+ for (const item of args)
813
+ rejectCircularInsertion(target, item, "Array.push result", node);
814
+ target.push(...args);
815
+ return Effect.succeed(target.length);
816
+ }
817
+ case "unshift": {
818
+ for (const item of args)
819
+ rejectCircularInsertion(target, item, "Array.unshift result", node);
820
+ target.unshift(...args);
821
+ return Effect.succeed(target.length);
822
+ }
823
+ case "pop":
824
+ return Effect.succeed(target.pop());
825
+ case "shift":
826
+ return Effect.succeed(target.shift());
827
+ case "splice": {
828
+ if (args.length === 0)
829
+ return Effect.succeed(target.splice(0, 0));
830
+ const start = optNumber(args[0], "start") ?? 0;
831
+ if (args.length === 1)
832
+ return Effect.succeed(target.splice(start));
833
+ const deleteCount = optNumber(args[1], "delete count") ?? 0;
834
+ const inserted = args.slice(2);
835
+ for (const item of inserted)
836
+ rejectCircularInsertion(target, item, "Array.splice result", node);
837
+ return Effect.succeed(target.splice(start, deleteCount, ...inserted));
838
+ }
839
+ case "toSpliced": {
840
+ if (args.length === 0)
841
+ return Effect.succeed([...target]);
842
+ const start = optNumber(args[0], "start") ?? 0;
843
+ if (args.length === 1) {
844
+ const copied = [...target];
845
+ copied.splice(start);
846
+ return Effect.succeed(copied);
847
+ }
848
+ const deleteCount = optNumber(args[1], "delete count") ?? 0;
849
+ const copied = [...target];
850
+ copied.splice(start, deleteCount, ...args.slice(2));
851
+ return Effect.succeed(copied);
852
+ }
853
+ case "fill": {
854
+ rejectCircularInsertion(target, args[0], "Array.fill result", node);
855
+ return Effect.succeed(target.fill(args[0], optNumber(args[1], "start"), optNumber(args[2], "end")));
856
+ }
857
+ case "copyWithin":
858
+ return Effect.succeed(target.copyWithin(optNumber(args[0], "target index") ?? 0, optNumber(args[1], "start") ?? 0, optNumber(args[2], "end")));
859
+ case "keys":
860
+ return Effect.succeed(Array.from(target.keys()));
861
+ case "values":
862
+ return Effect.succeed([...target]);
863
+ case "entries":
864
+ return Effect.succeed(Array.from(target.entries(), ([index, item]) => [index, item]));
865
+ }
866
+ const apply = applyCollectionCallback(runner, args[0], `Array.${name}`, node);
867
+ return Effect.gen(function* () {
868
+ // Fix iteration length while reading existing elements live.
869
+ const length = target.length;
870
+ switch (name) {
871
+ case "map": {
872
+ const values = [];
873
+ values.length = length;
874
+ for (let index = 0; index < length; index += 1) {
875
+ if (!(index in target))
876
+ continue;
877
+ values[index] = yield* apply([target[index], index, target]);
878
+ }
879
+ return values;
880
+ }
881
+ case "flatMap": {
882
+ const values = [];
883
+ for (let index = 0; index < length; index += 1) {
884
+ if (!(index in target))
885
+ continue;
886
+ const mapped = yield* apply([target[index], index, target]);
887
+ if (Array.isArray(mapped))
888
+ values.push(...mapped);
889
+ else
890
+ values.push(mapped);
891
+ }
892
+ return values;
893
+ }
894
+ case "filter": {
895
+ const values = [];
896
+ for (let index = 0; index < length; index += 1) {
897
+ if (!(index in target))
898
+ continue;
899
+ const item = target[index];
900
+ if (yield* apply([item, index, target]))
901
+ values.push(item);
902
+ }
903
+ return values;
904
+ }
905
+ case "find":
906
+ for (let index = 0; index < length; index += 1) {
907
+ const item = target[index];
908
+ if (yield* apply([item, index, target]))
909
+ return item;
910
+ }
911
+ return undefined;
912
+ case "findIndex":
913
+ for (let index = 0; index < length; index += 1) {
914
+ if (yield* apply([target[index], index, target]))
915
+ return index;
916
+ }
917
+ return -1;
918
+ case "some":
919
+ for (let index = 0; index < length; index += 1) {
920
+ if (!(index in target))
921
+ continue;
922
+ if (yield* apply([target[index], index, target]))
923
+ return true;
924
+ }
925
+ return false;
926
+ case "every":
927
+ for (let index = 0; index < length; index += 1) {
928
+ if (!(index in target))
929
+ continue;
930
+ if (!(yield* apply([target[index], index, target])))
931
+ return false;
932
+ }
933
+ return true;
934
+ case "forEach":
935
+ for (let index = 0; index < length; index += 1) {
936
+ if (index in target)
937
+ yield* apply([target[index], index, target]);
938
+ }
939
+ return undefined;
940
+ case "reduce": {
941
+ let start = 0;
942
+ let accumulator = args[1];
943
+ if (args.length < 2) {
944
+ while (start < length && !(start in target))
945
+ start += 1;
946
+ if (start === length)
947
+ throw new InterpreterRuntimeError("Array.reduce of an empty array with no initial value.", node).as("TypeError");
948
+ accumulator = target[start];
949
+ start += 1;
950
+ }
951
+ for (let index = start; index < length; index += 1) {
952
+ if (!(index in target))
953
+ continue;
954
+ accumulator = yield* apply([accumulator, target[index], index, target]);
955
+ }
956
+ return accumulator;
957
+ }
958
+ case "reduceRight": {
959
+ let start = length - 1;
960
+ let accumulator = args[1];
961
+ if (args.length < 2) {
962
+ while (start >= 0 && !(start in target))
963
+ start -= 1;
964
+ if (start < 0)
965
+ throw new InterpreterRuntimeError("Array.reduceRight of an empty array with no initial value.", node).as("TypeError");
966
+ accumulator = target[start];
967
+ start -= 1;
968
+ }
969
+ for (let index = start; index >= 0; index -= 1) {
970
+ if (!(index in target))
971
+ continue;
972
+ accumulator = yield* apply([accumulator, target[index], index, target]);
973
+ }
974
+ return accumulator;
975
+ }
976
+ case "findLast":
977
+ for (let index = length - 1; index >= 0; index -= 1) {
978
+ const item = target[index];
979
+ if (yield* apply([item, index, target]))
980
+ return item;
981
+ }
982
+ return undefined;
983
+ case "findLastIndex":
984
+ for (let index = length - 1; index >= 0; index -= 1) {
985
+ if (yield* apply([target[index], index, target]))
986
+ return index;
987
+ }
988
+ return -1;
989
+ }
990
+ throw new InterpreterRuntimeError(`Array method '${name}' is not available.`, node);
991
+ });
992
+ };
993
+ const sortArray = (runner, target, comparator, name, node) => {
994
+ if (comparator === undefined) {
995
+ return Effect.sync(() => [...target].sort((a, b) => {
996
+ const left = coerceToString(a);
997
+ const right = coerceToString(b);
998
+ return left < right ? -1 : left > right ? 1 : 0;
999
+ }));
1000
+ }
1001
+ const apply = applyCollectionCallback(runner, comparator, name, node);
1002
+ const mergeSort = (items) => {
1003
+ if (items.length <= 1)
1004
+ return Effect.succeed(items);
1005
+ const midpoint = Math.floor(items.length / 2);
1006
+ return Effect.gen(function* () {
1007
+ const left = yield* mergeSort(items.slice(0, midpoint));
1008
+ const right = yield* mergeSort(items.slice(midpoint));
1009
+ const merged = [];
1010
+ let leftIndex = 0;
1011
+ let rightIndex = 0;
1012
+ while (leftIndex < left.length && rightIndex < right.length) {
1013
+ // Treat a NaN comparator result as equal to preserve stable ordering.
1014
+ const order = coerceToNumber(yield* apply([left[leftIndex], right[rightIndex]]));
1015
+ if (Number.isNaN(order) || order <= 0)
1016
+ merged.push(left[leftIndex++]);
1017
+ else
1018
+ merged.push(right[rightIndex++]);
1019
+ }
1020
+ return [...merged, ...left.slice(leftIndex), ...right.slice(rightIndex)];
1021
+ });
1022
+ };
1023
+ const defined = target.filter((item) => item !== undefined);
1024
+ const undefinedCount = target.length - defined.length;
1025
+ return Effect.map(mergeSort(defined), (items) => [...items, ...Array(undefinedCount).fill(undefined)]);
1026
+ };