gforth
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Attempt at implementing labeled loops


From: Anton Ertl
Subject: Re: Attempt at implementing labeled loops
Date: Wed, 3 Feb 2021 19:02:44 +0100
User-agent: NeoMutt/20170113 (1.7.2)

On Wed, Feb 03, 2021 at 05:01:28PM +0100, JFLF wrote:
> 
> Hello all,
> 
> Apologies if this is not the right place to ask. I have been attempting for a 
> few days to solve a specific problem with GForth 0.7.3, but so far I have 
> failed. Could anyone provide some advice? Disclaimer: I'm still learning 
> Forth.
> 
> I have been trying to implement something akin to continue in C loops, but 
> with labels. Ideally I'd like to achieve this:
> 
>     *: begin looplabel: loop1**
>     **
>     **    <condition>**
>     **    while**
>     **        ...**
>     **        <test> if loop1 again then**
>     **        ...**
>     **    repeat**
>     **;*
> 
> 
> Essentially I'm looking at replacing *[ x cs-pick ] again* by something a bit 
> more manageable, especially with nested control-flow items.

You may be interested in the word CONTOF (which ends an OF or ?OF part
in a CASE structure) in the development version of Gforth.  Your
example (if I understand it) would become

case
  <condition> ?of endof
  ...
  <test> ?of contof
  ...
next-case

> Side note: the way *while* tucks its orig cf item under *begin*'s dest also 
> caused me quite a bit a trouble, as it disrupts the obvious index progression 
> of cf items on the cf stack. It's defined as such in the standard, but does 
> it make sense?

Yes, it allows doing things like

begin
  ... while
    ... while
      ... while
        ...
again then then then

> My current implementation and a test word look like this:
> 
>     *: looplabel:
> 
>         create
>             2 pick , 2dup 2,
>         does> immediate
>             dup @ swap cell+ 2@
>     ; immediate*
> 
> 
>     *: testbegin     ( -- )
> 
>         3
>         begin looplabel: loop2
>             1- dup 0<>
>         while
>             dup 2 = if ." IF taken" cr loop2 again then
>             ." After the test: " dup . cr
>         repeat
>         drop
>     ;*

You are trying to CREATE LOOP2 in the middle of the code for
TESTBEGIN; that's not going to work.  The development version has some
support for this kind of stuff, but I don't think we have documented
it completely yet.

> Dumping the control stack at compilation time (with additional 
> instrumentation in the *testbegin* word), things /seem/ to be fine. For 
> example:
> 
>     *Before begin:  <4> 0 140421634250120 140421634250152 0
>     After begin    <7> 0 140421634250120 140421634250152 0 0 140421634250184 3
>     After llabel:  <7> 0 140421634250120 140421634250152 0 0 140421634250184 3
>     loop2          0 140421634250184 3
>     After while    <10> 0 140421634250120 140421634250152 0 0 140421634250408 
> 1 0 140421634250184 3
>     After if       <13> 0 140421634250120 140421634250152 0 0 140421634250408 
> 1 0 140421634250184 3 0 140421634250456 1
>     After loop2    <16> 0 140421634250120 140421634250152 0 0 140421634250408 
> 1 0 140421634250184 3 0 140421634250456 1 0 140421634250184 3
>     After again    <13> 0 140421634250120 140421634250152 0 0 140421634250408 
> 1 0 140421634250184 3 0 140421634250456 1
>     After then     <10> 0 140421634250120 140421634250152 0 0 140421634250408 
> 1 0 140421634250184 3
>     After repeat   <4> 0 140421634250120 140421634250152 0 *
> 
> 
> But any attempt at executing *testbegin* gives that kind of result:
> 
>     *testbegin 
>     :2: Invalid memory address
>     >>>testbegin<<<
>     Backtrace:
>     $7FF9C41F95C0 lit *

That's probably because the header and body of LOOP2 is in the middle
of TESTBEGIN.  I am surprised that TESTBEGIN is REVEALed at all.

> The exact error changes, I have seen some stack underflows for example.
> 
> *see*-ing the words includes a few surprises:
> 
>     *see looplabel:  *
>     *: looplabel:  *
>     *  Create 2 pick , 2dup 2, 140710713988408 (does>2) ; immediate ok*
> 
>     *see testbegin *
>     *noname : *
>     *  3 *
>     *  BEGIN  BEGIN  <140710713988240> <-4611686018427387899> 
> <2314885609475239788> <94220049618193> <140710713988424> <0> <3> 
> <140710713988552> .\" In begin:    TOS " dup . cr 1- dup 0<> *
>     *         WHILE  dup 2 = *
>     *         WHILE  .\" IF taken" cr *
>     *         REPEAT *
>     *         .\" After the test" cr *
>     *  REPEAT *
>     *  drop ; ok*

The numbers in <...> are the header and body of LOOP2.

> Replacing *looplabel:* by *cs-pick* produces the right results, but *see* 
> still looks weird:
> 
>     *: testbegin2     ( -- )
> 
>         cr 3
>         begin
>             1- dup 0<>
>         while
>             dup 2 = if ." IF taken" cr [ 1 cs-pick ] again then
>             ." After the test: " dup . cr
>         repeat
>         drop
>     ;*
> 
> 
>     *testbegin2 
>     IF taken
>     After the test: 1
>      ok*
> 
> 
>     *see testbegin2 
>     noname :
>       cr 3
>       BEGIN  BEGIN  1- dup 0<>
>              WHILE  dup 2 =
>              WHILE  .\" IF taken" cr
>              REPEAT
>              .\" After the test: " dup . cr
>       REPEAT
>       drop ; ok*

The decompiler tries to reconstruct the control structure from the
branches that the code is compiled to.  This decompiler result gives
an idea of how this could be coded without CS-PICK.  Thinking through
it, this is indeed the loop you wanted: The CS-PICK is replaced by
having a second BEGIN (in the same place), the first WHILE branches
after the second REPEAT, the second WHILE after the first REPEAT. and
both REPEATs branch back to the BEGINs.

> So here are my questions:
> 
> 1) I feel that I am missing some compile-time side effect of the *looplabel:* 
> word but after two days of going through the GForth doc I can't figure out 
> what. Any hint?

LOOPLABEL produces stuff in the dictionary.  The threaded code of
TESTBEGIN also resides in the dictionary; so you get the LOOP2 stuff
in the middle of the threaded code, which leads to breakage.

If you want to go ahead with named labels, a way to do it would be to
define it outside the colon definition, and then use it inside, maybe
like:

: looplabel:
  create 0 , immediate
does> here swap ! ;

: goto
  postpone branch ' >body @ , ; immediate

LOOPLABEL: LOOP2:

: testbegin     ( -- )
    begin loop2:
        1- dup 0<>
    while
        dup 2 = if ." IF taken" cr goto loop2: then
        ." After the test: " dup . cr
    repeat
    drop
;

> 2) In both test words, the nested *if* compiles as a second *begin while 
> repeat*. Why is that?

WHILE and IF compile a conditional branch forward.

REPEAT is AGAIN THEN and compile an unconditional branch backwards
followed by a branch target.

So you can write it either way, and get the same result.  And the
decompiler then guesses at how to decompile it.

> 4) Ideally I would want the scope of those loop labels to be strictly limited 
> to the current word definition. I thought of locals, but I believe that 
> they're still visible in called words, right?

No.

> Is there a mechanism to limit the scope of locals in GForth?

SCOPE ... ENDSCOPE

But that's for limiting the scope within a colon definition.

- anton



reply via email to

[Prev in Thread] Current Thread [Next in Thread]