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
IFis 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.
dupthe 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.
swapthe 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
orfor 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