Curious has to how a compiler can truly create a closure iterator, I've been diving into the code a bit.
Would it be accurate to say, that, behind the scenes the compiler is deconstructing and rearranging the nodes such that?:
iterator zing(): int {.closure.} =
let a = 3
yield 10
let b = 3
yield 20
let c = 3
yield 30
let d = 3
var zInst = zing
let x = zInst()
echo $x
let y = zInst()
echo $y
essentially becomes:
proc iterator_zing(state: var int, finishFlag: var bool): int =
case state:
of 0:
let a = 3
result = 10
of 1:
let b = 3
result = 20
of 2:
let c = 3
result = 30
of 3:
let d = 3
finishFlag = true
else:
finishFlag = true
if not finishFlag:
inc state
var zingState = 0
var zingFinished = false
let x = iterator_zing(zingState, zingFinished)
echo $x
let y = iterator_zing(zingState, zingFinished)
echo $y
Except slightly less readable :). And perhaps with a fancy closure iterator struct to hold the state details.
Anything I'm missing in my psuedo-translation?
This seems straightforward, so why is JS excluded for these?
Actually, in my analysis, I'm not saving/restoring local iterator variables in context (my example did not have any.) I'm going to look at things further. Perhaps an iterator struct with a cached heap space...
Right. Local "stack" variables are lifted into the heap allocated closure environment, hence "closure" iterators. The state is stored in the environment as well.
Anything I'm missing in my psuedo-translation?
Just a few tiny details. There's a loop around the switch statement, and a try-catch block for functions where try-catch is broken by yield.
This seems straightforward, so why is JS excluded for these?
It's more of a historical reason. Previously closure iterators were not transformed in the way they are, and relied on C's behavior of goto/labels, which was not possible to reproduce in JS. With current transformation in place it should be relatively trivial to adopt the JS backend to it.