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 >