gforth
[Top][All Lists]
Advanced

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

Re: Attempt at implementing labeled loops


From: JFLF
Subject: Re: Attempt at implementing labeled loops
Date: Wed, 3 Feb 2021 22:43:22 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.7.0

Hello Anton,

Thank you very much for your answer! It clarifies a lot of thing, in particular 
the definition-within-a-definition that I was attempting. My mistake.

The real goal that I was trying to achieve (and which I finally solved with 
CS-PICK), is a command line argument parser:


: 1continue      ( dest -- dest )    \ compilation
    1 cs-pick postpone again
; immediate

\ Parse command line arguments
: parseargs     ( -- f )
    begin
        next-arg 2dup 0 0 d<>
    while
        \ switch case over strings
        2dup s" -h" str= if 2drop false exit then
        2dup s" -p" str= if 2drop true to pp? 1continue then
        2dup s" -1" str= if 2drop true to 1only? 1continue then
        \ nothing matched -> it's a wrong parameter
        ." ERROR: unknown command line parameter: " type cr false exit
    repeat
    true        \ no error, signal it to caller
;


It works but I am not happy with the hackiness of it.

I could use a CASE switch between WHILE and REPEAT, using ?OF for a generic 
test which would be a lot cleaner, but GForth 0.7.3 doesn't support it (or at 
least the version in the Debian repos). NEXT-CASE isn't there either, nor 
CONTOF.

I was probably being overzealous with the labeled loops, although I still think 
that in absolute terms it would be nice syntactic sugar:

begin: loop1
begin: loop2
loop2 break
loop1 again ( from within loop2 )

etc.

Thanks again!
JF


On 03/02/2021 19.02, Anton Ertl wrote:
> 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]