Digest #304 2025-08-05

Contributions

[393] 2025-08-04 13:11:17 EricBlake wrote:

referenceImplementation - Reference implementation should not use -ROT

-rot is not part of the standard; the reference implementation should be written using only other words in the standard. A working replacement, although not necessarily the most efficient, would be rot rot. (A future version of the standard should consider standardizing -rot.)

For that matter, the reference implementations for n>r and nr> use an atypical style of using \ rather than ( ) for giving the comment listing stack effects. And both examples document that they have a dependency on a non-standard use of r> on a value that was not previously placed on the return stack by r> by assuming that the return stack has exactly one cell occupied by the return pointer to get back to the caller of n>r and nr>. It's not entirely wrong to have a reference that is not portable to all implementations since that limitation is documented.

However, it would be possible to rewrite these examples as immediates in order to be entirely portable, although no longer as compact (every caller duplicates code, rather than being a single call into shared code). That said, such a rewrite still matches with the fact that the standard documents only that its reference implementations demonstrate how the word can work, not that they are the most efficient:

VARIABLE nr-scratch \ scratch space for N>R and NR>
: N>R ( xn .. x1 n -- ; R: -- x1 .. xn n )
\ Transfer N items and count to the return stack.
  POSTPONE DUP POSTPONE nr-scratch POSTPONE !
  POSTPONE BEGIN
    POSTPONE DUP
  POSTPONE WHILE
    POSTPONE SWAP POSTPONE >R
    POSTPONE 1-
  POSTPONE REPEAT
  POSTPONE DROP
  POSTPONE nr-scratch POSTPONE @ POSTPONE >R
; IMMEDIATE
: NR> ( -- xn .. x1 n ; R: x1 .. xn n -- )
\ Pull N items and count off the return stack.
  POSTPONE R>
  POSTPONE DUP POSTPONE nr-scratch POSTPONE !
  POSTPONE BEGIN
    POSTPONE DUP
  POSTPONE WHILE
    POSTPONE R> POSTPONE SWAP
    POSTPONE 1-
  POSTPONE REPEAT
  POSTPONE DROP
  POSTPONE nr-scratch POSTPONE @
; IMMEDIATE

Or, we could go the route of documenting the use of a buffer, by depending on the memory-allocation word set; again, using immediate so that the example does not depend on non-portable use of r> on a return value not placed by >r:

: N>R ( xn .. x1 n -- ; R: -- buf-sys n )
\ Transfer N items and count to the return stack.
\ This version throws ior if ALLOCATE fails
  POSTPONE DUP POSTPONE >R
  POSTPONE DUP POSTPONE ALLOCATE POSTPONE THROW ( xn .. x1 n a-addr ; R: n )
  POSTPONE BEGIN
    POSTPONE OVER
  POSTPONE WHILE
    POSTPONE ROT POSTPONE OVER POSTPONE !
    POSTPONE CELL+ POSTPONE SWAP POSTPONE 1- POSTPONE SWAP ( xn .. x2 n-1 a-addr+1 ; R: n )
  POSTPONE REPEAT ( 0 a-addr+n ; R: n )
  POSTPONE R> POSTPONE SWAP POSTPONE 2>R POSTPONE DROP
; IMMEDIATE
: NR> ( -- buf-sys n ; R: x1 .. xn n -- )
\ Pull N items and count off the return stack.
\ This version throws ior if FREE fails
  POSTPONE 2R> POSTPONE OVER POSTPONE >R ( n a-addr+n ; R: n )
  POSTPONE BEGIN
    POSTPONE OVER
  POSTPONE WHILE
    -1 POSTPONE LITERAL POSTPONE CELLS POSTPONE + POSTPONE DUP POSTPONE @ ( n a-addr+n-1 xn )
    POSTPONE ROT POSTPONE 1- POSTPONE ROT ( xn n-1 a-addr+n-1 ; R: n )
  POSTPONE REPEAT ( xn .. x1 0 a-addr ; R: n )
  POSTPONE FREE POSTPONE THROW POSTPONE DROP POSTPONE R>
; IMMEDIATE

[394] 2025-08-04 14:23:53 EricBlake wrote:

testcase - Enhance n>r test to validate that n>r r@ r>n sees n

The standard is clear that although the implementation can store j*x words for the rest of the saved portion of the data stack with no 1:1 correspondence to the original i*x words, the return stack MUST have n as its top entry.

There is debate on whether this should be relaxed; ruv mentioned at https://forth-standard.org/standard/tools/NtoR#reply-964 changing things in a backward-incompatible way as:

    N>R ( u*x u -- ) ( R: -- nr-sys )
    NR> ( -- u*x u ) ( R: nr-sys -- )

But unless that change is made, we should probably test that an implementation complies with the current wording, which is observable to a standard program via R@. I (temporarily) made the mistake of not complying in one of my attempted reference implementations at https://forth-standard.org/standard/tools/NRfrom#contribution-393, when I had swapped the order of n and a buffer address on the return stack. Thus, I suggest adding:

: TNR3 N>R SWAP R@ NR> ;
T{ 1 2 10 20 30 3 TNR3 -> 2 1 3 10 20 30 3 }T

[395] 2025-08-04 15:28:09 EricBlake wrote:

referenceImplementation - Suggested implementations

I can see that tests for 2>R were not added until version 0.10 of the testsuite (Aug 2014), which post-dates Forth-2012 and thus explains why those tests are not visible in Annex F. Had I seen that test sooner, I would have caught the bug in my initial attempted implementation. Namely:

: 2>R SWAP >R >R ;   \ buggy implementation

appears to be a straightforward transcription of the normative requirement on semantics, except that it has the problem of not being immediate, and thus falls foul of the ambiguous behavior when using >r within a word that is not matched by r> in the same word. A simple fix is to make things immediate (so that all manipulations of the return stack are being done in the context of the code being compiled, rather than in the body of a helper word that may have pushed a nest-sys to the return stack), so it could be wise to list this as a reference implementation:

: 2>R ( x1 x2 -- ) ( R: -- x1 x2 )
  POSTPONE SWAP POSTPONE >R POSTPONE >R
; IMMEDIATE

and similarly for 2R@ and 2R>. Or, if you take the approach of the reference implementation of NR> which includes a disclaimer that it has an environmental dependency on the system placing exactly one cell on the return stack for nest-sys (we already document that reference examples need not be universal):

: 2>R ( x1 x2 -- ) ( R: -- x1 x2 )
\ This reference implementation has an environmental dependency on being able to safely stash a one-cell nest-sys containing the return back to the caller
  SWAP R> SWAP >R SWAP >R >R ;

Replies

[r1470] 2025-08-02 05:23:00 AntonErtl replies:

testcase - Compilation of a VALUE child word should not inline its current value

This website currently reflects Forth-2012, and AFAIK the tests come from the Forth-2012 document (which took them out of the test suite around 2012). When we release the next standard, the document and the tests in there will be updated (hopefully in some automated way). And maybe we will do the tests on this website directly from the test suite at some point. In any case, if you want to use up-to-date tests, go to the source.


[r1471] 2025-08-02 05:41:50 AntonErtl replies:

requestClarification - May OF modify the enclosing case-sys on the control stack?

At some point I came to the conclusion that the technique outlined in A.3.2.3.2 does not comply with standard requirements, but at the moment I don't see why; i.e., I don't see how to write a standard program where the implementation in A.3.2.3.2 would not behave (in ways that a standard program can determine) as specified.

In any case, I have implemented a slightly different technique in compat/caseext.fs; this implementation should work on any standard system where control-flow stack items consume at least one cell on the data stack. This is the implementation used by Gforth. As additional benefit, you also get ?of, contof, and next-case, which allow to use case also for arbitrary conditions and for loops.


[r1472] 2025-08-02 06:06:31 AntonErtl replies:

requestClarification - May OF modify the enclosing case-sys on the control stack?

Thinking about it again: Only endof can consume an of-sys, and endof takes case-sys1 of-sys as input, so at of there has to be a case-sys on top. A standard program cannot tell whether of modified that case-sys, so it seems to me that doing it in the A.3.2.3.2 way is standard-compliant (i.e., no standard program will fail on such an implementation).


[r1473] 2025-08-04 14:11:41 EricBlake replies:

referenceImplementation - Reference implementation should not use -ROT

An improved version of my buffer example, producing less code in the body of each caller (again, throwing ior if allocate or free fails), and fixing my last minute change of swap r> r> to 2r> but forgetting to remove the swap. The standard documents an explicit n at the top of R: rather than allowing the implementation-defined data to encode n in some other manner (in my case; I was accidentally storing 'n buf-sys' instead of 'buf-sys n', but the mistake was symmetric); at any rate, this is observable to a standard program using r@, so the testsuite for N>R ought to be enhanced to test that.

: (n>r) ( xn .. x1 n -- buf-sys )
\ Helper for n>r: allocate a buffer and populate it with n items from the stack, returning a buf-sys
  DUP ALLOCATE THROW
  BEGIN
    OVER
  WHILE
    ROT OVER !
    CELL+ SWAP 1- SWAP
  REPEAT
  NIP
;
: (nr>) ( buf-sys n -- xn .. x1 )
\ Helper for nr>: extract n items from buf-sys to the stack, then free the buffer
  SWAP
  BEGIN
    OVER
  WHILE
    -1 CELLS + DUP @
    ROT 1- ROT
  REPEAT
  FREE THROW DROP
;
: N>R ( xn .. x1 n -- ; R: -- buf-sys n )
\ Transfer N items and count to the return stack.
  POSTPONE DUP POSTPONE >R
  POSTPONE (n>r)
  POSTPONE R> POSTPONE 2>R
; IMMEDIATE
: NR> ( -- buf-sys n ; R: x1 .. xn n -- )
\ Pull N items and count off the return stack.
  POSTPONE 2R> POSTPONE DUP POSTPONE >R
  POSTPONE (nr>)
  POSTPONE R>
; IMMEDIATE

In case it was not obvious to the reader, one benefit of moving code into a helper is that clients of n>r and nr> compile smaller; but it only works if the code being factored into a helper is not itself manipulating >r and r> . Thus, I can only refactor the second example with a buffer, and not the first example (the first example depends on inlining >r within the begin/repeat loop into the body of the word being compiled). Of course, if you are implementing things and KNOW how R is affected by calling into a non-immediate word, then you can exploit that knowledge and write your own 2>r back in the style of the original example by using r> on a return value not placed by >r.