@opencode/codemode 2.0.12 → 2.0.14

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 (49) hide show
  1. package/dist/data.d.ts +5 -4
  2. package/dist/interpreter/callback.d.ts +10 -15
  3. package/dist/interpreter/callback.js +15 -7
  4. package/dist/interpreter/errors.d.ts +3 -3
  5. package/dist/interpreter/errors.js +1 -2
  6. package/dist/interpreter/execute.d.ts +2 -1
  7. package/dist/interpreter/extensions.d.ts +2 -1
  8. package/dist/interpreter/extensions.js +7 -35
  9. package/dist/interpreter/globals.d.ts +2 -1
  10. package/dist/interpreter/globals.js +1 -1
  11. package/dist/interpreter/interpreter.d.ts +8 -19
  12. package/dist/interpreter/interpreter.js +96 -98
  13. package/dist/interpreter/intrinsics.d.ts +3 -3
  14. package/dist/interpreter/intrinsics.js +2 -0
  15. package/dist/interpreter/model.d.ts +7 -8
  16. package/dist/interpreter/model.js +0 -1
  17. package/dist/interpreter/native.d.ts +5 -5
  18. package/dist/interpreter/objects.d.ts +148 -47
  19. package/dist/interpreter/objects.js +224 -26
  20. package/dist/interpreter/promises.d.ts +7 -7
  21. package/dist/interpreter/promises.js +32 -11
  22. package/dist/interpreter/references.d.ts +8 -7
  23. package/dist/interpreter/references.js +10 -46
  24. package/dist/interpreter/scope.d.ts +5 -4
  25. package/dist/stdlib/array.d.ts +2 -1
  26. package/dist/stdlib/array.js +29 -51
  27. package/dist/stdlib/bytes.js +14 -24
  28. package/dist/stdlib/collections.js +27 -30
  29. package/dist/stdlib/console.d.ts +2 -2
  30. package/dist/stdlib/console.js +6 -50
  31. package/dist/stdlib/date.js +1 -2
  32. package/dist/stdlib/headers.js +10 -22
  33. package/dist/stdlib/iterator.d.ts +1 -1
  34. package/dist/stdlib/iterator.js +294 -6
  35. package/dist/stdlib/json.js +3 -5
  36. package/dist/stdlib/math.js +5 -17
  37. package/dist/stdlib/number.js +8 -21
  38. package/dist/stdlib/object.d.ts +3 -4
  39. package/dist/stdlib/object.js +5 -16
  40. package/dist/stdlib/regexp.d.ts +3 -3
  41. package/dist/stdlib/regexp.js +1 -2
  42. package/dist/stdlib/string.js +10 -20
  43. package/dist/stdlib/url.d.ts +2 -1
  44. package/dist/stdlib/url.js +6 -16
  45. package/dist/stdlib/value.d.ts +0 -3
  46. package/dist/stdlib/value.js +2 -74
  47. package/dist/stdlib/web.js +1 -2
  48. package/dist/tool-runtime.js +38 -28
  49. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { CallSite, Throw, rangeError, typeError } from "./model.js";
4
4
  import { Callable, define, get, hidden, Arr, Fn, Obj, PromiseObj, record } from "./objects.js";
5
5
  import { constructor, fn, methods, native, receiver, requiresNew } from "./native.js";
6
6
  import { createAggregateErrorValue, locate, materialize, normalizeError } from "./errors.js";
7
- import { typeofValue } from "./references.js";
7
+ import { describeValue, typeofValue } from "./references.js";
8
8
  import { applyCollectionCallback, isSupportedCallback } from "./callback.js";
9
9
  // A `resolve`/`reject` handed to an executor or thenable: calling it settles the capability.
10
10
  const capability = (ctx, name, settle) => fn(ctx.builtins, name, 1, (_, args) => {
@@ -119,7 +119,7 @@ export const resolvePromise = (ctx, value) => {
119
119
  return Effect.succeed(value);
120
120
  return ctx.pending.createWithSelf((self) => resolvePromiseValue(ctx, value, self));
121
121
  };
122
- const promiseStatics = ["all", "allSettled", "race", "any", "resolve", "reject"];
122
+ const promiseStatics = ["all", "allSettled", "race", "any", "resolve", "reject", "withResolvers", "try"];
123
123
  const invokePromiseMethod = (ctx, name, args) => {
124
124
  if (name === "resolve") {
125
125
  return resolvePromise(ctx, args[0]);
@@ -127,10 +127,26 @@ const invokePromiseMethod = (ctx, name, args) => {
127
127
  if (name === "reject") {
128
128
  return ctx.pending.create(Effect.fail(new Throw(args[0])));
129
129
  }
130
+ if (name === "withResolvers") {
131
+ return Effect.map(promiseCapability(ctx), (made) => record(ctx.builtins.Object, { promise: made.promise, resolve: made.resolve, reject: made.reject }));
132
+ }
133
+ if (name === "try") {
134
+ if (typeofValue(args[0]) !== "function") {
135
+ throw typeError(`Promise.try expects a function, received ${describeValue(args[0])}.`);
136
+ }
137
+ return Effect.flatMap(Effect.exit(ctx.call(args[0], undefined, args.slice(1))), (called) => {
138
+ if (Exit.isSuccess(called))
139
+ return resolvePromise(ctx, called.value);
140
+ if (Cause.hasInterruptsOnly(called.cause))
141
+ return Effect.failCause(called.cause);
142
+ return ctx.pending.create(Effect.fail(Cause.squash(called.cause)));
143
+ });
144
+ }
130
145
  return ctx.pending.create(Effect.gen(function* () {
131
146
  const cursor = yield* ctx.iterate(args[0]);
132
- if (cursor === undefined)
133
- throw typeError(`Promise.${name} expects an array or other synchronous iterable.`);
147
+ if (cursor === undefined) {
148
+ throw typeError(`Promise.${name} expects a synchronous iterable, received ${describeValue(args[0])}.`);
149
+ }
134
150
  const items = [];
135
151
  while (true) {
136
152
  const step = yield* cursor.next;
@@ -188,22 +204,27 @@ const instanceMethod = (ctx, name, thisValue, args) => {
188
204
  const onRejected = reactionHandler(name === "then" ? args[1] : args[0], method);
189
205
  return chainReaction(ctx, promise, onFulfilled, onRejected, method);
190
206
  };
207
+ /** NewPromiseCapability: a pending promise with the resolve/reject callables that settle it exactly once. */
208
+ const promiseCapability = (ctx) => Effect.gen(function* () {
209
+ const deferred = Deferred.makeUnsafe();
210
+ const promise = yield* ctx.pending.createWithSelf((self) => Effect.flatMap(Deferred.await(deferred), (value) => resolvePromiseValue(ctx, value, self)));
211
+ const resolve = capability(ctx, "resolve", (value) => Deferred.doneUnsafe(deferred, Exit.succeed(value)));
212
+ const reject = capability(ctx, "reject", (value) => Deferred.doneUnsafe(deferred, Exit.fail(new Throw(value))));
213
+ return { promise, resolve, reject, deferred };
214
+ });
191
215
  const constructPromise = (ctx, executor) => {
192
216
  if (!(executor instanceof Fn)) {
193
217
  throw typeError("new Promise(...) expects an executor function (e.g. new Promise((resolve, reject) => { ... })).");
194
218
  }
195
219
  return Effect.gen(function* () {
196
- const deferred = Deferred.makeUnsafe();
197
- const promise = yield* ctx.pending.createWithSelf((self) => Effect.flatMap(Deferred.await(deferred), (value) => resolvePromiseValue(ctx, value, self)));
198
- const resolve = capability(ctx, "resolve", (value) => Deferred.doneUnsafe(deferred, Exit.succeed(value)));
199
- const reject = capability(ctx, "reject", (value) => Deferred.doneUnsafe(deferred, Exit.fail(new Throw(value))));
200
- const executed = yield* Effect.exit(ctx.call(executor, undefined, [resolve, reject]));
220
+ const made = yield* promiseCapability(ctx);
221
+ const executed = yield* Effect.exit(ctx.call(executor, undefined, [made.resolve, made.reject]));
201
222
  if (!Exit.isSuccess(executed)) {
202
223
  if (Cause.hasInterruptsOnly(executed.cause))
203
224
  return yield* Effect.failCause(executed.cause);
204
- Deferred.doneUnsafe(deferred, Exit.fail(Cause.squash(executed.cause)));
225
+ Deferred.doneUnsafe(made.deferred, Exit.fail(Cause.squash(executed.cause)));
205
226
  }
206
- return promise;
227
+ return made.promise;
207
228
  });
208
229
  };
209
230
  // Settle one reaction turn after the deciding member, after its existing reactions.
@@ -1,7 +1,8 @@
1
- /** Values that cannot cross the data boundary. */
2
- export declare const isRuntimeReference: (value: unknown) => boolean;
3
- export declare const containsRuntimeReference: (value: unknown) => boolean;
4
- export declare const containsOpaqueReference: (value: unknown) => boolean;
5
- export declare const rejectCircularInsertion: (container: object, value: unknown, label: string, seen?: Set<object>) => void;
6
- export declare const describeValue: (value: unknown) => string;
7
- export declare const typeofValue: (value: unknown) => string;
1
+ import { Obj, type Value } from "./objects.js";
2
+ /** Interpreter machinery that is never data, unlike a Date or Map, which cross some boundaries as copies. */
3
+ export declare const isOpaque: (value: Value) => boolean;
4
+ export declare const containsRuntimeReference: (value: Value) => boolean;
5
+ export declare const containsOpaqueReference: (value: Value) => boolean;
6
+ export declare const rejectCircularInsertion: (container: Obj, value: Value, label: string, seen?: Set<object>) => void;
7
+ export declare const describeValue: (value: Value) => string;
8
+ export declare const typeofValue: (value: Value) => string;
@@ -1,19 +1,8 @@
1
1
  import { ToolReference } from "../tool-runtime.js";
2
2
  import { invalidData } from "./model.js";
3
- import { Callable, getOwn, isWrapper, ownKeys, Arr, Bytes, DateObj, GeneratorObj, IteratorObj, MapObj, Obj, PromiseObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, HeadersObj, } from "./objects.js";
4
- /** Values that cannot cross the data boundary. */
5
- export const isRuntimeReference = (value) => value instanceof Callable ||
6
- value instanceof GeneratorObj ||
7
- value instanceof IteratorObj ||
8
- value instanceof ToolReference ||
9
- value instanceof PromiseObj ||
10
- isWrapper(value);
11
- function* childValues(value) {
12
- if (!(value instanceof Obj))
13
- return;
14
- for (const key of ownKeys(value))
15
- yield getOwn(value, key);
16
- }
3
+ import { Callable, getOwn, isRuntimeReference, Obj, Opaque, ownKeys } from "./objects.js";
4
+ /** Interpreter machinery that is never data, unlike a Date or Map, which cross some boundaries as copies. */
5
+ export const isOpaque = (value) => value instanceof Opaque || value instanceof ToolReference;
17
6
  // Depth-first search over a value tree. `match` stops the walk; `skip` prunes a subtree without matching it.
18
7
  const find = (value, match, skip, seen) => {
19
8
  const pending = [[value].values()];
@@ -26,17 +15,18 @@ const find = (value, match, skip, seen) => {
26
15
  const current = next.value;
27
16
  if (match(current))
28
17
  return true;
29
- if (current === null || typeof current !== "object" || skip(current) || seen.has(current))
18
+ if (!(current instanceof Obj) || skip(current) || seen.has(current))
30
19
  continue;
31
20
  seen.add(current);
32
- pending.push(childValues(current));
21
+ pending.push(ownKeys(current)
22
+ .map((key) => getOwn(current, key))
23
+ .values());
33
24
  }
34
25
  return false;
35
26
  };
36
27
  const never = () => false;
37
28
  export const containsRuntimeReference = (value) => find(value, isRuntimeReference, never, new Set());
38
- // Wrapper values are data here, not opaque interpreter references.
39
- export const containsOpaqueReference = (value) => find(value, (current) => !isWrapper(current) && isRuntimeReference(current), isWrapper, new Set());
29
+ export const containsOpaqueReference = (value) => find(value, isOpaque, (current) => isRuntimeReference(current) && !isOpaque(current), new Set());
40
30
  // Reject cycles before mutation so later boundary walks remain safe.
41
31
  export const rejectCircularInsertion = (container, value, label, seen = new Set()) => {
42
32
  if (find(value, (current) => current === container, isRuntimeReference, seen)) {
@@ -46,36 +36,10 @@ export const rejectCircularInsertion = (container, value, label, seen = new Set(
46
36
  export const describeValue = (value) => {
47
37
  if (value === null || value === undefined)
48
38
  return String(value);
49
- if (value instanceof Arr)
50
- return "an array";
51
- if (value instanceof PromiseObj)
52
- return "an un-awaited Promise";
39
+ if (value instanceof Obj)
40
+ return value.describe;
53
41
  if (value instanceof ToolReference)
54
42
  return "a tool reference";
55
- if (value instanceof DateObj)
56
- return "a Date";
57
- if (value instanceof RegExpObj)
58
- return "a RegExp";
59
- if (value instanceof MapObj)
60
- return "a Map";
61
- if (value instanceof SetObj)
62
- return "a Set";
63
- if (value instanceof URLObj)
64
- return "a URL";
65
- if (value instanceof URLSearchParamsObj)
66
- return "a URLSearchParams";
67
- if (value instanceof HeadersObj)
68
- return "a Headers";
69
- if (value instanceof Bytes)
70
- return "a Uint8Array";
71
- if (value instanceof GeneratorObj)
72
- return "a generator";
73
- if (value instanceof IteratorObj)
74
- return "an iterator";
75
- if (isRuntimeReference(value))
76
- return "a function";
77
- if (typeof value === "object")
78
- return "a data object";
79
43
  return `a ${typeof value}`;
80
44
  };
81
45
  export const typeofValue = (value) => {
@@ -1,12 +1,13 @@
1
1
  import { type AstNode, type Binding } from "./model.js";
2
+ import type { Value } from "./objects.js";
2
3
  export declare class ScopeStack {
3
4
  private readonly scopes;
4
5
  constructor(scopes: Array<Map<string, Binding>>);
5
6
  reserve(name: string, mutable: boolean, node: AstNode): void;
6
- initialize(name: string, value: unknown, node: AstNode): void;
7
- declare(name: string, value: unknown, mutable: boolean, node: AstNode): void;
8
- get(name: string, node: AstNode): unknown;
9
- set(name: string, value: unknown, node: AstNode): unknown;
7
+ initialize(name: string, value: Value, node: AstNode): void;
8
+ declare(name: string, value: Value, mutable: boolean, node: AstNode): void;
9
+ get(name: string, node: AstNode): Value;
10
+ set(name: string, value: Value, node: AstNode): Value;
10
11
  resolve(name: string): Binding | undefined;
11
12
  current(): Map<string, Binding>;
12
13
  push(scope?: Map<string, Binding>): void;
@@ -1,4 +1,5 @@
1
1
  import { Effect } from "effect";
2
+ import { type Value } from "../interpreter/objects.js";
2
3
  import type { Interpreter } from "../interpreter/interpreter.js";
3
- export declare const sortArray: <R>(ctx: Interpreter<R>, target: Array<unknown>, comparator: unknown, name: string) => Effect.Effect<Array<unknown>, unknown, R>;
4
+ export declare const sortArray: <R>(ctx: Interpreter<R>, target: Array<Value>, comparator: Value, name: string) => Effect.Effect<Array<Value>, unknown, R>;
4
5
  export declare const arrayGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
@@ -2,19 +2,18 @@ import { Effect } from "effect";
2
2
  import { constructor, methods, prototypeFrom, receiver } from "../interpreter/native.js";
3
3
  import { checkArrayLength, checkStringLength, MAX_ARRAY_LENGTH } from "../interpreter/limits.js";
4
4
  import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
- import { define, get, hidden, Arr, GeneratorObj, IteratorObj, Obj } from "../interpreter/objects.js";
5
+ import { define, get, hidden, Arr, GeneratorObj, hostIterator, Obj, PromiseObj, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
6
  import { describeValue, rejectCircularInsertion } from "../interpreter/references.js";
7
7
  import { applyCollectionCallback, invoke, preserveConsumerError } from "../interpreter/callback.js";
8
8
  import { compareText } from "../tool-runtime.js";
9
- import { coerceToNumber, coerceToString } from "./value.js";
10
9
  const arrayLikeSource = (source) => {
11
- if (source instanceof Obj && typeof get(source, "length") === "number") {
12
- const length = get(source, "length");
13
- const normalized = Number.isNaN(length) || length <= 0 ? 0 : Math.trunc(length);
14
- checkArrayLength(normalized);
15
- return { length: normalized, source };
10
+ // JS would treat a promise as an empty array-like; that would hide a missing `await`.
11
+ if (source instanceof Obj && !(source instanceof PromiseObj)) {
12
+ const length = Math.max(0, coerceToInteger(get(source, "length")));
13
+ checkArrayLength(length);
14
+ return { length, source };
16
15
  }
17
- throw invalidData(`Array.from expects an array, string, Map, Set, or array-like value, received ${describeValue(source)}.`);
16
+ throw invalidData(`Array.from expects an iterable or array-like value, received ${describeValue(source)}.`);
18
17
  };
19
18
  const arrayFrom = (ctx, args) => {
20
19
  const source = args[0];
@@ -24,7 +23,7 @@ const arrayFrom = (ctx, args) => {
24
23
  const cursor = yield* ctx.iterate(source);
25
24
  if (cursor === undefined) {
26
25
  if (source instanceof GeneratorObj) {
27
- throw typeError("Array.from expects a synchronous iterable or array-like value.");
26
+ throw typeError(`Array.from expects a synchronous iterable or array-like value, received ${describeValue(source)}.`);
28
27
  }
29
28
  const arrayLike = arrayLikeSource(source);
30
29
  const values = [];
@@ -40,7 +39,7 @@ const arrayFrom = (ctx, args) => {
40
39
  const step = yield* cursor.next;
41
40
  if (step.done)
42
41
  return new Arr(proto, values);
43
- values.push(apply === undefined ? step.value : yield* preserveConsumerError(cursor, apply([step.value, index])));
42
+ values.push(apply === undefined ? step.value : yield* preserveConsumerError(cursor.close, apply([step.value, index])));
44
43
  index += 1;
45
44
  }
46
45
  });
@@ -103,14 +102,7 @@ export const arrayGlobal = (ctx) => {
103
102
  ["from", 1, (_, args) => arrayFrom(ctx, args)],
104
103
  ]);
105
104
  const self = (thisValue, name) => receiver(Arr, thisValue, `Array.prototype.${name}`);
106
- const optNumber = (name, value, label) => {
107
- if (value === undefined)
108
- return undefined;
109
- if (typeof value !== "number") {
110
- throw typeError(`Array.${name} expects ${label} to be a number.`);
111
- }
112
- return value;
113
- };
105
+ const optNumber = (value) => (value === undefined ? undefined : coerceToInteger(value));
114
106
  // Callback methods fix the iteration length while reading existing elements live.
115
107
  const iterate = (name, length, body) => [
116
108
  name,
@@ -125,13 +117,9 @@ export const arrayGlobal = (ctx) => {
125
117
  "join",
126
118
  1,
127
119
  (thisValue, args) => {
128
- const target = self(thisValue, "join").items;
129
- if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
130
- throw typeError("Array.join expects zero arguments or one string separator.");
131
- }
132
- const joined = target
133
- .map((item) => coerceToString(item ?? ""))
134
- .join(args.length === 0 ? "," : args[0]);
120
+ const joined = self(thisValue, "join")
121
+ .items.map((item) => coerceToString(item ?? ""))
122
+ .join(args[0] === undefined ? "," : coerceToString(args[0]));
135
123
  checkStringLength(joined.length);
136
124
  return joined;
137
125
  },
@@ -147,33 +135,23 @@ export const arrayGlobal = (ctx) => {
147
135
  "includes",
148
136
  1,
149
137
  (thisValue, args) => {
150
- const target = self(thisValue, "includes").items;
151
- if (args.length === 0 || args.length > 2) {
152
- throw typeError("Array.includes expects a value and optional start index.");
153
- }
154
- return target.includes(args[0], optNumber("includes", args[1], "start index"));
138
+ return self(thisValue, "includes").items.includes(args[0], optNumber(args[1]));
155
139
  },
156
140
  ],
157
- [
158
- "indexOf",
159
- 1,
160
- (thisValue, args) => self(thisValue, "indexOf").items.indexOf(args[0], optNumber("indexOf", args[1], "start index")),
161
- ],
141
+ ["indexOf", 1, (thisValue, args) => self(thisValue, "indexOf").items.indexOf(args[0], optNumber(args[1]))],
162
142
  [
163
143
  "lastIndexOf",
164
144
  1,
165
145
  (thisValue, args) => {
166
146
  const target = self(thisValue, "lastIndexOf").items;
167
- return args[1] === undefined
168
- ? target.lastIndexOf(args[0])
169
- : target.lastIndexOf(args[0], optNumber("lastIndexOf", args[1], "start index"));
147
+ return args[1] === undefined ? target.lastIndexOf(args[0]) : target.lastIndexOf(args[0], optNumber(args[1]));
170
148
  },
171
149
  ],
172
- ["at", 1, (thisValue, args) => self(thisValue, "at").items.at(optNumber("at", args[0], "index") ?? 0)],
150
+ ["at", 1, (thisValue, args) => self(thisValue, "at").items.at(optNumber(args[0]) ?? 0)],
173
151
  [
174
152
  "slice",
175
153
  2,
176
- (thisValue, args) => wrap(self(thisValue, "slice").items.slice(optNumber("slice", args[0], "start"), optNumber("slice", args[1], "end"))),
154
+ (thisValue, args) => wrap(self(thisValue, "slice").items.slice(optNumber(args[0]), optNumber(args[1]))),
177
155
  ],
178
156
  [
179
157
  "concat",
@@ -189,7 +167,7 @@ export const arrayGlobal = (ctx) => {
189
167
  0,
190
168
  (thisValue, args) => {
191
169
  const flatten = (items, depth) => items.flatMap((item) => (item instanceof Arr && depth > 0 ? flatten(item.items, depth - 1) : [item]));
192
- const flattened = flatten(self(thisValue, "flat").items, optNumber("flat", args[0], "depth") ?? 1);
170
+ const flattened = flatten(self(thisValue, "flat").items, optNumber(args[0]) ?? 1);
193
171
  checkArrayLength(flattened.length);
194
172
  return wrap(flattened);
195
173
  },
@@ -234,7 +212,7 @@ export const arrayGlobal = (ctx) => {
234
212
  2,
235
213
  (thisValue, args) => {
236
214
  const target = self(thisValue, "with").items;
237
- const index = optNumber("with", args[0], "index") ?? 0;
215
+ const index = optNumber(args[0]) ?? 0;
238
216
  const resolved = index < 0 ? target.length + index : index;
239
217
  if (resolved < 0 || resolved >= target.length)
240
218
  throw rangeError("Array.with index is out of range.");
@@ -273,10 +251,10 @@ export const arrayGlobal = (ctx) => {
273
251
  const target = self(thisValue, "splice");
274
252
  if (args.length === 0)
275
253
  return wrap(target.items.splice(0, 0));
276
- const start = optNumber("splice", args[0], "start") ?? 0;
254
+ const start = optNumber(args[0]) ?? 0;
277
255
  if (args.length === 1)
278
256
  return wrap(target.items.splice(start));
279
- const deleteCount = optNumber("splice", args[1], "delete count") ?? 0;
257
+ const deleteCount = optNumber(args[1]) ?? 0;
280
258
  const inserted = args.slice(2);
281
259
  for (const item of inserted)
282
260
  rejectCircularInsertion(target, item, "Array.splice result");
@@ -290,11 +268,11 @@ export const arrayGlobal = (ctx) => {
290
268
  const copied = [...self(thisValue, "toSpliced").items];
291
269
  if (args.length === 0)
292
270
  return wrap(copied);
293
- const start = optNumber("toSpliced", args[0], "start") ?? 0;
271
+ const start = optNumber(args[0]) ?? 0;
294
272
  if (args.length === 1)
295
273
  copied.splice(start);
296
274
  else
297
- copied.splice(start, optNumber("toSpliced", args[1], "delete count") ?? 0, ...args.slice(2));
275
+ copied.splice(start, optNumber(args[1]) ?? 0, ...args.slice(2));
298
276
  return wrap(copied);
299
277
  },
300
278
  ],
@@ -304,7 +282,7 @@ export const arrayGlobal = (ctx) => {
304
282
  (thisValue, args) => {
305
283
  const target = self(thisValue, "fill");
306
284
  rejectCircularInsertion(target, args[0], "Array.fill result");
307
- target.items.fill(args[0], optNumber("fill", args[1], "start"), optNumber("fill", args[2], "end"));
285
+ target.items.fill(args[0], optNumber(args[1]), optNumber(args[2]));
308
286
  return target;
309
287
  },
310
288
  ],
@@ -313,7 +291,7 @@ export const arrayGlobal = (ctx) => {
313
291
  2,
314
292
  (thisValue, args) => {
315
293
  const target = self(thisValue, "copyWithin");
316
- target.items.copyWithin(optNumber("copyWithin", args[0], "target index") ?? 0, optNumber("copyWithin", args[1], "start") ?? 0, optNumber("copyWithin", args[2], "end"));
294
+ target.items.copyWithin(optNumber(args[0]) ?? 0, optNumber(args[1]) ?? 0, optNumber(args[2]));
317
295
  return target;
318
296
  },
319
297
  ],
@@ -324,12 +302,12 @@ export const arrayGlobal = (ctx) => {
324
302
  ? Effect.succeed("")
325
303
  : Effect.map(invoke(ctx, item, "toLocaleString", "Array.prototype.toLocaleString"), coerceToString)), (parts) => parts.join(",")),
326
304
  ],
327
- ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").items.keys())],
328
- ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").items.values())],
305
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").items.keys())],
306
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").items.values())],
329
307
  [
330
308
  "entries",
331
309
  0,
332
- (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
310
+ (thisValue) => hostIterator(builtins, self(thisValue, "entries")
333
311
  .items.entries()
334
312
  .map(([index, item]) => wrap([index, item]))),
335
313
  ],
@@ -2,9 +2,8 @@ import { Effect } from "effect";
2
2
  import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
3
3
  import { constructor, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
4
4
  import { IteratorSymbol, rangeError, syntaxError, typeError } from "../interpreter/model.js";
5
- import { define, defineAccessor, get, hidden, Arr, Bytes, IteratorObj, Obj } from "../interpreter/objects.js";
5
+ import { define, defineAccessor, get, hidden, Arr, Bytes, hostIterator, Obj, coerceToInteger, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
6
  import { describeValue } from "../interpreter/references.js";
7
- import { coerceToNumber, coerceToString } from "./value.js";
8
7
  /** The bytes a Uint8Array, array, or other iterable of numbers describes; the host array clamps each value. */
9
8
  const collectBytes = (ctx, source, name) => {
10
9
  if (source instanceof Bytes)
@@ -62,34 +61,28 @@ export const uint8ArrayGlobal = (ctx) => {
62
61
  ["fromHex", 1, (_, args) => decode("fromHex", args, (text) => Uint8Array.fromHex(text))],
63
62
  ]);
64
63
  const self = (thisValue, name) => receiver(Bytes, thisValue, `Uint8Array.prototype.${name}`);
65
- const optNumber = (name, value, label) => {
66
- if (value === undefined)
67
- return undefined;
68
- if (typeof value !== "number")
69
- throw typeError(`Uint8Array.${name} expects ${label} to be a number.`);
70
- return value;
71
- };
64
+ const optNumber = (value) => (value === undefined ? undefined : coerceToInteger(value));
72
65
  const wrapAll = (items) => new Arr(builtins.Array, items);
73
66
  defineAccessor(proto, "length", (thisValue) => self(thisValue, "length").bytes.length);
74
67
  methods(builtins, proto, [
75
- ["at", 1, (thisValue, args) => self(thisValue, "at").bytes.at(optNumber("at", args[0], "index") ?? 0)],
68
+ ["at", 1, (thisValue, args) => self(thisValue, "at").bytes.at(optNumber(args[0]) ?? 0)],
76
69
  [
77
70
  "slice",
78
71
  2,
79
- (thisValue, args) => wrap(self(thisValue, "slice").bytes.slice(optNumber("slice", args[0], "start"), optNumber("slice", args[1], "end"))),
72
+ (thisValue, args) => wrap(self(thisValue, "slice").bytes.slice(optNumber(args[0]), optNumber(args[1]))),
80
73
  ],
81
74
  // A view on the same bytes, as in JS: writes through one are visible through the other.
82
75
  [
83
76
  "subarray",
84
77
  2,
85
- (thisValue, args) => wrap(self(thisValue, "subarray").bytes.subarray(optNumber("subarray", args[0], "start"), optNumber("subarray", args[1], "end"))),
78
+ (thisValue, args) => wrap(self(thisValue, "subarray").bytes.subarray(optNumber(args[0]), optNumber(args[1]))),
86
79
  ],
87
80
  [
88
81
  "set",
89
82
  1,
90
83
  (thisValue, args) => {
91
84
  const target = self(thisValue, "set");
92
- const offset = optNumber("set", args[1], "offset") ?? 0;
85
+ const offset = optNumber(args[1]) ?? 0;
93
86
  return Effect.map(collectBytes(ctx, args[0], "Uint8Array.set"), (source) => {
94
87
  if (!Number.isInteger(offset) || offset < 0 || source.length + offset > target.bytes.length) {
95
88
  throw rangeError("Uint8Array.set: the source does not fit at that offset.");
@@ -104,7 +97,7 @@ export const uint8ArrayGlobal = (ctx) => {
104
97
  1,
105
98
  (thisValue, args) => {
106
99
  const target = self(thisValue, "fill");
107
- target.bytes.fill(coerceToNumber(args[0]), optNumber("fill", args[1], "start"), optNumber("fill", args[2], "end"));
100
+ target.bytes.fill(coerceToNumber(args[0]), optNumber(args[1]), optNumber(args[2]));
108
101
  return target;
109
102
  },
110
103
  ],
@@ -120,7 +113,7 @@ export const uint8ArrayGlobal = (ctx) => {
120
113
  [
121
114
  "indexOf",
122
115
  1,
123
- (thisValue, args) => self(thisValue, "indexOf").bytes.indexOf(coerceToNumber(args[0]), optNumber("indexOf", args[1], "start index")),
116
+ (thisValue, args) => self(thisValue, "indexOf").bytes.indexOf(coerceToNumber(args[0]), optNumber(args[1])),
124
117
  ],
125
118
  [
126
119
  "lastIndexOf",
@@ -129,22 +122,19 @@ export const uint8ArrayGlobal = (ctx) => {
129
122
  const target = self(thisValue, "lastIndexOf").bytes;
130
123
  return args[1] === undefined
131
124
  ? target.lastIndexOf(coerceToNumber(args[0]))
132
- : target.lastIndexOf(coerceToNumber(args[0]), optNumber("lastIndexOf", args[1], "start index"));
125
+ : target.lastIndexOf(coerceToNumber(args[0]), optNumber(args[1]));
133
126
  },
134
127
  ],
135
128
  [
136
129
  "includes",
137
130
  1,
138
- (thisValue, args) => self(thisValue, "includes").bytes.includes(coerceToNumber(args[0]), optNumber("includes", args[1], "start index")),
131
+ (thisValue, args) => self(thisValue, "includes").bytes.includes(coerceToNumber(args[0]), optNumber(args[1])),
139
132
  ],
140
133
  [
141
134
  "join",
142
135
  1,
143
136
  (thisValue, args) => {
144
- if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
145
- throw typeError("Uint8Array.join expects zero arguments or one string separator.");
146
- }
147
- const joined = self(thisValue, "join").bytes.join(args.length === 0 ? "," : args[0]);
137
+ const joined = self(thisValue, "join").bytes.join(args[0] === undefined ? "," : coerceToString(args[0]));
148
138
  checkStringLength(joined.length);
149
139
  return joined;
150
140
  },
@@ -152,12 +142,12 @@ export const uint8ArrayGlobal = (ctx) => {
152
142
  ["toString", 0, (thisValue) => self(thisValue, "toString").bytes.join(",")],
153
143
  ["toBase64", 0, (thisValue) => self(thisValue, "toBase64").bytes.toBase64()],
154
144
  ["toHex", 0, (thisValue) => self(thisValue, "toHex").bytes.toHex()],
155
- ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").bytes.keys())],
156
- ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").bytes.values())],
145
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").bytes.keys())],
146
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").bytes.values())],
157
147
  [
158
148
  "entries",
159
149
  0,
160
- (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
150
+ (thisValue) => hostIterator(builtins, self(thisValue, "entries")
161
151
  .bytes.entries()
162
152
  .map(([index, byte]) => wrapAll([index, byte]))),
163
153
  ],