@opencode/codemode 0.0.0-dev-19942 → 0.0.0-dev-19959

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.
@@ -1,14 +1,9 @@
1
1
  import { Effect } from "effect";
2
2
  import type { Interpreter } from "./interpreter.js";
3
- import { Callable, type Value } from "./objects.js";
4
- export type IteratorCursor<R> = {
5
- readonly next: Effect.Effect<{
6
- readonly done: boolean;
7
- readonly value: Value;
8
- }, unknown, R>;
9
- readonly close: Effect.Effect<void, unknown, R>;
10
- };
11
- export declare const preserveConsumerError: <A, R>(cursor: IteratorCursor<R>, effect: Effect.Effect<A, unknown, R>) => Effect.Effect<A, unknown, R>;
3
+ import { Callable, type Cursor, type Value } from "./objects.js";
4
+ /** IteratorClose: a consumer failure closes the iterator and wins over any close failure, except that a generator's
5
+ * return() is a return completion, so a failing close wins over it, as after `break`. */
6
+ export declare const preserveConsumerError: <A, R>(close: Cursor<R>["close"], effect: Effect.Effect<A, unknown, R>) => Effect.Effect<A, unknown, R>;
12
7
  /**
13
8
  * ToPrimitive: calls `valueOf`/`toString` in hint order and returns the first primitive result. Dates treat the
14
9
  * default hint as "string", like their `Symbol.toPrimitive`.
@@ -1,11 +1,20 @@
1
- import { Effect, Exit } from "effect";
1
+ import { Cause, Effect, Exit } from "effect";
2
2
  import { primitivePrototype } from "./intrinsics.js";
3
- import { typeError } from "./model.js";
4
- import { Callable, get, Native, DateObj, Obj, coerceToNumber, coerceToString } from "./objects.js";
3
+ import { GeneratorReturn, typeError } from "./model.js";
4
+ import { Callable, get, Native, DateObj, Obj, coerceToNumber, coerceToString, } from "./objects.js";
5
5
  import { typeofValue } from "./references.js";
6
- export const preserveConsumerError = (cursor, effect) => Effect.flatMap(Effect.exit(effect), (exit) => Exit.isSuccess(exit)
7
- ? Effect.succeed(exit.value)
8
- : Effect.andThen(Effect.exit(cursor.close), Effect.failCause(exit.cause)));
6
+ /** IteratorClose: a consumer failure closes the iterator and wins over any close failure, except that a generator's
7
+ * return() is a return completion, so a failing close wins over it, as after `break`. */
8
+ export const preserveConsumerError = (close, effect) => Effect.flatMap(Effect.exit(effect), (exit) => {
9
+ if (Exit.isSuccess(exit))
10
+ return Effect.succeed(exit.value);
11
+ return Effect.flatMap(Effect.exit(close), (closed) => {
12
+ if (!Exit.isSuccess(closed) && Cause.squash(exit.cause) instanceof GeneratorReturn) {
13
+ return Effect.failCause(closed.cause);
14
+ }
15
+ return Effect.failCause(exit.cause);
16
+ });
17
+ });
9
18
  /**
10
19
  * ToPrimitive: calls `valueOf`/`toString` in hint order and returns the first primitive result. Dates treat the
11
20
  * default hint as "string", like their `Symbol.toPrimitive`.
@@ -60,6 +60,7 @@ const table = {
60
60
  console: (ctx) => consoleGlobal(ctx),
61
61
  Promise: (ctx) => promiseGlobal(ctx),
62
62
  Symbol: (ctx) => symbolGlobal(ctx),
63
+ Iterator: (ctx) => iteratorGlobals(ctx),
63
64
  Number: (ctx) => numberGlobal(ctx),
64
65
  String: (ctx) => stringGlobal(ctx),
65
66
  Boolean: (ctx) => booleanGlobal(ctx),
@@ -91,6 +92,5 @@ export const globalNames = new Set(Object.keys(table));
91
92
  /** The immutable global bindings of every program, in declaration order. */
92
93
  export const globals = (ctx) => {
93
94
  generatorGlobals(ctx);
94
- iteratorGlobals(ctx);
95
95
  return Object.entries(table).map(([name, factory]) => [name, factory(ctx)]);
96
96
  };
@@ -3,7 +3,7 @@ import { Effect } from "effect";
3
3
  import { type Json } from "../data.js";
4
4
  import { type ToolRuntime } from "../tool-runtime.js";
5
5
  import { type Builtins } from "./intrinsics.js";
6
- import { PromiseObj, type Value } from "./objects.js";
6
+ import { type Cursor, PromiseObj, type Value } from "./objects.js";
7
7
  import { Pending } from "./promises.js";
8
8
  /** One program execution: the tool bridge, promise scheduler, captured logs, and the global scope built once. */
9
9
  export declare class Interpreter<R> {
@@ -22,13 +22,8 @@ export declare class Interpreter<R> {
22
22
  run(program: Program): Effect.Effect<Value, unknown, R>;
23
23
  call(callable: Value, thisValue: Value, args: Array<Value>): Effect.Effect<Value, unknown, R>;
24
24
  await(promise: PromiseObj): Effect.Effect<Value, unknown, never>;
25
- iterate(value: Value): Effect.Effect<{
26
- next: Effect.Effect<{
27
- done: boolean;
28
- value: Value;
29
- }, unknown, R>;
30
- close: Effect.Effect<void, unknown, R>;
31
- } | undefined, unknown, R>;
25
+ iterate(value: Value): Effect.Effect<Cursor<R> | undefined, unknown, R>;
26
+ iterateDirect(value: Value): Cursor<R>;
32
27
  /** Runs one host tool: arguments cross as JSON and the result comes back as program values. */
33
28
  tool(run: (args: Array<Json | undefined>) => Effect.Effect<Json | undefined, unknown, R>, args: Array<Value>): Effect.Effect<Value, unknown, R>;
34
29
  }
@@ -6,7 +6,7 @@ import { checkStringLength } from "./limits.js";
6
6
  import { locate, materialize } from "./errors.js";
7
7
  import { primitivePrototype } from "./intrinsics.js";
8
8
  import { globals } from "./globals.js";
9
- import { assign, Callable, define, get, has, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
9
+ import { assign, Callable, define, get, hostCursor, IteratorObj, has, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
10
10
  import { preserveConsumerError } from "./callback.js";
11
11
  import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js";
12
12
  import { containsOpaqueReference, describeValue, rejectCircularInsertion, typeofValue } from "./references.js";
@@ -188,6 +188,9 @@ export class Interpreter {
188
188
  iterate(value) {
189
189
  return this.root.iterate(value);
190
190
  }
191
+ iterateDirect(value) {
192
+ return this.root.iterateDirect(value);
193
+ }
191
194
  /** Runs one host tool: arguments cross as JSON and the result comes back as program values. */
192
195
  tool(run, args) {
193
196
  const ctx = this;
@@ -492,7 +495,7 @@ class Frame {
492
495
  if (declared?.lexical)
493
496
  self.predeclarePattern(declared.pattern, declared.mutable, left);
494
497
  const right = yield* self.evaluateExpression(node.right);
495
- const cursor = self.hostCursor(right);
498
+ const cursor = self.builtinCursor(right);
496
499
  const iterator = cursor === undefined ? yield* self.customIterator(right, node, awaiting) : undefined;
497
500
  if (iterator === undefined && cursor === undefined) {
498
501
  throw invalidData(`${awaiting ? "for await...of" : "for...of"} requires an iterable value, received ${describeValue(right)}.`, node);
@@ -533,10 +536,9 @@ class Frame {
533
536
  const bodyExit = yield* Effect.exit(evaluateBody(step.value));
534
537
  if (!Exit.isSuccess(bodyExit)) {
535
538
  // Process interruption must remain prompt; user cleanup cannot extend a timeout.
536
- if (!Cause.hasInterruptsOnly(bodyExit.cause)) {
537
- yield* Effect.exit(close());
538
- }
539
- return yield* Effect.failCause(bodyExit.cause);
539
+ if (Cause.hasInterruptsOnly(bodyExit.cause))
540
+ return yield* Effect.failCause(bodyExit.cause);
541
+ return yield* preserveConsumerError(close(), Effect.failCause(bodyExit.cause));
540
542
  }
541
543
  const exit = loopExit(bodyExit.value, labels);
542
544
  if (exit !== undefined) {
@@ -565,32 +567,40 @@ class Frame {
565
567
  });
566
568
  }
567
569
  iterate(value, node) {
568
- const cursor = this.hostCursor(value);
570
+ const cursor = this.builtinCursor(value);
569
571
  if (cursor !== undefined)
570
572
  return Effect.succeed(cursor);
571
- const self = this;
572
- return Effect.map(this.customIterator(value, node, false), (iterator) => iterator === undefined
573
- ? undefined
574
- : {
575
- next: self.nextIteratorResult(iterator, node, false),
576
- close: Effect.suspend(() => self.closeIterator(iterator, node, false)),
577
- });
573
+ return Effect.map(this.customIterator(value, node, false), (iterator) => iterator === undefined ? undefined : this.customCursor(iterator, node));
574
+ }
575
+ /** GetIteratorDirect: drive an iterator by its own `next`, without asking for `[Symbol.iterator]`. */
576
+ iterateDirect(value, node) {
577
+ if (value instanceof IteratorObj)
578
+ return value.cursor;
579
+ if (!(value instanceof Obj)) {
580
+ throw typeError(`An iterator must be an object, received ${describeValue(value)}.`, node);
581
+ }
582
+ return this.customCursor({
583
+ iterator: value,
584
+ next: this.requireIteratorMethod(get(value, "next"), "Iterator next", node),
585
+ asynchronous: false,
586
+ }, node);
578
587
  }
579
- hostCursor(value) {
588
+ customCursor(iterator, node) {
589
+ return {
590
+ next: this.nextIteratorResult(iterator, node, false),
591
+ close: Effect.suspend(() => this.closeIterator(iterator, node, false)),
592
+ };
593
+ }
594
+ builtinCursor(value) {
595
+ // Natives build their cursors without knowing R, like `lift` in native.ts.
596
+ if (value instanceof IteratorObj)
597
+ return value.cursor;
580
598
  const iterator = typeof value === "string"
581
599
  ? value[Symbol.iterator]()
582
600
  : value instanceof Obj
583
601
  ? value.iterator(this.ctx.builtins)
584
602
  : undefined;
585
- if (iterator === undefined)
586
- return undefined;
587
- return {
588
- next: Effect.sync(() => {
589
- const step = iterator.next();
590
- return { done: Boolean(step.done), value: step.value };
591
- }),
592
- close: Effect.void,
593
- };
603
+ return iterator === undefined ? undefined : hostCursor(iterator);
594
604
  }
595
605
  customIterator(value, node, allowAsync = true) {
596
606
  if (!(value instanceof Obj))
@@ -933,7 +943,7 @@ class Frame {
933
943
  return;
934
944
  }
935
945
  const consumed = consume(element, step.done ? undefined : step.value, pattern);
936
- yield* step.done ? consumed : preserveConsumerError(cursor, consumed);
946
+ yield* step.done ? consumed : preserveConsumerError(cursor.close, consumed);
937
947
  }
938
948
  if (!done)
939
949
  yield* cursor.close;
@@ -1505,7 +1515,7 @@ class Frame {
1505
1515
  delegateYield(value, node) {
1506
1516
  const self = this;
1507
1517
  return Effect.gen(function* () {
1508
- const cursor = self.hostCursor(value);
1518
+ const cursor = self.builtinCursor(value);
1509
1519
  if (cursor !== undefined) {
1510
1520
  while (true) {
1511
1521
  const step = yield* cursor.next;
@@ -2,7 +2,7 @@ import { ErrorObj, Obj, type Value } from "./objects.js";
2
2
  export declare const errorTypes: readonly ["Error", "TypeError", "RangeError", "SyntaxError", "ReferenceError", "EvalError", "URIError", "AggregateError"];
3
3
  export type ErrorType = (typeof errorTypes)[number];
4
4
  export declare const isErrorType: (name: string) => name is ErrorType;
5
- declare const builtins: readonly ["Object", "Function", "Array", "String", "Number", "Boolean", "Date", "RegExp", "Map", "Set", "URL", "URLSearchParams", "Headers", "Uint8Array", "TextEncoder", "TextDecoder", "Promise", "Iterator", "AsyncIterator", "Generator", "AsyncGenerator"];
5
+ declare const builtins: readonly ["Object", "Function", "Array", "String", "Number", "Boolean", "Date", "RegExp", "Map", "Set", "URL", "URLSearchParams", "Headers", "Uint8Array", "TextEncoder", "TextDecoder", "Promise", "Iterator", "IteratorHelper", "AsyncIterator", "Generator", "AsyncGenerator"];
6
6
  /**
7
7
  * The built-in prototype objects of one runtime, allocated empty in dependency order. The globals populate them
8
8
  * and attach their constructors when the runtime is built.
@@ -30,6 +30,7 @@ const builtins = [
30
30
  "TextDecoder",
31
31
  "Promise",
32
32
  "Iterator",
33
+ "IteratorHelper",
33
34
  "AsyncIterator",
34
35
  "Generator",
35
36
  "AsyncGenerator",
@@ -85,6 +86,7 @@ export const createBuiltins = () => {
85
86
  TextDecoder: plain(),
86
87
  Promise: plain(),
87
88
  Iterator: iterator,
89
+ IteratorHelper: new Obj(iterator),
88
90
  AsyncIterator: asyncIterator,
89
91
  Generator: new Obj(iterator),
90
92
  AsyncGenerator: new Obj(asyncIterator),
@@ -1,5 +1,5 @@
1
1
  import type { BlockStatement, Expression, Pattern } from "acorn";
2
- import type { Effect, Fiber } from "effect";
2
+ import { Effect, type Fiber } from "effect";
3
3
  import { ToolReference } from "../tool-runtime.js";
4
4
  import type { Builtins } from "./intrinsics.js";
5
5
  import { type Binding, type GeneratorRequestKind, type PendingThrow } from "./model.js";
@@ -73,6 +73,7 @@ export declare class ErrorObj extends Obj {
73
73
  host?: PendingThrow;
74
74
  /** Error.prototype.toString: "name: message", or just one when the other is empty. */
75
75
  toString(): string;
76
+ inspect(): string;
76
77
  }
77
78
  /** Interpreter machinery a program can hold but never inspect, serialize, or hand to the host. */
78
79
  export declare abstract class Opaque extends Obj {
@@ -122,14 +123,27 @@ export declare class GeneratorObj extends Opaque {
122
123
  constructor(proto: Obj, asynchronous: boolean, request: (kind: GeneratorRequestKind, value: Value) => Effect.Effect<Value, unknown, unknown>);
123
124
  get describe(): string;
124
125
  }
125
- /** A built-in collection iterator: live over the host collection, yielding program values. */
126
+ /** One pull from an iterator, as `for...of` sees it. */
127
+ export type Step = {
128
+ readonly done: boolean;
129
+ readonly value: Value;
130
+ };
131
+ /** How the interpreter drives any iterator: pull the next step, or close it early. */
132
+ export type Cursor<R = unknown> = {
133
+ readonly next: Effect.Effect<Step, unknown, R>;
134
+ readonly close: Effect.Effect<void, unknown, R>;
135
+ };
136
+ /** A cursor over a host iterator; there is nothing to close. */
137
+ export declare const hostCursor: (iterator: Iterator<Value, undefined>) => Cursor<never>;
138
+ /** A built-in iterator: a live cursor over a host collection or an iterator helper, yielding program values. */
126
139
  export declare class IteratorObj extends Opaque {
127
- readonly source: IteratorObject<Value, undefined>;
140
+ readonly cursor: Cursor;
128
141
  readonly tag = "Iterator";
129
- constructor(proto: Obj, source: IteratorObject<Value, undefined>);
142
+ constructor(proto: Obj, cursor: Cursor);
130
143
  get describe(): string;
131
- iterator(): IteratorObject<Value, undefined, unknown>;
132
144
  }
145
+ /** A built-in iterator over a host iterator, e.g. `array.values()`. */
146
+ export declare const hostIterator: (builtins: Builtins, iterator: Iterator<Value, undefined>) => IteratorObj;
133
147
  /** A built-in object around a host value: data-like, so it prints as itself and crosses to extensions as a copy. */
134
148
  export declare abstract class Wrapper extends Obj {
135
149
  inspect(item: (value: Value) => string): string;
@@ -1,3 +1,4 @@
1
+ import { Effect } from "effect";
1
2
  import { ToolReference } from "../tool-runtime.js";
2
3
  import { checkArrayLength } from "./limits.js";
3
4
  import { AsyncIteratorSymbol, IteratorSymbol, } from "./model.js";
@@ -96,6 +97,9 @@ export class ErrorObj extends Obj {
96
97
  return shownMessage;
97
98
  return `${shownName}: ${shownMessage}`;
98
99
  }
100
+ inspect() {
101
+ return this.toString();
102
+ }
99
103
  }
100
104
  /** Interpreter machinery a program can hold but never inspect, serialize, or hand to the host. */
101
105
  export class Opaque extends Obj {
@@ -168,21 +172,28 @@ export class GeneratorObj extends Opaque {
168
172
  return "a generator";
169
173
  }
170
174
  }
171
- /** A built-in collection iterator: live over the host collection, yielding program values. */
175
+ /** A cursor over a host iterator; there is nothing to close. */
176
+ export const hostCursor = (iterator) => ({
177
+ next: Effect.sync(() => {
178
+ const step = iterator.next();
179
+ return { done: Boolean(step.done), value: step.value };
180
+ }),
181
+ close: Effect.void,
182
+ });
183
+ /** A built-in iterator: a live cursor over a host collection or an iterator helper, yielding program values. */
172
184
  export class IteratorObj extends Opaque {
173
- source;
185
+ cursor;
174
186
  tag = "Iterator";
175
- constructor(proto, source) {
187
+ constructor(proto, cursor) {
176
188
  super(proto);
177
- this.source = source;
189
+ this.cursor = cursor;
178
190
  }
179
191
  get describe() {
180
192
  return "an iterator";
181
193
  }
182
- iterator() {
183
- return this.source;
184
- }
185
194
  }
195
+ /** A built-in iterator over a host iterator, e.g. `array.values()`. */
196
+ export const hostIterator = (builtins, iterator) => new IteratorObj(builtins.Iterator, hostCursor(iterator));
186
197
  /** A built-in object around a host value: data-like, so it prints as itself and crosses to extensions as a copy. */
187
198
  export class Wrapper extends Obj {
188
199
  inspect(item) {
@@ -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.
@@ -2,7 +2,7 @@ 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, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
5
+ import { define, get, hidden, Arr, GeneratorObj, hostIterator, Obj, 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";
@@ -13,7 +13,7 @@ const arrayLikeSource = (source) => {
13
13
  checkArrayLength(normalized);
14
14
  return { length: normalized, source };
15
15
  }
16
- 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)}.`);
17
17
  };
18
18
  const arrayFrom = (ctx, args) => {
19
19
  const source = args[0];
@@ -23,7 +23,7 @@ const arrayFrom = (ctx, args) => {
23
23
  const cursor = yield* ctx.iterate(source);
24
24
  if (cursor === undefined) {
25
25
  if (source instanceof GeneratorObj) {
26
- 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)}.`);
27
27
  }
28
28
  const arrayLike = arrayLikeSource(source);
29
29
  const values = [];
@@ -39,7 +39,7 @@ const arrayFrom = (ctx, args) => {
39
39
  const step = yield* cursor.next;
40
40
  if (step.done)
41
41
  return new Arr(proto, values);
42
- 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])));
43
43
  index += 1;
44
44
  }
45
45
  });
@@ -323,12 +323,12 @@ export const arrayGlobal = (ctx) => {
323
323
  ? Effect.succeed("")
324
324
  : Effect.map(invoke(ctx, item, "toLocaleString", "Array.prototype.toLocaleString"), coerceToString)), (parts) => parts.join(",")),
325
325
  ],
326
- ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").items.keys())],
327
- ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").items.values())],
326
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").items.keys())],
327
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").items.values())],
328
328
  [
329
329
  "entries",
330
330
  0,
331
- (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
331
+ (thisValue) => hostIterator(builtins, self(thisValue, "entries")
332
332
  .items.entries()
333
333
  .map(([index, item]) => wrap([index, item]))),
334
334
  ],
@@ -2,7 +2,7 @@ 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, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
5
+ import { define, defineAccessor, get, hidden, Arr, Bytes, hostIterator, Obj, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
6
  import { describeValue } from "../interpreter/references.js";
7
7
  /** The bytes a Uint8Array, array, or other iterable of numbers describes; the host array clamps each value. */
8
8
  const collectBytes = (ctx, source, name) => {
@@ -151,12 +151,12 @@ export const uint8ArrayGlobal = (ctx) => {
151
151
  ["toString", 0, (thisValue) => self(thisValue, "toString").bytes.join(",")],
152
152
  ["toBase64", 0, (thisValue) => self(thisValue, "toBase64").bytes.toBase64()],
153
153
  ["toHex", 0, (thisValue) => self(thisValue, "toHex").bytes.toHex()],
154
- ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").bytes.keys())],
155
- ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").bytes.values())],
154
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").bytes.keys())],
155
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").bytes.values())],
156
156
  [
157
157
  "entries",
158
158
  0,
159
- (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
159
+ (thisValue) => hostIterator(builtins, self(thisValue, "entries")
160
160
  .bytes.entries()
161
161
  .map(([index, byte]) => wrapAll([index, byte]))),
162
162
  ],
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, fn, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
3
3
  import { invalidData, IteratorSymbol, typeError } from "../interpreter/model.js";
4
- import { define, defineAccessor, get, getOwn, hidden, Arr, coerceToString, IteratorObj, MapObj, Obj, PromiseObj, SetObj, } from "../interpreter/objects.js";
4
+ import { define, defineAccessor, get, getOwn, hidden, Arr, coerceToString, hostCursor, hostIterator, MapObj, Obj, PromiseObj, SetObj, } from "../interpreter/objects.js";
5
5
  import { describeValue, isOpaque } from "../interpreter/references.js";
6
6
  import { applyCollectionCallback, isSupportedCallback, preserveConsumerError, toPrimitiveNumber, toPrimitiveString, } from "../interpreter/callback.js";
7
7
  const coerceGroupByPropertyKey = (ctx, value) => {
@@ -33,7 +33,7 @@ export const groupBy = (ctx, namespace) => fn(ctx.builtins, "groupBy", 2, (_, ar
33
33
  if (step.done)
34
34
  return result;
35
35
  const item = step.value;
36
- const key = yield* preserveConsumerError(cursor, apply([item, index]));
36
+ const key = yield* preserveConsumerError(cursor.close, apply([item, index]));
37
37
  const group = result.map.get(key);
38
38
  if (group === undefined)
39
39
  result.map.set(key, new Arr(builtins.Array, [item]));
@@ -50,7 +50,7 @@ export const groupBy = (ctx, namespace) => fn(ctx.builtins, "groupBy", 2, (_, ar
50
50
  if (step.done)
51
51
  return result;
52
52
  const item = step.value;
53
- const key = yield* preserveConsumerError(cursor, Effect.flatMap(apply([item, index]), (value) => coerceGroupByPropertyKey(ctx, value)));
53
+ const key = yield* preserveConsumerError(cursor.close, Effect.flatMap(apply([item, index]), (value) => coerceGroupByPropertyKey(ctx, value)));
54
54
  const group = getOwn(result, key);
55
55
  if (group === undefined)
56
56
  define(result, key, new Arr(builtins.Array, [item]));
@@ -67,13 +67,13 @@ const constructMap = (ctx, init, proto) => {
67
67
  return Effect.gen(function* () {
68
68
  const cursor = yield* ctx.iterate(init);
69
69
  if (cursor === undefined) {
70
- throw typeError("new Map(...) expects an iterable of [key, value] pairs or no argument.");
70
+ throw typeError(`new Map(...) expects an iterable of [key, value] pairs, received ${describeValue(init)}.`);
71
71
  }
72
72
  while (true) {
73
73
  const step = yield* cursor.next;
74
74
  if (step.done)
75
75
  return target;
76
- yield* preserveConsumerError(cursor, Effect.sync(() => {
76
+ yield* preserveConsumerError(cursor.close, Effect.sync(() => {
77
77
  if (!(step.value instanceof Obj)) {
78
78
  throw typeError("new Map(...) expects [key, value] pairs as entry objects.");
79
79
  }
@@ -89,7 +89,7 @@ const constructSet = (ctx, init, proto) => {
89
89
  return Effect.gen(function* () {
90
90
  const cursor = yield* ctx.iterate(init);
91
91
  if (cursor === undefined) {
92
- throw typeError("new Set(...) expects a synchronous iterable or no argument.");
92
+ throw typeError(`new Set(...) expects a synchronous iterable, received ${describeValue(init)}.`);
93
93
  }
94
94
  while (true) {
95
95
  const step = yield* cursor.next;
@@ -156,9 +156,9 @@ export const mapGlobal = (ctx) => {
156
156
  return undefined;
157
157
  },
158
158
  ],
159
- ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").map.keys())],
160
- ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").map.values())],
161
- ["entries", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries").iterator(builtins))],
159
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").map.keys())],
160
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").map.values())],
161
+ ["entries", 0, (thisValue) => hostIterator(builtins, self(thisValue, "entries").iterator(builtins))],
162
162
  [
163
163
  "forEach",
164
164
  1,
@@ -207,12 +207,16 @@ const loadSetRecord = (ctx, source, name) => {
207
207
  return {
208
208
  size: Math.max(Math.trunc(size), 0),
209
209
  has: (item) => Effect.map(ctx.call(has, source, [item]), Boolean),
210
- keys: () => Effect.flatMap(ctx.call(keys, source, []), (result) => {
211
- if (result instanceof IteratorObj)
212
- return Effect.succeed(result.source);
213
- if (result instanceof Arr)
214
- return Effect.succeed(result.items);
215
- throw typeError(`Set.${name} expected 'keys' to return an iterator.`);
210
+ keys: () => Effect.gen(function* () {
211
+ const result = yield* ctx.call(keys, source, []);
212
+ const cursor = result instanceof Arr ? hostCursor(result.items.values()) : ctx.iterateDirect(result);
213
+ const items = [];
214
+ while (true) {
215
+ const step = yield* cursor.next;
216
+ if (step.done)
217
+ return items;
218
+ items.push(step.value);
219
+ }
216
220
  }),
217
221
  };
218
222
  });
@@ -336,12 +340,12 @@ export const setGlobal = (ctx) => {
336
340
  return undefined;
337
341
  },
338
342
  ],
339
- ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").set.values())],
340
- ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").set.values())],
343
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").set.values())],
344
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").set.values())],
341
345
  [
342
346
  "entries",
343
347
  0,
344
- (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
348
+ (thisValue) => hostIterator(builtins, self(thisValue, "entries")
345
349
  .set.values()
346
350
  .map((item) => wrap([item, item]))),
347
351
  ],
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
3
3
  import { IteratorSymbol, typeError } from "../interpreter/model.js";
4
- import { define, entries, get, hidden, Arr, HeadersObj, IteratorObj, Obj, coerceToString, isRuntimeReference, } from "../interpreter/objects.js";
4
+ import { define, entries, get, hidden, Arr, HeadersObj, hostIterator, Obj, coerceToString, isRuntimeReference, } from "../interpreter/objects.js";
5
5
  import { applyCollectionCallback } from "../interpreter/callback.js";
6
6
  import { readPairs } from "./url.js";
7
7
  // The host validates header names and values and throws its own TypeError; the program gets one of its own.
@@ -92,18 +92,9 @@ export const headersGlobal = (ctx) => {
92
92
  return undefined;
93
93
  },
94
94
  ],
95
- // Iterator.from because Bun's Headers typings predate iterator helpers; the runtime iterators already have them.
96
- [
97
- "keys",
98
- 0,
99
- (thisValue) => new IteratorObj(builtins.Iterator, Iterator.from(self(thisValue, "keys").headers.keys())),
100
- ],
101
- [
102
- "values",
103
- 0,
104
- (thisValue) => new IteratorObj(builtins.Iterator, Iterator.from(self(thisValue, "values").headers.values())),
105
- ],
106
- ["entries", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries").iterator(builtins))],
95
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").headers.keys())],
96
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").headers.values())],
97
+ ["entries", 0, (thisValue) => hostIterator(builtins, self(thisValue, "entries").iterator(builtins))],
107
98
  [
108
99
  "forEach",
109
100
  1,
@@ -1,2 +1,2 @@
1
1
  import type { Interpreter } from "../interpreter/interpreter.js";
2
- export declare const iteratorGlobals: <R>(ctx: Interpreter<R>) => void;
2
+ export declare const iteratorGlobals: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
@@ -1,17 +1,305 @@
1
- import { methods, receiver } from "../interpreter/native.js";
2
- import { IteratorObj, record } from "../interpreter/objects.js";
3
- // Every built-in collection iterator shares the Iterator prototype; JS gives each collection its own, which is only
1
+ import { Cause, Effect, Exit } from "effect";
2
+ import { applyCollectionCallback } from "../interpreter/callback.js";
3
+ import { rangeError, typeError } from "../interpreter/model.js";
4
+ import { constructor, fn, methods, receiver, requiresNew } from "../interpreter/native.js";
5
+ import { Arr, coerceToNumber, define, hasPrototype, hidden, IteratorObj, Obj, record, } from "../interpreter/objects.js";
6
+ import { describeValue } from "../interpreter/references.js";
7
+ const finished = { done: true, value: undefined };
8
+ // Every built-in iterator and helper shares the Iterator prototype; JS gives each collection its own, which is only
4
9
  // observable through getPrototypeOf.
5
10
  export const iteratorGlobals = (ctx) => {
6
11
  const builtins = ctx.builtins;
7
- methods(builtins, builtins.Iterator, [
12
+ const proto = builtins.Iterator;
13
+ const method = (name) => `Iterator.prototype.${name}`;
14
+ // A step that runs program code: when it throws, close the source first, as IteratorClose does.
15
+ const guarded = (source, body) => Effect.flatMap(Effect.exit(body), (exit) => {
16
+ if (Exit.isSuccess(exit))
17
+ return Effect.succeed(exit.value);
18
+ if (Cause.hasInterruptsOnly(exit.cause))
19
+ return Effect.failCause(exit.cause);
20
+ return Effect.andThen(Effect.exit(source.close), Effect.failCause(exit.cause));
21
+ });
22
+ // A lazy helper: `pull` advances it once. Once it reports done, throws, or is closed, it stays done. A callback
23
+ // that re-enters its own helper is a TypeError, as for a running generator.
24
+ const helper = (source, pull, close = source.close) => {
25
+ let done = false;
26
+ let running = false;
27
+ const enter = () => {
28
+ if (running)
29
+ throw typeError("Iterator helper is already running.");
30
+ running = true;
31
+ };
32
+ return new IteratorObj(builtins.IteratorHelper, {
33
+ next: Effect.suspend(() => {
34
+ if (done)
35
+ return Effect.succeed(finished);
36
+ enter();
37
+ return Effect.flatMap(Effect.exit(pull), (exit) => {
38
+ running = false;
39
+ done = Exit.isSuccess(exit) ? exit.value.done : true;
40
+ return exit;
41
+ });
42
+ }),
43
+ close: Effect.suspend(() => {
44
+ if (done)
45
+ return Effect.void;
46
+ enter();
47
+ done = true;
48
+ return Effect.ensuring(close, Effect.sync(() => {
49
+ running = false;
50
+ }));
51
+ }),
52
+ });
53
+ };
54
+ // GetIteratorFlattenable: an iterable by `[Symbol.iterator]`, otherwise the object itself as an iterator.
55
+ const flattenable = (value, name, strings) => Effect.gen(function* () {
56
+ if (typeof value === "string" ? !strings : !(value instanceof Obj)) {
57
+ throw typeError(`${name} expects an iterable or iterator, received ${describeValue(value)}.`);
58
+ }
59
+ return (yield* ctx.iterate(value)) ?? ctx.iterateDirect(value);
60
+ });
61
+ const limit = (value, name) => {
62
+ const count = Math.trunc(coerceToNumber(value));
63
+ if (Number.isNaN(count) || count < 0) {
64
+ throw rangeError(`${method(name)} expects a non-negative count, received ${count}.`);
65
+ }
66
+ return count;
67
+ };
68
+ methods(builtins, proto, [
8
69
  [
9
70
  "next",
10
71
  0,
72
+ (thisValue) => Effect.map(receiver(IteratorObj, thisValue, method("next")).cursor.next, (step) => record(builtins.Object, { value: step.value, done: step.done })),
73
+ ],
74
+ [
75
+ "map",
76
+ 1,
77
+ (thisValue, args) => {
78
+ const source = ctx.iterateDirect(thisValue);
79
+ const apply = applyCollectionCallback(ctx, args[0], method("map"));
80
+ let counter = 0;
81
+ return helper(source, Effect.gen(function* () {
82
+ const step = yield* source.next;
83
+ if (step.done)
84
+ return finished;
85
+ return { done: false, value: yield* guarded(source, apply([step.value, counter++])) };
86
+ }));
87
+ },
88
+ ],
89
+ [
90
+ "filter",
91
+ 1,
92
+ (thisValue, args) => {
93
+ const source = ctx.iterateDirect(thisValue);
94
+ const apply = applyCollectionCallback(ctx, args[0], method("filter"));
95
+ let counter = 0;
96
+ return helper(source, Effect.gen(function* () {
97
+ while (true) {
98
+ const step = yield* source.next;
99
+ if (step.done)
100
+ return finished;
101
+ if (yield* guarded(source, apply([step.value, counter++])))
102
+ return step;
103
+ }
104
+ }));
105
+ },
106
+ ],
107
+ [
108
+ "take",
109
+ 1,
110
+ (thisValue, args) => {
111
+ const source = ctx.iterateDirect(thisValue);
112
+ let remaining = limit(args[0], "take");
113
+ return helper(source, Effect.gen(function* () {
114
+ if (remaining === 0) {
115
+ yield* source.close;
116
+ return finished;
117
+ }
118
+ remaining -= 1;
119
+ return yield* source.next;
120
+ }));
121
+ },
122
+ ],
123
+ [
124
+ "drop",
125
+ 1,
126
+ (thisValue, args) => {
127
+ const source = ctx.iterateDirect(thisValue);
128
+ let remaining = limit(args[0], "drop");
129
+ return helper(source, Effect.gen(function* () {
130
+ while (remaining > 0) {
131
+ remaining -= 1;
132
+ const step = yield* source.next;
133
+ if (step.done)
134
+ return finished;
135
+ }
136
+ return yield* source.next;
137
+ }));
138
+ },
139
+ ],
140
+ [
141
+ "flatMap",
142
+ 1,
143
+ (thisValue, args) => {
144
+ const source = ctx.iterateDirect(thisValue);
145
+ const apply = applyCollectionCallback(ctx, args[0], method("flatMap"));
146
+ let counter = 0;
147
+ let inner;
148
+ return helper(source, Effect.gen(function* () {
149
+ while (true) {
150
+ if (inner !== undefined) {
151
+ const step = yield* guarded(source, inner.next);
152
+ if (!step.done)
153
+ return step;
154
+ inner = undefined;
155
+ }
156
+ const step = yield* source.next;
157
+ if (step.done)
158
+ return finished;
159
+ inner = yield* guarded(source, Effect.flatMap(apply([step.value, counter++]), (mapped) => flattenable(mapped, method("flatMap"), false)));
160
+ }
161
+ }), Effect.suspend(() => (inner === undefined ? source.close : Effect.andThen(inner.close, source.close))));
162
+ },
163
+ ],
164
+ [
165
+ "reduce",
166
+ 1,
167
+ (thisValue, args) => {
168
+ const source = ctx.iterateDirect(thisValue);
169
+ const apply = applyCollectionCallback(ctx, args[0], method("reduce"));
170
+ return Effect.gen(function* () {
171
+ let counter = 0;
172
+ let accumulator = args[1];
173
+ if (args.length < 2) {
174
+ const first = yield* source.next;
175
+ if (first.done)
176
+ throw typeError("Iterator.prototype.reduce of an empty iterator with no initial value.");
177
+ accumulator = first.value;
178
+ counter = 1;
179
+ }
180
+ while (true) {
181
+ const step = yield* source.next;
182
+ if (step.done)
183
+ return accumulator;
184
+ accumulator = yield* guarded(source, apply([accumulator, step.value, counter++]));
185
+ }
186
+ });
187
+ },
188
+ ],
189
+ [
190
+ "toArray",
191
+ 0,
11
192
  (thisValue) => {
12
- const step = receiver(IteratorObj, thisValue, "Iterator.prototype.next").source.next();
13
- return record(builtins.Object, { value: step.value, done: Boolean(step.done) });
193
+ const source = ctx.iterateDirect(thisValue);
194
+ return Effect.gen(function* () {
195
+ const items = [];
196
+ while (true) {
197
+ const step = yield* source.next;
198
+ if (step.done)
199
+ return new Arr(builtins.Array, items);
200
+ items.push(step.value);
201
+ }
202
+ });
203
+ },
204
+ ],
205
+ [
206
+ "forEach",
207
+ 1,
208
+ (thisValue, args) => {
209
+ const source = ctx.iterateDirect(thisValue);
210
+ const apply = applyCollectionCallback(ctx, args[0], method("forEach"));
211
+ return Effect.gen(function* () {
212
+ let counter = 0;
213
+ while (true) {
214
+ const step = yield* source.next;
215
+ if (step.done)
216
+ return undefined;
217
+ yield* guarded(source, apply([step.value, counter++]));
218
+ }
219
+ });
220
+ },
221
+ ],
222
+ [
223
+ "some",
224
+ 1,
225
+ (thisValue, args) => {
226
+ const source = ctx.iterateDirect(thisValue);
227
+ const apply = applyCollectionCallback(ctx, args[0], method("some"));
228
+ return Effect.gen(function* () {
229
+ let counter = 0;
230
+ while (true) {
231
+ const step = yield* source.next;
232
+ if (step.done)
233
+ return false;
234
+ if (yield* guarded(source, apply([step.value, counter++]))) {
235
+ yield* source.close;
236
+ return true;
237
+ }
238
+ }
239
+ });
240
+ },
241
+ ],
242
+ [
243
+ "every",
244
+ 1,
245
+ (thisValue, args) => {
246
+ const source = ctx.iterateDirect(thisValue);
247
+ const apply = applyCollectionCallback(ctx, args[0], method("every"));
248
+ return Effect.gen(function* () {
249
+ let counter = 0;
250
+ while (true) {
251
+ const step = yield* source.next;
252
+ if (step.done)
253
+ return true;
254
+ if (!(yield* guarded(source, apply([step.value, counter++])))) {
255
+ yield* source.close;
256
+ return false;
257
+ }
258
+ }
259
+ });
260
+ },
261
+ ],
262
+ [
263
+ "find",
264
+ 1,
265
+ (thisValue, args) => {
266
+ const source = ctx.iterateDirect(thisValue);
267
+ const apply = applyCollectionCallback(ctx, args[0], method("find"));
268
+ return Effect.gen(function* () {
269
+ let counter = 0;
270
+ while (true) {
271
+ const step = yield* source.next;
272
+ if (step.done)
273
+ return undefined;
274
+ if (yield* guarded(source, apply([step.value, counter++]))) {
275
+ yield* source.close;
276
+ return step.value;
277
+ }
278
+ }
279
+ });
14
280
  },
15
281
  ],
16
282
  ]);
283
+ // Helpers and `Iterator.from` wrappers can be closed; collection iterators, as in JS, cannot.
284
+ methods(builtins, builtins.IteratorHelper, [
285
+ [
286
+ "return",
287
+ 0,
288
+ (thisValue) => Effect.map(receiver(IteratorObj, thisValue, "Iterator.prototype.return").cursor.close, () => record(builtins.Object, { value: undefined, done: true })),
289
+ ],
290
+ ]);
291
+ const iterator = constructor(builtins, proto, {
292
+ name: "Iterator",
293
+ length: 0,
294
+ call: requiresNew("Iterator"),
295
+ construct: () => Effect.sync(() => {
296
+ throw typeError("Iterator is abstract; use Iterator.from(...) or a built-in iterator.");
297
+ }),
298
+ });
299
+ define(iterator, "from", fn(builtins, "from", 1, (_, args) => Effect.gen(function* () {
300
+ if (args[0] instanceof Obj && hasPrototype(args[0], proto))
301
+ return args[0];
302
+ return new IteratorObj(builtins.IteratorHelper, yield* flattenable(args[0], "Iterator.from", true));
303
+ })), hidden);
304
+ return iterator;
17
305
  };
@@ -91,7 +91,7 @@ export const mathGlobal = (ctx) => {
91
91
  const step = yield* cursor.next;
92
92
  if (step.done)
93
93
  return Math.sumPrecise(numbers);
94
- yield* preserveConsumerError(cursor, Effect.sync(() => {
94
+ yield* preserveConsumerError(cursor.close, Effect.sync(() => {
95
95
  if (typeof step.value !== "number") {
96
96
  throw typeError("Math.sumPrecise expects an iterable of numbers.");
97
97
  }
@@ -56,7 +56,7 @@ const objectFromEntries = (ctx, source) => {
56
56
  const step = yield* cursor.next;
57
57
  if (step.done)
58
58
  return out;
59
- yield* preserveConsumerError(cursor, Effect.sync(() => {
59
+ yield* preserveConsumerError(cursor.close, Effect.sync(() => {
60
60
  if (!(step.value instanceof Obj) || containsOpaqueReference(step.value)) {
61
61
  throw typeError("Object.fromEntries expects [key, value] entry objects.");
62
62
  }
@@ -2,7 +2,7 @@ import { Effect } from "effect";
2
2
  import { constructor, fn, methods } from "../interpreter/native.js";
3
3
  import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
4
4
  import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
- import { define, hidden, Arr, IteratorObj, RegExpObj, record, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
5
+ import { define, hidden, Arr, hostIterator, RegExpObj, record, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
6
  import { containsOpaqueReference, typeofValue } from "../interpreter/references.js";
7
7
  import { applyCollectionCallback, isSupportedCallback } from "../interpreter/callback.js";
8
8
  import { matchToValue, toHostRegex } from "./regexp.js";
@@ -229,6 +229,6 @@ export const stringGlobal = (ctx) => {
229
229
  return joined;
230
230
  }),
231
231
  ]);
232
- define(builtins.String, IteratorSymbol, fn(builtins, "[Symbol.iterator]", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "[Symbol.iterator]")[Symbol.iterator]())), hidden);
232
+ define(builtins.String, IteratorSymbol, fn(builtins, "[Symbol.iterator]", 0, (thisValue) => hostIterator(builtins, self(thisValue, "[Symbol.iterator]")[Symbol.iterator]())), hidden);
233
233
  return string;
234
234
  };
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, fn, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
3
3
  import { IteratorSymbol, PendingThrow, typeError, uriError } from "../interpreter/model.js";
4
- import { define, defineAccessor, entries, get, hidden, Arr, IteratorObj, Obj, URLObj, URLSearchParamsObj, coerceToString, isRuntimeReference, } from "../interpreter/objects.js";
4
+ import { define, defineAccessor, entries, get, hidden, Arr, hostIterator, Obj, URLObj, URLSearchParamsObj, coerceToString, isRuntimeReference, } from "../interpreter/objects.js";
5
5
  import { applyCollectionCallback, preserveConsumerError } from "../interpreter/callback.js";
6
6
  const urlProperties = [
7
7
  "href",
@@ -105,7 +105,7 @@ const readPair = (ctx, value, label) => Effect.gen(function* () {
105
105
  const step = yield* cursor.next;
106
106
  if (step.done)
107
107
  return items;
108
- items.push(yield* preserveConsumerError(cursor, Effect.sync(() => coerceToString(step.value))));
108
+ items.push(yield* preserveConsumerError(cursor.close, Effect.sync(() => coerceToString(step.value))));
109
109
  }
110
110
  });
111
111
  /**
@@ -124,7 +124,7 @@ export const readPairs = (ctx, init, label) => Effect.gen(function* () {
124
124
  throw typeError(`${label} expects iterable [name, value] pairs.`);
125
125
  return pairs;
126
126
  }
127
- pairs.push(yield* preserveConsumerError(cursor, readPair(ctx, step.value, label)));
127
+ pairs.push(yield* preserveConsumerError(cursor.close, readPair(ctx, step.value, label)));
128
128
  }
129
129
  });
130
130
  const constructURLSearchParams = (ctx, init, proto) => {
@@ -233,9 +233,9 @@ export const urlSearchParamsGlobal = (ctx) => {
233
233
  return undefined;
234
234
  },
235
235
  ],
236
- ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").params.keys())],
237
- ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").params.values())],
238
- ["entries", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries").iterator(builtins))],
236
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").params.keys())],
237
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").params.values())],
238
+ ["entries", 0, (thisValue) => hostIterator(builtins, self(thisValue, "entries").iterator(builtins))],
239
239
  ["toString", 0, (thisValue) => self(thisValue, "toString").params.toString()],
240
240
  [
241
241
  "forEach",
@@ -90,7 +90,7 @@ const describeTool = (visible) => {
90
90
  // Joining the final fragments avoids retaining the rendering's intermediate string ropes in JSC.
91
91
  return (signature ??= [
92
92
  toolExpression(visible.path),
93
- isEmptyInput(visible.tool) ? "()" : `(input: ${inputTypeScript(visible.tool, true)})`,
93
+ isEmptyInput(visible.tool) ? "()" : `(${inputTypeScript(visible.tool, true)})`,
94
94
  `: Promise<${outputTypeScript(visible.tool, true)}>`,
95
95
  ].join(""));
96
96
  },
@@ -118,10 +118,16 @@ const makeSearchTool = (searchIndex) => ({
118
118
  const request = input;
119
119
  const query = request.query ?? "";
120
120
  const offset = request.offset ?? 0;
121
- const scoped = request.namespace === undefined
121
+ let ns = request.namespace;
122
+ if (ns !== undefined && !searchIndex.some((entry) => entry.description.path.startsWith("tools."))) {
123
+ if (ns === "tools")
124
+ ns = undefined;
125
+ else if (ns.startsWith("tools."))
126
+ ns = ns.slice("tools.".length);
127
+ }
128
+ const scoped = ns === undefined
122
129
  ? searchIndex
123
- : searchIndex.filter((entry) => entry.description.path === request.namespace ||
124
- entry.description.path.startsWith(`${request.namespace}.`));
130
+ : searchIndex.filter((entry) => entry.description.path === ns || entry.description.path.startsWith(`${ns}.`));
125
131
  const trimmed = query.trim();
126
132
  const pathQuery = trimmed.startsWith("tools.") ? trimmed.slice("tools.".length) : trimmed;
127
133
  const exact = pathQuery === ""
@@ -160,7 +166,7 @@ const makeSearchTool = (searchIndex) => ({
160
166
  /** Exact callable signature of the built-in `search` function, for host-owned instructions. */
161
167
  export const searchSignature = (() => {
162
168
  const tool = makeSearchTool([]);
163
- return `search(input: ${inputTypeScript(tool, true)}): ${outputTypeScript(tool, true)}`;
169
+ return `search(${inputTypeScript(tool, true)}): ${outputTypeScript(tool, true)}`;
164
170
  })();
165
171
  const toSearchEntry = (visible) => ({
166
172
  description: describeTool(visible),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/codemode",
4
- "version": "0.0.0-dev-19942",
4
+ "version": "0.0.0-dev-19959",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",