David Given
David Given

Reputation: 13701

How to properly implement POSTPONE in a Forth system?

John Heyes' ANS Forth test suite contains the following definition:

: IFFLOORED [ -3 2 / -2 = INVERT ] LITERAL IF POSTPONE \ THEN ;

This is then used to conditionally define various words depending on whether we're using floored or symmetric division:

IFFLOORED : T/MOD  >R S>D R> FM/MOD ;

So IFFLOORED acts like either a noop or a \ depending on the result of the expression. Fine. That's easily implementable on my threaded interpreter by doing this:

: POSTPONE ' , ; IMMEDIATE

...and now IFFLOORED works; the definition is equivalent to : IFFLOORED -1 IF ['] \ EXECUTE THEN ;.

Unfortunately, further down the test suite is the following code:

: GT1 123 ;
: GT4 POSTPONE GT1 ; IMMEDIATE
: GT5 GT4 ;
\ assertion here that the stack is empty

The same implementation doesn't work here. If POSTPONE compiles a reference to its word, then GT4 becomes the equivalent of : GT4 123 ;... but GT4 is immediate. So when GT5 is defined, 123 is pushed onto the compiler's stack and GT5 becomes a noop. But that's not right; the test suite expects calling GT5 to leave 123 on the stack. So for this to work, POSTPONE must generate code which generates code:

: POSTPONE  ' LITERAL  ['] , LITERAL ;

And, indeed, if I play with gForth, I see that POSTPONE actually works like this:

: GT1 123 ;
: GT4 POSTPONE GT1 ; IMMEDIATE
SEE GT4

<long number> compile, ;

But these two definitions are not compatible. If I use the second definition, the first test fails (because now IFFLOORED tries to compile \ rather than executing it). If I use the first definition, the second test fails (because GT4 pushes onto the compiler stack rather than compiling a literal push).

...but both tests pass in gForth.

So what's going on?

Upvotes: 9

Views: 2007

Answers (3)

ruvim
ruvim

Reputation: 8564

The behavior of postpone word in compilation state is to determine compilation semantics for its parsed argument and append these semantics to the current definition.

Compilation semantics for a word can be either special or ordinary (see the section 3.4.3.3 Compilation semantics of Forth-2012). To work correctly, postpone should distinguish these cases and generate code according to the different patterns.

A problem of your implementations is that they are correct either for ordinary compilation semantics or for special compilation semantics.

A standard compliant implementation is as follows:

: state-on  ( -- )  1 state ! ;
: state-off ( -- )  0 state ! ;

: execute-compiling ( i*x xt --j*x )
  state @ if  execute  exit  then
  state-on  execute  state-off
;
: postpone ( "name" -- )
  bl word find dup 0= -13 and throw 1 = ( xt flag-special )
  swap lit, if ['] execute-compiling else ['] compile, then compile,
; immediate

See more details in my post How POSTPONE should work.

Upvotes: 0

vukung
vukung

Reputation: 1884

Let me answer here, as the question changed considerably. I am still not sure I understand the question, though :)

In your example, you define

: GT4 POSTPONE GT1 ; IMMEDIATE

What happens here, is the following:

  1. : is executed, reading GT4 and creating the new word
  2. POSTPONE's compilation semantics is executed, which is to compile the compilation semantics of GT1 - as you have seen in GForth.
  3. ; is executed, ending the definition
  4. IMMEDIATE is executed, marking the last defined word as immediate.

POSTPONE is called only when compiling GT4, and it does not appear in the compiled code. So when later using this immediate word in the definition of GT5, the interpretation semantics of POSTPONE is not needed.

By the way, according to the standard, POSTPONE has only compilation semantics, and the interpretation semantics is undefined.

See also the POSTPONE tutorial in the GForth manual.

EDIT Examples of interpretation and compilation semantics:

: TEST1 ." interpretation" ;  => ok
: TEST2 ." compilation" ; IMMEDIATE  => ok
: TEST3 TEST1 TEST2 ;  => compilation ok
TEST3  => interpretation ok
: TEST4 POSTPONE TEST1 ; IMMEDIATE  => ok
: TEST5 TEST4 ;  => ok
TEST5  => interpretation ok
: TEST6 POSTPONE TEST2 ; IMMEDIATE  => ok
TEST6  => compilation ok

If you have any more questions, you can reference these tests.

Upvotes: 9

vukung
vukung

Reputation: 1884

The snippet you've quoted does the following things:

  1. Evaluate -3/2 (at compile time), and check if it is -2.
  2. If it is, store a 0 (false), otherwise store a -1 (true) in IFFLOORED, so when it is evaluated, it will put this value on the stack. (This is the effect of LITERAL.)
  3. When evaluating IFFLOORED, after pushing the value on the stack, comes an IF - THEN expression. When the value is true, it means that we are not in a floored environment, so we want to comment out the rest of the line, and that is what \ does.

So here comes the tricky part - \ is IMMEDIATE, i.e., you cannot use it inside a colon definition, because it will comment out the rest of the line. You have to explicitly tell the compiler that you want to compile this function, and not execute it, which is what POSTPONE does.

Upvotes: 1

Related Questions