4.1 Conditional compilation

When you've reached this chapter, you must have quite some experience with 4tH. This chapter will help you to use 4tH to its full capacity. You'll be able to use software exceptions, conditional compilation, lookup tables, fixed point calculation and much, much more.

We'll start with something that can be very handy when you're designing a 4tH program for different environments or even different Forth compilers. Let's say you've written a general ledger program in 4tH that is so good, you can sell it. Your customers want a demo, of course. You're willing to give one to them, but you're afraid they're going to use the demo without ever paying for it.

One thing you can do is limit the number of entries they can make. So, you copy the source and make a special demo version. But you have to do that for every new release. Wouldn't it just be easier to have one version of the program and just change one single constant? You can with conditional compilation:

     false constant PRODUCTION
     true  constant DEMO

     DEMO [if]
     256 constant #Entries
     [then]

     variable CurrentEntry

     PRODUCTION [if]
     limit constant #Entries
     [then]

     #Entries array Entries

We defined two constants, called "DEMO" and "PRODUCTION". "PRODUCTION" is false and "DEMO" is true. So, when the compiler reaches the "DEMO [if]" line, it knows that it has to compile "256 constant Entries", since "DEMO" is true. When it comes to "PRODUCTION [if]", it knows it has to skip everything upto "[then]" since "PRODUCTION" is false. So, in this case the compiler behaves like you've written:

     256 constant #Entries
     variable CurrentEntry
     #Entries array Entries

Would you change "PRODUCTION" to true and "DEMO" to false, the compiler would behave as if you wrote:

     variable CurrentEntry
     limit constant #Entries
     #Entries array Entries

The word '[IF]' only works at compile time and is never compiled into the object. '[IF]' takes a literal expression. If this expression is true, the code following the '[IF]' is compiled, just as '[IF]' wasn't there. Is this expression false, everything upto '[THEN]' is discarded as if it wasn't there.

That also means you can discard any code that is superfluous in the program. E.g. when you're making a colon-definition to check whether you can make any more entries. If you didn't use conditional compila- tion, you might have written it like this:

     : CheckIfFull                   ( n -- n)
           dup #Entries =            ( n f)
           if                        ( n)
                drop                 ( --)

                DEMO                 ( f)
                if                   ( --)
                     ." Buy the full version"
                else                 \ give message and exit program
                     ." No more entries"
                then                 ( --)

                cr quit
           then                      ( n)
     ;

But his one is nicer and will take up less code:

     : CheckIfFull                   ( n -- n)
           dup #Entries =            ( n f)
           if                        ( n)
                drop                 ( --)

                DEMO [if]            ( n f)
                ." Buy the full version"
                [then]

                PRODUCTION [if]
                ." No more entries"
                [then]
                cr quit
           then                      ( n)
     ;

You can also use conditional compilation to discard large chunks of code. This is a much better way than to comment all the lines out, e.g. this won't work anyway:

     (
          : room?                  \ is it a valid variable?
               dup                 ( n n)
               size 1- invert and  ( n f)
               if                  \ exit program
                    drop ." Not an element of ROOM" cr quit
               then
          ;
     )

This is pretty cumbersome and prone to error:

     \    : room?                  \ is it a valid variable?
     \         dup                 ( n n)
     \         size 1- invert and  ( n f)
     \         if                  \ exit program
     \              drop ." Not an element of ROOM" cr quit
     \         then
     \    ;

But this is something that can easily be handled:

     false [if]
          : room?                  \ is it a valid variable?
               dup                 ( n n)
               size 1- invert and  ( n f)
               if                  \ exit program
                    drop ." Not an element of ROOM" cr quit
               then
          ;
     [then]

Just change "false" to "true" and the colon-definition is part of the program again. Note that '[IF] .. [THEN]' can be nested! Conditional compilation is very powerful and one of the easiest features a language can have. And it's ANS-Forth compatible!