• tail calls or jumps, standardization?

    From Paul Rubin@no.email@nospam.invalid to comp.lang.forth on Sun Sep 20 16:04:52 2026
    From Newsgroup: comp.lang.forth

    Is there any reasonably portable Forth way to do a tail call or tail
    jump? If not, is there a way to do it in gforth? Is there any
    likelihood of standardizing such a mechanism?

    Purpose is to jump from one word to another, or to the beginning of the
    current word, without pushing the stack. It might have to do something
    like UNLOOP if invoked in a loop.

    It does NOT have to do tail call optimization in the compiler, which
    could require making the compiler smarter. It's enough to just have a
    way to explicitly jump to another word, or to a previous word. I guess
    the jump target in the low level implementation would be an xt.

    Example use case: a state machine that jumps around between states that
    are implemented as Forth words.

    Another example: actual implementation of tail recursive algorithms, e.g.:

    : (factorial) {: n a -- n :}
    n 0= IF a ELSE n 1- a n * TAILREC THEN ;
    : factorial ( n -- n ) 1 (factorial) ;

    TAILREC here is supposed to denote a tail recursive self-call, like
    RECURSE but without a return stack push.

    For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP jumping
    to an xt that is supplied on the data stack.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Paul Rubin@no.email@nospam.invalid to comp.lang.forth on Sun Sep 20 16:41:30 2026
    From Newsgroup: comp.lang.forth

    Paul Rubin <no.email@nospam.invalid> writes:
    For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP
    jumping to an xt that is supplied on the data stack.

    I guess TAILJUMP could also be called -EXECUTE ("minus execute") or
    (EXECUTE). It would be like EXECUTE except it wouldn't push a return
    address.

    There could also be something for coroutine jump, that would manipulate
    the implementation's return address in the case where the return address doesn't live on the return stack. Traditionally with the return stack,
    CO just swaps the program counter with TOR.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Mon Sep 21 05:22:27 2026
    From Newsgroup: comp.lang.forth

    Paul Rubin <no.email@nospam.invalid> writes:
    Is there any reasonably portable Forth way to do a tail call or tail
    jump?

    A call followed by an EXIT or ";" should do it on systems that perform automatic tail-call optimization (e.g., SwiftForth).

    If not, is there a way to do it in gforth?

    For now, Gforth does not perform automatic tail-call optimization.

    In development Gforth, you can perform an optimized tail-call (or
    actually a tail-execute) explicitly with EXECUTE-EXIT.

    For direct calls, you can do it as follows:

    : foo ." foo" ;
    : bar [ exit-like ] branch [ ' foo >body , ] ;

    How long this method will continue to work in the future is unclear.

    Is there any
    likelihood of standardizing such a mechanism?

    The current practice is that some Forth systems (e.g., SwiftForth)
    perform automatic tail-call optimization (SwiftForth does it for
    direct calls, including RECURSE, for EXECUTE, and for deferred words),
    so if anything in that direction would be standardized at all, a
    guarantee for that would be standardized. Maybe also a mechanism for
    turning it off.

    However, given that there is little use for recursion in Forth, and
    little use of tail-recursion for loops, I doubt that there is any
    consensus on requiring such a guarantee from all Forth systems. But
    if you see the need, don't let my doubts stop you.

    Purpose is to jump from one word to another, or to the beginning of the >current word, without pushing the stack. It might have to do something
    like UNLOOP if invoked in a loop.

    If you put an EXIT in a counted loop, you have to insert UNLOOP
    yourself. That does not change with tail-call optimization. It may
    be a good idea to do the UNLOOP(s) before the tail call, however, in
    order to get the optimization.

    It does NOT have to do tail call optimization in the compiler, which
    could require making the compiler smarter.

    Only slightly. Even Chuck Moore, who has argued (and implemented) for
    letting the programmer, rather than the computer do work, has
    implemented automatic tail-call optimization in cmForth, machine
    Forth, and AFAIK colorForth.

    Another example: actual implementation of tail recursive algorithms, e.g.:

    : (factorial) {: n a -- n :}
    n 0= IF a ELSE n 1- a n * TAILREC THEN ;
    : factorial ( n -- n ) 1 (factorial) ;

    That's perverse. The natural recursive formulation of the factorial
    is not tail-recursive, so one has to transform it into "(factorial)"
    to get tail recursion. But what for? Forth has loops. No need to
    use tail recursion where it does not fit.

    For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP jumping
    to an xt that is supplied on the data stack.

    For deferred words and EXECUTE, we have two kinds of systems:

    1) Systems based on threaded code (or following the principles used in
    threaded code; I think the decisive issue here is having a Forth
    instruction pointer (IP) separate from the machine's program counter
    (PC)), where EXECUTE and deferred words jump to a word-specific
    run-time routine (such as docon, docol, dodefer, dodoes, etc.), and
    where docol and dodoes push IP on the return stack, but other run-time
    routines do not. In these systems tail-call optimization for EXECUTE
    and deferred words can be performed by popping the return address from
    the return stack and storing it into IP, and then doing whatever
    EXECUTE or the deferred word does normally.

    2) Native-code systems usually have no separate IP, so they tend to
    call all EXECUTEd and deferred words, whether they are colon
    definitions or constants, and at the end of the called word, there is
    a return. For these systems, it is sufficient to convert the
    (possibly indirect) call into a jump.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Mon Sep 21 06:36:18 2026
    From Newsgroup: comp.lang.forth

    Paul Rubin <no.email@nospam.invalid> writes:
    Paul Rubin <no.email@nospam.invalid> writes:
    For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP
    jumping to an xt that is supplied on the data stack.

    I guess TAILJUMP could also be called -EXECUTE ("minus execute") or >(EXECUTE). It would be like EXECUTE except it wouldn't push a return >address.

    In Gforth, EXECUTE does not push a return address. If a return
    address is pushed, it is pushed by docol or dodoes.

    EXECUTE-EXIT pops the return address into IP and then does what
    EXECUTE does; if EXECUTE invokes docol, it pushes IP to the return
    stack.

    In the case of EXECUTE-EXITing a colon definition, this results in
    more work, but when EXECUTEing a constant, variable, primitive, or
    other word that does not push IP, it results in less work.

    There could also be something for coroutine jump, that would manipulate
    the implementation's return address in the case where the return address >doesn't live on the return stack. Traditionally with the return stack,
    CO just swaps the program counter with TOR.

    Much of the stuff that has traditionally been does with return-address manipulation can be done in a designated-standard way with quotations.

    The de-standardization of return-address manipulation has made it easy
    enough to implement tail-call optimizations and inlining that Forth
    systems have actually performed them. I doubt we will see any
    consensus on reverting that.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From peter@peter.noreply@tin.it to comp.lang.forth on Mon Sep 21 10:36:37 2026
    From Newsgroup: comp.lang.forth

    On Mon, 21 Sep 2026 05:22:27 GMT
    anton@mips.complang.tuwien.ac.at (Anton Ertl) wrote:

    Paul Rubin <no.email@nospam.invalid> writes:
    Is there any reasonably portable Forth way to do a tail call or tail
    jump?

    A call followed by an EXIT or ";" should do it on systems that perform automatic tail-call optimization (e.g., SwiftForth).

    If not, is there a way to do it in gforth?

    For now, Gforth does not perform automatic tail-call optimization.

    In development Gforth, you can perform an optimized tail-call (or
    actually a tail-execute) explicitly with EXECUTE-EXIT.

    For direct calls, you can do it as follows:

    : foo ." foo" ;
    : bar [ exit-like ] branch [ ' foo >body , ] ;

    How long this method will continue to work in the future is unclear.

    Is there any
    likelihood of standardizing such a mechanism?

    The current practice is that some Forth systems (e.g., SwiftForth)
    perform automatic tail-call optimization (SwiftForth does it for
    direct calls, including RECURSE, for EXECUTE, and for deferred words),
    so if anything in that direction would be standardized at all, a
    guarantee for that would be standardized. Maybe also a mechanism for
    turning it off.

    However, given that there is little use for recursion in Forth, and
    little use of tail-recursion for loops, I doubt that there is any
    consensus on requiring such a guarantee from all Forth systems. But
    if you see the need, don't let my doubts stop you.

    Purpose is to jump from one word to another, or to the beginning of the >current word, without pushing the stack. It might have to do something >like UNLOOP if invoked in a loop.

    If you put an EXIT in a counted loop, you have to insert UNLOOP
    yourself. That does not change with tail-call optimization. It may
    be a good idea to do the UNLOOP(s) before the tail call, however, in
    order to get the optimization.

    It does NOT have to do tail call optimization in the compiler, which
    could require making the compiler smarter.

    Only slightly. Even Chuck Moore, who has argued (and implemented) for letting the programmer, rather than the computer do work, has
    implemented automatic tail-call optimization in cmForth, machine
    Forth, and AFAIK colorForth.

    Another example: actual implementation of tail recursive algorithms, e.g.:

    : (factorial) {: n a -- n :}
    n 0= IF a ELSE n 1- a n * TAILREC THEN ;
    : factorial ( n -- n ) 1 (factorial) ;

    That's perverse. The natural recursive formulation of the factorial
    is not tail-recursive, so one has to transform it into "(factorial)"
    to get tail recursion. But what for? Forth has loops. No need to
    use tail recursion where it does not fit.

    For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP jumping
    to an xt that is supplied on the data stack.

    For deferred words and EXECUTE, we have two kinds of systems:

    1) Systems based on threaded code (or following the principles used in threaded code; I think the decisive issue here is having a Forth
    instruction pointer (IP) separate from the machine's program counter
    (PC)), where EXECUTE and deferred words jump to a word-specific
    run-time routine (such as docon, docol, dodefer, dodoes, etc.), and
    where docol and dodoes push IP on the return stack, but other run-time routines do not. In these systems tail-call optimization for EXECUTE
    and deferred words can be performed by popping the return address from
    the return stack and storing it into IP, and then doing whatever
    EXECUTE or the deferred word does normally.

    2) Native-code systems usually have no separate IP, so they tend to
    call all EXECUTEd and deferred words, whether they are colon
    definitions or constants, and at the end of the called word, there is
    a return. For these systems, it is sufficient to convert the
    (possibly indirect) call into a jump.

    This is how lxf, and now also lxf64 works. For lxf64 this happens
    already in the tokenizer for calls at exit and ;.
    This does not stop the inliner to inline words that contain tail jumps,
    it is smart enough to change them back to calls. There are only two
    situations that stop the inliner, recursive calls and early exits.
    It used to stop on words using locals but I found out that that works
    just fine. In fact it is a better way to use locals as their scope
    are naturally limited to the inlined word!

    BR
    Peter


    - anton


    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From albert@albert@spenarnc.xs4all.nl to comp.lang.forth on Mon Sep 21 11:04:52 2026
    From Newsgroup: comp.lang.forth

    In article <2026Sep21.083618@mips.complang.tuwien.ac.at>,
    Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote:
    Paul Rubin <no.email@nospam.invalid> writes:
    Paul Rubin <no.email@nospam.invalid> writes:
    For mutual recursion you'd use DEFER and TAILJUMP, with TAILJUMP
    jumping to an xt that is supplied on the data stack.

    I guess TAILJUMP could also be called -EXECUTE ("minus execute") or >>(EXECUTE). It would be like EXECUTE except it wouldn't push a return >>address.

    In Gforth, EXECUTE does not push a return address. If a return
    address is pushed, it is pushed by docol or dodoes.

    No sane implementation does. Think of
    ' DROP EXECUTE
    DROP ends its execution by popping the return stack.


    EXECUTE-EXIT pops the return address into IP and then does what
    EXECUTE does; if EXECUTE invokes docol, it pushes IP to the return
    stack.

    In the case of EXECUTE-EXITing a colon definition, this results in
    more work, but when EXECUTEing a constant, variable, primitive, or
    other word that does not push IP, it results in less work.

    There could also be something for coroutine jump, that would manipulate
    the implementation's return address in the case where the return address >>doesn't live on the return stack. Traditionally with the return stack,
    CO just swaps the program counter with TOR.

    Much of the stuff that has traditionally been does with return-address >manipulation can be done in a designated-standard way with quotations.

    The de-standardization of return-address manipulation has made it easy
    enough to implement tail-call optimizations and inlining that Forth
    systems have actually performed them. I doubt we will see any
    consensus on reverting that.
    Most implementation allows return-address manipulation in a
    system dependant way. De-standardization doesn't make tail-call
    any easier. It serves to make programs non-standard, rightly so.

    To Rubin:
    Also CO doesn't get rid of return stack items. Instead it
    saves them for later use.


    - anton

    Groetjes Albert
    --
    The Chinese government is satisfied with its military superiority over USA.
    The next 5 year plan has as primary goal to advance life expectancy
    over 80 years, like Western Europe.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From albert@albert@spenarnc.xs4all.nl to comp.lang.forth on Mon Sep 21 12:09:06 2026
    From Newsgroup: comp.lang.forth

    In article <2026Sep21.072227@mips.complang.tuwien.ac.at>,
    Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote:
    <SNIP>
    However, given that there is little use for recursion in Forth, and
    little use of tail-recursion for loops, I doubt that there is any
    consensus on requiring such a guarantee from all Forth systems. But
    if you see the need, don't let my doubts stop you.

    Tail recursion mostly comes from lisp style programming.
    For mathematical formulation it is elegant. Knuth formulates
    its algorithms with possibly infinite stack but no recursion.
    In my experience, I needed to have a gigantic data stack in some
    eulerproject problems. In the sparse case where recursion was
    indicated, an optimisation was in no way essential.

    So indeed you probably formulated the consensus for Forth systems.


    - anton

    Groetjes Albert
    --
    The Chinese government is satisfied with its military superiority over USA.
    The next 5 year plan has as primary goal to advance life expectancy
    over 80 years, like Western Europe.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Stephen Pelc@stephen@vfxforth.com to comp.lang.forth on Mon Sep 21 11:10:26 2026
    From Newsgroup: comp.lang.forth

    On 21 Sep 2026 at 00:04:52 GMT+1, "Paul Rubin" <no.email@nospam.invalid>
    wrote:

    Is there any reasonably portable Forth way to do a tail call or tail
    jump? If not, is there a way to do it in gforth? Is there any
    likelihood of standardizing such a mechanism?

    Any simple mechanim has to make assumptions about the target hardware, e.g. that
    a call pushes onto a return stack.

    In general, when a sufficiently powerful code generator is used, the space and cycle saving
    of tail call elimination (TCE) makes it mostly irrelevant. TCE also complcates tools such as
    the disassembler. IMHO TCE is another micro-optimisation for threaded-code systems.

    Stephen
    --
    Stephen Pelc, stephen@vfxforth.com
    Wodni & Pelc GmbH
    Vienna, Austria
    Tel: +44 (0)7803 903612, +34 649 662 974 http://www.vfxforth.com/downloads/VfxCommunity/
    free VFX Forth downloads
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Mon Sep 21 14:35:01 2026
    From Newsgroup: comp.lang.forth

    Stephen Pelc <stephen@vfxforth.com> writes:

    Any simple mechanim has to make assumptions about the target
    hardware, e.g. that a call pushes onto a return stack.

    It's sufficient to replace a call followed by a return with a jump.
    Depending on how locals cleanup is implemented, and whether you want
    to tail-call optimize cases where that is in play, it needs some
    additional considerations.

    TCE also complcates tools such as the disassembler.

    How?

    IMHO TCE is another micro-optimisation for threaded-code
    systems.

    What makes you think so? The only systems that come to my mind that
    use it are native-code systems (SwiftForth, cmForth, machine Forth);
    why would they use a "micro-optimisation for threaded-code systems"?
    And can you name any threaded-code systems that use it?

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Hans Bezemer@the.beez.speaks@gmail.com to comp.lang.forth on Mon Sep 21 18:58:26 2026
    From Newsgroup: comp.lang.forth

    On 21-09-2026 01:04, Paul Rubin wrote:
    : (factorial) {: n a -- n :}
    n 0= IF a ELSE n 1- a n * TAILREC THEN ;
    : factorial ( n -- n ) 1 (factorial) ;

    First, lemme translate that to actual Forth:

    : (factorial) over if over 1- spin * recurse ;then nip ;
    : factorial ( n -- n ) 1 (factorial) ;

    Look mum, no hands:

    4tH message: No errors at word 18
    Object size: 18 words
    String size: 0 chars
    Variables : 0 cells
    Strings : 0 chars
    Symbols : 2 names
    Reliable : Yes

    Addr| Opcode Operand Argument

    0| branch 11 (factorial)
    1| over 0
    2| 0branch 8
    3| over 0
    4| +literal -1
    5| swap 0
    6| rot 0
    7| * 0
    8| branch 0 (factorial)
    This is your tail call -- automatically applied.

    9| swap 0
    10| drop 0
    11| exit 0
    This is the rest of (factorial)

    12| branch 14 factorial
    13| literal 1
    14| branch 0 (factorial)
    This is factorial (and another tail call!)

    15| literal 9
    16| call 12 factorial
    17| . 0
    And this is a small test..

    P.S. No locals were harmed in this example!

    Hans Bezemer

    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From jkn@jkn+nin@nicorp.co.uk to comp.lang.forth on Mon Sep 21 20:08:56 2026
    From Newsgroup: comp.lang.forth

    On 21/09/2026 15:35, Anton Ertl wrote:
    Stephen Pelc <stephen@vfxforth.com> writes:

    Any simple mechanim has to make assumptions about the target
    hardware, e.g. that a call pushes onto a return stack.

    It's sufficient to replace a call followed by a return with a jump.
    Depending on how locals cleanup is implemented, and whether you want
    to tail-call optimize cases where that is in play, it needs some
    additional considerations.

    TCE also complcates tools such as the disassembler.

    How?

    IMHO TCE is another micro-optimisation for threaded-code
    systems.

    What makes you think so? The only systems that come to my mind that
    use it are native-code systems (SwiftForth, cmForth, machine Forth);
    why would they use a "micro-optimisation for threaded-code systems"?
    And can you name any threaded-code systems that use it?


    ISTM that you are misreading Stephen's comment, which might perhaps be phrased:

    "For threaded systems, TCE is an optimisation which is of such small
    benefit that it is not worth it
    "
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Paul Rubin@no.email@nospam.invalid to comp.lang.forth on Mon Sep 21 23:09:05 2026
    From Newsgroup: comp.lang.forth

    anton@mips.complang.tuwien.ac.at (Anton Ertl) writes:
    In development Gforth, you can perform an optimized tail-call (or
    actually a tail-execute) explicitly with EXECUTE-EXIT.

    Thanks, that sounds like what I wanted.

    That's perverse. The natural recursive formulation of the factorial
    is not tail-recursive, so one has to transform it into "(factorial)"
    to get tail recursion.

    It's a translation of a Lisp idiom, just as an example. You could also
    use mutual tail recursion to implement a state machine jumping between
    states.

    But what for? Forth has loops. No need to use tail recursion where
    it does not fit.

    The attraction (if it's something that appeals to you) is to program
    without using any mutable variables such as a loop index. I agree with
    you that it's not idiomatic Forth by any stretch.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Tue Sep 22 07:50:26 2026
    From Newsgroup: comp.lang.forth

    Paul Rubin <no.email@nospam.invalid> writes:
    The attraction (if it's something that appeals to you) is to program
    without using any mutable variables such as a loop index.

    In Forth the loop index is not a variable, and it's immutable.

    I agree with
    you that it's not idiomatic Forth by any stretch.

    Doing a state machine with one word per state seems very idiomatic to
    me. Doing it with tail calls instead of having the next state as
    stack item or variable content has a very Forthy smell to me.

    But converting the factorial into a tail-recursive word and calling
    that? No!

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Tue Sep 22 07:56:52 2026
    From Newsgroup: comp.lang.forth

    Hans Bezemer <the.beez.speaks@gmail.com> writes:
    On 21-09-2026 01:04, Paul Rubin wrote:
    : (factorial) {: n a -- n :}
    n 0= IF a ELSE n 1- a n * TAILREC THEN ;
    : factorial ( n -- n ) 1 (factorial) ;

    First, lemme translate that to actual Forth:

    : (factorial) over if over 1- spin * recurse ;then nip ;

    "Actual Forth"?

    Gforth:
    *the terminal*:1:31: error: Undefined word
    : (factorial) over if over 1- >>>spin<<< * recurse ;then nip ;

    lxf:
    terminal col: 35
    undefined word spin

    SwiftForth:
    : (factorial) over if over 1- spin * recurse ;then nip ; spin ?

    VFX:
    Err# -13 ERR: Undefined word.
    : (factorial) over if over 1- spin * recurse ;then nip ;

    And I expect that they all would give a similar response for the
    ;THEN.

    : factorial ( n -- n ) 1 (factorial) ;

    Let's do idiomatic Forth instead:

    : factorial ( u1 -- u2 )
    1 swap 0 ?do i 1+ * loop ;

    Much nicer. The only thing that's not so nice is the 1+. If we do
    not care for a correct result for "0 factorial", it can be changed
    into

    : factorial1 ( u1 -- u2 )
    dup 1 ?do i * loop ;

    but why that works and where it does not is not as obvious as it may
    seem. And using u+do instead of ?do does not fix it.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Hans Bezemer@the.beez.speaks@gmail.com to comp.lang.forth on Tue Sep 22 13:26:24 2026
    From Newsgroup: comp.lang.forth

    On 22-09-2026 09:56, Anton Ertl wrote:
    Hans Bezemer <the.beez.speaks@gmail.com> writes:
    On 21-09-2026 01:04, Paul Rubin wrote:
    : (factorial) {: n a -- n :}
    n 0= IF a ELSE n 1- a n * TAILREC THEN ;
    : factorial ( n -- n ) 1 (factorial) ;

    First, lemme translate that to actual Forth:

    : (factorial) over if over 1- spin * recurse ;then nip ;

    "Actual Forth"?

    Well, it should be awfully clear to those present that I don't consider
    LOCALS to be "actual Forth". Because it isn't.

    It was the gift of the Forth-2012 committee to those who desperately
    want to WRITE Forth, but actually CAN'T -- so there is nothing left for
    them but to hide behind the ugliest, most Unfortlike code ever invented
    (let alone published in any standard), desperately trying to mimic C
    behavior.

    However, I have no doubt whatsoever that almost the entire audience here (except those who tend to post trivial code with locals) is able to
    define these two 4tH words in zero seconds flat. "SPIN" for example is
    given away in the decompilation:

    9| swap 0
    10| drop 0
    11| exit 0

    Which (if I must spell it out for this audience) is:

    : SPIN SWAP ROT ;

    ";THEN" should even be more than obvious, because I'm not the first one
    to have adopted it. Better men than me came up with the outrageously brilliant:

    : ;THEN POSTPONE EXIT POSTPONE THEN ; IMMEDIATE

    I owe these geniuses for the rest of my miserable life for their right
    out most stunning contribution to Forth.

    I'm so sorry I wasn't able to keep it a secret any longer <snif>.

    Note that my code essentially reflects the original algorithm posted.
    That someone knows other algorithms -- fine. I know people who implement FACTORIAL as a table lookup. Fastest code I ever saw. Anyone interested?

    Hans Bezemer

    P.S. A complete list of 4tH-isms can be found here -- bookmark it for
    future reference: https://sourceforge.net/p/forth-4th/code/HEAD/tree/trunk/4th.src/lib/easy.4th







    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Paul Rubin@no.email@nospam.invalid to comp.lang.forth on Tue Sep 22 10:02:43 2026
    From Newsgroup: comp.lang.forth

    Hans Bezemer <the.beez.speaks@gmail.com> writes:
    desperately trying to mimic C behavior.

    Are you sure it wasn't Lisp behavior? ;-)
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Paul Rubin@no.email@nospam.invalid to comp.lang.forth on Fri Sep 25 13:41:41 2026
    From Newsgroup: comp.lang.forth

    albert@spenarnc.xs4all.nl writes:
    Also CO doesn't get rid of return stack items. Instead it
    saves them for later use.

    I don't understand what you mean by that. Is there a description
    somewhere of exactly what it does? On the GA144 and relatives, the
    coroutine switch ("EX") simply swaps the PC with the top of the R stack.
    I had thought your CO word did something similar.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From albert@albert@spenarnc.xs4all.nl to comp.lang.forth on Sat Sep 26 12:17:15 2026
    From Newsgroup: comp.lang.forth

    In article <87bj9lgj22.fsf@nightsong.com>,
    Paul Rubin <no.email@nospam.invalid> wrote:
    albert@spenarnc.xs4all.nl writes:
    Also CO doesn't get rid of return stack items. Instead it
    saves them for later use.

    I don't understand what you mean by that. Is there a description
    somewhere of exactly what it does? On the GA144 and relatives, the
    coroutine switch ("EX") simply swaps the PC with the top of the R stack.
    I had thought your CO word did something similar.

    CO swaps the interpreter pointer with the top of the R stack
    so the interpreter pointer is available later.
    EXIT discards the interpreter pointer and removes the top of
    the R-stack. Information loss.

    I thought it was obvious.

    Groetjes Albert
    --
    The Chinese government is satisfied with its military superiority over USA.
    The next 5 year plan has as primary goal to advance life expectancy
    over 80 years, like Western Europe.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From Paul Rubin@no.email@nospam.invalid to comp.lang.forth on Sat Sep 26 12:23:13 2026
    From Newsgroup: comp.lang.forth

    albert@spenarnc.xs4all.nl writes:
    CO swaps the interpreter pointer with the top of the R stack
    so the interpreter pointer is available later.

    Ok, that is what I thought. I must have gotten confused by one of your
    earlier posts that seemed to say something different.

    Do you run into hazards if you have something like DO loops with loop
    indexes on the return stack? It occurs to me that Chuck's
    implementations bypass this problem by having FOR...NEXT instead of DO
    loops. There's a similar issue if the implementation supports locals.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From albert@albert@spenarnc.xs4all.nl to comp.lang.forth on Sun Sep 27 14:54:00 2026
    From Newsgroup: comp.lang.forth

    In article <877bk7hl5q.fsf@nightsong.com>,
    Paul Rubin <no.email@nospam.invalid> wrote:
    albert@spenarnc.xs4all.nl writes:
    CO swaps the interpreter pointer with the top of the R stack
    so the interpreter pointer is available later.

    Ok, that is what I thought. I must have gotten confused by one of your >earlier posts that seemed to say something different.

    Do you run into hazards if you have something like DO loops with loop
    indexes on the return stack? It occurs to me that Chuck's
    implementations bypass this problem by having FOR...NEXT instead of DO
    loops. There's a similar issue if the implementation supports locals.

    CO is non standard. It states explicitly that it manipulates return
    addresses. It is not supposedly a universal extension whatever.
    It follow that you will take care in ciforth if the return stack is
    otherwise engaged. It is an implementation adjacent word.

    This doesn't detract from it usefullness. https://home.hccnet.nl/a.w.m.van.der.horst/forthlectures.html
    E.g. from forthlecture6.html

    FOR-WORDS hides the chaining mechanism of the vocabulary, returns
    dictionary address ("name tokens") and ends with a NULL.

    \ Loop over a wordlist starting with dea .
    : FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
    \ ISO
    : WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;

    Groetjes Albert
    --
    The Chinese government is satisfied with its military superiority over USA.
    The next 5 year plan has as primary goal to advance life expectancy
    over 80 years, like Western Europe.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Sun Sep 27 16:39:11 2026
    From Newsgroup: comp.lang.forth

    albert@spenarnc.xs4all.nl writes:
    FOR-WORDS hides the chaining mechanism of the vocabulary, returns
    dictionary address ("name tokens") and ends with a NULL.

    \ Loop over a wordlist starting with dea .
    : FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
    \ ISO
    : WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;

    This is exactly the kind of usage pattern where quotations result in
    clearer code and do not need return-address manipulation:

    : context@ ( -- wid ) get-order over >r set-order r> ;
    : words ( -- )
    [: name>string type space true ;] context@ traverse-wordlist ;

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From albert@albert@spenarnc.xs4all.nl to comp.lang.forth on Tue Sep 29 00:47:16 2026
    From Newsgroup: comp.lang.forth

    In article <2026Sep27.183911@mips.complang.tuwien.ac.at>,
    Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote:
    albert@spenarnc.xs4all.nl writes:
    FOR-WORDS hides the chaining mechanism of the vocabulary, returns >>dictionary address ("name tokens") and ends with a NULL.

    \ Loop over a wordlist starting with dea .
    : FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
    \ ISO
    : WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;

    This is exactly the kind of usage pattern where quotations result in
    clearer code and do not need return-address manipulation:

    I object that CO is called "return address manipulation".
    It is a documented control structure.

    : context@ ( -- wid ) get-order over >r set-order r> ;
    : words ( -- )
    [: name>string type space true ;] context@ traverse-wordlist ;

    No. The comparison is between the implementation of traverse-wordlist
    and FOR-WORDS.

    In ciforth without using the CO words it is similar
    : WORDS 'ID. CONTEXT @ FOR-WORDS ;

    This is however an uglier FOR-WORDS.

    Groetjes Albert




    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html >comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
    --
    The Chinese government is satisfied with its military superiority over USA.
    The next 5 year plan has as primary goal to advance life expectancy
    over 80 years, like Western Europe.
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From anton@anton@mips.complang.tuwien.ac.at (Anton Ertl) to comp.lang.forth on Tue Sep 29 07:06:54 2026
    From Newsgroup: comp.lang.forth

    albert@spenarnc.xs4all.nl writes:
    In article <2026Sep27.183911@mips.complang.tuwien.ac.at>,
    Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote: >>albert@spenarnc.xs4all.nl writes:
    FOR-WORDS hides the chaining mechanism of the vocabulary, returns >>>dictionary address ("name tokens") and ends with a NULL.

    \ Loop over a wordlist starting with dea .
    : FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
    \ ISO
    : WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;

    This is exactly the kind of usage pattern where quotations result in >>clearer code and do not need return-address manipulation:

    I object that CO is called "return address manipulation".

    It takes the return address off the return stack and does something
    with it. If you do "0 >R CO R> DROP" instead of just CO, it does not
    work.

    It is a documented control structure.

    What is it's documentation?

    : context@ ( -- wid ) get-order over >r set-order r> ;
    : words ( -- )
    [: name>string type space true ;] context@ traverse-wordlist ;

    No. The comparison is between the implementation of traverse-wordlist
    and FOR-WORDS.

    Why should that be more relevant than the implementation of WORDS?

    But anyway, here's Gforth's implementation of TRAVERSE-WORDLIST.

    : traverse-wordlist ( ... xt wid -- ... ) \ tools-ext
    \G perform @i{xt} ( ... nt -- f ... ) once for every word @i{nt}
    \G in the wordlist @i{wid}, until @i{f} is false or the wordlist
    \G is exhausted. @i{xt} is free to use the stack underneath.
    swap >r wordlist-id @
    BEGIN
    dup
    WHILE
    r@ over >r execute WHILE r> name>link
    REPEAT r>
    THEN drop rdrop ;

    You can "beautify" it by removing all comments, upper-casing every
    word, and eliminating indentation and newlines.

    The remaining differences are:

    1) TRAVERSE-WORDLIST uses EXECUTE instead of CO (the decisive difference).

    2) TRAVERSE-WORDLIST allows an early-out through the returned flag of
    the executed xt, and therefore contains a second while.

    3) TRAVERSE-WORDLIST moves its internal state to the return stack,
    such that the called xt can directly work with the stack passed to
    TRAVERSE-WORDLIST (example below). By contrast, FOR-WORDS leaves a
    second NFA on the stack that obstructs access to the stack items
    below.

    4) In Gforth you need "WORDLIST-ID @" to get from a wid to the start
    of the linked list (no, I don't have an explanation for the name).

    5) TRAVERSE-WORDLIST uses NAME>LINK instead of ">LFA @" to get to the
    next word.

    An example where it's useful that TRAVERSE-WORDLIST moves its internal
    state out of the way: Count the words in the wordlist:

    : wordlist-count ( wid -- u )
    0 [: drop 1+ true ;] rot traverse-wordlist ;

    In ciforth without using the CO words it is similar
    : WORDS 'ID. CONTEXT @ FOR-WORDS ;

    A much nicer WORDS.

    This is however an uglier FOR-WORDS.

    Which you will show so that we can judge for ourselves, right? My
    guess is that it will have difference 1 from the other FOR-WORDS, but
    not 2-5.

    But the fact that you have not shown this FOR-WORDS supports my point
    that it's more important how a word is used than how it is
    implemented.

    One thing I find interesting is that your return-address manipulation
    technique requires using a loop in the client of FOR-WORDS, whereas in
    earlier discussions we have been discussing a word LIST>, used like
    this:

    : foo bar list> bla blub ;

    where the BLA BLUB part is executed repeatedly, once for every element
    in the list. Or, to look at a standard word using this return-address manipulation technique (at least in the original and many following implementations):

    : foo
    create bar
    does>
    bla blub ;

    Again, the BLA BLUB part is executed repeatedly.

    - anton
    --
    M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
    comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
    New standard: https://forth-standard.org/
    EuroForth 2026 CFP: http://www.euroforth.org/ef26/cfp.html
    --- Synchronet 3.22a-Linux NewsLink 1.2
  • From albert@albert@spenarnc.xs4all.nl to comp.lang.forth on Tue Sep 29 12:00:50 2026
    From Newsgroup: comp.lang.forth

    In article <2026Sep29.090654@mips.complang.tuwien.ac.at>,
    Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote:
    albert@spenarnc.xs4all.nl writes:
    In article <2026Sep27.183911@mips.complang.tuwien.ac.at>,
    Anton Ertl <anton@mips.complang.tuwien.ac.at> wrote: >>>albert@spenarnc.xs4all.nl writes:
    FOR-WORDS hides the chaining mechanism of the vocabulary, returns >>>>dictionary address ("name tokens") and ends with a NULL.

    \ Loop over a wordlist starting with dea .
    : FOR-WORDS BEGIN DUP WHILE DUP CO >LFA @ REPEAT DROP ;
    \ ISO
    : WORDS CONTEXT @ FOR-WORDS BEGIN DUP WHILE ID. CO REPEAT DROP ;

    This is exactly the kind of usage pattern where quotations result in >>>clearer code and do not need return-address manipulation:

    I object that CO is called "return address manipulation".

    It takes the return address off the return stack and does something
    with it. If you do "0 >R CO R> DROP" instead of just CO, it does not
    work.



    It is a documented control structure.

    What is it's documentation?

    Return to the caller, suspending interpretation of the
    current definition, such that when the caller exits, this definition is resumed.


    : context@ ( -- wid ) get-order over >r set-order r> ;
    : words ( -- )
    [: name>string type space true ;] context@ traverse-wordlist ;

    No. The comparison is between the implementation of traverse-wordlist
    and FOR-WORDS.

    Why should that be more relevant than the implementation of WORDS?

    But anyway, here's Gforth's implementation of TRAVERSE-WORDLIST.

    : traverse-wordlist ( ... xt wid -- ... ) \ tools-ext
    \G perform @i{xt} ( ... nt -- f ... ) once for every word @i{nt}
    \G in the wordlist @i{wid}, until @i{f} is false or the wordlist
    \G is exhausted. @i{xt} is free to use the stack underneath.
    swap >r wordlist-id @
    BEGIN
    dup
    WHILE
    r@ over >r execute WHILE r> name>link
    REPEAT r>
    THEN drop rdrop ;


    My actual FOR-WORDS is similar to TRAVERSE-WORDLIST
    I don't consider CO introducing as a technique into WORDS, as
    it is not a simplification taking everything into account.

    - anton

    Groetjes Albert
    --
    The Chinese government is satisfied with its military superiority over USA.
    The next 5 year plan has as primary goal to advance life expectancy
    over 80 years, like Western Europe.
    --- Synchronet 3.22a-Linux NewsLink 1.2