pascal in forth ( excerpts from )
Table of Contents
- 1. Part 1
- 1.1. Pascal in Forth
- 1.2. Basic Ideas
- 1.3. The Algorithm
- 1.4. TODO fig 4 ( railroad diagram )
- 1.5. Fig 5. the "begin" statement ( example outline only )
- 1.6. The Forth Basics
- 1.7.
[3/7]
{ code for the tokenizer }- 1.7.1. DONE block 2000 - pascal extensions to forth
- 1.7.2. DONE block 2001 - reseverd words
- 1.7.3. DONE block 2002 - cfa table ( "code field address ")
- 1.7.4. TODO block 2003
- 1.7.5. TODO block 2004 variables for the parser
- 1.7.6. TODO block 2005 - next ( parses for tokens )
- 1.7.7. TODO block 2006 forth parser tweaks
- 1.8. The Expression Compiler
- 1.9. DONE Infix Expressions
- 1.10. TODO { code for expression compiler }
- 1.11. error handling
- 2. PART 2 :
- 2.1. block 2017 assignment
- 2.2. block 2018 expression printer
- 2.3. block 2019 write statement
- 2.4. block 2020 block compiler
- 2.5. block 2021 if stmt compiler
- 2.6. block 2022 while and repeat loops
- 2.7. block 2023 for loop compiler
- 2.8. block 2024 ENDFOR
- 2.9. block 2025 pascal for loop
- 2.10. block 2026 <statement> rule compiler
- 3. – retroforth phrasebook --
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
- 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 -->
- 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
- 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 --> --->
- 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 ")
- 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.
- 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
- 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 ) -->
- 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
- 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 ) -->
- 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 )
- 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 )
- 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
- 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 ) -->
- 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 |