You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Seventh piece of the Python-ergonomics catch-up. Any fn whose body
contains `yield expr;` is a generator. Calling it runs the body to
completion, collecting yielded values into an Array, and returns the
Array. Composes naturally with `for x in gen` — the for loop just
iterates the result array.
fn fibs(n) {
h a = 0;
h b = 1;
h k = 0;
while k < n {
yield a;
h t = a + b;
a = b;
b = t;
k = k + 1;
}
}
for v in fibs(10) { ... } # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34
Implementation strategy — EAGER list-building:
AST:
Statement::Yield(Expression) for `yield expr;`
Detection:
stmts_contain_yield() — recursive walker matching the existing
stmts_contain_return() pattern. Generators are statically
identifiable from the AST.
Runtime:
Interpreter gains `yield_stacks: Vec<Vec<Value>>` — a stack of
yield-collectors for the current call chain. invoke_user_function
detects a generator body, pushes a fresh collector, runs the
body, pops the collector, returns Value::Array. The Yield
statement's executor appends to the top of the stack.
This is NOT a real lazy generator. `yield` in an infinite loop
hangs (the body runs to completion before returning). Honest
trade-off for shipping the syntax now:
+ Composes cleanly with the existing for-loop iteration over
Value::Array (no for-loop changes needed)
+ No coroutine state machine, no CPS transform, no separate
iterator object — single Value type covers everything
+ Generator detection is a static AST walk; zero runtime cost
for non-generator fns
- Memory grows with yield count (no streaming)
- Infinite generators don't terminate
- Side effects between yields can't be observed lazily (caller
sees the whole sequence at once)
Lazy generators via continuation-passing or stackful coroutines
are future work — the deferred-implementation comment in the
relevant code explains the path. For most uses today
(comprehension-style generation, finite-sequence factories, helper
generators that feed for loops), the eager approach matches
Python's surface behavior exactly.
Tests (examples/tests/test_generators.omc — 8 tests, all pass):
- yield in a while loop produces an array
- empty body / zero iterations yields []
- yield-computed values (squares)
- yield in if-conditional branch (only-evens)
- multiple yields per iteration
- generator composes with for-loop iteration
- nested generator call (outer generator that uses naturals() inside)
- yield + substrate primitives (yield nth_fibonacci(i))
Regression: 225 OMC tests pass (was 217 + 8 new generator tests).
All previous suites green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
0 commit comments