repl: Render iterators and generators

Evaluating an expression that yields a generator or other iterator object
printed an empty object, since such objects have no own enumerable keys.
Encode them as a new tagged value by pulling up to 100 items, marking when
more remain, and label them by their toStringTag so a generator shows as
Generator[...] and a Map iterator as Map Iterator[...]. The iterator is
consumed by the display, as it would be by any other means of looking at
its items.
This commit is contained in:
Håvard Sørbø authored and Ole André Vadla Ravnås committed 2026-10-17 11:18:32 +02:00
1 parent 19048c41ce
commit 24f5b4d214
2 files changed
+56 -1

No files matched your search

+29
View File
@@ -8,6 +8,7 @@ export interface EncodeOptions {
}
const DEFAULT_MAX_DEPTH = 3;
const MAX_ITERATOR_ITEMS = 100;
export type PackedValue = [EncodedValueTree, ArrayBuffer | null];
@@ -40,6 +41,7 @@ const enum ValueTag {
WeakSet = 19,
DepthLimit = 20,
Circular = 21,
Iterator = 22,
}
type TypedArrayName =
@@ -145,6 +147,7 @@ function collectTree(
const isArray = Array.isArray(obj);
const isMap = obj instanceof Map;
const isSet = obj instanceof Set;
const isIterator = typeof obj.next === "function" && typeof obj[Symbol.iterator] === "function";
const existingId = state.seen.get(obj);
if (existingId !== undefined) {
@@ -161,6 +164,9 @@ function collectTree(
if (isSet) {
return [ValueTag.DepthLimit, ValueTag.Set];
}
if (isIterator) {
return [ValueTag.DepthLimit, ValueTag.Iterator];
}
return [ValueTag.DepthLimit, ValueTag.Object];
}
@@ -194,6 +200,20 @@ function collectTree(
return [ValueTag.Array, id, elements];
}
if (isIterator) {
const label = obj[Symbol.toStringTag] ?? "Iterator";
const items: EncodedValueTree[] = [];
let truncated = false;
for (const v of obj as Iterable<unknown>) {
if (items.length === MAX_ITERATOR_ITEMS) {
truncated = true;
break;
}
items.push(collectTree(v, chunks, maxDepth, depth + 1, state));
}
return [ValueTag.Iterator, id, label, items, truncated];
}
try {
const maybeToJSON = obj.toJSON;
if (typeof maybeToJSON === "function") {
@@ -349,6 +369,15 @@ function patchTree(
return [ValueTag.Set, id, patchedItems];
}
case ValueTag.Iterator: {
const id = rest[0] as number;
const label = rest[1] as string;
const items = rest[2] as EncodedValueTree[];
const truncated = rest[3] as boolean;
const patchedItems = items.map(child => patchTree(child, offsets));
return [ValueTag.Iterator, id, label, patchedItems, truncated];
}
default:
return tree;
}
+27 -1
View File
@@ -33,6 +33,7 @@ WEAKMAP = 18
WEAKSET = 19
DEPTH_LIMIT = 20
CIRCULAR = 21
ITERATOR = 22
_CYAN = "fg:#64d2ff"
_MINT = "fg:#66d4cf"
@@ -47,7 +48,7 @@ _MAX_ITEMS = 100
_HEX_BYTES_PER_ROW = 16
_CONTAINER_NAMES = {OBJECT: "Object", ARRAY: "Array", MAP: "Map", SET: "Set"}
_CONTAINER_NAMES = {OBJECT: "Object", ARRAY: "Array", MAP: "Map", SET: "Set", ITERATOR: "Iterator"}
Node = Sequence[Any]
Fragment = Tuple[str, str]
@@ -147,6 +148,9 @@ def _append(node: Node, level: int, out: List[Fragment], blob: Sequence[int]) ->
elif tag == CIRCULAR:
out.append((_ORANGE, "⟳ circular *%d" % node[1]))
elif tag == ITERATOR:
_append_iterator(node, level, out, blob)
def _append_object(node: Node, level: int, out: List[Fragment], blob: Sequence[int]) -> None:
properties = node[2]
@@ -263,6 +267,28 @@ def _append_set(node: Node, level: int, out: List[Fragment], blob: Sequence[int]
out.append((_CYAN, "]"))
def _append_iterator(node: Node, level: int, out: List[Fragment], blob: Sequence[int]) -> None:
label, elements, truncated = node[2], node[3], node[4]
if not elements:
out.append((_CYAN, "%s[]" % label))
return
out.append((_CYAN, "%s[" % label))
out.append(("", "\n"))
for index, element in enumerate(elements):
_indent(level + 1, out)
_append(element, level + 1, out, blob)
if _has_more(index, elements, truncated):
out.append(("", ","))
out.append(("", "\n"))
if truncated:
_indent(level + 1, out)
out.append((_ORANGE, "… more"))
out.append(("", "\n"))
_indent(level, out)
out.append((_CYAN, "]"))
def _capped(items: Sequence[Any]) -> Tuple[Sequence[Any], int]:
shown = items[:_MAX_ITEMS]
return shown, len(items) - len(shown)