pascal in forth ( excerpts from )

Table of Contents

Pascal in Forth, by Alan Winfield (1983) (partial transcription)

1 Part 1

1.1 Pascal in Forth

This is first of two articles describing a set of Pascal like extensions to Forth. The intention here is not for a full-blown Pascal compiler but to provide the Forth programmer with the option of writing those parts of a task which are best described with a Pascal like syntax in Pascal. The philosophy behind this idea is in the widely held view that, while most languages are good for some things few, if any, are good at everything and instead of trying to design new all embracing wonder-languages, it might be more productive to develop multilingual programming environments! Whether you hold this view or not, the extension of Forth into Pascal is still an interesting exercise and a worthy test of power and flexibility of Forth.

Before going any further, perhaps I should givne an example of the way I see this amalgam of Forth and Pascal working in practice. Suppose that we need to sort a list of numbers into ascending order. A standard algorithm for doing this might be expressed very clearly in a Pascal/Forth hybrid as shown in fig 1.

#+name fig1

( Create a 100 element array 'list' )
CREATE list 200 ALLOT

( create some working variables ... )
VARIABLE i VARIABLE j VARIABLE k
VARIABLE noswops

( and constants ... )
1 CONSTANT true 0 CONSTANT false


( Sort 'list' into ascending numerical order )
: sortlist {pascal
  repeat noswops := true ;
    for i := 1 to 99 do
    begin
      j := list [ i ] ;
      k := list [ i + 1 ] ;
      if j > k then
      begin
        noswops := false ;
        list [ i ] := k ;
        list [ i + 1 ] := j
      end
    end
  until noswops
} ;

1.2 Basic Ideas

Initially one of the principal requirements was for complete interchangeability between Forth and Pascal, so that Forth routines may call previously defined words written in Pascal and vice versa. It is particularly useful for Pascal to be able to make use of the large set of standard Forth words. As an example, suppose we need to print a number, right justified, from within a Pascal routine. A convenient way would be to call the Forth word .R as if it were a Pascal procedure, ie:

.R( i, 5 );

to print the conents of variable i, in a field width of 5. This should work very well if we can arrange that the expressions enclosed by brackets leave their results on the stack at run time, which is precisely where the Forth word .R will expect to find its input parameters!

Passing results back from Pascal defined words into Forth is not quite so straightforward, since a Pascal program cannot easily take values off (or leave them on) the stack. Pascal does all of its arithmetic with variables, so it would seem sensible to allow Pascal and Forth to share the same variables, and communicate results through them. In id:fig1, the array list was created in Forth, but manipulated in sortlist exactly as if it were a 1-dimensional Pascal ARRAY. A Forth definition to sort any list, whose address is supplied on the stack, might then be written as in fig 2:

( sort any 100 element list into ascending order )
: sort                   ( addr -> )
    DUP list 100 MOVE    ( Copy anylist into list )
    sortlist             ( sort it )
    list SWAP 100 MOVE   ( Copy it back into anylist ) ;

( Generate two lists and sort them ... )
CREATE list1 200 ALLOT list1 sort
CREATE list2 200 ALLOT list2 sort

Fig 3 summarises the structure of a colon definition incorporating both Forth and Pascal. The Pascal statements are enclosed by {pascal and } (the word {pascal is the new 'compiling' word which is really the subject of these articles). There may be any number of segments of Forth and Pascal in the same definition, although more than one or two might be confusing!

: new-word
          ... some FORTH words ...
  {pascal ... some Pascal statements ... }
          ... more FORTH words ...
  etc. ;

Possibly the most demanding design constraint is that the compiled Pascal should be as close as possible to the equivalent compiled Forth, so that there is little or no compromise on execution speed for routines written in Pascal. Thus, as an example, the Pascal statement:

IF a = 2 THEN i := j + k;

should, ideally, compile into the same internal form as the Forth phrase:

a @ 2 = IF j @ k @ + i! THEN

Of course it is unlikely that a program written entirely in Forth would make such a heavy use of variables as this, but the ability to mix Pascal and Forth (and Assembler, on many Forth systems) should allow us plenty of opportunity for optimising any time-critical part of a program.

1.3 The Algorithm

Readers of the two excellent articles in issue 1 of SOFT: 'Writing Compilers in Basic' and 'Infix Maths in Forth' will already be aware of the power and versatility of the technique known as 'recursive descent' and so I make no apology for using this here. In fact, structured languages like Pascal lend themselves particularly well to compilation by recursive descent, because their syntax is often highly recursive – that is, defined in terms of itself. An example of this in pascal is that a 'statement' (amongst other things) might consist of the reserved word BEGIN, followed by any number of statements separated by ;, and terminated by END. Thus, when the statement compiler encounters the word BEGIN it will simply call itself.

This structure is difficult to describe in words, but very much easier to see when expressed as a 'syntax graph'. For example fig 4 illustrates the syntax graph for begin-statement.

1.4 TODO fig 4 ( railroad diagram )

The principle funciton of the syntax graph is to specify exactly the syntax of a programming language. That is its primary use to 'users' of the language. However, for compiler writers, syntax graphs do lead almost directly to a compilation algorithm to the extent that each syntax graph will normally have one subroutine associated with it in the compiler. Of course the syntax graph doesn't tell us what the output of the compiler should be – that is a question of 'semantics' – but it does dictate the fundimental structure of the compiler. To illustrate this fig 5 show an outline colon-definition for begin-statement, which comes directly from the syntax graph in fig 4. This would be called by the statement routine whenever the reserved wourd BEGIN has been found.

1.5 Fig 5. the "begin" statement ( example outline only )

( begin-statement compiler )
: begin
    statement  ( must be at least one statement )
    BEGIN
      ...      ( test here for a semi-colon )
    WHILE
      statement
    REPEAT
    ...        ( test here for end )
    NOT IF syntaxerror THEN ;

1.6 The Forth Basics

The total program splits very conveniently into two parts; the arithmetic expression compiler, and the statement compiler, so I shall describe the former here and the later in the second part of this article. Although I am at risk of re-inventing some wheels here (see the second reference!) it does turn out that the expression compiler is useful in its own right as an infix arithmetic compiler/interpreter, as I will show later.

First, however, we must establish the basic routines for parsing the input stream and identifying Pascal reserved words. Given the design constraints outlined earlier, we can see that a Pascal program will contain five basic word types:

  • Pascal reserved words
  • Numbers
  • References to Forth variables
  • References to Forth constants
  • Other words to be treated as Forth functions or procedures.

What we require is a basic routine to 'fetch the next thing from the input stream' and classify it as one of these five basic types.

Since the final three types (variable, constant, or function) will all be words in the dictionary when the Pascal program is compiled, it is clear that a dictionary search wil lbe needed somewhere in this routine. Why not, therefore, create a special vocabulary containing the Pascal reseverd words and let the Forth dictionary search word FIND do all the work for us?

When FIND is executed it will fetch the next word from the input stream (delimited by space), and search the CONTEXT vocabulary for a matching word. If successful it returns the (unique) address of the dictionary entry for that word, or the value zero if not. (This is the Code Field Address or CFA). All that we need to do in our basic parsing routine (which I call next), is to arrange that the Pascal reserved word vocabulary is CONTEXT when FIND is executed. This will ensure that it is searched before the rest of the Forth dictionary, avoiding any conflict between similar Pascal and Forth words. We can then use the CFA to classify the word into one of the five types above.

All of this happens in blocks 2001-2006, listed at the end of this article. The pascal reserved words are a set of empty definitions in the vocabulary pascal-words, in block 2001. The classification of reserved words is achieved by attempting to match the CFA returned by FIND with one of the entries in a special table containing the reserved words CFAs (cfa-table, 1.7.4), thena value from 1 to 40 is returned, corresponding to the position of the reserved word in cfa-table. If no match is found then next (blocks 2005 and 2006) compares the CFA with vcode and ccode, the CFAs for variable and constant, respectively. No further matching is necessary, since if all of these tests fail the word must have been either a number, or a Forth function or procedure call, and a value of zero returned by FIND will, of course, indicate a number (or a syntax error!)

The final result of next is returned in the variable type; values 1-40 indicate Pascal reserved words, -2 constants, -1 variables, 0 numbers, or any other value for references to Forth words (defined by colon definitions). Thus, after loading blocks 2001-2006 into a Forth system, we may test these by typing:

next +      type ? 1  ok     (Pascal '+')
next begin  type ? 20 ok     (and 'begin')
next 27     type ? 0  ok     (a number)
VARIABLE fred
1 CONSTANT true
next fred   type ? -1 ok     (a variable)
next true   type ? -2 ok     (a constant)
next .R     type L 12524 ok  (the CFA of .R)

Although it may come as a surprise, the development of the word next was one of the most difficult problems of this entire application. Everything else falls into place with remarkable ease!

1.7 [3/7] { code for the tokenizer }

1.7.1 DONE block 2000 - pascal extensions to forth

  1. forth
    ( Pascal extensions to FORTH
      ---------------------------------------------------
      The following blocks define two new compiling words, '{' and
      '{pascal'. '{' allows infix expressions to be incorporated
      into FORTH by writing '{ infix expression }' and '{pascal' 
      allows Pascal statements to be included in a FORTH colon
      definition, '{pascal ...Pascal statements... }'.
    
      The FORTH conforms to the FORTH-79 standard with the common
      extensions of a CASE structure, and MYSELF to allow a recursive
      colon definition. Also <= >= <> and 2+.
    
      Developed on sForth.   A.F.T. Winfield 1983 )
    
    FORTH DEFINITIONS DECIMAL -->
    
  2. retro
    ( Pascal extensions to retro                                    )
    ( ------------------------------------------------------------  )
    ( The following blocks define two new compiling words, '{' and  )
    ( '{pascal'. '{' allows infix expressions to be incorporated    )
    ( into retro by writing '{ infix expression }' and '{pascal'    )
    ( allows Pascal statements to be included in a retro colon      )
    ( definition, '{pascal ...Pascal statements... }'.              )
    
    ( The code is based on a pair of 1983 articles in SOFT magazine )
    ( by [now Dr.] Alan Winfield, entitled "Pascal in Forth"        )
    
    reset global decimal                ( normalize the environment )
    needs forth' with forth'            ( forth-like vocabulary     )
    

1.7.2 DONE block 2001 - reseverd words

  1. forth
    ( Define all Pascal reserved words. Keep these in a separate )
      vocabulary to avoid conflict with similar FORTH words. )
    VOCABULARY pascal-words IMMEDIATE pascal-words DEFINITIONS
    
    : C CREATE ; : F FIND ;             ( abbreviations to save )
    : | , ;     : ---> [COMPILE] --> ;  ( typing and conflicts )
    
    ( Define as empty definitions so that FIND will work. )
    ( NOTE: do not insert any comment until after next FORTH. )
    
    C +     C -     C *       C /       C mod     C and
    C or    C xor   C <       C >       C <=      C >=
    C <>    C =     C (       C )       C {       C }
    C ;     C begin C end     C if      C then    C else
    C while C do    C repeat  C until   C write   C writeln
    C "     C for   C to      C by      C [       C ]
    C .     C not   C :=      C -->
    
    --->
    
  2. retro
    ( Define all Pascal reserved words. Keep these in a separate )
    ( vocabulary to avoid conflict with normal retro words       )
    chain: pascal-words
    
    : C create ;                        ( abbreviations to save  )
    : | , ;                             ( typing and conflicts   )
    
    ( dr winfield had : F find ; here. looks like in forth 79,   )
    ( 'find' read a token. so we'll do the same. )
    : F  ` getToken find 0= if drop 0 then ;
    "( '-a ) parse for word and return its address. zero if not found" :doc
    
    ( Define as empty definitions so that FIND will work.        )
    ( NOTE: do not insert any comment until after next FORTH.    )
    
    C +     C -     C *       C /       C mod     C and
    C or    C xor   C <       C >       C <=      C >=
    C <>    C =     C (       C )       C {       C }
    C ;     C begin C end     C if      C then    C else
    C while C do    C repeat  C until   C write   C writeln
    C "     C for   C to      C by      C [       C ]
    C .     C not   C :=
    

1.7.3 DONE block 2002 - cfa table ( "code field address ")

  1. forth
    C cfa-table
    F +     | F -     | F *      | F /      | F mod    | F and     |
    F or    | F xor   | F <      | F >      | F <=     | F >=      |
    F <>    | F =     | F (      | F )      | F {      | F }       |
    F ;     | F begin | F end    | F if     | F then   | F else    |
    F while | F do    | F repeat | F until  | F write  | F writeln |
    F "     | F for   | F to     | F by     | F [      | F ]       |
    F .     | F not   | F :=     | F -->    | --->
    
    FORTH DEFINITIONS ( Go back into forth )
    
    ( 'cfa-table' is a table of the Code Field Addresses of the 
      reserved word definitions in the previous block. Note that ','
      is redefined as '|' to allow definition of the pascal ',' )
    
    -->
    

    Why repeat the list?

    In the first section, he's creating a bunch of empty definitions in the forth dictionary.

    Here, he redefined "|" to mean "," – which means write to memory. So he's essentially creating an array of pointers to the empty dictionary entries.

  2. retro
    C cfa-table
    F +     | F -     | F *      | F /      | F mod    | F and     |
    F or    | F xor   | F <      | F >      | F <=     | F >=      |
    F <>    | F =     | F (      | F )      | F {      | F }       |
    F ;     | F begin | F end    | F if     | F then   | F else    |
    F while | F do    | F repeat | F until  | F write  | F writeln |
    F "     | F for   | F to     | F by     | F [      | F ]       |
    F .     | F not   | F :=     |
    
    ;chain ( Go back into retro )
    
    ( 'cfa-table' is a table of the Code Field Addresses of the      )
    ( reserved word definitions in the previous block. Note that ',' )
    ( is redefined as '|' to allow definition of the pascal ','      )
    

    Why repeat the list?

    In the first section, he's creating a bunch of empty definitions in the forth dictionary.

    Here, he redefined "|" to mean "," – which means write to memory. So he's essentially creating an array of pointers to the empty dictionary entries.

1.7.4 TODO block 2003

  1. forth
    40 CONSTANT words ( total number of reserved words above )
    
    ( Search the pascal CFA table for a match with the CFA on top  )
    ( of the stack, return a value 1..words if found, or leave the )
    ( input CFA on TOS if not )
    
    : match-cfas
        words 0 do                     ( step through cfa-table )
          DUP
          I 2*                                 ( fetch I'th cfa )
          pascal-words cfa-table FORTH + @
          = IF                                 ( if match found )
              DROP I 1+ LEAVE        ( exit, leaving I+1 on TOS )
            THEN
          LOOP ;                            ( else try next cfa )
    
    -->
    
  2. retro
    38 constant numTokens   ( total number of reserved words above )
    
    ( Search the pascal CFA table for a match with the CFA on top  )
    ( of the stack, return a value 1..words if found, or leave the )
    ( input CFA on TOS if not )
    
    : match-cfas ( a-t|a )
      numTokens 0 do                     ( step through cfa-table )
          dup
          I 2* ^pascal-words'cfa-table + @       ( fetch I'th cfa )
          = if                                   ( if match found )
              drop I 1+ leave          ( exit, leaving I+1 on TOS )
            then
          loop ;                              ( else try next cfa )
    

1.7.5 TODO block 2004 variables for the parser

  1. forth
    ( parsing variables .. )
    VARIABLE >in    ( temporary storage of >IN value )
    VARIABLE find   ( result of FIND on next input word )
    VARIABLE type   ( index of token as position in pascal cfa table )
                    ( '+' = 1, '-' = 2, etc )
    
    ( identify the value of the code pointers for variable / create )
    ( and constant, so that 'next' will be able to differentiate )
    
    FIND type  @ CONSTANT vcode   ( code pointer for variable )
    FIND words @ CONSTANT ccode   ( code pointer for constant )
    -->
    
  2. retro
    ( parsing variables .. )
    VARIABLE >in    ( temporary storage of >IN value )
    VARIABLE find   ( result of FIND on next input word )
    VARIABLE type   ( index of token as position in pascal cfa table )
                    ( '+' = 1, '-' = 2, etc )
    
    ( identify the value of the code pointers for variable / create )
    ( and constant, so that 'next' will be able to differentiate )
    
    FIND type  @ CONSTANT vcode   ( code pointer for variable )
    FIND words @ CONSTANT ccode   ( code pointer for constant )
    

1.7.6 TODO block 2005 - next ( parses for tokens )

  1. forth
    ( 'next' assigns a 'type' to the next word in the input stream )
    (  type = 1..words : pascal reserved word     )
    (         -1       : a variable               )
    (         -2       : a constant               )
    (  any other value is an existing forth word. )
    : next-2 >IN @ >in ! ( save >IN in case we need to backtrack )
        ( try to find next word in pascal words at compile time )
        [COMPILE] pascal-words FIND [COMPILE] FORTH
        DUP find !    ( save result of FIND in find )
        match-cfas    ( and search the cfa table for match )
        DUP type !    ( place the result in type )
        DUP words SWAP U< IF   ( if no match was found ... )
           @ DUP vcode = IF -1 type ! THEN  ( test for var )
                 ccode = IF -2 type ! THEN  ( test for const )
        ELSE DROP THEN ; -->  ( leave stack empty )
    
  2. retro
    ( 'next' assigns a 'type' to the next word in the input stream )
    (  type = 1..words : pascal reserved word     )
    (         -1       : a variable               )
    (         -2       : a constant               )
    (  any other value is an existing forth word. )
    : next-2 >IN @ >in ! ( save >IN in case we need to backtrack )
        ( try to find next word in pascal words at compile time )
        [COMPILE] pascal-words FIND [COMPILE] FORTH
        DUP find !    ( save result of FIND in find )
        match-cfas    ( and search the cfa table for match )
        DUP type !    ( place the result in type )
        DUP words SWAP U< IF   ( if no match was found ... )
           @ DUP vcode = IF -1 type ! THEN  ( test for var )
                 ccode = IF -2 type ! THEN  ( test for const )
        ELSE DROP THEN ; -->  ( leave stack empty )
    

1.7.7 TODO block 2006 forth parser tweaks

  1. forth
    ( redefine 'next' to allow comment in pascal programs )
    : next-1
      BEGIN
        next-2 type @ 17 =   ( if next word is '(' .. )
      WHILE
        125 WORD DROP        ( skip input up to ')' )
      REPEAT ;               ( and fetch the next thing )
    
    ( redefine again to allow pascal to go over more than one block )
    : next
      BEGIN
        next-1 type @ 40 =    ( if next word is '-->' .. )
      WHILE
        1 BLK +! 0 >IN !      ( go to start of next block )
      REPEAT ;                ( and fetch the next thing )
    
    -->
    
  2. retro
    ( redefine 'next' to allow comment in pascal programs )
    : next-1
      BEGIN
        next-2 type @ 17 =   ( if next word is '(' .. )
      WHILE
        125 WORD DROP        ( skip input up to ')' )
      REPEAT ;               ( and fetch the next thing )
    
    ( redefine again to allow pascal to go over more than one block )
    : next
      BEGIN
        next-1 type @ 40 =    ( if next word is '-->' .. )
      WHILE
        1 BLK +! 0 >IN !      ( go to start of next block )
      REPEAT ;                ( and fetch the next thing )
    

1.8 The Expression Compiler

Blocks 2009-2015 contain the arithmetic expression compiler starting, in usual Forth fashion, with the lowest level definitions: number, variable, etc, and working up to the highest level: expression in block 2015. These closely follow, both in name and structure, the corresponding syntax graphs of fig 6 (A reasonably faithful subset of standard Pascal arithmetic syntax, as described in the third reference).

Now, while I do not propose to examine in detail each of the colon definitions in these blocks, it is important at this stage to establish what the output of the compiler should be, and how to achieve it. If we consider as an example the simple arithmetic expression:

A * 2

the compiler should translate this into the equivalent Forth expression:

A @ 2 *

We notice straight away that when the compiler comes across a variable it should generate the code to 'push the value of the variable onto the stack' and, likewise, when a number appears in the input expression, the compiler should generate the code to 'push the number'. This is precisely what variable and number do (variable is complicated a little by single dimensonal array handling as well). Providing that A is a predefined Forth variable, then A and 2 are both syntactically correct 'factors' (see fig 6), and tracing a route through the syntax graphs, starting at expression, will eventuall arrive at the starred position in the graph for term. At this point we know that we must have had two valid factors, separated by * (otherwise this route would not have been followed) and the compiler may output the code for *.

There is really very little structural difference between a compiler and an interpreter. The principle distinction is that an interpreter actually performs the operation indicated by the input expression during translation, whereas a compiler instead generates some code that will perform the same operation later, at run-time. Combining this observation (which was a revelation to me when I first realised it!), with the Forth convention that anything insidea colon definition is compiled, and anything else interpreted leads to the possibility of making the compiler into an interpreter as well! To make this happen we need only write, for example:

STATE @ IF           ( if we are inside a colon definition... )
          COMPILE *  ( then compile '*' )
        ELSE         ( else we are not compiling )
          *          ( so do it now )
        THEN

in the term routine, after having picked up two 'factors' separated by *. Since this is rather long winded I have defined a new compiling word comp/int * with exactly the same effect.

1.9 DONE Infix Expressions

An unexpected and useful byproduct of this quest for Pascal in Forth is that the arithmetic expression compiler/interpreter may be used as a stand-alone utility enabling us to write arithmetic expressions – in Forth – in 'infix' notation rather than the usual Reverse Polish. To this end I have defined the IMMEDIATE word {, in block 2016, which simply calls expression. Now expression will exit when a word is found that is recognised but is not syntactically correct (and oll of the CASE comparisions fail), thus } is a suitable terminator. So, to write an infix expression, just place it between curly brackets, as in the following examples:

VARIABLE A ok          ( Define A )
VARIABLE B ok          ( Define B )
10 A ! 20 B ! ok       ( A=10, B=20 )
{ A + 2 * B } . 50 ok
{ ( A + 2 ) * B ) . 240 ok

: testA<B { A < B } IF ." yes" THEN ; ok
testA<B yes ok

: formula { A * A + B * B } ." =" . ; ok
formula = 500 ok

Notice that the result of evaluating an infix expression remains on the stack where it may be used by subsequent Forth operation in the normal way.

1.10 TODO { code for expression compiler }

1.10.1 block 2009 number

( convert the number in the input stream, pointed to by >in    )
( into binary and compile/interpret it. exit with error number )
( zero if any non-numeric characters )
: number
    >in @ >IN !
    0 0 32 WORD
    CONVERT
    C@ 32 = NOT IF <> perror THEN
    DROP
    STATE @ IF
       [compile] LITERAL
    THEN
    next ;
-->

1.10.2 block 2010 variables

( forward reference to 'expression' )
VARIABLE expr
: callexpr expr @ EXECUTE ;

( variable handling )
: variable
    find @
    STATE @ IF , ELSE 2+ THEN
    next type @
    35 = IF next callexpr
            36 ?error
            comp/int 2*
            comp/int +
            next
         THEN comp/int @ ;
-->

1.10.3 block 2011 function calls

( function calls - use these to call forth routines )
: function
  find @ >R
  IF
    next callexpr
    BEGIN
      type @ 37 =
    WHILE
      next callexpr
    REPEAT
    16 ?error
    next
  THEN R>
  STATE @ IF , ELSE EXECUTE THEN ;

-->

1.10.4 block 2012 factor

( handle a constant )
: constant find @
  STATE @ IF , ELSE 2+ @ THEN next ;

: subexpr next callexpr
  16 ?error next ;

: factor
  type @ CASE
    15 OF subexpr ENDOF
     0 OF number ENDOF
    -1 OF variable ENDOF
    -2 OF constant ENDOF
    38 OF next MYSELF comp/int NOT ENDOF
    DEFAULT DROP function ENDCASE ;

-->

1.10.5 block 2013 - term

: term factor
  BEGIN type @
    CASE 3 OF next factor comp/int *   ENDOF
         4 OF next factor comp/int /   ENDOF
         5 OF next factor comp/int MOD ENDOF
         6 OF next factor comp/int AND ENDOF
         DEFAULT DROP EXIT ENDCASE
  0 UNTIL ;
-->

1.10.6 block 2014 - simple expressions

: simpleexpr
  type @ CASE
    1 OF next term ( ignore unary + ) ENDOF
    2 OF next term comp/int NEGATE ENDOF
    DEFAULT DROP term
  ENDCASE
  BEGIN
    type @ CASE
      1 OF next term comp/int +   ENDOF
      2 OF next term comp/int -   ENDOF
      7 OF next term comp/int OR  ENDOF
      8 OF next term comp/int XOR ENDOF
      DEFAULT DROP EXIT
    ENDCASE
  0 UNTIL ;

-->

1.10.7 block 2015 - full expression compiler / interpreter

: expression simpleexpr
  BEGIN type @
    CASE 9 OF  next term comp/int <  ENDOF
        10 OF  next term comp/int >  ENDOF
        11 OF  next term comp/int <= ENDOF
        12 OF  next term comp/int >= ENDOF
        13 OF  next term comp/int <> ENDOF
        14 OF  next term comp/int =  ENDOF
        DEFAULT DROP EXIT ENDCASE
  0 UNTIL ;
-->

1.11 error handling

1.11.1 block 2007 - error handling

( error handler. print line, put BLOCK on TOS line # on NOS )
: .line BLOCK ( fetch block number )
  SWAP 64 * + ( start address of line )
  63 TYPE ;   ( print 63 characters )

( print error message, error number TOS, and abandon )
: perror
  BLK @ IF CR ." pascal compilation error .."
           CR ." block - " BLK ? ." Line - " >in @ 64 / DUP .
           CR . BLK @ .line THEN
        ( point to current pointer position )
        CR >in @ 64 MOD SPACES ." ^ "
        CR ." Error number - " . CR QUIT ;

( Check if TOS equals current 'type', error if not )
: ?error DUP type @ <> ( not equal ? )
         IF perror ELSE DROP THEN ; -->

1.11.2 block 2008 compile / interpret

( compile / execute, depending on state )
( 'comp/int *' is same as 'STATE @ IF COMPILE * ELSE * THEN' . )
: (comp/int) R> DUP 2+ >R
             @
             STATE @ IF , ELSE EXECUTE THEN ;

: comp/int COMPILE (comp/int)   ( compile runtime code )
            FIND , ;            ( and CFA iof next word )

( do these things when 'comp/int word' is compiled )
IMMEDIATE

-->

1.11.3 block 2016 ( resolve forward reference to expression )

FIND expression expr
: ( next expression ; IMMEDIATE

: ) . " Improper termination " ;

-->

2 PART 2 :

2.1 block 2017 assignment

( assignment statement )
: assign                 ( we've already found the variable )
  find @ ,              ( compile its address )
  next                  ( we might have [ next )
  type @ 35 = if           ( is it? if so... )
    next expression        ( read the expression )
    36 ?error              ( followed by ] )
    compile 2*             ( double it )
    compile +              ( add add to form addr )
    next                   ( next up should be := )
  then 39 ?error        ( error if it isn't )
  next expression       ( get the rhs )
  compile swap          ( put address on top )
  compile !             ( and store it )
;

-->

2.2 block 2018 expression printer

( print out expression, or string )
: writex type @ 31 = ( do we have a ' ? )
  if
    [compile] ."     ( it's a string then )
    next             ( exit pointing to next )
  else
    expression       ( an expression )
    compile .        ( print result )
  then
;

-->

2.3 block 2019 write statement

( write statement )
: write next type @ 15 =  ( followed by ( ? )
  if
    next writer           ( we have an expression then )
    begin
      type @ 37 =         ( and optionally another )
    while
      next writex
    repeat
    16 ?error             ( must have a close bracket )
    next
  then ;

( writeln )
: writeln write COMPILE CR ; ( write and newline )

-->

2.4 block 2020 block compiler

( forward reference to statement )
VARIABLE stmt : stmtex stat @ EXECUTE ;

( compile multiple statements separated by ; )
: mstat statex   ( must be at least one statement )
    begin
      type @ 19 =    ( is there a semi-colon? )
    while
      stmtex         ( another statement then )
    repeat ;

( begin statement, enter after we've got 'begin' )
: begin mstmt
    21 ?error        ( should end with END )
    next ;

-->

2.5 block 2021 if stmt compiler

( if statement, enter after 'if' )
: if next expression ( need an expression )
  23 ?error          ( followed by THEN )
  [compile] if       ( generate IF, same as forth )
  stmtex
  type @ 24 =        ( do we have an else? .. )
  if
    [compile] else   ( .. if so )
    stmtex           ( compile false statement )
  then
  [compile] then ;   ( finish off total statement )

-->

2.6 block 2022 while and repeat loops

( while .. do loop, enter after we've got the 'while' )
: while [COMPILE] BEGIN   ( start the construct )
  next expression         ( need an expression )
  26 ?error               ( followed by 'do' )
  [COMPILE] WHILE         ( forth while is ok )
  stmtex                  ( looped statement )
  [COMPILE] REPEAT ;      ( end the forth structure )

( repeat .. until loop, enter here after 'repeat' )
: repeat [COMPILE] BEGIN  ( start the loop )
  mstmt                   ( multiple statements )
  28 ?error               ( expect 'until' )
  next expression         ( need an expression now )
  [compile] UNTIL ;       ( followed by FORTH UNTIL )

-->

2.7 block 2023 for loop compiler

( define a for loop, in forth, using a variable, i.e.
  step end start variable FOR .. ENDFOR )

( runtime FOR action .. )
: (FOR) SWAP OVER !               ( set variable = start value )
        R>                        ( fetch this return adddress )
        SWAP >R SWAP >R SWAP >R   ( var, end, start ->R stack  )
        >R ;                      ( and replace return address )

( compile time FOR action )
: FOR COMPILE (FOR)
      HERE ; IMMEDIATE            ( save HERE for ENDFOR )

-->

2.8 block 2024 ENDFOR

( ENDFOR runtime action .. the hard part! )
: (ENDF) R>                             ( fetch return address )
         R> R>                          ( step then end values )
         SWAP DUP R@ +!         ( add step value into variable )
         OVER OVER R@ @ SWAP       ( some stack rearrangement! )
         0<                 ( sign of step value determines... )
         IF > ELSE < THEN              ( ..which test to apply )
         IF                           ( has the loop finished? )
           R> DROP 2DROP           ( drop everything if it has )
           2+ >R                 ( and skip the branch address )
         ELSE
           SWAP >R >R               ( else resave end and step )
           @ >R                                     ( and loop )
         THEN ;                                        ( phew! )

: ENDFOR COMPILE (ENDF)                  ( compile time action )
         , ; IMMEDIATE -->            ( compile branch address )

2.9 block 2025 pascal for loop

( now define pascal for loop )
: for next -1 ? error        ( need a variable name )
      find @                 ( save its address on stack )
      next 39 ?error         ( next we need := )
      next expression        ( the start value expression )
      33 ?error              ( the next word must be 'to' )
      next expression        ( then end expression )
      type @ 34 =            ( optionally followed by 'by' )
      IF next expression     ( and a step expression )
      ELSE COMPILE ! THEN    ( otherwise step value of +! )
      26 ?error              ( must have 'do' next )
      COMPILE ROT COMPILE ROT COMPILE SWAP ( rearrange )
      ,                      ( compile variable address )
      [COMPILE] FOR          ( then forth for )
      statex                 ( a looped statement )
      [COMPILE] ENDFOR ;     ( and we've finished )
-->

2.10 block 2026 <statement> rule compiler

( statement compiler )
: statement next   ( get the first thing )
  type @
  CASE -1 OF assign   ENDOF
       20 OF begin    ENDOF
       22 OF if       ENDOF
       25 OF while    ENDOF
       27 OF repeat   ENDOF
       29 OF write    ENDOF
       30 OF writeln  ENDOF
       -2 OF 1 perror ENDOF ( cannot be a constant )
     DEFAULT words swap U<  ( is it a forth word? )
        IF function ELSE    ( treat as a function call )
           1 perror THEN    ( else a syntax error )
     ENDCASE
; -->

2.10.1 block 2027 {pascal .. }

FIND statement stmt !  { resolve forward reference }

( compile pascal statement in forth colon definition )
: {pascal STATE @ 0= IF ." not available outside colon "
                        ." definition " CR QUIT THEN
          statement     ( call the compiler )
          18 ?error     ( exit on curly bracket )
; IMMEDIATE

( ------ end of pascal extensions -------------------- )

3 – retroforth phrasebook --

CREATE create
FIND find