compile/yield

Symbol: compile/yield

Syntax: (yield VAL ...) – variadic.

Form

Result

(yield)

yields None

(yield VAL)

yields VAL

(yield VAL ...)

yields a tuple of the values

(yield K: V ...)

yields a values object over the keyword pairs

Use-case: Yield from a generator (or coroutine). Suspends the current frame and returns the value to the driver; resumption is via send(value) or throw(). yield is variadic. With no argument it yields None; with one it yields that value; with two or more positional arguments it yields a tuple; and with keyword arguments it yields a multi-value values object.

Bare form: the lone symbol yield expands to (yield), so the Python-style spelling yields None. Governed by the compiler/bare-control-words pragma of the module being compiled (convert by default, or warn / error); because the bare symbol always expands, yield can never name a variable.

Behaviour: Compiles each positional argument and emits YIELD N, where N is the argument count. YIELD 0 yields None, YIELD 1 yields the single value, and YIELD N (for N > 1) packages the values as a tuple. A keyword argument re-heads the form as (values ...), so (yield a: 1 b: 2) yields a values object; positional and keyword arguments must not be mixed.

At runtime, YIELD finds the running frame’s generator node (kos_dyn_find_gen) and freezes the chain above it into the node (kos_dyn_gen_freeze); the open extents inside the generator are kept, not unwound, so no finally or __exit__ runs at a yield. When the generator is consumed on the same chain (a for-each, a NEXT, or a delegating yield-from), the value is delivered to that consumer and evaluation continues there without leaving koskari_eval. When the generator is driven from outside (a Python next or send), the opcode returns EVAL_YIELD(val), SUSPENSION_EVENT = YIELD is set, and koskari_eval returns KOS_RES_YIELD. On resume, revive_yield pushes send_val onto the stack.

A StopIteration or StopAsyncIteration that escapes a generator’s body is not the generator’s own end signal. As in Python (PEP 479, PEP 525), it leaves the generator as RuntimeError("generator raised StopIteration") or RuntimeError("generator raised StopAsyncIteration"), with the original exception as __cause__, on every surface: a Python next or list, a Python async for, and a Koskari for-each over the same generator. A function awaited from Python that raises StopIteration likewise raises RuntimeError("coroutine raised StopIteration"). A generator ends normally only by returning; an unguarded (next it) in its body that runs out is an error, not the end of the generator.

Examples:

(yield)                     ;; -> None
(yield (compute-next))      ;; -> the computed value
(yield a b c)               ;; -> #(a b c)  (a tuple)
(yield a: a b: b c: c)      ;; -> values(a=a, b=b, c=c)