I just used x
and y
so I didn’t have to write a longer equivalent example that overrides some variables that don’t exist in the Interpreter’s scope, such as
(
value {
var xx = 42;
value {
var yy = xx.postln, xx = 5;
}
}
)
There’s actually still something I don’t understand with the last one. Why is xx
is posted as 5
and not nil
… It seems some initializers get run before others… and not necessarily in the order they were given! It looks like some constant-initializers get put into the function prologue, because they don’t show up in the byte-code for the inner function:
BYTECODES: (7)
0 31 PushTempZeroVar 'xx'
1 C1 3A SendSpecialMsg 'postln'
3 80 00 StoreTempVar 'yy'
5 6E PushSpecialValue nil
6 F2 BlockReturn
In contrast
(
value {
var xx = 42;
value {
var yy = xx.postln, xx = 2 + 3;
}
}
)
prints nil
, because the initializer for xx
is not a constant now; the bytecode generated for this 2+3
initializer is visible on a dump, and comes after the postln
call.
So, the actual rule/algorithm presently implemented by the SC mini-hoisting seems to be:
- Pull all vars into a local scope table; this is use to resolve all name accesses, in preference to the outer scopes.
- Initialize all vars that have constant initializers via the function prologue.
- Generate byte code for the rest of the initializers in the function body, in the order in which these non-constant-expression initializers appear in the var statements.
(1 & 2 are probably a single step/pass.)
Since name lookups in initializers (of the form x = y
) are treated as non-constant initializers , they are done in step 3, so they can access constant-initialized vars from step 2 (seemingly) “out of order”.
And sure enough
value { |yy = xx, xx = 5| yy.postln }
is a parse error, has to be written as
value { |yy = (xx), xx = 5| yy.postln }