不定ループ(Indefinite Loop)とは、繰り返し回数の限界が予め決まっていないループである。ワードBEGINで始まる。繰り返しをやめるか続けるかを判定するには、何らかのフラグを用いるが、その判定の仕方の違いに対応していくつかのバリエーションがある。以下のループも、全て、ワード定義内でしか利用できない。

不定ループは、指標 I を利用できない。もしも繰り返し回数を数える必要があるなら、変数を定義するなどして、明示的にカウントしなければならない。他方で、リターンスタックの配備がないため、途中でEXITする場合でも、UNLOOP等の後処理ワードは必要ない。

BEGIN UNTIL


BEGINで始まり、UNTILで終わるループでは、まずBEGIN以降のコードが実行される。そのコードは最後にUNTILに与えるためにスタック上にフラグを残さなければならない。UNTILはこのフラグをスタックから取り、0であればBEGINに実行を戻し、0でないときにはそのままループを抜ける。UNTILはフラグが"真になるまで"ループを繰り返すという意味であろうが、0でない値はここではtrueとみなされる。
コンパイルの際のスタック効果は:
BEGIN  ( C: -- dest ) \ コントロールフロー(CF)スタックにBEGINに戻るコードを生成するための手掛かり(dest)を置く
UNTIL  ( C: dest -- )  \ CFスタックの値を取って条件に応じたループをコンパイルする
実行の際のスタック効果は:
BEGIN ( -- )  \ 繰り返しの開始点を標す
UNTIL ( b -- ) \ b=0ならBEGINに戻し、そうでないなら、抜けて次に移る。
概形としては次のようになる。
... BEGIN [処理]  [判定 -- フラグ] UNTIL  ...
Mops拡張として、NUNTILも定義されている。UNTILの代わりにこれを使った場合は条件が逆になり、フラグが0でないなら繰り返し、0のときにはループを抜ける。
NUNTIL ( b -- ) \ bが非0ならBEGINに戻し、b=0のときは抜けて次に移る。

BEGIN WHILE REPEAT


やや複雑なフローをもつBEGIN WHILE REPEATループは、BEGINとWHILEの間にループ実行の可否を判定するためのコードがあると想定されている。そのコードは、真(非0)または偽(=0)のフラグをスタックに残さなければならない。WHILEはスタックからフラグを取り、それが非0であれば実行がそのまま進み、REPEATのところで、再びBEGINに実行が折り返される。フラグが0であったときは、実行はREPEATの直後までジャンプし、ループを抜けることになる。WHILEはしたがって、フラグが真である間はループを繰り返す、という意味である。
まず、コンパイル時のスタック効果は:
WHILE  ( C: dest -- orig dest ) \ 新たにループ終了条件分岐の手掛かりをCFスタックに格納するが、BEGINの指標をトップに保つ
REPEAT ( C: orig dest -- )  \ CFスタックの値を使って折り返しと脱出のコードをコンパイルする。
そして、実行時のスタック効果は、
WHILE  ( b -- )  \ bが非0なら次のREPEATまで実行してループ、bが0なら次のREPEATの直後までジャンプ
REPEAT ( -- )  \ 実行をBEGINまで折り返す。
BEGIN WHILE REPEATは、この順序で使用される。つまり、概形としては
... BEGIN [処理1] [判定コード -- フラグ]  WHILE  [処理2]  REPEAT ...
のようになる。[処理1]は特に必要的ではない。

ところで、WHILE-REPEATは、本質的にはIF-THENと同じであり、条件成就のときにはBEGINまで折り返すというところが異なっているに過ぎない。そのことを利用してELSE THENと組み合わせた、複合的な使用法にも耐えることになっている。
例えば、
... BEGIN  [判定1] WHILE  [判定2] WHILE  [処理1] REPEAT [処理2] ELSE [処理3] THEN [処理4] ...
のようなコードもコンパイルできる。フローを説明すると、
  • [判定1]の結果が0であった場合、実行はELSEまで飛び、[処理3]に移り、続けて[処理4]を行う。
  • [判定1]の結果が非0であった場合、そのまま続行され、[判定2]に移る。
    • [判定2]の結果が0であった場合、実行はREPEATの直後まで飛び、[処理2]を行い、[処理3]は飛ばして[処理4]に移る。
    • [判定2]の結果が非0であった場合、[処理1]が行われ、BEGINに戻る。
構造的に表記すれば、
BEGIN
[判定1]
WHILE
    [判定2]
    WHILE
         [処理1]
    REPEAT
    [処理2]
ELSE
    [処理3]
THEN
[処理4]
となっている。

また、次のような組み合わせも可能とされている。
... BEGIN [判定1] WHILE [処理1] [判定2] UNTIL [処理2] ELSE [処理3] THEN [処理4]
これも説明すれば、
  • [判定1]が0であるとき、処理はELSEまで飛び、[処理3]、そして[処理4]が実行される。
  • [判定1]が非0であるとき、[処理1]、[判定2]と実行される
    • [判定2]が0であるとき、実行はBEGINに戻され、ループを繰り返す
    • [判定2]が非0であるとき、ループを抜けて[処理2]が実行され、THENに飛んで[処理4]が実行される。

いくらでも組み合わせられるというわけではなく、コンパイル時のコントロールフロースタック上の効果を考える必要がある。
敢えてコンパイル時のCFスタック効果を上に書いたのは、そのためである。ちなみに、IF ELSE THENについても書くと、
IF   ( C: -- orig )
ELSE  ( C: orig1 -- orig2 )
THEN ( C: orig -- )
である。しかし、ループも含めて、こういった内部実装仕様を言語の標準規格として決めるというのは、いかがなものかとも思う。
制限はあるものの、かなり複雑な分岐を含むループが可能であることはわかるであろう。しかし、見た目にも、慣れれば何とかなるとしても、一見して理解しやすいコードになるとは決していえないし、実際にもあまり有用な場面はないように思われる。

Mops拡張としてNWHILEが定義されている。WHILEの条件反転版である。WHILEの代わりにNWHILEを置けば、判定値が0のときにループを続行する形になる。
NWHILE  ( b -- )  \ b=0ならば次のREPEATまで実行してループを続行。bが非0なら次のREPEATの直後までジャンプしてループを抜ける

BEGIN AGAIN


終わりにAGAINを持つものは無限ループである。脱出するにはEXITを用いてワードごと抜ける他はない。
AGAIN ( -- )  \ 直前のBEGINまで返す
概形としては、
.... BEGIN   [処理1]  [判定] IF EXIT THEN [処理2] AGAIN  ;
のようになる。AGAINの後に何かコードを書いても実行されることはない。条件判定と脱出の個所は複数あっても良い。

なお、MopsではすべてのループにLEAVEが利用できるように拡張されている。したがって、AGAINについても、LEAVEを用いて、AGAINの直後にコントロールを飛ばしループを抜ける方法が利用できる。



最終更新:2013年10月02日 10:16