Bitforth
How much Forth do you really need for Turing completeness? Surprisingly little.
Bitforth is an ultra-minimal subset of Forth that achieves Turing completeness using only:
- A data stack and a call stack
- Literals 0, 1, -1
- Colon definitions with calls/returns (including recursion)
- Multi-way branches (
CASE/OF
)
By using the data stack as a queue and employing a recursive technique to enqueue bits at the bottom of the data stack, Bitforth can simulate any tag system, and therefore any Turing machine.
In particular, Bitforth does not use the >R
and
R>
words of Forth which can implement a two-stack
pushdown automaton by providing direct access to the call stack as a second data stack. Instead,
Bitforth manipulates the call stack only through standard calls and
returns.
Furthermore, if the data is self-delimited, it should be possible to omit the
-1
literal entirely and rely solely on 0 and 1, employing
IF/ELSE/THEN
in place of CASE
.
Queue Implementation
To represent a queue, we store bits on the stack and terminate them with a sentinel value of -1
. For example, the stack
(BOTTOM) -1 0 1 1 0 1 (TOP)
represents the queue [0, 1, 1, 0, 1].
Dequeuing a bit from the top is done with a CASE
test, as usual. To enqueue a bit at the bottom, we use the word append
defined thus:
: append ( -1 ... b -- -1 b ... ) CASE 0 OF CASE 0 OF 0 recurse 0 ENDOF 1 OF 0 recurse 1 ENDOF -1 OF -1 recurse 0 ENDOF ENDCASE ENDOF 1 OF CASE 0 OF 1 recurse 0 ENDOF 1 OF 1 recurse 1 ENDOF -1 OF -1 recurse 1 ENDOF ENDCASE ENDOF -1 OF -1 ENDOF ENDCASE ;
Each recursive call pushes its return address onto the call stack, encoding a bit for later recovery. This way the entire data stack contents are saved onto the call stack, while the bit to append is propagated to
the bottom of the stack (marked by -1
) and is inserted there. As calls return, each restores its corresponding bit, recovering the queue contents on the data stack with the newly appended bit now at the bottom of the stack.
Simulating tag systems
To simulate a tag system in Bitforth, encode the alphabet symbols as distinct bit strings, and write a word that:
- Uses nested
CASE
statements to read and remove the first symbol in the queue. - Drops a fixed number of symbols at the head (again using
CASE
). - Appends a bit pattern via
append
- Repeats by recursion.
Reversibility
The append
word above is designed to be step-by-step reversible to support RTM-universality (Axelsen and Glück). The discussion here, however, focuses on TM-universality.