@opencode/codemode 0.0.0-beta-19500 → 0.0.0-dev-19274

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 (65) hide show
  1. package/README.md +0 -6
  2. package/dist/codemode.d.ts +12 -9
  3. package/dist/codemode.js +9 -5
  4. package/dist/index.d.ts +0 -1
  5. package/dist/index.js +0 -1
  6. package/dist/interpreter/errors.d.ts +6 -4
  7. package/dist/interpreter/errors.js +10 -25
  8. package/dist/interpreter/execute.d.ts +3 -4
  9. package/dist/interpreter/execute.js +18 -11
  10. package/dist/interpreter/iterator.d.ts +13 -0
  11. package/dist/interpreter/iterator.js +4 -0
  12. package/dist/interpreter/methods.d.ts +16 -3
  13. package/dist/interpreter/methods.js +290 -101
  14. package/dist/interpreter/model.d.ts +79 -10
  15. package/dist/interpreter/model.js +102 -2
  16. package/dist/interpreter/promises.d.ts +13 -15
  17. package/dist/interpreter/promises.js +52 -70
  18. package/dist/interpreter/references.d.ts +0 -1
  19. package/dist/interpreter/references.js +77 -57
  20. package/dist/interpreter/runtime.d.ts +95 -16
  21. package/dist/interpreter/runtime.js +960 -549
  22. package/dist/openapi/spec.js +6 -3
  23. package/dist/stdlib/collections.d.ts +1 -6
  24. package/dist/stdlib/collections.js +1 -117
  25. package/dist/stdlib/console.d.ts +2 -3
  26. package/dist/stdlib/console.js +28 -39
  27. package/dist/stdlib/date.d.ts +4 -5
  28. package/dist/stdlib/date.js +12 -34
  29. package/dist/stdlib/json.d.ts +6 -3
  30. package/dist/stdlib/json.js +63 -40
  31. package/dist/stdlib/math.d.ts +7 -3
  32. package/dist/stdlib/math.js +153 -85
  33. package/dist/stdlib/number.d.ts +4 -2
  34. package/dist/stdlib/number.js +37 -30
  35. package/dist/stdlib/object.d.ts +6 -6
  36. package/dist/stdlib/object.js +87 -84
  37. package/dist/stdlib/promise.d.ts +2 -0
  38. package/dist/stdlib/promise.js +1 -0
  39. package/dist/stdlib/regexp.d.ts +7 -6
  40. package/dist/stdlib/regexp.js +34 -48
  41. package/dist/stdlib/string.d.ts +3 -1
  42. package/dist/stdlib/string.js +17 -20
  43. package/dist/stdlib/url.d.ts +6 -10
  44. package/dist/stdlib/url.js +25 -102
  45. package/dist/stdlib/value.d.ts +7 -8
  46. package/dist/stdlib/value.js +56 -56
  47. package/dist/tool-runtime.d.ts +15 -16
  48. package/dist/tool-runtime.js +150 -13
  49. package/dist/values.d.ts +16 -22
  50. package/dist/values.js +17 -23
  51. package/package.json +1 -1
  52. package/dist/data.d.ts +0 -25
  53. package/dist/data.js +0 -153
  54. package/dist/interpreter/globals.d.ts +0 -13
  55. package/dist/interpreter/globals.js +0 -63
  56. package/dist/interpreter/host.d.ts +0 -41
  57. package/dist/interpreter/host.js +0 -44
  58. package/dist/interpreter/objects.d.ts +0 -37
  59. package/dist/interpreter/objects.js +0 -151
  60. package/dist/interpreter/runner.d.ts +0 -24
  61. package/dist/interpreter/runner.js +0 -45
  62. package/dist/stdlib/array.d.ts +0 -3
  63. package/dist/stdlib/array.js +0 -68
  64. package/dist/stdlib/web.d.ts +0 -4
  65. package/dist/stdlib/web.js +0 -20
@@ -1,18 +1,30 @@
1
1
  import { Effect } from "effect";
2
- import { toProgram } from "../data.js";
3
- import { dateSetterArgumentCount, invokeDateMethod } from "../stdlib/date.js";
4
- import { invokeNumberMethod } from "../stdlib/number.js";
5
- import { invokeRegExpMethod, matchToValue, toHostRegex } from "../stdlib/regexp.js";
6
- import { invokeURLMethod, uriArgument } from "../stdlib/url.js";
7
- import { coerceToNumber, coerceToString } from "../stdlib/value.js";
8
- import { compareText } from "../tool-runtime.js";
9
- import { Values } from "../values.js";
10
- import { IntrinsicReference, InterpreterRuntimeError } from "./model.js";
11
- import { get, ProgramArray, ProgramObject, record } from "./objects.js";
12
- import { containsOpaqueReference, rejectCircularInsertion, typeofValue } from "./references.js";
13
- import { applyCollectionCallback, isSupportedCallback, toPrimitive } from "./runner.js";
14
- export const invokeIntrinsic = (runner, ref, args, node) => Effect.map(invoke(runner, ref, args, node), (result) => (Array.isArray(result) ? new ProgramArray(result) : result));
15
- const invoke = (runner, ref, args, node) => {
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) => {
16
28
  if (typeof ref.receiver === "string") {
17
29
  if (ref.name === "replace" || ref.name === "replaceAll") {
18
30
  if (isSupportedCallback(args[1]))
@@ -26,10 +38,10 @@ const invoke = (runner, ref, args, node) => {
26
38
  if (typeof ref.receiver === "number") {
27
39
  return Effect.succeed(invokeNumberMethod(ref.receiver, ref.name, args, node));
28
40
  }
29
- if (ref.receiver instanceof ProgramArray) {
41
+ if (Array.isArray(ref.receiver)) {
30
42
  return invokeArrayMethod(runner, ref.receiver, ref.name, args, node);
31
43
  }
32
- if (ref.receiver instanceof Values.Date) {
44
+ if (ref.receiver instanceof CodeModeDate) {
33
45
  const target = ref.receiver;
34
46
  const argumentCount = dateSetterArgumentCount(ref.name);
35
47
  if (argumentCount === undefined)
@@ -40,31 +52,70 @@ const invoke = (runner, ref, args, node) => {
40
52
  concurrency: 1,
41
53
  }), (values) => invokeDateMethod(target, ref.name, values, node, initialTime));
42
54
  }
43
- if (ref.receiver instanceof Values.RegExp) {
55
+ if (ref.receiver instanceof CodeModeRegExp) {
44
56
  return Effect.succeed(invokeRegExpMethod(ref.receiver, ref.name, args, node));
45
57
  }
46
- if (ref.receiver instanceof Values.Map) {
58
+ if (ref.receiver instanceof CodeModeMap) {
47
59
  return invokeMapMethod(runner, ref.receiver, ref.name, args, node);
48
60
  }
49
- if (ref.receiver instanceof Values.Set) {
61
+ if (ref.receiver instanceof CodeModeSet) {
50
62
  return invokeSetMethod(runner, ref.receiver, ref.name, args, node);
51
63
  }
52
- if (ref.receiver instanceof Values.URL) {
64
+ if (ref.receiver instanceof CodeModeURL) {
53
65
  return Effect.succeed(invokeURLMethod(ref.receiver, ref.name, node));
54
66
  }
55
- if (ref.receiver instanceof Values.URLSearchParams) {
67
+ if (ref.receiver instanceof CodeModeURLSearchParams) {
56
68
  return invokeURLSearchParamsMethod(runner, ref.receiver, ref.name, args, node);
57
69
  }
58
70
  throw new InterpreterRuntimeError(`Method '${ref.name}' is not available.`, node);
59
71
  };
60
- /**
61
- * ToPrimitive: tries an object's own `valueOf`/`toString` in hint order and returns the first
62
- * primitive result. Runtime values behave like their JS counterparts (Date yields its time under a
63
- * number hint; the rest yield their string form). An inherited `toString` yields the default
64
- * string form, so plain objects become "[object Object]" and arrays join.
65
- */
66
- const coerceNumericArgument = (runner, value, node) => Effect.map(toPrimitive(runner, value, "number", node), coerceToNumber);
67
- // console is intercepted by the interpreter before reaching here.
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
+ };
68
119
  const requireDataArgument = (name, index, arg, node) => {
69
120
  if (containsOpaqueReference(arg)) {
70
121
  throw new InterpreterRuntimeError(`String.${name} expects argument ${index + 1} to be a data value.`, node, "InvalidDataValue");
@@ -78,7 +129,7 @@ const invokeStringMethod = (value, name, args, node) => {
78
129
  const optNum = (index) => (args[index] === undefined ? undefined : num(index));
79
130
  const optStr = (index) => (args[index] === undefined ? undefined : str(index));
80
131
  const rejectRegex = () => {
81
- if (args[0] instanceof Values.RegExp) {
132
+ if (args[0] instanceof CodeModeRegExp) {
82
133
  throw new InterpreterRuntimeError(`String.${name} cannot take a regular expression; use regex.test(string) or String.search instead.`, node).as("TypeError");
83
134
  }
84
135
  };
@@ -94,11 +145,9 @@ const invokeStringMethod = (value, name, args, node) => {
94
145
  result = value.trim();
95
146
  break;
96
147
  case "trimStart":
97
- case "trimLeft":
98
148
  result = value.trimStart();
99
149
  break;
100
150
  case "trimEnd":
101
- case "trimRight":
102
151
  result = value.trimEnd();
103
152
  break;
104
153
  // Locale/options are deliberately unsupported; comparison uses the host default locale.
@@ -123,7 +172,7 @@ const invokeStringMethod = (value, name, args, node) => {
123
172
  result = requestedLimit !== undefined && requestedLimit >>> 0 === 0 ? [] : [value];
124
173
  break;
125
174
  }
126
- if (args[0] instanceof Values.RegExp) {
175
+ if (args[0] instanceof CodeModeRegExp) {
127
176
  result = value.split(args[0].regex, optNum(1));
128
177
  break;
129
178
  }
@@ -154,7 +203,7 @@ const invokeStringMethod = (value, name, args, node) => {
154
203
  break;
155
204
  case "replace":
156
205
  case "replaceAll": {
157
- if (args[0] instanceof Values.RegExp) {
206
+ if (args[0] instanceof CodeModeRegExp) {
158
207
  const pattern = args[0].regex;
159
208
  const replacement = str(1);
160
209
  if (name === "replaceAll" && !pattern.global) {
@@ -177,7 +226,7 @@ const invokeStringMethod = (value, name, args, node) => {
177
226
  return null;
178
227
  // Preserve the own `index` and `groups` properties on non-global matches.
179
228
  if (pattern.global)
180
- return toProgram(matched, "String.match result");
229
+ return boundedData(matched, "String.match result");
181
230
  return matchToValue(matched);
182
231
  }
183
232
  case "matchAll": {
@@ -185,7 +234,7 @@ const invokeStringMethod = (value, name, args, node) => {
185
234
  if (!pattern.global) {
186
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);
187
236
  }
188
- return new ProgramArray(Array.from(value.matchAll(pattern), matchToValue));
237
+ return Array.from(value.matchAll(pattern), matchToValue);
189
238
  }
190
239
  case "search": {
191
240
  result = value.search(toHostRegex(args[0], name, node));
@@ -213,15 +262,6 @@ const invokeStringMethod = (value, name, args, node) => {
213
262
  case "substring":
214
263
  result = value.substring(optNum(0) ?? 0, optNum(1));
215
264
  break;
216
- case "substr":
217
- result = value.substr(optNum(0) ?? 0, optNum(1));
218
- break;
219
- case "isWellFormed":
220
- result = value.isWellFormed();
221
- break;
222
- case "toWellFormed":
223
- result = value.toWellFormed();
224
- break;
225
265
  case "charCodeAt":
226
266
  result = value.charCodeAt(optNum(0) ?? 0);
227
267
  break;
@@ -238,7 +278,138 @@ const invokeStringMethod = (value, name, args, node) => {
238
278
  default:
239
279
  throw new InterpreterRuntimeError(`String method '${name}' is not available.`, node);
240
280
  }
241
- return toProgram(result, `String.${name} result`);
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
+ });
242
413
  };
243
414
  const invokeStringReplacer = (runner, value, name, args, node) => {
244
415
  const apply = applyCollectionCallback(runner, args[1], `String.${name}`, node);
@@ -251,13 +422,19 @@ const invokeStringReplacer = (runner, value, name, args, node) => {
251
422
  if (typeof match !== "string" || typeof offset !== "number") {
252
423
  throw new InterpreterRuntimeError(`String.${name} produced an invalid replacement match.`, node);
253
424
  }
254
- if (hasGroups)
255
- callbackArgs[callbackArgs.length - 1] = record(groups);
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
+ }
256
433
  matches.push({ match, offset, args: callbackArgs });
257
434
  return match;
258
435
  };
259
436
  const pattern = args[0];
260
- if (pattern instanceof Values.RegExp) {
437
+ if (pattern instanceof CodeModeRegExp) {
261
438
  if (name === "replaceAll" && !pattern.regex.global) {
262
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);
263
440
  }
@@ -278,15 +455,27 @@ const invokeStringReplacer = (runner, value, name, args, node) => {
278
455
  let end = 0;
279
456
  for (const match of matches) {
280
457
  const replacement = yield* apply(match.args);
281
- output.push(value.slice(end, match.offset), replacement instanceof Values.Promise
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
282
460
  ? "[object Promise]"
283
- : coerceToString(toProgram(replacement, `String.${name} replacer result`)));
461
+ : errorBrandName(replacement)
462
+ ? coerceToString(replacement)
463
+ : coerceToString(boundedData(replacement, `String.${name} replacer result`)));
284
464
  end = match.offset + match.match.length;
285
465
  }
286
466
  output.push(value.slice(end));
287
- return toProgram(output.join(""), `String.${name} result`);
467
+ return boundedData(output.join(""), `String.${name} result`);
288
468
  });
289
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
+ };
290
479
  const invokeMapMethod = (runner, target, name, args, node) => {
291
480
  switch (name) {
292
481
  case "get":
@@ -310,7 +499,7 @@ const invokeMapMethod = (runner, target, name, args, node) => {
310
499
  case "values":
311
500
  return Effect.sync(() => Array.from(target.map.values()));
312
501
  case "entries":
313
- return Effect.sync(() => Array.from(target.map.entries(), ([key, item]) => new ProgramArray([key, item])));
502
+ return Effect.sync(() => Array.from(target.map.entries(), ([key, item]) => [key, item]));
314
503
  case "forEach": {
315
504
  const apply = applyCollectionCallback(runner, args[0], "Map.forEach", node);
316
505
  return Effect.gen(function* () {
@@ -343,7 +532,7 @@ const invokeSetMethod = (runner, target, name, args, node) => {
343
532
  case "values":
344
533
  return Effect.sync(() => Array.from(target.set.values()));
345
534
  case "entries":
346
- return Effect.sync(() => Array.from(target.set.values(), (item) => new ProgramArray([item, item])));
535
+ return Effect.sync(() => Array.from(target.set.values(), (item) => [item, item]));
347
536
  case "forEach": {
348
537
  const apply = applyCollectionCallback(runner, args[0], "Set.forEach", node);
349
538
  return Effect.gen(function* () {
@@ -373,7 +562,7 @@ const invokeSetOperation = (runner, target, name, source, node) => Effect.gen(fu
373
562
  return result;
374
563
  }
375
564
  if (name === "intersection") {
376
- const result = new Values.Set();
565
+ const result = new CodeModeSet();
377
566
  if (target.set.size <= other.size) {
378
567
  for (const item of target.set.values()) {
379
568
  if (yield* other.has(item))
@@ -442,45 +631,46 @@ const invokeSetOperation = (runner, target, name, source, node) => Effect.gen(fu
442
631
  return true;
443
632
  });
444
633
  const copySet = (source) => {
445
- const result = new Values.Set();
634
+ const result = new CodeModeSet();
446
635
  for (const item of source.set.values())
447
636
  result.set.add(item);
448
637
  return result;
449
638
  };
450
639
  const loadSetRecord = (runner, source, name, node) => {
451
- if (source instanceof Values.Set) {
640
+ if (source instanceof CodeModeSet) {
452
641
  return Effect.succeed({
453
642
  size: source.set.size,
454
643
  has: (item) => Effect.succeed(source.set.has(item)),
455
644
  keys: () => Effect.succeed(source.set.values()),
456
645
  });
457
646
  }
458
- if (source instanceof Values.Map) {
647
+ if (source instanceof CodeModeMap) {
459
648
  return Effect.succeed({
460
649
  size: source.map.size,
461
650
  has: (item) => Effect.succeed(source.map.has(item)),
462
651
  keys: () => Effect.succeed(source.map.keys()),
463
652
  });
464
653
  }
465
- if (!(source instanceof ProgramObject)) {
654
+ if (source === null || typeof source !== "object" || isCodeModeValue(source)) {
466
655
  throw new InterpreterRuntimeError(`Set.${name} expects a Set-like object.`, node).as("TypeError");
467
656
  }
657
+ const object = source;
468
658
  return Effect.gen(function* () {
469
- const size = yield* coerceNumericArgument(runner, get(source, "size"), node);
659
+ const size = yield* coerceNumericArgument(runner, object.size, node);
470
660
  if (Number.isNaN(size)) {
471
661
  throw new InterpreterRuntimeError(`Set.${name} received a Set-like object with an invalid size.`, node).as("TypeError");
472
662
  }
473
- const has = get(source, "has");
474
- const keys = get(source, "keys");
475
- if (!isSupportedCallback(has) || !isSupportedCallback(keys)) {
663
+ if (!isSupportedCallback(object.has) || !isSupportedCallback(object.keys)) {
476
664
  throw new InterpreterRuntimeError(`Set.${name} expects callable 'has' and 'keys' methods.`, node).as("TypeError");
477
665
  }
666
+ const has = object.has;
667
+ const keys = object.keys;
478
668
  return {
479
669
  size: Math.max(Math.trunc(size), 0),
480
670
  has: (item) => Effect.map(runner.invokeCallable(has, [item], node), Boolean),
481
671
  keys: () => Effect.flatMap(runner.invokeCallable(keys, [], node), (result) => {
482
- if (result instanceof ProgramArray)
483
- return Effect.succeed(result.items);
672
+ if (Array.isArray(result))
673
+ return Effect.succeed(result);
484
674
  throw new InterpreterRuntimeError(`Set.${name} expected 'keys' to return an iterator.`, node).as("TypeError");
485
675
  }),
486
676
  };
@@ -537,7 +727,7 @@ const invokeURLSearchParamsMethod = (runner, target, name, args, node) => {
537
727
  case "values":
538
728
  return Effect.sync(() => Array.from(target.params.values()));
539
729
  case "entries":
540
- return Effect.sync(() => Array.from(target.params.entries(), ([key, value]) => new ProgramArray([key, value])));
730
+ return Effect.sync(() => Array.from(target.params.entries(), ([key, value]) => [key, value]));
541
731
  case "toString":
542
732
  return Effect.sync(() => target.params.toString());
543
733
  case "forEach": {
@@ -553,8 +743,7 @@ const invokeURLSearchParamsMethod = (runner, target, name, args, node) => {
553
743
  throw new InterpreterRuntimeError(`URLSearchParams method '${name}' is not available.`, node);
554
744
  }
555
745
  };
556
- const invokeArrayMethod = (runner, receiver, name, args, node) => {
557
- const target = receiver.items;
746
+ const invokeArrayMethod = (runner, target, name, args, node) => {
558
747
  const optNumber = (value, label) => {
559
748
  if (value === undefined)
560
749
  return undefined;
@@ -567,7 +756,8 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
567
756
  if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
568
757
  throw new InterpreterRuntimeError("Array.join expects zero arguments or one string separator.", node);
569
758
  }
570
- return Effect.succeed(target.map((item) => coerceToString(item ?? "")).join(args.length === 0 ? "," : args[0]));
759
+ const input = boundedData(target, "Array.join input");
760
+ return Effect.succeed(input.map((item) => coerceToString(item ?? "")).join(args.length === 0 ? "," : args[0]));
571
761
  }
572
762
  case "includes":
573
763
  if (args.length === 0 || args.length > 2)
@@ -584,14 +774,11 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
584
774
  case "slice":
585
775
  return Effect.succeed(target.slice(optNumber(args[0], "start"), optNumber(args[1], "end")));
586
776
  case "concat":
587
- return Effect.succeed(target.concat(...args.map((item) => (item instanceof ProgramArray ? item.items : item))));
588
- case "flat": {
589
- const flatten = (items, depth) => items.flatMap((item) => (item instanceof ProgramArray && depth > 0 ? flatten(item.items, depth - 1) : [item]));
590
- return Effect.succeed(flatten(target, optNumber(args[0], "depth") ?? 1));
591
- }
777
+ return Effect.succeed(target.concat(...args));
778
+ case "flat":
779
+ return Effect.succeed(target.flat(optNumber(args[0], "depth") ?? 1));
592
780
  case "reverse":
593
- target.reverse();
594
- return Effect.succeed(receiver);
781
+ return Effect.succeed(target.reverse());
595
782
  case "sort": {
596
783
  const length = target.length;
597
784
  const holeCount = Array.from({ length }, (_, index) => Object.hasOwn(target, index)).filter((own) => !own).length;
@@ -603,7 +790,7 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
603
790
  Array.from({ length: holeCount }, (_, index) => itemCount + index).forEach((index) => {
604
791
  Reflect.deleteProperty(target, index);
605
792
  });
606
- return receiver;
793
+ return target;
607
794
  });
608
795
  }
609
796
  case "toSorted":
@@ -623,13 +810,13 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
623
810
  case "push": {
624
811
  // Validate all insertions before mutating to avoid partial cyclic updates.
625
812
  for (const item of args)
626
- rejectCircularInsertion(receiver, item, "Array.push result", node);
813
+ rejectCircularInsertion(target, item, "Array.push result", node);
627
814
  target.push(...args);
628
815
  return Effect.succeed(target.length);
629
816
  }
630
817
  case "unshift": {
631
818
  for (const item of args)
632
- rejectCircularInsertion(receiver, item, "Array.unshift result", node);
819
+ rejectCircularInsertion(target, item, "Array.unshift result", node);
633
820
  target.unshift(...args);
634
821
  return Effect.succeed(target.length);
635
822
  }
@@ -646,7 +833,7 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
646
833
  const deleteCount = optNumber(args[1], "delete count") ?? 0;
647
834
  const inserted = args.slice(2);
648
835
  for (const item of inserted)
649
- rejectCircularInsertion(receiver, item, "Array.splice result", node);
836
+ rejectCircularInsertion(target, item, "Array.splice result", node);
650
837
  return Effect.succeed(target.splice(start, deleteCount, ...inserted));
651
838
  }
652
839
  case "toSpliced": {
@@ -664,19 +851,17 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
664
851
  return Effect.succeed(copied);
665
852
  }
666
853
  case "fill": {
667
- rejectCircularInsertion(receiver, args[0], "Array.fill result", node);
668
- target.fill(args[0], optNumber(args[1], "start"), optNumber(args[2], "end"));
669
- return Effect.succeed(receiver);
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")));
670
856
  }
671
857
  case "copyWithin":
672
- target.copyWithin(optNumber(args[0], "target index") ?? 0, optNumber(args[1], "start") ?? 0, optNumber(args[2], "end"));
673
- return Effect.succeed(receiver);
858
+ return Effect.succeed(target.copyWithin(optNumber(args[0], "target index") ?? 0, optNumber(args[1], "start") ?? 0, optNumber(args[2], "end")));
674
859
  case "keys":
675
860
  return Effect.succeed(Array.from(target.keys()));
676
861
  case "values":
677
862
  return Effect.succeed([...target]);
678
863
  case "entries":
679
- return Effect.succeed(Array.from(target.entries(), ([index, item]) => new ProgramArray([index, item])));
864
+ return Effect.succeed(Array.from(target.entries(), ([index, item]) => [index, item]));
680
865
  }
681
866
  const apply = applyCollectionCallback(runner, args[0], `Array.${name}`, node);
682
867
  return Effect.gen(function* () {
@@ -689,7 +874,7 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
689
874
  for (let index = 0; index < length; index += 1) {
690
875
  if (!(index in target))
691
876
  continue;
692
- values[index] = yield* apply([target[index], index, receiver]);
877
+ values[index] = yield* apply([target[index], index, target]);
693
878
  }
694
879
  return values;
695
880
  }
@@ -698,9 +883,9 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
698
883
  for (let index = 0; index < length; index += 1) {
699
884
  if (!(index in target))
700
885
  continue;
701
- const mapped = yield* apply([target[index], index, receiver]);
702
- if (mapped instanceof ProgramArray)
703
- values.push(...mapped.items);
886
+ const mapped = yield* apply([target[index], index, target]);
887
+ if (Array.isArray(mapped))
888
+ values.push(...mapped);
704
889
  else
705
890
  values.push(mapped);
706
891
  }
@@ -712,7 +897,7 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
712
897
  if (!(index in target))
713
898
  continue;
714
899
  const item = target[index];
715
- if (yield* apply([item, index, receiver]))
900
+ if (yield* apply([item, index, target]))
716
901
  values.push(item);
717
902
  }
718
903
  return values;
@@ -720,13 +905,13 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
720
905
  case "find":
721
906
  for (let index = 0; index < length; index += 1) {
722
907
  const item = target[index];
723
- if (yield* apply([item, index, receiver]))
908
+ if (yield* apply([item, index, target]))
724
909
  return item;
725
910
  }
726
911
  return undefined;
727
912
  case "findIndex":
728
913
  for (let index = 0; index < length; index += 1) {
729
- if (yield* apply([target[index], index, receiver]))
914
+ if (yield* apply([target[index], index, target]))
730
915
  return index;
731
916
  }
732
917
  return -1;
@@ -734,7 +919,7 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
734
919
  for (let index = 0; index < length; index += 1) {
735
920
  if (!(index in target))
736
921
  continue;
737
- if (yield* apply([target[index], index, receiver]))
922
+ if (yield* apply([target[index], index, target]))
738
923
  return true;
739
924
  }
740
925
  return false;
@@ -742,14 +927,14 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
742
927
  for (let index = 0; index < length; index += 1) {
743
928
  if (!(index in target))
744
929
  continue;
745
- if (!(yield* apply([target[index], index, receiver])))
930
+ if (!(yield* apply([target[index], index, target])))
746
931
  return false;
747
932
  }
748
933
  return true;
749
934
  case "forEach":
750
935
  for (let index = 0; index < length; index += 1) {
751
936
  if (index in target)
752
- yield* apply([target[index], index, receiver]);
937
+ yield* apply([target[index], index, target]);
753
938
  }
754
939
  return undefined;
755
940
  case "reduce": {
@@ -766,7 +951,7 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
766
951
  for (let index = start; index < length; index += 1) {
767
952
  if (!(index in target))
768
953
  continue;
769
- accumulator = yield* apply([accumulator, target[index], index, receiver]);
954
+ accumulator = yield* apply([accumulator, target[index], index, target]);
770
955
  }
771
956
  return accumulator;
772
957
  }
@@ -784,20 +969,20 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
784
969
  for (let index = start; index >= 0; index -= 1) {
785
970
  if (!(index in target))
786
971
  continue;
787
- accumulator = yield* apply([accumulator, target[index], index, receiver]);
972
+ accumulator = yield* apply([accumulator, target[index], index, target]);
788
973
  }
789
974
  return accumulator;
790
975
  }
791
976
  case "findLast":
792
977
  for (let index = length - 1; index >= 0; index -= 1) {
793
978
  const item = target[index];
794
- if (yield* apply([item, index, receiver]))
979
+ if (yield* apply([item, index, target]))
795
980
  return item;
796
981
  }
797
982
  return undefined;
798
983
  case "findLastIndex":
799
984
  for (let index = length - 1; index >= 0; index -= 1) {
800
- if (yield* apply([target[index], index, receiver]))
985
+ if (yield* apply([target[index], index, target]))
801
986
  return index;
802
987
  }
803
988
  return -1;
@@ -807,7 +992,11 @@ const invokeArrayMethod = (runner, receiver, name, args, node) => {
807
992
  };
808
993
  const sortArray = (runner, target, comparator, name, node) => {
809
994
  if (comparator === undefined) {
810
- return Effect.sync(() => [...target].sort((a, b) => compareText(coerceToString(a), coerceToString(b))));
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
+ }));
811
1000
  }
812
1001
  const apply = applyCollectionCallback(runner, comparator, name, node);
813
1002
  const mergeSort = (items) => {