home     ⇐ course03     course05 ⇒

Implementing a FORTH virtual machine - 4

course04.c (304 lines) - conditionals

Now we implement conditions like if and while and unconditional loops


Well, now as we have conditionals in our programs it looks a bit alien to braces used developers. As one gets more comfortable with this language, the syntax become natural.
$ ./course04
: is-true? if "yes, it is true" else "no, it is not" then type cr ;
ok> 0 is-true?
no, it is not
ok> 1 is-true?
yes, it is true
Display the square of the numbers 10 to 1. Counter always is on top of Stack. Don't put in the comments, since the compiler doesn't know how to handle them.

( begins a comment in Forth
) ends a comment in Forth
( a b--c) a stack comment which means a on next of stack (NOS), b as top of stack (TOS) and c is the result
while is a conditional jump (jump on true on top of stack) which discards the top of stack. To keep the top of stack (which is our counter) we have to duplicate it.
$ ./course04
: square ( n--n*n) dup * ;
ok> 4 square
16 ok> drop
ok> : .square ( n--n) dup . dup square . cr ;
ok> 4 .square
4 16
4 ok> drop
ok> : squares ( n--) begin .square -1 + dup while drop ;
ok> 10 squares
10 100
9 81
8 64
7 49
6 36
5 25
4 16
3 9
2 4
1 1
again is an uncoditional jump to begin
$ ./course04
: endless-loop ( --) begin again ;
ok> endless-loop
... never comes back


The compiling words (if, else, then, begin, again, while)) are all macros which pushes some code address on stack to be resolved later as a jump or conditional jump address.

f_0branch, f_1_branch are conditional jumps on zero or not zero which disacrds the top of stack.
f_branch is an uncoditional jump.

static void register_primitives(void) {
	xt_0branch=add_word("0branch", f_0branch); // jump if zero
	xt_1branch=add_word("1branch", f_1branch); // jump if not zero
	xt_branch =add_word("branch",  f_branch); // unconditional jump

	add_word("if",    f_if);    // compiles an if condition
	add_word("then",  f_then);  // this is the endif
	add_word("else",  f_else);
	add_word("begin", f_begin); // begin of while loop
	add_word("while", f_while); // while loop (condition at end of loop)
	add_word("again", f_again); // unconditional loop to begin

static void f_if(void) { // macro, execute at compiletime
	sp_push((cell_t)code++); // push forward reference on stack
static void f_else(void) { // macro, execute at compiletime
	xt_t ***dest=(void*)sp_pop(); // pop address (from f_if) 
	*code++=xt_branch; // compile a jump
	sp_push((cell_t)code++); push forward reference on stack
	*dest=code; resolve forward reference given by f_if
static void f_then(void) { // macro, execute at compiletime
	xt_t ***dest=(void*)sp_pop();
	*dest=code; resolve forward reference given by f_if or f_else
static void f_begin(void) { // macro, execute at compiletime
	sp_push((cell_t)code); // push current compilation address for loop
static void f_while(void) { // macro, execute at compiletime
	*code++=xt_1branch; // compile a jump if not zero
	*code++=(void*)sp_pop(); // jump back to f_begin address
static void f_again(void) { // macro, execute at compiletime, unconditional loop
	*code++=xt_branch; // compile a jump 
	*code++=(void*)sp_pop();// jump back to f_begin address

Now go to the last part of out course, how to implement an disassembler course05 ⇒