Control Flow in Forth
Learn conditionals, CASE branching, and counted and indefinite loops in Forth using stack flags and runnable Docker examples
Control flow decides which words run and how often. In most languages a conditional reads a boolean expression in parentheses; in Forth, control flow is just more words operating on the data stack. A comparison word leaves a flag on the stack, and a structuring word like IF consumes that flag to decide what happens next.
This is a direct consequence of Forth’s stack-based, concatenative paradigm. There are no parentheses, no condition expressions, and no statement terminators — only words separated by whitespace. The control-flow words (IF, ELSE, THEN, DO, LOOP, BEGIN, UNTIL, WHILE, REPEAT, CASE) are themselves part of the language and can only appear inside a definition (: ... ;), because they compile branching logic into the word being defined.
A flag in Forth is just a number: 0 is false, and any non-zero value is true. The standard comparison words leave the canonical true flag -1 (all bits set) or 0. Because flags are ordinary stack values, you can compute, store, and combine them like any other number.
In this tutorial you will learn how to branch with IF...ELSE...THEN, select among many cases with CASE, run counted loops with DO...LOOP, and write condition-driven loops with BEGIN...WHILE...REPEAT and BEGIN...UNTIL.
Conditionals: IF, ELSE, THEN
A conditional reads a flag from the stack. IF consumes the top of the stack: if it is non-zero (true), the words between IF and ELSE run; otherwise the words between ELSE and THEN run. THEN simply marks the end of the conditional — it is not a “then do this” keyword as in other languages. The ELSE clause is optional.
Comparison words like >, <, and = take two values and leave a flag. Note that IF requires a flag to already be on the stack, so the comparison must come before IF.
Create a file named conditionals.fth:
| |
SHOW-SIGN shows nested conditionals. DUP copies the number so the comparison can consume one copy while keeping the original for the next test or the final DROP. Each IF needs a matching THEN, which is why two THENs appear at the end. ?BIG shows the single-branch form: with no ELSE, nothing happens when the flag is false.
Multi-way Branching: CASE
When you need to select among many specific values, nesting IFs becomes hard to read. Forth provides CASE...OF...ENDOF...ENDCASE for this. The value to test sits on the stack; each value OF ... ENDOF clause compares against it, and the first match runs its body. Any words after the last ENDOF (before ENDCASE) act as the default case. ENDCASE automatically drops the test value.
Create a file named multiway.fth:
| |
For 7 WEEKDAY, no OF clause matches, so the default text prints. The original value (7) is still on the stack when the default runs, and ENDCASE discards it for you.
Counted Loops: DO, LOOP, and friends
When you know how many iterations you need, use a definite loop. DO takes two values — a limit and a starting index — and runs the body for each index from the start up to (but not including) the limit. Inside the loop, the word I pushes the current index onto the stack.
?DOis likeDObut skips the body entirely if the limit equals the start (aDOloop with equal bounds would run a huge number of times).+LOOPtakes a step value, letting you count by amounts other than 1.- In nested loops,
Iis the inner index andJis the next-outer index. LEAVEexits the innermost loop immediately.
Create a file named definite_loops.fth:
| |
SQUARES prints the squares of 1 through n — note 1+ so the limit is one past the last value we want. EVENS steps by 2 with +LOOP. MULT-TABLE nests two loops and uses both I and J to build a small multiplication grid. FIND-FIVE uses ?DO to guard against an empty range and LEAVE to stop early once the target index is reached.
Indefinite Loops: BEGIN, WHILE, REPEAT, and UNTIL
When the number of iterations depends on a condition rather than a count, use an indefinite loop. Forth offers two shapes:
BEGIN ... condition WHILE ... REPEAT— tests at the top. The body runs only while the condition is true (this can run zero times).BEGIN ... condition UNTIL— tests at the bottom. The body always runs at least once, and repeats until the condition becomes true.
Create a file named indefinite_loops.fth:
| |
COUNTDOWN keeps a value on the stack, prints and decrements it with 1-, and loops while it stays above zero; when it reaches 0, WHILE fails and DROP cleans up the leftover. COUNTUP keeps a counter beneath the limit, increments and prints it, then uses 2DUP = to test whether the counter has reached the limit — UNTIL exits once it has, and 2DROP clears both values.
Running with Docker
Each file is a complete, self-contained Forth program. Use the official Gforth image to run them:
| |
Expected Output
conditionals.fth:
positive
negative
zero
that is a big number
multiway.fth:
Monday
Friday
Weekend or invalid
definite_loops.fth:
1 4 9 16 25
0 2 4 6 8
1 2 3
2 4 6
3 6 9
found 5 at index 5
indefinite_loops.fth:
5 4 3 2 1
1 2 3 4 5
Key Concepts
- Conditions are flags on the stack — comparison words like
>,<, and=leave a flag (0for false, non-zero for true) thatIF,UNTIL, andWHILEconsume. THENends anIF, it does not begin a branch — readIF...ELSE...THENas “if / else / end-if”. EveryIFneeds a matchingTHEN.- Control-flow words live inside definitions —
IF,DO,BEGINand friends compile branching into a word, so they belong between:and;. CASEreplaces deepIFnesting — usevalue OF ... ENDOFclauses for clean multi-way selection;ENDCASEdrops the test value automatically.DO...LOOPis counted;IandJgive you the indices — supply a limit and a start, use?DOto guard empty ranges,+LOOPfor custom steps, andLEAVEto break out early.- Choose your loop test position —
BEGIN...WHILE...REPEATtests at the top (may run zero times);BEGIN...UNTILtests at the bottom (runs at least once). - Mind the stack inside loops — use
DUP,DROP,2DUP, and2DROPto keep the right values available for the next iteration and to clean up when the loop ends.
Running Today
All examples can be run using Docker:
docker pull forthy42/gforth:latest
Comments
Loading comments...
Leave a Comment