Lesson 12
Contents
Conditionals
A decision (both the human and the computer kind) is little more than the result of a test of conditions. For example: if it is true that the light switch is ON when you leave the room, then you make a small detour to hit the switch on your way out. In other words, you are testing for a certain condition in the course of your normal operation. If the condition is true, then you do something accordingly. If the condition is false, then you carry on with your normal operation as if nothing had happened.
This IF
...THEN
decision construction is precisely what goes on inside the computer when your program needs to test for a specific condition — like whether a number is odd or even or whether the program user typed in the correct answer, etc.
In Mops (as in other Forths) the IF
...THEN
decision process is a bit different from some other languages you may know, largely because of the stack orientation. The formal description of the IF
...THEN
construction is as follows:
IF xx THEN zz

( n  )

If ‘ 

The IF
part of the Mops decision process takes a number off of the stack and tests it to see if it is a nonzero (i.e., any number but zero). If the number on the stack is indeed nonzero, it performs the operation(s) immediately following IF
. Execution then proceeds to perform operations written after THEN
. However, if the IF
statement encounters a zero on the stack, it ignores all operations between IF
and THEN
and only performs operation written after the THEN
statement. In Mops the "THEN
" means to proceed with the program after the test, as in first do this, then do that.
You won't be able to experiment with the IF
...THEN
construction as easily as the operations you learned so far. That's because this construction must be compiled as part of a colon or method definition before it will run on Mops. For this example, we'll put the code for the IF
...THEN
statement inside a colon definition and compile it before we can run it. Type the following:
: TEST IF ." There is a nonzero number on the stack." THEN cr ;
This defines TEST
as a word that performs a check on the top number on the stack. If the number is nonzero, then the statement to that effect shows on the screen. If the top of the stack contains a zero, then the statement does not appear. Try it by placing various numbers (including zero and negative numbers) on the stack and typing TEST
. (Remember that an empty stack contains no numbers, and the IF
operation will cause a stack underflow error message to appear if there are no numbers to test. A zero, on the other hand, is indeed a number, and occupies space on the stack.)
Two Alternatives
Some decisions, however, are more complex because they involve two possible alternatives before proceeding. Take, for example, one of the most difficult decisions: getting up for work in the morning. After the alarm has gone off, and you lie in bed deciding whether you should really get going or grab another half hour, your mind is testing certain conditions. IF
you get up now, THEN
you'll be on time for work, or ELSE
you'll risk losing your job. IF
you get up now, THEN
you can get all the hot water, or ELSE
you'll have to rush through the shower to get the few drops that are left after the rest of the family has showered.
This kind of decision construction has been included in Mops. Its formal description is:
ELSE

(  )

If ‘ 

If its meaning isn't already clear to you, it is used like this:
IF xx ELSE yy THEN zz
As with the IF
...THEN
construction, this decision process looks first to see if the number on the top of the stack is nonzero before it makes any decision. Now redefine TEST
so it takes into account the ELSE
provision.
: TEST IF ." Nonzero number on stack." ELSE ." Zero on stack." THEN cr ;
Place three numbers (1, 0, and 3) on the stack and perform three tests:
1 0 3
test
Nonzero number on stack.test
Zero on stack.test
Nonzero number on stack.
Remember that the IF
operation takes the top number off the stack when it performs its check, just like most of Mops' operations. If you will need the number that is removed by IF
for a subsequent operation, then execute DUP
before IF
, or better still, convert your definition to use named input parameters or local variables to preserve the value for later calculation.
Truths, Falsehoods, and Comparisons
You may be wondering how the IF
...THEN
construction can be useful if it can only determine whether or not the number on the stack is zero. You might think that this kind of test would be rather limiting in light of the "realworld" decisions that a program may have to make, such as whether two integers are equal to each other, whether one is larger than the other, or whether a number is positive or negative. Actually, the IF
...THEN
construction frequently operates at the tail end of a fuller decision procedure that makes the realworld decisions possible. The first part of the procedure consists of one or more comparison operators whose results are either a zero or nonzero, depending on the outcome of the comparison.
To simplify the zero and nonzero terminology, Mops adheres to a programming language convention revolving around the terms TRUE and FALSE. These are also Mops words, and represent the values that appear in the stack as a result of the comparison operations. FALSE
represents a zero in the stack; TRUE
represents any nonzero number in the stack, including negative numbers. The Mops word TRUE
returns a nonzero number, that is, it returns a number which is all ones (in binary). As we'll see a bit later, this corresponds to the value 1.
Type these words now:
true false
You'll see from the stack display that FALSE
is the same as (zero), and TRUE
is 1.
Since these words — or rather the numbers they represent — are actually symbolic of a condition that has just been tested, they are sometimes referred to as flags. Flags in programs are something like markers planted in key places that symbolize a certain condition. A "true" flag signifies that a nonzero number is on the stack; a "false" flag signifies that a zero is on the stack. (Another term that is used is boolean”this really means the same as "flag".)
To help ingrain this difference between TRUE
and FALSE
n your mind, redefine test
yet again so that it reinforces the way the IF
...THEN
...ELSE
construction responds to TRUE
and FALSE
flags existing in the stack.
: TEST IF ." True!" ELSE ." False!" THEN cr ;
Now, place the numbers (zero) and 4 on the stack, leaving the the true and false flags from before underneath them on the stack. Then run the test four times:
0 4
test
True!test
False!test
False!test
True!
Below is a list of comparison operations that test the values of one or more numbers on the stack and leave either TRUE
or FALSE
flags on the stack. It is operations like these that you would perform on realworld integers before performing decision operations like IF
...THEN
...ELSE
. A new term appears in the stack notations below: ‘boolean
’. This means that the result is either TRUE
or FALSE
flag on the stack. (The term boolean is named after George Boole, who developed a logic system based on TRUE
and FALSE
values.)
0<

( n  boolean )

Leaves a TRUE flag on the stack if ‘n ’ is less than zero, otherwise leaves a FALSE flag.


0=

( n  boolean )

Leaves a TRUE flag on the stack if ‘n ’ equals zero, otherwise leaves a FALSE flag.

0<>

( n  boolean )

Leaves a TRUE flag on the stack if ‘n ’ does not equal zero, otherwise leaves a FALSE flag.

0>

( n  boolean )

Leaves a TRUE flag on the stack if ˜n ' is greater than zero, otherwise leaves a FALSE flag.

<

( n1 n2  boolean )

Leaves a TRUE flag on the stack if ‘n1 ’ is less than ‘n2 ’, otherwise leaves a FALSE flag.

<=

( n1 n2  boolean )

Leaves a TRUE flag on the stack if ‘n1 ’ is less than or equal to ‘n2 ’, otherwise leaves a FALSE flag.

<>  ( n1 n2  boolean )

Leaves a TRUE flag on the stack if ‘n1 ’ does not equal ‘n2 ’, otherwise leaves a FALSE flag.

=

( n1 n2  boolean )

Leaves a TRUE flag on the stack if ‘n1 ’ equals ‘n2 ’, otherwise leaves a FALSE flag.

>

( n1 n2  boolean )

Leaves a TRUE flag on the stack if ‘n1 ’ is greater than ‘n2 ’, otherwise leaves a FALSE flag.

>=

( n1 n2  boolean )

Leaves a TRUE flag on the stack if ‘n1 ’ is greater than or equal to ‘n2 ’, otherwise leaves a FALSE flag.

All the math in these comparison operations should be familiar to you. Remember that these operations, like the simple arithmetic ones, are set up in postfix notation. To remember which order to put numbers on the stack, simply reconstruct in your mind how the formula would look in algebraic notation. For example, to find out if ‘n1
’ is greater than ‘n2
’, the algebraic test would be:
n1 > n2
In Mops, you simply move the operation sign to the right:
n1 n2 >
But in this case, Mops is testing the validity of the statement. The numbers are taken from the stack when they are tested. If the statement is true, then a TRUE
flag goes to the stack; otherwise, a FALSE
flag goes there. Then an IF
¦THEN
or IF
...THEN
...ELSE
decision can be made on the number(s) in question.
Nested Decisions
It is also possible to have more than one IF
...THEN
...ELSE
decision working at one time within a single definition. To accomplish this, you place IF
...THEN
...ELSE
inside one another. For example, you can set up a series of decision operations that will examine a number in the stack, test it for several conditions, and then announce on the screen what condition that number meets. To do this, you'll nest several IF
...THEN
statements inside one another:
: IFTEST { n  } n 0< IF ." less than " ELSE n 0> IF ." greater than " THEN THEN ." zero." cr ;
IFTEST
is defined to check whether a number is positive, negative, or zero. Enter a number in the stack and then perform an IFTEST
on it. Try positive and negative numbers, as well as zero.
In the definition, the number is assigned to a named input parameter (‘n
’) to preserve the value in case it is tested by both IF
statements; the first IF
removes the number from the stack, which otherwise would leave nothing for the second IF
to test. The number is then tested whether it is less than zero. If so, less than zero. is displayed (because the program jumps ahead to the second THEN
). If the number is not negative, it is next compared to see if it is greater than zero in the second, nested IF
...THEN
construction. If the number is greater than zero, then the TRUE
flag is noted by the second IF
statement, and greater than zero. is displayed. If the number (which has already proven to be not less than zero) is not greater than zero, then it must be zero, and only zero. is displayed on the screen.
The key point to remember when nesting IF
...THEN
constructions is that every IF
must have a corresponding THEN
somewhere in the same colon definition. They are nested much in the same way that parenthetical delimiters in math formulas are nested:
( a / ( a  ( b * c ) ) + c )
So, each THEN
matches the IF
with which it is lined up. Formatting your code this way,
IF xx IF ww IF uu ELSE zz THEN THEN qq THEN yy
with corresponding IF
s, ELSE
s and THEN
s lining up, is a good idea for readability, not to mention subsequent debugging should you run into a snag.
Logical Operators
There will probably be occasions in your future programs in which you will have performed two comparison operations, and the resulting flags from those operations will be sitting on top of the stack. How the program proceeds from there depends on the state of those two flags. If one flag is TRUE
and the other FALSE
, they may meet the prerequisite that only one of the comparisons needs to be true for a certain operation to take place (e.g., ‘n1
’ is less than ‘n2
’, but ‘n1
’ is not less than zero). Conversely, you may need both flags to be TRUE
for a certain operation to take place (‘n1
’ is both less than ‘n2
’ and less than zero). In these special cases, you can use the logical operators, AND
and OR
:
AND

( n1 n2  n3 )

Performs a bitwise AND of ‘n1 ’ and ‘n2 ’ and leaves the result on the stack.


OR

( n1 n2  n3 )

Performs a bitwise OR of ‘n1 ’ and ‘n2 ’ and leaves the result on the stack.

Both of these operations look at the binary makeup of two numbers and produce a result. For AND
, the result will have a 1 in each position where both the first and the second numbers have a 1. For OR
, the result will have a 1 in each position where either the first or the second numbers (or both numbers) have a 1
.
For example, let's what happens when we AND
and OR
the numbers 1 and 3:
1 3 and . cr
1
1 3 or . cr
3
Remember that AND
and OR
perform these operations on the binary equivalents of the integers 1 and 3. The binary form of 1 is
0001
and the binary form of 3 is
0011
When we perform an AND
on these numbers, the result is:
0001
because the AND
operation returns a 1 (TRUE) for the rightmost column of bits in these binary numbers because both bits are 1 (TRUE).
For the same two numbers, when we perform an OR
operation (instead of an AND
), the result is:
0011
because the OR
operation above returns a 1 for the two rightmost column of bits in the binary numbers because one or both bits in each column are 1.
The names for these operations,
AND
andOR
, are sometimes used as verbs, as in I want to AND 1 and 3.
There is one last logical operator you should know about, the word XOR
(pronounced "exclusiveor"). Here is the formal description:
XOR

( n1 n2  n3 )

Performs a bitwise XOR of ‘n1 ’ and ‘n2 ’ and leaves the result on the stack.


As you can see, you use it exactly like you would use AND
or OR
. Try this:
1 3 xor . cr
2
However, unlike a "normal" OR
operation (sometimes referred to as "inclusiveor"), the XOR
operation returns (FALSE) where both respective bit columns in each number are 1 (TRUE). (In other words, we can have one or the other, but not both.)
This is the binary form of our answer, 2:
0010
In the second rightmost column, only one of our two integers was 1 (TRUE), so the answer in that column is 1 (TRUE), just like a "normal" OR
. However, the rightmost bit column of our answer is (FALSE) because the rightmost columns of both of our two integers (1 and 3) is 1 (TRUE), unlike a "normal" OR
.
Experiment a little bit with AND
, OR
, and XOR
in this fashion. Remember that these operations are working on the binary equivalent of the decimal numbers you type into the stack. If you have difficulty understanding an answer, try working out the problem on paper by converting each number to binary and then performing the AND, OR, or XOR arithmetic on the numbers as shown above. Once you understand the concept, you can trust Mops to do these operations correctly for you at all times.
The CASE Decision
It's not uncommon to have an instance in a program in which the next step could be one of several possibilities, depending on the actual number on the stack — not just whether it's TRUE (nonzero) or FALSE (zero). For example, a program may ask you to type a number from zero to nine. For most of the numbers, the subsequent step is the same, but for numbers 2, 6, and 7, the outcome is different. In other words, if it is the case of a 2 on the stack, then a unique operation takes place. Sure, you could run a series of comparison operations and nested IF
...THEN
constructions on the number to narrow it down (e.g., testing if the number is not less than two nor greater than two), but that gets cumbersome when you're testing for many numbers.
Mops' shortcut for this multipledecision making is the CASE
structure. Using the example above, you could define a word like this:
: CASETEST ( n  ) \ Print TWO, SIX, SEVEN, or OTHER CASE 2 OF ." TWO" ENDOF 6 OF ." SIX" ENDOF 7 OF ." SEVEN" ENDOF ." OTHER" ENDCASE cr ;
This word takes the number on the stack and checks whether it is a CASE
OF 2, 6, or 7. If a particular CASE
is valid, then the branch executes statements until it encounters an ENDOF
delimiter. At that point, execution jumps to ENDCASE
, ignoring all other statements within the CASE
construct. If none of the cases are valid, then execution continues toward the ENDCASE
delimiter.
If a statement is inserted before ENDCASE
(like ‘." OTHER"
’ in the example), then it is executed whenever the test of all cases fail. This statement is known as the default statement, since it's the statementwhich gets executed by default if nothing else does.
Note: The
CASE
test retains the test value on the stack, and it is dropped at the end by theENDCASE
. In the default statement, particularly, you might want to make use of the test value. But if you're going to use it (take it off the stack), remember toDUP
it first (or just put a dummy value on the stack) to be dropped by theENDCASE
.
Lesson 11  Tutorial  Lesson 13 
Documentation 