@opencode/codemode 0.0.0-dev-19484 → 0.0.0-dev-19486

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 CHANGED
@@ -1,21 +1,19 @@
1
1
  export * as Data from "./data.js";
2
2
  import type { DiagnosticKind } from "./codemode.js";
3
- /** A null-prototype object owned by the program. */
4
- export type SafeObject = Record<string, unknown>;
5
3
  export declare class ToolRuntimeError extends Error {
6
4
  readonly kind: Extract<DiagnosticKind, "UnknownTool" | "InvalidToolInput" | "InvalidToolOutput" | "InvalidDataValue" | "ToolCallLimitExceeded">;
7
5
  readonly suggestions: ReadonlyArray<string>;
8
6
  constructor(kind: Extract<DiagnosticKind, "UnknownTool" | "InvalidToolInput" | "InvalidToolOutput" | "InvalidDataValue" | "ToolCallLimitExceeded">, message: string, suggestions?: ReadonlyArray<string>);
9
7
  }
10
8
  /**
11
- * Brings a host-produced runtime value into the program: runtime values pass through, their host
12
- * counterparts (Date, RegExp, Map, Set, URL, URLSearchParams) are wrapped, and objects become
13
- * null-prototype copies. Arrays keep extra enumerable properties such as `index` and `groups`.
9
+ * Brings a host-produced value into the program: program and runtime values pass through, their
10
+ * host counterparts (Date, RegExp, Map, Set, URL, URLSearchParams) are wrapped, and host objects
11
+ * and arrays are copied.
14
12
  */
15
13
  export declare const toProgram: (value: unknown, label: string) => unknown;
16
14
  /**
17
15
  * Brings host data into the program: Date and URL become strings, other host collections become
18
- * empty objects, and objects become null-prototype copies. Used for tool results and parsed JSON.
16
+ * empty objects, and objects become program copies. Used for tool results and parsed JSON.
19
17
  */
20
18
  export declare const fromData: (value: unknown, label: string) => unknown;
21
19
  /**
package/dist/data.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * as Data from "./data.js";
2
+ import { ownEntries, parseArrayIndex, ProgramArray, ProgramObject, set } from "./interpreter/objects.js";
2
3
  import { Values } from "./values.js";
3
4
  const MAX_VALUE_DEPTH = 32;
4
5
  export class ToolRuntimeError extends Error {
@@ -12,14 +13,14 @@ export class ToolRuntimeError extends Error {
12
13
  }
13
14
  }
14
15
  /**
15
- * Brings a host-produced runtime value into the program: runtime values pass through, their host
16
- * counterparts (Date, RegExp, Map, Set, URL, URLSearchParams) are wrapped, and objects become
17
- * null-prototype copies. Arrays keep extra enumerable properties such as `index` and `groups`.
16
+ * Brings a host-produced value into the program: program and runtime values pass through, their
17
+ * host counterparts (Date, RegExp, Map, Set, URL, URLSearchParams) are wrapped, and host objects
18
+ * and arrays are copied.
18
19
  */
19
20
  export const toProgram = (value, label) => copy(value, label, "program", 0, new Set());
20
21
  /**
21
22
  * Brings host data into the program: Date and URL become strings, other host collections become
22
- * empty objects, and objects become null-prototype copies. Used for tool results and parsed JSON.
23
+ * empty objects, and objects become program copies. Used for tool results and parsed JSON.
23
24
  */
24
25
  export const fromData = (value, label) => copy(value, label, "data", 0, new Set());
25
26
  /**
@@ -45,8 +46,9 @@ const copy = (value, label, mode, depth, seen) => {
45
46
  if (value instanceof Values.Promise) {
46
47
  throw new ToolRuntimeError("InvalidDataValue", `${label} contains an un-awaited Promise; await tool calls (e.g. \`const result = await tools.ns.tool(...)\`) before using their results.`);
47
48
  }
49
+ const plain = mode === "program" || mode === "data";
48
50
  if (mode === "program") {
49
- if (Values.isValue(value))
51
+ if (value instanceof ProgramObject || Values.isValue(value))
50
52
  return value;
51
53
  if (value instanceof Date)
52
54
  return new Values.Date(value.getTime());
@@ -70,7 +72,6 @@ const copy = (value, label, mode, depth, seen) => {
70
72
  if (value instanceof URLSearchParams)
71
73
  return new Values.URLSearchParams(new URLSearchParams(value));
72
74
  }
73
- const plain = mode === "program" || mode === "data";
74
75
  if (value instanceof Values.Date)
75
76
  return Number.isFinite(value.time) ? new Date(value.time).toISOString() : null;
76
77
  if (value instanceof Date)
@@ -85,24 +86,39 @@ const copy = (value, label, mode, depth, seen) => {
85
86
  value instanceof Map ||
86
87
  value instanceof Set ||
87
88
  value instanceof URLSearchParams) {
88
- return plain ? Object.create(null) : {};
89
+ return plain ? new ProgramObject() : {};
89
90
  }
90
91
  if (seen.has(value)) {
91
92
  throw new ToolRuntimeError("InvalidDataValue", `${label} contains a circular value.`);
92
93
  }
93
94
  seen.add(value);
95
+ if (value instanceof ProgramArray) {
96
+ const copied = Array.from(value.items, (item) => copy(item, label, mode, depth + 1, seen) ?? null);
97
+ seen.delete(value);
98
+ return copied;
99
+ }
100
+ if (value instanceof ProgramObject) {
101
+ const copied = {};
102
+ for (const [key, item] of ownEntries(value)) {
103
+ const next = copy(item, label, mode, depth + 1, seen);
104
+ if (next === undefined && mode === "json")
105
+ continue;
106
+ define(copied, key, next);
107
+ }
108
+ seen.delete(value);
109
+ return copied;
110
+ }
94
111
  if (Array.isArray(value)) {
95
- // Host output densifies holes to null like JSON; program copies keep them.
96
- const copied = plain
97
- ? value.map((item) => copy(item, label, mode, depth + 1, seen))
98
- : Array.from(value, (item) => copy(item, label, mode, depth + 1, seen) ?? null);
99
- if (mode === "program") {
112
+ if (plain) {
113
+ const copied = new ProgramArray(value.map((item) => copy(item, label, mode, depth + 1, seen)));
100
114
  for (const [key, item] of Object.entries(value)) {
101
- if (Object.hasOwn(copied, key))
102
- continue;
103
- define(copied, key, copy(item, label, mode, depth + 1, seen));
115
+ if (parseArrayIndex(key) === undefined)
116
+ set(copied, key, copy(item, label, mode, depth + 1, seen));
104
117
  }
118
+ seen.delete(value);
119
+ return copied;
105
120
  }
121
+ const copied = Array.from(value, (item) => copy(item, label, mode, depth + 1, seen) ?? null);
106
122
  seen.delete(value);
107
123
  return copied;
108
124
  }
@@ -110,7 +126,14 @@ const copy = (value, label, mode, depth, seen) => {
110
126
  if (prototype !== Object.prototype && prototype !== null) {
111
127
  throw new ToolRuntimeError("InvalidDataValue", `${label} must contain plain objects only.`);
112
128
  }
113
- const copied = plain ? Object.create(null) : {};
129
+ if (plain) {
130
+ const copied = new ProgramObject();
131
+ for (const [key, item] of Object.entries(value))
132
+ set(copied, key, copy(item, label, mode, depth + 1, seen));
133
+ seen.delete(value);
134
+ return copied;
135
+ }
136
+ const copied = {};
114
137
  for (const [key, item] of Object.entries(value)) {
115
138
  const next = copy(item, label, mode, depth + 1, seen);
116
139
  if (next === undefined && mode === "json")
@@ -120,8 +143,8 @@ const copy = (value, label, mode, depth, seen) => {
120
143
  seen.delete(value);
121
144
  return copied;
122
145
  };
123
- // Own data property regardless of the target's prototype, so a "__proto__" key on a host object or
124
- // array never reaches the Object.prototype setter.
146
+ // Own data property regardless of the target's prototype, so a "__proto__" key on a host object
147
+ // never reaches the Object.prototype setter.
125
148
  const define = (target, key, value) => {
126
149
  Object.defineProperty(target, key, { value, enumerable: true, writable: true, configurable: true });
127
150
  };
@@ -4,6 +4,7 @@ import { toData, ToolRuntimeError } from "../data.js";
4
4
  import { formatLocation, InterpreterRuntimeError, ProgramThrow, sourceLocation } from "./model.js";
5
5
  import { containsRuntimeReference } from "./references.js";
6
6
  import { HostFunction } from "./host.js";
7
+ import { get, ProgramError, ProgramObject } from "./objects.js";
7
8
  import {} from "./runner.js";
8
9
  import { coerceToString, createAggregateErrorValue, createErrorValue, errorBrandName, errorConstructors, } from "../stdlib/value.js";
9
10
  export const normalizeError = (error) => {
@@ -35,10 +36,8 @@ export const normalizeError = (error) => {
35
36
  else if (typeof value === "string") {
36
37
  message = value;
37
38
  }
38
- else if (value !== null &&
39
- typeof value === "object" &&
40
- typeof value.message === "string") {
41
- message = value.message;
39
+ else if (value instanceof ProgramObject && typeof get(value, "message") === "string") {
40
+ message = get(value, "message");
42
41
  }
43
42
  else {
44
43
  try {
@@ -4,13 +4,15 @@ import { dateSetterArgumentCount, invokeDateMethod } from "../stdlib/date.js";
4
4
  import { invokeNumberMethod } from "../stdlib/number.js";
5
5
  import { invokeRegExpMethod, matchToValue, toHostRegex } from "../stdlib/regexp.js";
6
6
  import { invokeURLMethod, uriArgument } from "../stdlib/url.js";
7
- import { coerceToNumber, coerceToString, errorBrandName } from "../stdlib/value.js";
7
+ import { coerceToNumber, coerceToString } from "../stdlib/value.js";
8
8
  import { compareText } from "../tool-runtime.js";
9
9
  import { Values } from "../values.js";
10
10
  import { IntrinsicReference, InterpreterRuntimeError } from "./model.js";
11
+ import { get, ProgramArray, ProgramObject, record } from "./objects.js";
11
12
  import { containsOpaqueReference, rejectCircularInsertion, typeofValue } from "./references.js";
12
13
  import { applyCollectionCallback, isSupportedCallback, toPrimitive } from "./runner.js";
13
- export const invokeIntrinsic = (runner, ref, args, node) => {
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) => {
14
16
  if (typeof ref.receiver === "string") {
15
17
  if (ref.name === "replace" || ref.name === "replaceAll") {
16
18
  if (isSupportedCallback(args[1]))
@@ -24,7 +26,7 @@ export const invokeIntrinsic = (runner, ref, args, node) => {
24
26
  if (typeof ref.receiver === "number") {
25
27
  return Effect.succeed(invokeNumberMethod(ref.receiver, ref.name, args, node));
26
28
  }
27
- if (Array.isArray(ref.receiver)) {
29
+ if (ref.receiver instanceof ProgramArray) {
28
30
  return invokeArrayMethod(runner, ref.receiver, ref.name, args, node);
29
31
  }
30
32
  if (ref.receiver instanceof Values.Date) {
@@ -183,7 +185,7 @@ const invokeStringMethod = (value, name, args, node) => {
183
185
  if (!pattern.global) {
184
186
  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);
185
187
  }
186
- return Array.from(value.matchAll(pattern), matchToValue);
188
+ return new ProgramArray(Array.from(value.matchAll(pattern), matchToValue));
187
189
  }
188
190
  case "search": {
189
191
  result = value.search(toHostRegex(args[0], name, node));
@@ -249,13 +251,8 @@ const invokeStringReplacer = (runner, value, name, args, node) => {
249
251
  if (typeof match !== "string" || typeof offset !== "number") {
250
252
  throw new InterpreterRuntimeError(`String.${name} produced an invalid replacement match.`, node);
251
253
  }
252
- if (hasGroups) {
253
- const safeGroups = Object.create(null);
254
- for (const [key, group] of Object.entries(groups)) {
255
- safeGroups[key] = group;
256
- }
257
- callbackArgs[callbackArgs.length - 1] = safeGroups;
258
- }
254
+ if (hasGroups)
255
+ callbackArgs[callbackArgs.length - 1] = record(groups);
259
256
  matches.push({ match, offset, args: callbackArgs });
260
257
  return match;
261
258
  };
@@ -281,12 +278,9 @@ const invokeStringReplacer = (runner, value, name, args, node) => {
281
278
  let end = 0;
282
279
  for (const match of matches) {
283
280
  const replacement = yield* apply(match.args);
284
- // Error values are branded plain objects; toProgram would strip the brand before coercion.
285
281
  output.push(value.slice(end, match.offset), replacement instanceof Values.Promise
286
282
  ? "[object Promise]"
287
- : errorBrandName(replacement)
288
- ? coerceToString(replacement)
289
- : coerceToString(toProgram(replacement, `String.${name} replacer result`)));
283
+ : coerceToString(toProgram(replacement, `String.${name} replacer result`)));
290
284
  end = match.offset + match.match.length;
291
285
  }
292
286
  output.push(value.slice(end));
@@ -316,7 +310,7 @@ const invokeMapMethod = (runner, target, name, args, node) => {
316
310
  case "values":
317
311
  return Effect.sync(() => Array.from(target.map.values()));
318
312
  case "entries":
319
- return Effect.sync(() => Array.from(target.map.entries(), ([key, item]) => [key, item]));
313
+ return Effect.sync(() => Array.from(target.map.entries(), ([key, item]) => new ProgramArray([key, item])));
320
314
  case "forEach": {
321
315
  const apply = applyCollectionCallback(runner, args[0], "Map.forEach", node);
322
316
  return Effect.gen(function* () {
@@ -349,7 +343,7 @@ const invokeSetMethod = (runner, target, name, args, node) => {
349
343
  case "values":
350
344
  return Effect.sync(() => Array.from(target.set.values()));
351
345
  case "entries":
352
- return Effect.sync(() => Array.from(target.set.values(), (item) => [item, item]));
346
+ return Effect.sync(() => Array.from(target.set.values(), (item) => new ProgramArray([item, item])));
353
347
  case "forEach": {
354
348
  const apply = applyCollectionCallback(runner, args[0], "Set.forEach", node);
355
349
  return Effect.gen(function* () {
@@ -468,26 +462,25 @@ const loadSetRecord = (runner, source, name, node) => {
468
462
  keys: () => Effect.succeed(source.map.keys()),
469
463
  });
470
464
  }
471
- if (source === null || typeof source !== "object" || Values.isValue(source)) {
465
+ if (!(source instanceof ProgramObject)) {
472
466
  throw new InterpreterRuntimeError(`Set.${name} expects a Set-like object.`, node).as("TypeError");
473
467
  }
474
- const object = source;
475
468
  return Effect.gen(function* () {
476
- const size = yield* coerceNumericArgument(runner, object.size, node);
469
+ const size = yield* coerceNumericArgument(runner, get(source, "size"), node);
477
470
  if (Number.isNaN(size)) {
478
471
  throw new InterpreterRuntimeError(`Set.${name} received a Set-like object with an invalid size.`, node).as("TypeError");
479
472
  }
480
- if (!isSupportedCallback(object.has) || !isSupportedCallback(object.keys)) {
473
+ const has = get(source, "has");
474
+ const keys = get(source, "keys");
475
+ if (!isSupportedCallback(has) || !isSupportedCallback(keys)) {
481
476
  throw new InterpreterRuntimeError(`Set.${name} expects callable 'has' and 'keys' methods.`, node).as("TypeError");
482
477
  }
483
- const has = object.has;
484
- const keys = object.keys;
485
478
  return {
486
479
  size: Math.max(Math.trunc(size), 0),
487
480
  has: (item) => Effect.map(runner.invokeCallable(has, [item], node), Boolean),
488
481
  keys: () => Effect.flatMap(runner.invokeCallable(keys, [], node), (result) => {
489
- if (Array.isArray(result))
490
- return Effect.succeed(result);
482
+ if (result instanceof ProgramArray)
483
+ return Effect.succeed(result.items);
491
484
  throw new InterpreterRuntimeError(`Set.${name} expected 'keys' to return an iterator.`, node).as("TypeError");
492
485
  }),
493
486
  };
@@ -544,7 +537,7 @@ const invokeURLSearchParamsMethod = (runner, target, name, args, node) => {
544
537
  case "values":
545
538
  return Effect.sync(() => Array.from(target.params.values()));
546
539
  case "entries":
547
- return Effect.sync(() => Array.from(target.params.entries(), ([key, value]) => [key, value]));
540
+ return Effect.sync(() => Array.from(target.params.entries(), ([key, value]) => new ProgramArray([key, value])));
548
541
  case "toString":
549
542
  return Effect.sync(() => target.params.toString());
550
543
  case "forEach": {
@@ -560,7 +553,8 @@ const invokeURLSearchParamsMethod = (runner, target, name, args, node) => {
560
553
  throw new InterpreterRuntimeError(`URLSearchParams method '${name}' is not available.`, node);
561
554
  }
562
555
  };
563
- const invokeArrayMethod = (runner, target, name, args, node) => {
556
+ const invokeArrayMethod = (runner, receiver, name, args, node) => {
557
+ const target = receiver.items;
564
558
  const optNumber = (value, label) => {
565
559
  if (value === undefined)
566
560
  return undefined;
@@ -573,8 +567,7 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
573
567
  if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
574
568
  throw new InterpreterRuntimeError("Array.join expects zero arguments or one string separator.", node);
575
569
  }
576
- const input = toProgram(target, "Array.join input");
577
- return Effect.succeed(input.map((item) => coerceToString(item ?? "")).join(args.length === 0 ? "," : args[0]));
570
+ return Effect.succeed(target.map((item) => coerceToString(item ?? "")).join(args.length === 0 ? "," : args[0]));
578
571
  }
579
572
  case "includes":
580
573
  if (args.length === 0 || args.length > 2)
@@ -591,11 +584,14 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
591
584
  case "slice":
592
585
  return Effect.succeed(target.slice(optNumber(args[0], "start"), optNumber(args[1], "end")));
593
586
  case "concat":
594
- return Effect.succeed(target.concat(...args));
595
- case "flat":
596
- return Effect.succeed(target.flat(optNumber(args[0], "depth") ?? 1));
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
+ }
597
592
  case "reverse":
598
- return Effect.succeed(target.reverse());
593
+ target.reverse();
594
+ return Effect.succeed(receiver);
599
595
  case "sort": {
600
596
  const length = target.length;
601
597
  const holeCount = Array.from({ length }, (_, index) => Object.hasOwn(target, index)).filter((own) => !own).length;
@@ -607,7 +603,7 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
607
603
  Array.from({ length: holeCount }, (_, index) => itemCount + index).forEach((index) => {
608
604
  Reflect.deleteProperty(target, index);
609
605
  });
610
- return target;
606
+ return receiver;
611
607
  });
612
608
  }
613
609
  case "toSorted":
@@ -627,13 +623,13 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
627
623
  case "push": {
628
624
  // Validate all insertions before mutating to avoid partial cyclic updates.
629
625
  for (const item of args)
630
- rejectCircularInsertion(target, item, "Array.push result", node);
626
+ rejectCircularInsertion(receiver, item, "Array.push result", node);
631
627
  target.push(...args);
632
628
  return Effect.succeed(target.length);
633
629
  }
634
630
  case "unshift": {
635
631
  for (const item of args)
636
- rejectCircularInsertion(target, item, "Array.unshift result", node);
632
+ rejectCircularInsertion(receiver, item, "Array.unshift result", node);
637
633
  target.unshift(...args);
638
634
  return Effect.succeed(target.length);
639
635
  }
@@ -650,7 +646,7 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
650
646
  const deleteCount = optNumber(args[1], "delete count") ?? 0;
651
647
  const inserted = args.slice(2);
652
648
  for (const item of inserted)
653
- rejectCircularInsertion(target, item, "Array.splice result", node);
649
+ rejectCircularInsertion(receiver, item, "Array.splice result", node);
654
650
  return Effect.succeed(target.splice(start, deleteCount, ...inserted));
655
651
  }
656
652
  case "toSpliced": {
@@ -668,17 +664,19 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
668
664
  return Effect.succeed(copied);
669
665
  }
670
666
  case "fill": {
671
- rejectCircularInsertion(target, args[0], "Array.fill result", node);
672
- return Effect.succeed(target.fill(args[0], optNumber(args[1], "start"), optNumber(args[2], "end")));
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);
673
670
  }
674
671
  case "copyWithin":
675
- return Effect.succeed(target.copyWithin(optNumber(args[0], "target index") ?? 0, optNumber(args[1], "start") ?? 0, optNumber(args[2], "end")));
672
+ target.copyWithin(optNumber(args[0], "target index") ?? 0, optNumber(args[1], "start") ?? 0, optNumber(args[2], "end"));
673
+ return Effect.succeed(receiver);
676
674
  case "keys":
677
675
  return Effect.succeed(Array.from(target.keys()));
678
676
  case "values":
679
677
  return Effect.succeed([...target]);
680
678
  case "entries":
681
- return Effect.succeed(Array.from(target.entries(), ([index, item]) => [index, item]));
679
+ return Effect.succeed(Array.from(target.entries(), ([index, item]) => new ProgramArray([index, item])));
682
680
  }
683
681
  const apply = applyCollectionCallback(runner, args[0], `Array.${name}`, node);
684
682
  return Effect.gen(function* () {
@@ -691,7 +689,7 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
691
689
  for (let index = 0; index < length; index += 1) {
692
690
  if (!(index in target))
693
691
  continue;
694
- values[index] = yield* apply([target[index], index, target]);
692
+ values[index] = yield* apply([target[index], index, receiver]);
695
693
  }
696
694
  return values;
697
695
  }
@@ -700,9 +698,9 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
700
698
  for (let index = 0; index < length; index += 1) {
701
699
  if (!(index in target))
702
700
  continue;
703
- const mapped = yield* apply([target[index], index, target]);
704
- if (Array.isArray(mapped))
705
- values.push(...mapped);
701
+ const mapped = yield* apply([target[index], index, receiver]);
702
+ if (mapped instanceof ProgramArray)
703
+ values.push(...mapped.items);
706
704
  else
707
705
  values.push(mapped);
708
706
  }
@@ -714,7 +712,7 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
714
712
  if (!(index in target))
715
713
  continue;
716
714
  const item = target[index];
717
- if (yield* apply([item, index, target]))
715
+ if (yield* apply([item, index, receiver]))
718
716
  values.push(item);
719
717
  }
720
718
  return values;
@@ -722,13 +720,13 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
722
720
  case "find":
723
721
  for (let index = 0; index < length; index += 1) {
724
722
  const item = target[index];
725
- if (yield* apply([item, index, target]))
723
+ if (yield* apply([item, index, receiver]))
726
724
  return item;
727
725
  }
728
726
  return undefined;
729
727
  case "findIndex":
730
728
  for (let index = 0; index < length; index += 1) {
731
- if (yield* apply([target[index], index, target]))
729
+ if (yield* apply([target[index], index, receiver]))
732
730
  return index;
733
731
  }
734
732
  return -1;
@@ -736,7 +734,7 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
736
734
  for (let index = 0; index < length; index += 1) {
737
735
  if (!(index in target))
738
736
  continue;
739
- if (yield* apply([target[index], index, target]))
737
+ if (yield* apply([target[index], index, receiver]))
740
738
  return true;
741
739
  }
742
740
  return false;
@@ -744,14 +742,14 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
744
742
  for (let index = 0; index < length; index += 1) {
745
743
  if (!(index in target))
746
744
  continue;
747
- if (!(yield* apply([target[index], index, target])))
745
+ if (!(yield* apply([target[index], index, receiver])))
748
746
  return false;
749
747
  }
750
748
  return true;
751
749
  case "forEach":
752
750
  for (let index = 0; index < length; index += 1) {
753
751
  if (index in target)
754
- yield* apply([target[index], index, target]);
752
+ yield* apply([target[index], index, receiver]);
755
753
  }
756
754
  return undefined;
757
755
  case "reduce": {
@@ -768,7 +766,7 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
768
766
  for (let index = start; index < length; index += 1) {
769
767
  if (!(index in target))
770
768
  continue;
771
- accumulator = yield* apply([accumulator, target[index], index, target]);
769
+ accumulator = yield* apply([accumulator, target[index], index, receiver]);
772
770
  }
773
771
  return accumulator;
774
772
  }
@@ -786,20 +784,20 @@ const invokeArrayMethod = (runner, target, name, args, node) => {
786
784
  for (let index = start; index >= 0; index -= 1) {
787
785
  if (!(index in target))
788
786
  continue;
789
- accumulator = yield* apply([accumulator, target[index], index, target]);
787
+ accumulator = yield* apply([accumulator, target[index], index, receiver]);
790
788
  }
791
789
  return accumulator;
792
790
  }
793
791
  case "findLast":
794
792
  for (let index = length - 1; index >= 0; index -= 1) {
795
793
  const item = target[index];
796
- if (yield* apply([item, index, target]))
794
+ if (yield* apply([item, index, receiver]))
797
795
  return item;
798
796
  }
799
797
  return undefined;
800
798
  case "findLastIndex":
801
799
  for (let index = length - 1; index >= 0; index -= 1) {
802
- if (yield* apply([target[index], index, target]))
800
+ if (yield* apply([target[index], index, receiver]))
803
801
  return index;
804
802
  }
805
803
  return -1;
@@ -1,7 +1,7 @@
1
1
  import type { BlockStatement, Expression, Node, Pattern } from "acorn";
2
2
  import type { Effect } from "effect";
3
3
  import type { DiagnosticKind } from "../codemode.js";
4
- import type { SafeObject } from "../data.js";
4
+ import type { ProgramObject } from "./objects.js";
5
5
  import type { Values } from "../values.js";
6
6
  /** Any parsed node; the interpreter narrows on `type` and reads `loc` for diagnostics. */
7
7
  export type AstNode = Node;
@@ -23,7 +23,7 @@ export type StatementResult = {
23
23
  label?: string;
24
24
  };
25
25
  export type MemberReference = {
26
- target: SafeObject | Array<unknown> | Values.RegExp | Values.URL;
26
+ target: ProgramObject | Values.RegExp | Values.URL;
27
27
  key: PropertyKey;
28
28
  };
29
29
  export declare class CodeModeFunction {
@@ -0,0 +1,25 @@
1
+ /** An object owned by the program: own properties plus a prototype link. */
2
+ export declare class ProgramObject {
3
+ proto: ProgramObject | null;
4
+ readonly props: Map<PropertyKey, unknown>;
5
+ constructor(proto?: ProgramObject | null);
6
+ }
7
+ export declare class ProgramArray extends ProgramObject {
8
+ readonly items: Array<unknown>;
9
+ constructor(items?: Array<unknown>);
10
+ }
11
+ export declare class ProgramError extends ProgramObject {
12
+ readonly errorName: string;
13
+ constructor(errorName: string);
14
+ }
15
+ export declare const parseArrayIndex: (key: string | number) => number | undefined;
16
+ export declare const hasOwn: (target: ProgramObject, key: PropertyKey) => boolean;
17
+ export declare const getOwn: (target: ProgramObject, key: PropertyKey) => unknown;
18
+ export declare const get: (target: ProgramObject, key: PropertyKey) => unknown;
19
+ export declare const has: (target: ProgramObject, key: PropertyKey) => boolean;
20
+ export declare const set: (target: ProgramObject, key: PropertyKey, value: unknown) => boolean;
21
+ export declare const remove: (target: ProgramObject, key: PropertyKey) => boolean;
22
+ export declare const ownKeys: (target: ProgramObject) => Array<string | symbol>;
23
+ export declare const ownEntries: (target: ProgramObject) => Array<[string, unknown]>;
24
+ export declare const record: (entries: Record<string, unknown>) => ProgramObject;
25
+ export declare const assign: (target: ProgramObject, source: ProgramObject, skip?: ReadonlySet<PropertyKey>) => void;
@@ -0,0 +1,122 @@
1
+ import { AsyncIteratorSymbol, IteratorSymbol } from "./model.js";
2
+ /** An object owned by the program: own properties plus a prototype link. */
3
+ export class ProgramObject {
4
+ proto;
5
+ props = new Map();
6
+ constructor(proto = null) {
7
+ this.proto = proto;
8
+ }
9
+ }
10
+ export class ProgramArray extends ProgramObject {
11
+ items;
12
+ constructor(items = []) {
13
+ super();
14
+ this.items = items;
15
+ }
16
+ }
17
+ export class ProgramError extends ProgramObject {
18
+ errorName;
19
+ constructor(errorName) {
20
+ super();
21
+ this.errorName = errorName;
22
+ }
23
+ }
24
+ const MAX_ARRAY_LENGTH = 4_294_967_295;
25
+ export const parseArrayIndex = (key) => {
26
+ const property = String(key);
27
+ if (!/^(0|[1-9]\d*)$/.test(property))
28
+ return undefined;
29
+ const index = Number(property);
30
+ return index < MAX_ARRAY_LENGTH ? index : undefined;
31
+ };
32
+ const canonical = (key) => (typeof key === "symbol" ? key : String(key));
33
+ const index = (target, key) => target instanceof ProgramArray && typeof key === "string" ? parseArrayIndex(key) : undefined;
34
+ export const hasOwn = (target, key) => {
35
+ const name = canonical(key);
36
+ const at = index(target, name);
37
+ if (at !== undefined)
38
+ return at in target.items;
39
+ if (name === "length" && target instanceof ProgramArray)
40
+ return true;
41
+ return target.props.has(name);
42
+ };
43
+ export const getOwn = (target, key) => {
44
+ const name = canonical(key);
45
+ const at = index(target, name);
46
+ if (at !== undefined)
47
+ return target.items[at];
48
+ if (name === "length" && target instanceof ProgramArray)
49
+ return target.items.length;
50
+ return target.props.get(name);
51
+ };
52
+ export const get = (target, key) => {
53
+ for (let current = target; current !== null; current = current.proto) {
54
+ if (hasOwn(current, key))
55
+ return getOwn(current, key);
56
+ }
57
+ return undefined;
58
+ };
59
+ export const has = (target, key) => {
60
+ for (let current = target; current !== null; current = current.proto) {
61
+ if (hasOwn(current, key))
62
+ return true;
63
+ }
64
+ return false;
65
+ };
66
+ export const set = (target, key, value) => {
67
+ const name = canonical(key);
68
+ const at = index(target, name);
69
+ if (at !== undefined) {
70
+ ;
71
+ target.items[at] = value;
72
+ return true;
73
+ }
74
+ if (name === "length" && target instanceof ProgramArray) {
75
+ const length = typeof value === "number" ? value : Number(value);
76
+ if (!Number.isInteger(length) || length < 0 || length > 4_294_967_295)
77
+ return false;
78
+ target.items.length = length;
79
+ return true;
80
+ }
81
+ target.props.set(name, value);
82
+ return true;
83
+ };
84
+ export const remove = (target, key) => {
85
+ const name = canonical(key);
86
+ const at = index(target, name);
87
+ if (at !== undefined)
88
+ return delete target.items[at];
89
+ if (name === "length" && target instanceof ProgramArray)
90
+ return false;
91
+ target.props.delete(name);
92
+ return true;
93
+ };
94
+ // JS order: array indexes, integer-like keys ascending, other strings, then symbols.
95
+ export const ownKeys = (target) => {
96
+ const strings = [...target.props.keys()].filter((key) => typeof key === "string");
97
+ const symbols = [...target.props.keys()].filter((key) => typeof key === "symbol");
98
+ return [
99
+ ...(target instanceof ProgramArray ? Object.keys(target.items) : []),
100
+ ...strings.filter((key) => parseArrayIndex(key) !== undefined).sort((a, b) => Number(a) - Number(b)),
101
+ ...strings.filter((key) => parseArrayIndex(key) === undefined),
102
+ ...symbols,
103
+ ];
104
+ };
105
+ export const ownEntries = (target) => ownKeys(target)
106
+ .filter((key) => typeof key === "string")
107
+ .map((key) => [key, getOwn(target, key)]);
108
+ export const record = (entries) => {
109
+ const target = new ProgramObject();
110
+ for (const [key, value] of Object.entries(entries))
111
+ set(target, key, value);
112
+ return target;
113
+ };
114
+ export const assign = (target, source, skip) => {
115
+ for (const key of ownKeys(source)) {
116
+ if (skip?.has(key))
117
+ continue;
118
+ if (typeof key === "symbol" && key !== IteratorSymbol && key !== AsyncIteratorSymbol)
119
+ continue;
120
+ set(target, key, getOwn(source, key));
121
+ }
122
+ };