In the last chapter we described the four phony flow-control `commands': if, for, while and do. Phony because they don't correspond to bytecode operators; the Yazoo compiler turns them into totally different expressions involving `goto's. This section describes the formulae which the compiler uses to translate these commands.
Yazoo sports three goto operators: an unconditional jump, and jump-if-true and and jump-if-false operators. Each goto sequence begins with the relevant operator ID (1 word) followed by a jump offset (1 word). The jump offset should be read as a signed long (so it can be negative). It gives the relative position, measured in bytecode words from the location of the jump-offset word, of the sentence to jump to if the goto is to be performed. The jump must be to the start of some compiled sentence (or else the terminating null word) -- otherwise transform() throws an error. In the case of the two conditional gotos, there is a final argument following the jump offset which is the condition on which to jump.
if
An if statement executes its code if its conditional argument is true. That is, it skips the code -- i.e. performs a goto -- if the argument is false. Thus the if translates into a jump-if-false operator, where the jump offset is to the next elseif, else or endif marker in that if-block.
Any elseif translates the same way as an if, since it is basically just another if in series with the first. On the other hand, elses and endifs are just markers delineating different region of the script, and don't translate into anything at all. Since the various conditions are mutually exclusive, every conditional block (except for the last one) ends in an unconditional goto to the endif.
The following script gives an example of every type of if-related marker:
if a == 1
c = 3
elseif a == 2
c = 7
else
c = 9
endif
print(c)
It has the following bytecode translation:
jf 14 ( eq ( sm $a , csl 1 ) )
equ ( sm $c , csl 3 )
j 22
jf 14 ( eq ( sm $a , csl 2 ) )
equ ( sm $c , csl 7 )
j 7
equ ( sm $c , csl 9 )
[print] ( def** ( sm $639 , scr { dqa* ( sm $640 , sm $c ) } ) )
Notice how the code blocks are demarcated by a chain of jump-if-falses (jf) at their beginnings, and unconditional jumps (j) at their ends. The else has no unconditional jump; that would be redundant since it would just jump to the next sentence.
for
The compiler boils a for-loop down to 1) a counter initialization, 2) a conditional jump-if-true (jt) to the endf when the loop is finished, then 3) the code to be looped, followed by 4) a counter-increment and finally 5) an unconditional jump at the end back to the conditional goto. Schematically, the following code
for counter in [start, end] step stp
...
end for
gets rearranged into
counter = start
loop_start:
if counter > end goto loop_end
...
counter = counter + stp
goto loop_start
loop_end:
The loop-fields counter, start, end and stp were written as above as variables, but each of these can be more general expressions as well. The entire compiled expression gets plunked down everywhere that field appears in the schematic above. For example, the following script
for (c1 :: ulong) in [0, top(array)]
array[c1] = 0
end for
print(array)
disassembles into:
equ ( def ( sID ( this , $c1 ) , ul ) , csl 0 )
jt 50 ( gt ( def ( sID ( this , $c1 ) , ul ) ,
[top] ( def** ( sm $650 , scr {
dqa* ( sm $651 , sm $array ) } ) ) ) )
equ ( sti ( sm $array , sm $c1 ) , csl 0 )
equ ( def ( sID ( this , $c1 ) , ul ) , add ( def ( sID ( this , $c1 ) , ul ) , csl 1 ) )
j -50
[print] ( def** ( sm $652 , scr { dqa* ( sm $653 , sm $array ) } ) )
Notice how the def ( sID ( this , $c1 ) , ul ) motif (corresponding to c1 :: ulong) occurs four times, so Yazoo will not only define c1 in the initialization step, but also `redefine' it three times per iteration of the loop. This is perfectly legal, but it can slow down short loops considerably. Likewise, the top() function is called anew (once) per loop iteration, which is also inefficient.
We can see now why negative steps don't work in Yazoo if the step is contained in a variable: the counter is always assumed to be increasing unless the step is a negative constant, and so the loop stops when the counter is greater than the second range field. A loop that tried to decrement its counter would probably satisfy this criterion without ever running the loop once; the other (worse) possibility is that the loop would run forever, continually decreasing the counter variable and falling ever further from its goal. Our example shows that if the optional step parameter is not specified, then it just defaults to 1: i.e. "csl 1" in bytecode-speak.
while
A while-loop is essentially an if-block with an extra goto at the end. The translation consists of a conditional goto to the endw if the condition is false, followed by the loop-code and lastly an unconditional jump back to the first goto. For example, the following script
while random() < .9
print(".")
end while
print("\n")
compiles into
jf 30 ( lt ( [random] ( def** ( sm $663 , scr { } ) ) , cf 0.9 ) )
[print] ( def** ( sm $664 , scr { deq* ( sm $665 , cst "." ) } ) )
j -30
[print] ( def** ( sm $666 , scr { deq* ( sm $667 , cst "#" ) } ) )
Since the condition is checked before the loop-code is encountered, the loop will not run even once if the condition starts out false.
do
The simplest flow-control block is the do-until loop. The compiled loop consists of the code within the loop, followed by a single conditional if-false jump back to the beginning of this code. Nothing comes before the loop code, so it is guaranteed to run once. For example, the following script
do
cmd = input()
until cmd == "quit"
corresponds to
equ ( sm $cmd , [input] ( def** ( sm $677 , scr { } ) ) )
jf -13 ( eq ( sm $cmd , cst "quit" ) )
Last update: July 28, 2013