Definitional Break and Continue... the Time is Now

I was writing what I thought to be a clever bit of code, discussed here

for-each file ... [
    parse file [thru any [".reb" ".r"] <end> | (continue)]

I thought it was pleasing to have the CONTINUE in the rules themselves.

Pleasing, except... a bit displeasing considering it didn't work.

BREAK and CONTINUE would break or continue the first loop they found above them in the stack. Since PARSE is currently written in usermode code, it uses loops in its implementation (to loop over the rules). so you wind up continuing some arbitrary loop inside the combinators, having random effects!

Happily Ren-C has pioneered answers to this kind of problem with definitional returns. But loops just have to get with the program and make CONTINUE and BREAK definitional in their bodies.

So I did it! And most things appeared to work (like the motivating example).

Still Complexity For Loops Implemented Without "Loops"

In bootstrap, there was some code implementing FOR-EACH-PLATFORM. Hand-waving a bit, it initially looked like this:

 for-each-platform: func ['var [word! tuple!] body [block!]] [
     parse platforms-table [
         while [not <end>] [
              ...  ; rules that build up an OBJECT! 
              (set var obj, do body)

But that didn't make CONTINUE work:

 for-each-platform p [
     if = 'HaikuOS [continue]

So I'd hacked up something at one point in history which looked like this monstrosity:

    completed*: false
    running*: false
    while [true] [  ; must be in loop for BREAK or CONTINUE
        if running* [  ; must have had a CONTINUE
            completed*: true
        running*: true
        do body
        completed*: true
    if not completed* [return null] 

That depended on non-definitional CONTINUE happening inside BODY finding the enclosing WHILE.

But now, that CONTINUE isn't definitionally bound anywhere. So that CONTINUE is just a reference to a default CONTINUE function in LIB, which will error telling you there are no loops providing continue available.

For the modern world, this works:

 repeat 1 body else [return null]

Reserving the pure NULL return value for BREAK means you can get that communication of when BREAK was encountered out of the return result. And in this case, CONTINUE is just supposed to bypass the remainder of code in BODY and go on parsing. So it works.

But it's suboptimal as the binding of the body to BREAK and CONTINUE happens on each run instead of once. Doing that more efficiently would need some new techniques and case studies.

Overall though, it's the sense that there's an answer that works slowly, vs. not working at all!

1 Like