2.4.4 The DO/END Instruction DO [ repetitor ] [ conditional ] ; [ clauses ] END [ symbol ] ; repetitor : = symbol = expri [ TO exprt ] [ BY exprb ] [ FOR exprf ] exprr FOREVER conditional : = WHILE exprw UNTIL expru The DO/END instruction is the instruction used for looping and grouping several statements into one block. This is a multi- clause instruction. The most simple case is when there is no repetitor or conditional, in which case it works like BEGIN/END in Pascal or {...} in C. I.e. it groups zero or more REXX clauses into one conceptual statement. The repetitor subclause controls the control variable of the loop, or the number of repetitions. The exprr subclause may specify a certain number of repetitions, or you may use FOREVER to go on looping forever. If you specify the control variable symbol, it must be a variable symbol, and it will get the initial value expri at the start of the loop. At the start of each iteration, including the first, it will be checked whether it has reached the value specified by exprt. At the end of each iteration the value exprb is added to the control variable. The loop will terminate after at most exprf iterations. Note that all these expressions are evaluated only once, before the loop is entered for the first iteration. You may also specify UNTIL or WHILE, which take a boolean expression. WHILE is checked before each iteration, immediately after the maximum number of iteration has been performed. UNTIL is checked after each iteration, immediately before the control variable is incremented. It is not possible to specify both UNTIL and WHILE in the same DO instruction. The FOREVER keyword is only needed when there is no conditional, and the repetitor would also be empty if FOREVER was not specified. Actually, you could rewrite this as DO WHILE 1. The two forms are equivalent, except for tracing output. The subclauses TO, BY, and FOR may come in any order, and their expressions are evaluated in the order in which they occur. However, the initial assignment must always come first. Their order may affect your program if these expressions have any side effects. However, this is seldom a problem, since it is quite intuitive. Note that the counting of iterations, if the FOR subclause has been specified, is never affected by the setting of NUMERIC DIGITS. Example: Evaluation order What may prove a real trap, is that although the value to which the control variable is set is evaluated before any other expressions in the repetitor, it is assigned to the control variable after all expressions in the repetitor have been evaluated. The following code illustrates this problem: ctrl = 1 do ctrl=f(2) by f(3) to f(5) call func(6) end call func(7) exit f: say ‘ctrl=’ctrl ‘arg=’arg(1) return arg(1) This code produces the following output: ctrl=1 arg=2 ctrl=1 arg=3 ctrl=1 arg=5 ctrl=2 arg=6 ctrl=5 arg=6 ctrl=8 arg=7 Make sure you understand why the program produces this output. Failure to understand this may give you a surprise later, when you happen to write a complex DO-instruction, and do not get the expected result. If the TO expression is omitted, there is no checking for an upper bound of the expression. If the BY subclause is omitted, then the default increment of 1 is used. If the FOR subclause is omitted, then there is no checking for a maximum number of iterations. Example: Loop convergence For the reasons just explained, the instruction: do ctrl=1 nop /* and other statements */ end will start with CTRL being 1, and then iterate through 2, 3, 4, ..., and never terminate except by LEAVE, RETURN, SIGNAL, or EXIT. Although similar constructs in other languages typically provokes an overflow at some point, something “strange” happens in REXX. Whenever the value of ctrl becomes too large, the incrementation of that variable produces a result that is identical to the old value of ctrl. For NUMERIC DIGITS set to 9, this happens when ctrl becomes 1.00000000E+9. When adding 1 to this number, the result is still 1.00000000E+9. Thus, the loop “converges” at that value. If the value of NUMERIC DIGITS is 1, then it will “converge” at 10, or 1E+1 which is the “correct” way of writing that number under NUMERIC DIGITS 1. You can in general disregard loop “convergence”, because it will only occur in very rare situations. Example: Difference between UNTIL and WHILE One frequent misunderstanding is that the WHILE and UNTIL subclauses of the DO/END instruction are equivalent, except that WHILE is checked before the first iteration, while UNTIL is first checked before the second iteration. This may be so in other languages, but in REXX. Because of the order in which the parts of the loop are performed, there are other differences. Consider the following code: count = 1 do i=1 while count \= 5 count = count + 1 end say i count count = 1 do i=1 until count=5 count = count + 1 end say i count After the first loop, the numbers 6 and 5, while in the second loop, the numbers 5 and 5 are written out. The reason is that a WHILE clause is checked after the control variable of the loop has been incremented, but an UNTIL expression is checked before the incrementation. A loop can be terminated in several ways. A RETURN or EXIT instruction terminates all active loops in the procedure levels terminated. Further, a SIGNAL instruction transferring control (i.e. neither a SIGNAL ON nor SIGNAL OFF) terminates all loops at the current procedural level. This applies even to “implicit” SIGNAL instructions, i.e. when triggering a condition handler by the method of SIGNAL. A LEAVE instruction terminates one or more loops. Last but not least, a loop can terminate itself, when it has reached its specified stop conditions. Note that the SIGNAL instruction terminates also non-repetitive loops (or rather: DO/END pairs), thus after an SIGNAL instruction, you must not execute an END instruction without having executed its corresponding DO first (and after the SIGNAL instruction). However, as long as you stay away from the ENDs, it is all right according to TRL to execute code within a loop without having properly activated the loop itself. Note that on exit from a loop, the value of the control variable has been incremented once after the last iteration of the loop, if the loop was terminated by the WHILE expression, by exceeding the number of max iterations, or if the control variable exceeded the stop value. However, the control variable has the value of the last iteration if the loop was terminated by the UNTIL expression, or by an instruction inside the loop (e.g. LEAVE, SIGNAL, etc.). The following algorithm in REXX code shows the execution of a DO instruction, assuming that expri, exprt, exprb, exprf, exprw, expru, and symbol have been taken from the syntax diagram of DO. @expri = expri @exprt = exprt @exprb = exprb @exprf = exprf @iters = 0 symbol = @expri start_of_loop: if symbol > @extrt then signal after_loop if @iters > @exprf then signal after_loop if exprw then signal after_loop instructions end_of_loop: if \expru then signal after_loop symbol = symbol + @exprb signal start_of_loop after_loop: Some notes are in order for this algorithm. First, it uses the SIGNAL instruction, which is defined to terminate all active loops. This aspect of the SIGNAL instruction has been ignored for the purpose of illustrating the DO, and consequently, the code shown above is not suitable for nested loops. Further, the order of the first four statements should be identical to the order in the corresponding subclauses in the repetitor. The code has also ignored that the WHILE and the UNTIL subclauses can not be used in the same DO instruction. And in addition, all variables starting with the at sign (@), are assumed to be internal variables, private to this particular loop. Within instructions, a LEAVE instruction is equivalent to signal after_loop, while a ITERATE instruction is equivalent to signal end_of_loop.
PREV NEXT