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

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, hidden, 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;
@@ -315,7 +318,16 @@ class Frame {
315
318
  }).pipe(Effect.ensuring(Effect.sync(() => self.scopes.pop())));
316
319
  }
317
320
  createFunction(node, name = node.type === "ArrowFunctionExpression" ? "" : (node.id?.name ?? "")) {
318
- return new Fn(this.ctx.builtins.Function, name, node.params, node.body, this.scopes.capture(), node.async, node.generator);
321
+ const builtins = this.ctx.builtins;
322
+ const fn = new Fn(builtins.Function, name, node.params, node.body, this.scopes.capture(), node.async, node.generator);
323
+ // Each generator function gets its own prototype, so `g() instanceof g` holds as in JS.
324
+ if (node.generator)
325
+ define(fn, "prototype", new Obj(node.async ? builtins.AsyncGenerator : builtins.Generator), hidden);
326
+ // The body of a named function expression sees its own name, read-only.
327
+ if (node.type === "FunctionExpression" && node.id) {
328
+ fn.capturedScopes.push(new Map([[node.id.name, { mutable: false, value: fn, initialized: true }]]));
329
+ }
330
+ return fn;
319
331
  }
320
332
  // NamedEvaluation: an anonymous function definition takes the name of what it is assigned to.
321
333
  evaluateNamed(node, name) {
@@ -324,11 +336,12 @@ class Frame {
324
336
  }
325
337
  return this.evaluateExpression(node);
326
338
  }
339
+ // Repeated `function` declarations and `var` clashes are legal: the last declaration wins.
327
340
  hoistFunctions(statements) {
328
341
  for (const node of statements) {
329
342
  if (node.type !== "FunctionDeclaration")
330
343
  continue;
331
- this.scopes.declare(node.id.name, this.createFunction(node), true, node);
344
+ this.scopes.current().set(node.id.name, { mutable: true, value: this.createFunction(node), initialized: true });
332
345
  }
333
346
  }
334
347
  // Hoisted `var` bindings start undefined, or copy a same-named parameter. Function bodies hoist
@@ -492,7 +505,7 @@ class Frame {
492
505
  if (declared?.lexical)
493
506
  self.predeclarePattern(declared.pattern, declared.mutable, left);
494
507
  const right = yield* self.evaluateExpression(node.right);
495
- const cursor = self.hostCursor(right);
508
+ const cursor = self.builtinCursor(right);
496
509
  const iterator = cursor === undefined ? yield* self.customIterator(right, node, awaiting) : undefined;
497
510
  if (iterator === undefined && cursor === undefined) {
498
511
  throw invalidData(`${awaiting ? "for await...of" : "for...of"} requires an iterable value, received ${describeValue(right)}.`, node);
@@ -533,10 +546,9 @@ class Frame {
533
546
  const bodyExit = yield* Effect.exit(evaluateBody(step.value));
534
547
  if (!Exit.isSuccess(bodyExit)) {
535
548
  // 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);
549
+ if (Cause.hasInterruptsOnly(bodyExit.cause))
550
+ return yield* Effect.failCause(bodyExit.cause);
551
+ return yield* preserveConsumerError(close(), Effect.failCause(bodyExit.cause));
540
552
  }
541
553
  const exit = loopExit(bodyExit.value, labels);
542
554
  if (exit !== undefined) {
@@ -565,32 +577,40 @@ class Frame {
565
577
  });
566
578
  }
567
579
  iterate(value, node) {
568
- const cursor = this.hostCursor(value);
580
+ const cursor = this.builtinCursor(value);
569
581
  if (cursor !== undefined)
570
582
  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
- });
583
+ return Effect.map(this.customIterator(value, node, false), (iterator) => iterator === undefined ? undefined : this.customCursor(iterator, node));
584
+ }
585
+ /** GetIteratorDirect: drive an iterator by its own `next`, without asking for `[Symbol.iterator]`. */
586
+ iterateDirect(value, node) {
587
+ if (value instanceof IteratorObj)
588
+ return value.cursor;
589
+ if (!(value instanceof Obj)) {
590
+ throw typeError(`An iterator must be an object, received ${describeValue(value)}.`, node);
591
+ }
592
+ return this.customCursor({
593
+ iterator: value,
594
+ next: this.requireIteratorMethod(get(value, "next"), "Iterator next", node),
595
+ asynchronous: false,
596
+ }, node);
578
597
  }
579
- hostCursor(value) {
598
+ customCursor(iterator, node) {
599
+ return {
600
+ next: this.nextIteratorResult(iterator, node, false),
601
+ close: Effect.suspend(() => this.closeIterator(iterator, node, false)),
602
+ };
603
+ }
604
+ builtinCursor(value) {
605
+ // Natives build their cursors without knowing R, like `lift` in native.ts.
606
+ if (value instanceof IteratorObj)
607
+ return value.cursor;
580
608
  const iterator = typeof value === "string"
581
609
  ? value[Symbol.iterator]()
582
610
  : value instanceof Obj
583
611
  ? value.iterator(this.ctx.builtins)
584
612
  : 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
- };
613
+ return iterator === undefined ? undefined : hostCursor(iterator);
594
614
  }
595
615
  customIterator(value, node, allowAsync = true) {
596
616
  if (!(value instanceof Obj))
@@ -933,7 +953,7 @@ class Frame {
933
953
  return;
934
954
  }
935
955
  const consumed = consume(element, step.done ? undefined : step.value, pattern);
936
- yield* step.done ? consumed : preserveConsumerError(cursor, consumed);
956
+ yield* step.done ? consumed : preserveConsumerError(cursor.close, consumed);
937
957
  }
938
958
  if (!done)
939
959
  yield* cursor.close;
@@ -1363,13 +1383,14 @@ class Frame {
1363
1383
  return yield* invocation.evaluateExpression(fn.body);
1364
1384
  });
1365
1385
  if (fn.generator)
1366
- return Effect.succeed(this.createGenerator(invocation, run, fn.async));
1386
+ return Effect.succeed(this.createGenerator(invocation, run, fn));
1367
1387
  if (!fn.async)
1368
1388
  return run;
1369
1389
  return this.ctx.pending.createWithSelf((self) => Effect.flatMap(run, (value) => resolvePromiseValue(invocation.ctx, value, self)));
1370
1390
  });
1371
1391
  }
1372
- createGenerator(invocation, run, asynchronous) {
1392
+ createGenerator(invocation, run, fn) {
1393
+ const asynchronous = fn.async;
1373
1394
  const state = { started: false, completed: false, draining: false, pending: [], pendingIndex: 0 };
1374
1395
  invocation.generatorState = state;
1375
1396
  invocation.generatorAsync = asynchronous;
@@ -1428,8 +1449,8 @@ class Frame {
1428
1449
  }
1429
1450
  return Deferred.await(request.response);
1430
1451
  };
1431
- const generator = new GeneratorObj(asynchronous ? builtins.AsyncGenerator : builtins.Generator, asynchronous, request);
1432
- return generator;
1452
+ const proto = get(fn, "prototype");
1453
+ return new GeneratorObj(proto instanceof Obj ? proto : builtins.Generator, asynchronous, request);
1433
1454
  }
1434
1455
  completeGeneratorRequests(state, asynchronous) {
1435
1456
  const self = this;
@@ -1505,7 +1526,7 @@ class Frame {
1505
1526
  delegateYield(value, node) {
1506
1527
  const self = this;
1507
1528
  return Effect.gen(function* () {
1508
- const cursor = self.hostCursor(value);
1529
+ const cursor = self.builtinCursor(value);
1509
1530
  if (cursor !== undefined) {
1510
1531
  while (true) {
1511
1532
  const step = yield* cursor.next;
@@ -1741,9 +1762,11 @@ class Frame {
1741
1762
  }
1742
1763
  evaluateDeleteExpression(argument) {
1743
1764
  const target = argument.type === "ChainExpression" ? argument.expression : argument;
1744
- if (target.type !== "MemberExpression") {
1765
+ if (target.type === "Identifier")
1745
1766
  throw typeError("Only data fields may be deleted.", argument);
1746
- }
1767
+ // `delete <non-reference>` evaluates the operand and is true, as in JS.
1768
+ if (target.type !== "MemberExpression")
1769
+ return Effect.map(this.evaluateExpression(target), () => true);
1747
1770
  return Effect.map(this.getMemberReference(target), (reference) => {
1748
1771
  if (reference === OptionalShortCircuit)
1749
1772
  return true;
@@ -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 {
@@ -86,10 +87,10 @@ export declare abstract class Callable extends Opaque {
86
87
  export declare class Fn extends Callable {
87
88
  readonly parameters: ReadonlyArray<Pattern>;
88
89
  readonly body: BlockStatement | Expression;
89
- readonly capturedScopes: ReadonlyArray<Map<string, Binding>>;
90
+ readonly capturedScopes: Array<Map<string, Binding>>;
90
91
  readonly async: boolean;
91
92
  readonly generator: boolean;
92
- constructor(proto: Obj, name: string, parameters: ReadonlyArray<Pattern>, body: BlockStatement | Expression, capturedScopes: ReadonlyArray<Map<string, Binding>>, async: boolean, generator: boolean);
93
+ constructor(proto: Obj, name: string, parameters: ReadonlyArray<Pattern>, body: BlockStatement | Expression, capturedScopes: Array<Map<string, Binding>>, async: boolean, generator: boolean);
93
94
  }
94
95
  export type NativeCall<R> = (thisValue: Value, args: Array<Value>) => Effect.Effect<Value, unknown, R>;
95
96
  export type NativeConstruct<R> = (args: Array<Value>, newTarget: Callable) => Effect.Effect<Value, unknown, R>;
@@ -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,
@@ -166,7 +166,7 @@ export const mapGlobal = (ctx) => {
166
166
  const target = self(thisValue, "forEach");
167
167
  const apply = applyCollectionCallback(ctx, args[0], "Map.forEach");
168
168
  return Effect.gen(function* () {
169
- for (const [key, item] of Array.from(target.map.entries()))
169
+ for (const [key, item] of target.map.entries())
170
170
  yield* apply([item, key, target]);
171
171
  return undefined;
172
172
  });
@@ -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
  ],
@@ -352,7 +356,7 @@ export const setGlobal = (ctx) => {
352
356
  const target = self(thisValue, "forEach");
353
357
  const apply = applyCollectionCallback(ctx, args[0], "Set.forEach");
354
358
  return Effect.gen(function* () {
355
- for (const item of Array.from(target.set.values()))
359
+ for (const item of target.set.values())
356
360
  yield* apply([item, item, target]);
357
361
  return undefined;
358
362
  });
@@ -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-19961",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",