Loops Lesson 7 Contents

Computer programs frequently need certain operations to be repeated a specific number of times.

For example, finding the sum of 10 numbers in the stack would normally take a stream of over nine statements. To a programmer's way of thinking, this makes the program several steps longer than necessary. Better to find a shortcut way of repeating the add operation as many times as is needed to do the job, without increasing program size with a long series of identical statements. That's where the loop construct comes in.

A loop sets up a kind of merry-go-round in your program, with a beginning and an end. At the end of the loop is an instruction that tells the program to "loop back" to the beginning of the loop. All the statements between the two are repeated each time program execution goes through the loop.

Mops has two major categories of loops: definite and indefinite. As their names imply, each category has a different way of figuring out when to stop going around the loop. The definite loop performs only as many loops as the program specifies; an indefinite loop, on the other hand, keeps looping until a certain condition is met.

Let's look at each kind of loop more closely.

Definite Loops
Consider the 10-number addition problem discussed above. Since you know ahead of time that there will be exactly ten numbers on the stack before any addition takes place, you could use a definite loop to perform nine addition operations on the stack.

A definite loop in Mops consists of a DO...LOOP statement, which expects to find two numbers on the stack before the DO executes. The two numbers represent the count of the repetitions that the DO...LOOP statement is to make; the second value is incremented before the loop begins.

Because loops work only in compiled statements, put them inside colon definitions to see how they operate. Define a new word that adds up 10 numbers from the stack by performing nine repetitions of addition:

: addten    ( n1…n10 -- sum )
       9 0  DO  +  LOOP .  cr  ;

During execution, this DO...LOOP counts up from zero to nine each time through the loop. After the ninth time around, the loop stops; the top of the stack (the sum) is displayed and a carriage return is executed.

You may be wondering where Mops keeps track of the loop counter if the parameter stack is used to hold all the numbers that get added. The answer to that involves a powerful feature called indexing, which will play an increasingly important role the more you learn about Mops.

When you entered the 9 and the 0 prior to the DO...LOOP construction in the example above, what you couldn't see was that the two numbers were automatically moved to another part of memory. The first number you typed (the 9) is called the limit, because that number represents the limit of how many times the loop is to be executed.

The second number (the 0) is called the index. This number increments by one each time through the loop. So, the first time the DO...LOOP construction is encountered in the above example, the index number bumps up to a one; the next time to a two, and so on. When the index and limit numbers are equal, then the DO...LOOP construction "knows" that it's time to move on.

What's interesting about this kind of indexing is that you can use the index number as a counter while executing a loop. By setting the limit and index numbers to integers you need to operate with inside a loop (they can be any integers you want), you can copy the index number to the parameter stack each time around the loop and use that number for a calculation, a graphics plot point, a multiplication factor, or whatever.

The Mops word that copies the index to the parameter stack is "I" (the letter I):

  I     ( -- n )     Copies the current index value to the parameter stack

Remember that this word only copies the index; it does not disturb the index in any way. Here are a couple of examples to demonstrate.

Define a word, fivecount, that displays a series of numbers from 101 to 105:

          { -- }
: fivecount    ( -- )
       106 101  DO  i  .   LOOP  ;

Notice that the limit is set to 106. That's because the index is incremented when execution reaches LOOP. The first time through, the index was 101, and the "I" word copied the index to the parameter stack; the dot command then displayed it on the screen. On the fifth execution, 105 was the index. When execution reached LOOP, the index incremented to 106, at which point it equalled the limit and broke out of the loop.

You can similarly use the index number to perform operations on a number passed on the parameter stack prior to execution. Consider the following definition:

: timestables          { n1 -- }
       13 1  DO  n1 i *  .  LOOP  cr  ;

If you then type "5 timestables", the program goes through twelve loops of multiplying 5 times the incrementing index number, one through twelve.

You have the flexibility in Mops to place all kinds of other statements within a DO...LOOP construction, including all those conditional decision constructs covered earlier.

There will be times when you'll want to use a DO...LOOP for the sake of compactness, but the increment you might wish to use is something other than the one automatically performed by the loop (increment by 1). For those occasions, you have the optional loop ending, +LOOP. Whatever number you place in front of the +LOOP ending will be the increment that the DO...LOOP uses to adjust the index. You can even use a negative number if you wish the loop to decrement.

Here's how you would use +LOOP to manage a countdown:

: countdown       ( -- )
       1 10
       DO         i  .  -1
       +LOOP  cr  ." Ignition...Liftoff! "   cr  ;

Notice that in this case, since the program is counting backwards, the limit is zero and the index is 10. Each time through the loop, the index is incremented by a -1. Also notice that the limit value 1 gets typed by the program. When the index is counted down and becomes equal to the limit, the loop continues and doesn't stop until the index is counted down to the limit minus 1, unlike the situation when the index is being incremented, where the loop stops when the index equals the limit. The best way to think about this is as if there is a "fence" in between the limit and the limit -1. Whenever the index crosses the fence, in either direction, the loop stops. This will be true even if you write a program in which the increment value changes sign during the running of the loop, i.e., goes from negative to positive .

Nested Loops
It is also sometimes desirable to have more than one DO...LOOP going on simultaneously. As with IF...THEN constructions, DO...LOOP operations can be nested inside one another. All you have to remember is to supply one LOOP (or +LOOP) for each DO within the colon definition. For example, you could add a delay loop in the "countdown" definition above to make it look like the seconds are being counted down (a better way would be to use the system clock, as is done in the word WaitASec in the Utility Code file of the Speech Stuff folder).

Insert:

60000 0 DO LOOP

after the dot statement inside the previous DO...LOOP operation, and again after the "Ignition" line:

: countdown
	1 10
	DO	i . cr
		600000 0 DO LOOP
	-1 +LOOP
	." Ignition"   cr
	600000 0 DO LOOP
	." Liftoff" cr ;

Type "countdown" and watch the seconds tick away:

countdown
10
9
8
7
6
5
4
3
2
1
Ignition
Liftoff 

Of course, in a real program, you would probably take out the "600000 0 DO LOOP" and make it another definition, perhaps called "delay". Then, if you needed to change the delay value you would only have to change your code in one place. This technique is called "factoring".

If you are in a nested loop and need access to the outer index, Mops has a predefined word that allows you to copy that number to the parameter stack, just like "I" copies the current loop index number to the stack. That word is "J".

  J     ( -- n )   Copies to the parameter stack the index of the next outer loop of a DO...LOOP construct

In other words, "J" looks up the index of the loop just outside the current DO...LOOP construction and copies that number to the parameter stack. But note that if you have factored out an inner loop into another definition, you can't use J this way—you won't get the right value. J only works with nested loops within the one definition.

Abort Loop
You may have a situation in which you need to bail out of a DO...LOOP before its normal completion—perhaps because of some special case situation. The word LEAVE is available for this purpose. Here's the countdown example again, appropriately modified:

: countdown
	1 10
	DO	i . cr
		600000 0 DO LOOP
		i 7 = IF ." Aborted!!" cr LEAVE THEN
	-1 +LOOP ;
countdown
10
9
8
7
Aborted!!

Note that we had to remove the "Ignition" and "Liftoff" messages, otherwise "Ignition" and "Liftoff" would have appeared after the countdown was aborted, which wouldn't really be what we want. We'll show a better way of handling this shortly.

Indefinite Loops
An indefinite loop is another kind of loop you'll use from often in a Mops program. As its name implies, an indefinite loop keeps going in circles until a certain condition exists. It can go around one time or thousands of times while waiting for that condition to occur. In Mops, that condition is the presence of a TRUE flag (non-zero number) on top of the stack.
One kind of indefinite loop is defined as:

BEGIN xxx
UNTIL

Performs xxx operations repeatedly until a TRUE flag exists on the stack. A useful variation is:

BEGIN xxx
NUNTIL

Performs xxx operations repeatedly until a FALSE flag exists on the stack.

Here's an example of how you might use a BEGIN...UNTIL construction. In this case, the indefinite loop will be waiting for you to enter a lower case "a" on the keyboard. The KEY operation pauses the program until you press a key, and then it pushes onto the stack a standard, equivalent code number (called an ASCII code—explained later) for the character keyed in. If the number on the stack is 97 decimal (the ASCII code number for the lower case a), then a 1 (TRUE flag) is placed on the stack, and the loop ends. Otherwise, a FALSE flag is placed on the stack, and execution returns to the beginning of the loop.

: begintest BEGIN key 97 = UNTIL ;

Now, type begintest, and tap all kinds of letters on the keyboard. Until you type an "a," the program keeps going around in circles. Another indefinite loop to remember is:

BEGIN   xxx
WHILE   yyy
REPEAT         

Executes xxx each time through the loop, and executes yyy only if a nonzero number is on top of the stack at the WHILE; the loop ends when top of stack is zero.

In this case, the operation after the WHILE statement may never execute if a FALSE flag exists on the stack after BEGIN's operation (xxx).

There is also a variation to this kind of loop:

BEGIN    xxx
NWHILE   yyy
REPEAT

Always executes xxx each time through the loop, and executes yyy only if a zero appears on the stack; loop ends when stack shows a nonzero value.

EXIT
This is a good place to mention another very useful operation, EXIT. This completely exits the current definition (word or method). Here's a modified version of beginTest:

: beginTest
	BEGIN
		key 97 = IF EXIT THEN
		key 98 =
	UNTIL ;

This definion will keep running until you type either an "a" (97) or a "b" (98).
You can also write:

: beginTest
	BEGIN
		key 97 = IF EXIT THEN
		key 98 = IF EXIT THEN
	AGAIN ;

The word AGAIN returns straight to the BEGIN, without testing anything. Of course, if you write a BEGIN...AGAIN loop, the loop must have some other way of terminating, such as EXIT.

If you write EXIT within a DO...LOOP, you have to remember one more thing—Mops (as with any kind of Forth) keeps some extra information around during DO...LOOP, and you have to remove this information if you are going to end a DO...LOOP in some unusual way (that is, not via LOOP, +LOOP or LEAVE. The word to use is UNLOOP. We'll illustrate this with the countdown example again:

: countdown
	1 10
	DO	i . cr
		600000 0 DO LOOP
		i 7 = IF ." Aborted!!" cr UNLOOP EXIT THEN
	-1 +LOOP
	." Ignition"   cr
	600000 0 DO LOOP
	." Liftoff" cr ;
countdown
10
9
8
7
Aborted!!

You'll notice that we've been able to reinstate the "Ignition" and "Liftoff" messages, but by aborting the loop via UNLOOP and EXIT we bypass them.

When you're designing loops, it is sometimes possible for an infinite loop to slip in accidentally. Try to avoid them! Double-check the stack operations of your indefinite loops to make sure that there is always at least one condition that will allow you or your program to terminate the loop. Otherwise, your program will appear to "lock up" and be unresponsive to your keyboard input. If this happens, you'll probably have to reset your Mac. This will generally be more time consuming than double-checking your loop terminating conditions.

Previous Chapter Contents Next Chapter
This page online:  http://PowerMops.com/MopsManual/Lessons/Chapter7.html