6.10.5 General control structures with case

Gforth provides an extended case that solves the problems of the multi-exit loops discussed above, and offers additional options. You can find a portable implementation of this extended case in compat/caseext.fs.

There are three additional words in the extension. The first is ?of which allows general tests (rather than just testing for equality) in a case; e.g.,

: sgn ( n -- -1|0|1 )
  ( n ) case
    dup 0 < ?of drop -1 endof
    dup 0 > ?of drop 1  endof
    \ otherwise leave the 0 on the stack
  0 endcase ;

Note that endcase drops a value, which works fine much of the time with of, but usually not with ?of, so we leave a 0 on the stack for endcase to drop. The n that is passed into sgn is also 0 if neither ?of triggers, and that is then passed out.

The second additional word is next-case, which allows turning case into a loop. Our triple-exit loop becomes:

case
  condition1 ?of exit-code1 endof
  condition2 ?of exit-code2 endof
  condition3 ?of exit-code3 endof
  ...
next-case
common code afterwards

As you can see, this solves both problems of the variants discussed above (see General loops with multiple exits). Note that next-case does not drop a value, unlike endcase.16

The last additional word is contof, which is used instead of endof and starts the next iteration instead of leaving the loop. This can be used in ways similar to Dijkstra’s guarded command do, e.g.:

: gcd ( n1 n2 -- n )
    case
        2dup > ?of tuck - contof
        2dup < ?of over - contof
    endcase ;

Here the two ?ofs have different ways of continuing the loop; when neither ?of triggers, the two numbers are equal and are the gcd. Endcase drops one of them, leaving the other as n.

You can also combine these words. Here’s an example that uses each of the case words once, except endcase:

: collatz ( u -- )
    \ print the 3n+1 sequence starting at u until we reach 1
    case
        dup .
        1 of endof
        dup 1 and ?of 3 * 1+ contof
        2/
    next-case ;

This example keeps the current value of the sequence on the stack. If it is 1, the of triggers, drops the value, and leaves the case structure. For odd numbers, the ?of triggers, computes 3n+1, and starts the next iteration with contof. Otherwise, if the number is even, it is divided by 2, and the loop is restarted with next-case.

The case words are:

case ( compilation  – case-sys ; run-time  –  ) core-ext “case”

Start a case structure.

endcase ( compilation case-sys – ; run-time x –  ) core-ext “end-case”

Finish the case structure; drop x, and continue behind the endcase. Dropping x is useful in the original case construct (with only ofs), but you may have to supply an x in other cases (especially when using ?of).

next-case ( compilation case-sys – ; run-time –  ) gforth-1.0 “next-case”

Restart the case loop by jumping to the matching case. Note that next-case does not drop a cell, unlike endcase.

of ( compilation  – of-sys ; run-time x1 x2 – |x1  ) core-ext “of”

If x1=x2, continue (dropping both); otherwise, leave x1 on the stack and jump behind endof or contof.

?of ( compilation  – of-sys ; run-time  f –  ) gforth-1.0 “question-of”

If f is true, continue; otherwise, jump behind endof or contof.

endof ( compilation case-sys1 of-sys – case-sys2 ; run-time  –  ) core-ext “end-of”

Exit the enclosing case structure by jumping behind endcase/next-case.

contof ( compilation case-sys1 of-sys – case-sys2 ; run-time  –  ) gforth-1.0 “cont-of”

Restart the case loop by jumping to the enclosing case.

Internally, of-sys is an orig; and case-sys is a cell and some stack-depth information, 0 or more origs, and a dest.


Footnotes

(16)

The name next-case has a -, unlike the other case words, because VFX Forth has a next-case that works like Gforth’s next-case, but also contains a nextcase that drops a value; in VFX you need to pair next-case with begincase, however.