Digest #307 2025-08-10
Contributions
referenceImplementation - Possible Reference Implementation
I suggest the following as a reference implementation:
: _cmp ( x1 x2 -- -1|0|1 ) - DUP IF 0< 1 OR THEN ;
: COMPARE ( addr1 u1 addr2 u2 -- -1|0|1 )
ROT 2DUP SWAP _cmp >R
MIN ?DUP IF
0 DO
COUNT >R SWAP COUNT R> _cmp ?DUP IF
NIP NIP UNLOOP R> DROP EXIT
THEN
SWAP
LOOP
THEN
2DROP
R>
;
Replies
I suppose the concept is somewhat niche, in the sense that it addresses a concern for Forth coders who wish to write code that is not only portable across multiple (or really all Core-supporting) implementations, but also ekes out every ounce of efficiency possible. I imagine cross-implementation coding is likely currently achieved with stanzas like:
platform-is-X [IF]
\ ... X-specific definitions
[THEN]
platform-is-Y [IF]
\ ... Y-specific definitions
[THEN]
where the specific optimizations and coverages are switched out based on known platform
optimizations, whereas the ?:
concept is more focused on individual words needed, and would
therefore adapt even to implementations unknown to the author and to improvements when some of
the words are added to new versions of existing implementations. Moreover, ?:
is certainly not
sufficient for words that work together, like TO
/VALUE
or DEFER
/IS
/etc. Such words
would still require [IF]
/[THEN]
stanzas.
I definitely prefer the Core-only implementation (although :NONAME
is Core-ext and VALUE
/TO
would need to be changed to VARIABLE
), as my main focus is tiny implementations used for custom
hardware, which may not have a full suite of extensions, and for which every bit of memory and performance matters.
While I understand that MARKER
is likely a much more comprehensive manner of rewinding a
definition, I'm not really proposing this particular implementation as much as I am the concept of a
word like ?:
. The implementation I present is really more a proof-of-concept demonstration of
behavior, and each Forth system would have whatever it considers most appropriate. That being said,
besides rewinding HERE
, I have no idea what other lasting effects a rewound :NONAME
definition
would have on the system (except as pertains to IMMEDIATE
and other IMMEDIATE
-like words).
This Core-only focus, however, implies that the ?:
would only be of use if it was found in Core,
itself. Otherwise, a stanza of the form [UNDEFINED] ?: [IF] ... [THEN]
would be necessary, and not
only that but, since [UNDEFINED]
, etc. are not even in Core, it may become challenging to include
an appropriate header that covered all possiblities...
\ poor man's Core-only [IF]/[THEN] (avoid using ')' in clauses):
: ) ;
: IF( 0= IF POSTPONE ( THEN ;
\ Core-only... can't rely on [UNDEFINED]:
BL WORD ?: FIND NIP 0= IF(
\ make sure `IMMEDIATE` is ok
\ to call even after a `:NONAME`
CREATE IMMEDIATE-OK 0 ,
: : : -1 IMMEDIATE-OK ! ;
: :NONAME :NONAME 0 IMMEDIATE-OK ! ; \ from Core-ext!
: IMMEDIATE IMMEDIATE-OK @ IF IMMEDIATE THEN ;
CREATE SKIP-DEF 0 ,
: ;
POSTPONE ;
SKIP-DEF @ IF
DROP HERE - ALLOT
0 SKIP-DEF !
THEN
; IMMEDIATE
\ usage: `?: -ROT ROT ROT ;`, for instance, will
\ define `-ROT` only if it's not already defined.
: ?:
>IN @
BL WORD FIND NIP
IF
\ skip definition:
DROP
HERE
-1 SKIP-DEF !
:NONAME \ from Core-ext!
ELSE
\ back up and define
>IN ! :
THEN
;
)
\ make sure `IMMEDIATE` is ok
\ to call even after a `:NONAME`
CREATE IMMEDIATE-OK 0 ,
: : : -1 IMMEDIATE-OK ! ;
: :NONAME :NONAME 0 IMMEDIATE-OK ! ; \ from Core-ext!
: IMMEDIATE IMMEDIATE-OK @ IF IMMEDIATE THEN ;
As written, this does not pass the testsuite. For a simple example:
:NONAME ; DROP
1 CONSTANT one IMMEDIATE
fails to make one
immediate, because it was defined with CONSTANT
, yet the most recent :NONAME
has not been countermanded by :
. One way to fix it is wrapping ALL the defining words (CONSTANT, VARIABLE, VALUE, BUFFER:, CREATE, 2VARIABLE, ...), but that doesn't scale (how do you portably learn which defining words a system natively supports, since some like 2VARIABLE are optional, and since the user can create more defining words, as you have just done with ?:
). So I tried a different approach:
https://github.com/ForthHub/discussion/discussions/197
By making IMMEDIATE immediate, you can write:
: name IMMEDIATE body... ;
as syntax sugar for the standard : name body... ; IMMEDIATE
, but more importantly, if you then use:
?: RDROP IMMEDIATE POSTPONE R> POSTPONE DROP ;
you either get a definition for RDROP that is immediate, or you get a :NONAME whose body is compiled to perform the effects of immediate at whatever future time(s) the xt is executed, in addition to its other semantics. You really don't want an RDROP that has the side effect of marking random words immediate; but on the other hand, your only use of :NONAME is for something you plan to throw away by rewinding HERE, so it will never be executed, and thus you never have an opportunity to see that subtle change in semantics between :
and :NONAME
for a leading immediate IMMEDIATE. Plus, once you switch to that style, you no longer have to implement IMMEDIATE-OK, and thus you manage to correctly support IMMEDIATE after all named defining words, without having to wrap every one of them.
Another point that I missed up to now and that others may have missed, too, is the order of recognizers in when using recognizer-sequence:
: As currently specified, you use this word as follows
' rec-float ` rec-num ` rec-nt 3 recognizer-sequence: tradforth
And tradforth
will first invoke rec-nt
, then rec-num
, and then rec-float
. I find this order counterintuitive, but an argument for it is that it agrees with the order in get-order
and set-order
. In any case, I think we should discuss this.
It would be helpful to use the proper spelling "Pronunciation" rather than "Pronounciation".
referenceImplementation - Possible Reference Implementation
I'm not sure your version works (assuming 4TH
means 3 PICK
), as towards the end of the string, COMPARE
may be accessing characters at caddr1+u1 and beyond. I think if you changed the DUP
to 2 PICK OVER <=
it may work better. I think this also points to a missing test(s) of this sort:
T{ : s8 S" xyz" ; -> }T
T{ s1 2 - s8 SEARCH -> s1 2 - \<FALSE\> }T
which tries to ensure that the code does not access characters that it shouldn't.
The corrected version is:
: SEARCH ( caddr1 u1 caddr2 u2 -- caddr3 u3 flag )
2OVER \ retain a copy of 1st string
BEGIN
2 PICK OVER <=
WHILE
2OVER 3 PICK OVER COMPARE
WHILE
1 /STRING
REPEAT
2NIP 2NIP TRUE EXIT \ string found
THEN
2DROP 2DROP FALSE \ string not found
;
referenceImplementation - Possible Reference Implementation
... apparently, <=
is not standard, so maybe > 0=
instead?
This is interesting, and my original Forth implementation had IMMEDIATE
as an immediate word (mostly because I didn't know Forth well enough... coincidentally, my first introduction being via JonesForth, as well). Unfortunately, I have the following:
create _to 0 ,
: TO 1 _to ! ; immediate
: VALUE
create ,
immediate
does>
STATE @ if
_to @ if
postpone literal
postpone !
else
postpone literal
postpone @
then
else
_to @ if ! else @ then
then
0 _to !
;
where VALUE
creates a word and then makes the created word immediate. I think that's one of the main capabilities intended by IMMEDIATE
not being immediate, is that you can define words that define other words which themselves are immediate. Of course, you could always use POSTPONE
in such situations, but unfortunately the die is cast.
Of course, again, all of this is just a rough draft at how it could work. If ?:
was included, an implementation could just simply ignore an IMMEDIATE
that occurs after a :NONAME
.
hmm... I see you actually handle the DOES>
case in your implementation. Still, all of this could be avoided if the standard simply said "Using IMMEDIATE
after a :NONAME
definition is a no-op", or something.