Nearly all computer programs, except for the simplest,
will check to see if a specified condition is either true or false,
and carry out different instructions based on the result.
We have already seen how a DO ... LOOP
works
in Forth. In this special case, the word LOOP
adds 1 to the loop counter and then checks whether
or not the condition that the loop counter is equal to the ending
count of the loop is true or false. Often, we will want to instruct
the computer to check conditions that are not related to loops and
then execute one sequence of words if the condition is true, or
another sequence of words if the condition is false. Let's
see how we can do this in Forth.
To start with, let's look at how to test a condition and how
the result of the test is represented. As an example, our condition
to be tested is whether or not the variable X
is greater
than 2
. In Forth, such a test would be written as
X @ 2 >
X
onto the stack, next
place the integer 2
on the stack, and then use the
word >
to check whether or not the number buried
one cell deep into the stack is greater than the number on the
top of the stack. The stack diagram for >
is>
removes both numbers from the stack
and leaves a boolean flag, written as "b" in the
stack diagram above. The flag "b" is itself another
number, but it is a number that is always either 0 or
-1. The value of the flag represents one of two
states: true, corresponding to the value -1 and
false, corresponding to the value 0. As a convenience,
Forth provides two predefined constants TRUE
and FALSE
. Try the following.TRUE .
FALSE .
>
,
other words in Forth can test for equality of two numbers,
a less than condition, and perform several other comparisons.
A flag on top of the stack is used by the word IF
to cause the computer to jump to different locations
inside the executing word, based on the flag's value. This
process is called conditional branching and all programming
languages provide a way to do this. The word IF
is
part of a control structure made up of the words
IF ... ELSE ... THEN
, where
...
represents some arbitrary word sequences.
Many other programming languages have a structure similar to
this, but in Forth its use is slightly different. The
word IF
assumes the conditional test has already
been performed and that there is a flag on top of the stack.
Let's illustrate the use of the IF ... ELSE ... THEN
structure with an example.
Suppose we want to write a word that prints whether a number
given to it is "even" or "odd". We could
define this word as follows
: parity ( n -- | print whether a number is even or odd )
2 MOD 0=
IF
." even"
ELSE
." odd"
THEN ;
parity
, the conditional
test is given by the line2 MOD 0=
MOD
performs a division, except
that it returns the remainder instead of the quotient.
An "even" number divided by 2 has a zero
remainder, so we check to see if the value returned by
MOD
, on top of the stack, is equal to zero.
The word 0=
returns a true flag when the number
on top of the stack is zero, a false flag otherwise.
When IF
examines this flag, if it finds the
flag to be true, execution jumps to the word following
IF
. On the other hand, if the flag is false,
execution branches to the word following ELSE
.
To see how it works, try typing a number followed by
the word parity
, e.g.4 parity
Here are a few other points to note about the
IF ... ELSE ... THEN
structure:
IF
examines the flag on
top of the stack, it treats any non-zero value
as representing true. A zero value always corresponds
to false. Therefore, we could define the word
parity
as:: parity ( n -- ) 2 MOD IF ." odd" ELSE ." even" THEN ;
." odd"
and ."even"
in the new version of parity
.ELSE
...
portion of the structure. For example, we can define: odd? ( n -- ) 2 MOD IF ." odd" THEN ;
odd?
,
e.g.5 odd?
IF
and ELSE
are executed;
when the flag is false, the words enclosed between
ELSE
and THEN
are executed. After
either branch is executed, the computer resumes execution
after the word THEN
. The two branches come
back together again following THEN
-- this is
a feature of structured programming, which makes it
easier for a person to trace the possible paths a computer
may take through a sequence of instructions.IF ... ELSE ... THEN
structure can be placed
inside a branch of another IF ... ELSE ... THEN
structure. This is called nesting, and
you will see an example of nested structures in the next section.
Because Forth words often use values on the stack for input data,
it may become difficult to keep the items ordered exactly as
needed during the calculation, especially when there are several
input values required. While Forth provides several stack
manipulation words such as DUP SWAP ROT
, etc.,
sometimes the most convenient operation is to temporarily remove
an item from the top of the stack, then place it back on the stack
when needed. Of course we may use variables for this purpose, but
Forth provides a simpler way to accomplish this by providing
another stack, called the return stack.
An item on the stack can be temporarily moved onto the return stack
by using the word >R
. The item may be moved
back from the return stack to the ordinary data stack with the word
R>
.
The following example also illustrates the use of the return
stack.
: this_date ( -- day month year )
time&date >r >r >r 2drop drop r> r> r> ;
this_date
returns today's date on the
stack with the year on top. It does this by calling kForth's
built-in word, TIME&DATE
, which has the following
stack diagram:
time&date ( -- secs mins hours day month year )
this_date
to only return
the day, month, and year, so we must remove secs, mins, and hours
left on the stack by TIME&DATE
. However, day, month,
and year are on top and the three numbers we want to drop
(secs, mins, and hours) are buried underneath.
Using >R
three times, we remove the year, month, and
day from the stack, in that order. These numbers are moved onto
the return stack. Now we use 2DROP
and DROP
to remove hours, mins, and secs from the stack. Finally, we use the
word R>
three times to move day, month, and year
from the return stack back to the data stack.
A word of caution to the novice Forth user: the return stack must be
used with the following restrictions because Forth itself places items
on the return stack at the beginning of executing a word and also
when executing DO
loops:
>R
must be "popped" from the return stack
with a corresponding R>
before the end of the word.
R>
for every >R
inside of a DO ... LOOP
.
DO
loops, the loop index words I
and J
must not be used when items have been pushed onto the
return stack but not yet popped.
In this example, we will make use of what we have learned up to now
to compute the age of a person given their birth date. Following good
Forth practice, we will first define a few simple words which we
anticipate will be helpful for writing the actual age calculator:
: this_year ( -- year )
this_date >r 2drop r> ;
: this_month ( -- month )
this_date drop nip ;
: this_day ( -- day )
this_date 2drop ;
this_year
, this_month
, and
this_day
all use this_date
, defined
previously, and remove any extra items from the stack. A couple
more words will be helpful in our calculation:
: date< ( day1 month1 day2 month2 -- flag )
rot swap
2dup \ is month1 less than month2?
< if
2drop 2drop \ remove items on stack --- no further test needed
true \ leave true flag on the stack
else
= if \ is month1 equal to month2?
< \ flag represents day1 less than day2
else
2drop \ remove items on stack --- month1 is greater than
false \ month2 so return false flag
then
then ;
IF ... ELSE ... THEN
structures in our definition of DATE<
. The first
IF
examines the flag returned by <
, which
tests whether or not month1 is less than month2. If month1 is not less
than month2, we must then check to see if month1 is equal to month2.
The word =
tests this condition and returns the appropriate
flag, which is examined by the second IF
.
: after_today ( day month -- flag | test whether day and month are in future)
this_day this_month 2swap date< ;
: age ( day month year -- age | calculate age given birth date )
this_year swap - \ number of years between birth year and this year
-rot \ move top item to bottom of stack
after_today if \ is birthday later than today?
1- \ yes, subtract one from number of years
then ;
AGE
by typing age .
You may have noticed that in our example of the age calculator,
we defined several words, not just one. Breaking the calculation
into individual short words is a way to make writing a
program simpler, easier to understand, and easier to test
when, as is inevitable, a program doesn't work like you imagined.
Previously defined words can be used to write higher level words,
making the higher level words more readable. Well-written Forth
programs will often have short low-level words, each of which performs
a single and simple computation matching well the name of that word.
As an example, consider the game tetris.4th
written in Forth
(
pure source). Notice how the words defined towards the beginning
of the program, such as DRAW-PIT
and UPDATE-SCORE
are short words with well-defined functions matching their names. Near
the end of the program, various words are combined to define the
higher level word, PLAY-GAME
. Although the working of
the lower level words may not be immediately apparent from reading
their definitions, in a properly factored Forth program,
the high level word(s), such as PLAY-GAME
, are very
readable and often times resemble a natural language description of
the actions performed by the word. Good factoring is a skill acquired
through practice with writing programs in any programming language, and
often results in programs which are more easy to diagnose and repair when
things don't work as expected.