4.4 Interpreters

Those of you who know Forth will be very surprised to see that 4tH doesn't have a prompt. Some will be even more surprised to see that 4tH does have an interpreter. It is a skeleton application, written in 4tH, that can easily be adapted and expanded. If you can write 4tH and maintain a table, you can use it. Example:

     : _+ + ;
     : _. . ;
     : id ." This is 4tH" cr ;

Well, that isn't very hard, is it. Now we add a table to all that, called "dictionary":

     0 constant NULL

     create dictionary
          " +" , ' _+ ,
          " ." , ' _. ,
          " id" , ' id ,
          NULL ,

Remember to terminate your table with "NULL"! Every entry consists of a string and an address to your routine. What will happen is that your user enters the string and the appropriate routine will be called. In this case, your interpreter has three commands: "+", "." and "id".

The next question you have to ask yourself, is do you want your interpreter to be case sensitive or not? If it is, "id" will work, but "Id" or "ID" will not. If you want it to be case sensitive, change the constant "ignore?" to "false":

     false constant ?ignore          \ ignore case

Now you can compile your application and run it. Enter:

     45 12 + .

And it will print:

     57

Yes, it's just as easy as that! If you enter something the interpreter doesn't recognize it will try to convert it to a number and throw it on the stack. But you will also see that it exits after you've entered that single line. That is because the interpreter is called just once. If you change that to:

     begin refill drop interpret again

It will return with an new prompt. In that case it is wise to add a routine like:

     : _quit quit ;

And add it to your interpreter, because otherwise your user will not be able to leave the application. Note that you have to do all the error-checking. E.g., if your user calls "_+" without putting sufficient items on the stack, 4tH will exit with an error.

Of course, you could use exceptions. You only have to change the interpreter routine slightly:

     1 constant #UndefName

     : interpret                     ( -- )
           begin
                bl word count 0<>    ( a f )
           while                     ( a )
                find                 ( xt f ¦ a -f )
                if                   ( xt )
                     execute         ( -- )
                else                 ( a )
                     number          ( n )
                     dup (error) =   ( n f )
                          if         ( n )
                                drop ( -- )
                                #UndefName throw
                          then
                then
           repeat                    ( a )
           drop                      ( -- )
     ;

When a word is not found, a user exception is throw. What you have to do now is integrate that in a routine that calls "interpret" and catches any errors. This example is taken from "DC.4TH":

     : dc
          begin                    \ main interpretation loop
               ." OK" cr           \ print prompt
               refill drop         \ get input from use
               ['] interpret       \ interpret it
               catch dup           \ catch any errors
               if                  \ if one occurred
                    ShowMessage    \ show a message
               else                \ otherwise
                    drop           \ drop the code
               then
          again                    \ loop back
     ;

You can still see the basic structure, but this one is much more advanced. You can also remove the code from the interpreter that decodes number. In that case, if a word is not found in the "dictio- nary" table it will exit immediately and report an error:

     1 constant #UndefName

     : interpret                     ( -- )
           begin
                bl word count 0<>    ( a f )
           while                     ( a )
                find                 ( xt f ¦ a -f )
                if                   ( xt )
                     execute         ( -- )
                else                 ( a )
                     drop            ( -- )
                     #UndefName throw
                then
           repeat                    ( a )
           drop                      ( -- )
     ;

Don't let anybody ever tell you you can't make interactive applications with 4tH. As you have seen, you can with very little effort.