Forth control flow
Contents
- Sources
- Boolean values
- Operator equivalents
- Conditional phrasing:
if
…else
…then
- Logical
and
,or
- invert
- Words with included conditionals
- Execute this file
Sources
- Leo Brodie, Starting Forth: An Introduction to the FORTH Language and Operating System for Beginners and Professionals, online edition, Chapter 4: Decisions, Decisions…
- Edward K. Conklin and Elizabeth D. Rather, Forth Programmer’s Handbook 3rd edition, Chapter 4 Structured Programming
Boolean values
Logical evaluation leaves a boolean flag on the stack. Nonzero values, including both 1 and -1, are interpreted as true, while a zero value is interpreted as false.
The words true
and false
can also be used.
: test-bools-words
clearstack true true and assert( true = )
clearstack true false and assert( false = )
clearstack true true or assert( true = )
clearstack true false or assert( true = )
clearstack false false or assert( false = )
;
test-bools-words
However, when using these words, only the value -1 is interpreted as
true
:
: test-bools-vals
-1 -1 and assert( true = )
-1 0 and assert( false = )
-1 -1 or assert( true = )
-1 0 or assert( true = )
0 0 or assert( false = )
;
test-bools-vals
Operator equivalents
These leave either a nonzero (true) or a zero (false) on the stack.
=
equal, <>
not equal, >
and <
.
1 1 = 0= s" fail " exception and throw
1 2 <> 0= s" fail " exception and throw
1 2 < 0= s" fail " exception and throw
2 1 > 0= s" fail " exception and throw
0=
equals zero, 0<
negative, 0>
positive.
0 0= 0= s" fail " exception and throw
-1 0< 0= s" fail " exception and throw
1 0> 0= s" fail " exception and throw
Conditional phrasing: if
…else
…then
Compile-time only, so can only be used in word definitions.
if
evaluates any non-zero value as true, and proceeds.
Here, push 1 onto the stack and evaluate it using if
. Because 1 is
truthy, push 2 on to the stack.
: f ( -- 2 ) 1 if 2 then ;
: f-test ( -- ) f assert( 2 = ) ;
f-test
Note that if
modifies the stack. I did not understand this at first.
When
IF
is executed, the item on top of the stack is removed and examined. (Conklin and Rather, Forth Programmer’s Handbook 100)
Demo via debugger:
: test if ." NONZERO " else ." ZERO" then ; ok
-1 dbg test
: test
Scanning code...
Nesting debugger ready!
[ 1 ] 18446744073709551615
101E20710 1017E7458 IF -> [ 0 ]
101E20720 1017E7450 .\" NONZERO " -> NONZERO [ 0 ]
101E20768 1017E7450 ELSE -> [ 0 ]
101E207C0 1017E7428 THEN ; -> ok
0 dbg test
: test
Scanning code...
Nesting debugger ready!
[ 1 ] 00000
101E20710 1017E7458 IF -> [ 0 ]
101E20778 1017E7450 .\" ZERO" -> ZERO[ 0 ]
101E207C0 1017E7428 THEN ; -> ok
1 dbg test
: test
Scanning code...
Nesting debugger ready!
[ 1 ] 00001
101E20710 1017E7458 IF -> [ 0 ]
101E20720 1017E7450 .\" NONZERO " -> NONZERO [ 0 ]
101E20768 1017E7450 ELSE -> [ 0 ]
101E207C0 1017E7428 THEN ; -> ok
Because if
evaluates any non-zero value as true, you can omit a
comparison operator when, for example, you want to check if a number
is zero:
: iszero ( n -- ) dup if ." NONZERO " else ." ZERO " drop then ;
Or if you want to check if two numbers are equal, here using subtraction:
: eq ( n n -- ) - if ." NOT EQUAL " else ." EQUAL " then ;
Flag arithmetic
You can use swap
to combine flags from successive tests. Here, we
want a flag indicating whether a number is either negative or greater
than five.
dup
the number so we can test it twice.- Test the number to see if it is negative. This will leave a logical flag on the stack.
swap
the flag with the original number.- Test the number to see if it is greater than five.
- We now have two flags on the stack. If we add them together, their
sum is a flag indicating the result of a logical
or
for these two conditions.
: f ( n -- b ) dup 0< swap 5 > + ;
: test-f ( -- )
-2 f assert( 0< )
-1 f assert( 0< )
0 f assert( 0= )
1 f assert( 0= )
2 f assert( 0= )
5 f assert( 0= )
6 f assert( 0< )
;
test-f
Logical and
, or
For cases where adding flags would not produce an accurate logical
result, there are the Forth words and
and or
.
Here, we want to know if a number is greater than 5 and a multiple of 5:
: f ( n -- b ) dup 5 > swap 5 mod 0= and ;
: test-f ( -- )
-1 f assert( 0= )
0 f assert( 0= )
1 f assert( 0= )
5 f assert( 0= )
6 f assert( 0= )
9 f assert( 0= )
10 f assert( 0< )
11 f assert( 0= )
15 f assert( 0< )
;
test-f
Here, we want to know if a number is less than -5 or greater than 5:
: f ( n -- b ) dup -5 < swap 5 > or ;
: test-f ( -- )
-10 f assert( 0< )
-5 f assert( 0= )
-1 f assert( 0= )
0 f assert( 0= )
1 f assert( 0= )
5 f assert( 0= )
6 f assert( 0< )
10 f assert( 0< )
;
test-f
invert
Reverses the flag on the stack.
Here, push 0 onto the stack and evaluate it using if
. 0 is falsy, but
we invert the flag on the stack.
: f ( -- 2 ) 0 invert if 2 then ;
: f-test ( -- ) f assert( 2 = ) ;
f-test
Words with included conditionals
?dup
duplicates the top stack value only if it is non-zero:
: test-?dup
1 ?dup + assert( 2 = )
0 ?dup assert( 0 = ) ;
test-?dup
abort
aborts if the flag on the stack is true. Compile-time only.
Useful for aborting execution in case of an error that you can test for
(e.g. division by zero).
$ gforth-itc
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: test-abort 1 abort" aborted " ; ok
test-abort
:2: aborted
>>>test-abort<<<
Backtrace:
$10DC789A8 throw
$10DCCB2D8 c(abort")
.s <0> ok
Execute this file
[Running] codedown forth < control-flow.md | grep . | gforth
Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: test-bools-words compiled
clearstack true true and assert( true = ) compiled
clearstack true false and assert( false = ) compiled
clearstack true true or assert( true = ) compiled
clearstack true false or assert( true = ) compiled
clearstack false false or assert( false = ) compiled
; ok
test-bools-words ok
: test-bools-vals compiled
-1 -1 and assert( true = ) compiled
-1 0 and assert( false = ) compiled
-1 -1 or assert( true = ) compiled
-1 0 or assert( true = ) compiled
0 0 or assert( false = ) compiled
; ok
test-bools-vals ok
1 1 = 0= s" fail " exception and throw ok
1 2 <> 0= s" fail " exception and throw ok
1 2 < 0= s" fail " exception and throw ok
2 1 > 0= s" fail " exception and throw ok
0 0= 0= s" fail " exception and throw ok
-1 0< 0= s" fail " exception and throw ok
1 0> 0= s" fail " exception and throw ok
: f ( -- 2 ) 1 if 2 then ; ok
: f-test ( -- ) f assert( 2 = ) ; ok
f-test ok
: iszero ( n -- ) dup if ." NONZERO " else ." ZERO " drop then ; ok
: eq ( n n -- ) - if ." NOT EQUAL " else ." EQUAL " then ; ok
redefined f : f ( n -- b ) dup 0< swap 5 > + ; ok
: test-f ( -- ) compiled
-2 f assert( 0< ) compiled
-1 f assert( 0< ) compiled
0 f assert( 0= ) compiled
1 f assert( 0= ) compiled
2 f assert( 0= ) compiled
5 f assert( 0= ) compiled
6 f assert( 0< ) compiled
; ok
test-f ok
redefined f : f ( n -- b ) dup 5 > swap 5 mod 0= and ; ok
: test-f ( -- ) compiled
-1 f assert( 0= ) compiled
0 f assert( 0= ) compiled
1 f assert( 0= ) compiled
5 f assert( 0= ) compiled
6 f assert( 0= ) compiled
9 f assert( 0= ) compiled
10 f assert( 0< ) compiled
11 f assert( 0= ) compiled
15 f assert( 0< ) compiled
; ok
redefined test-f test-f ok
redefined f : f ( n -- b ) dup -5 < swap 5 > or ; ok
: test-f ( -- ) compiled
-10 f assert( 0< ) compiled
-5 f assert( 0= ) compiled
-1 f assert( 0= ) compiled
0 f assert( 0= ) compiled
1 f assert( 0= ) compiled
5 f assert( 0= ) compiled
6 f assert( 0< ) compiled
10 f assert( 0< ) compiled
; ok
redefined test-f test-f ok
redefined f : f ( -- 2 ) 0 invert if 2 then ; ok
redefined f-test : f-test ( -- ) f assert( 2 = ) ; ok
f-test ok
: test-?dup compiled
1 ?dup + assert( 2 = ) compiled
0 ?dup assert( 0 = ) ; ok
test-?dup ok
[Done] exited with code=0 in 0.096 seconds