MikoScript 言語仕様

 制御の流れ(構造化系)

 プログラムの実行は、通常、上から下へ進みます。「制御構文」は、この制御の流れを
変えるのに使います。本章では、制御構文のうち、構造化プログラミング(制御の流れの
構造化)に寄与する構文を中心に扱います。
 本言語のこの種の制御構文の書き方は、基本的に、C言語に合わせています。これは、
多くのプログラマーがC言語系の構文に慣れ親しんでいる現状では、むやみに独自の構文
にしても、プログラミング時の感覚を混乱させるだけで、大したメリットは得られないと
判断したからです。とはいえ、本言語では、インタープリタとしての特性をフルに活かし
て、拡張しているところもあります。

●条件式
 「条件式」に依存する制御構文( if文, for文, while文等 )では、その条件式の評価
結果に応じて、制御の流れが分岐します。条件式には、任意の式が使えますが、その評価
結果は、特に不正演算でない限り、必ず、真か偽に帰結します。

 条件式において、その式の評価結果が明示的に真偽値になる場合、たとえば、比較演算
子や論理演算子を使った式では、当然、その結果がそのまま採用されます。一方、その式
の評価結果が明示的に真偽値にならない場合、たとえば、1, 0 以外の整数値、浮動小数
点数値、文字列、箱への参照などになる式では、その結果に対して「真偽判定」が行なわ
れ、その結果が、その条件式の評価値になります。

 真偽判定に関しては、関係各所で述べていますが、ここでまとめて以下に示します。
    ・数値は、整数値でも浮動小数点数値でも、その値が 0 なら偽、さもなくば真
    ・文字列は、文字が1字以上あれば真、1字もなければ(空なら)偽
    ・null は、常に偽
    ・箱は、その中身が評価対象で、中身が数値/文字列/空なら上記の判定に準拠し、
     それ以外なら真、但し、複合箱に関しては、その真偽判定を演算子関数で個別に
     定義できます。これに関しては、「演算子の多重定義」の章の
       ・後置演算子の多重定義
     の節で説明しています。

● if 文
 「if 文」は、真偽判定による2分岐を制御する構文で、書式は以下のようになります。
    if( 条件式 )
        文1
    else
        文2
 この「if 文」では、「条件式」の評価値が、真の場合に「文1」が実行され、偽の場
合に「文2」が実行されます。

 この書式で、else の部分は、以下のように省略できます。
    if( 条件式 )
        文1
この場合、条件式の評価値が、真の場合にのみ「文1」が実行され、偽の場合には「文1」
は実行されません。

 この書式で「文1」「文2」は、単文でも、波括弧 { ... } で囲った複文(ブロック
文)でもかまいません。また、単文の場合、複数の式をコンマで区切った
    式1,  式2,  式3 ;
のような「リスト式」の文も記述できます。これは、以降の制御構文においても同様です。

 「if 文」自身も「文」なので、上記書式の「文1」と「文2」に記述できますが、若
干注意が必要です。まず、「文1」に別の「if 文」を記述した場合、以下のようになり
ます。
    if( 条件式1 )
        if( 条件式2 )
            文21
        else
            文22
ところが、この構文と、この構文から else 部を省略して外側の if に対応する else 部
を記述した以下の構文との区別がつきません。
    if( 条件式1 )
        if( 条件式2 )
            文21
    else
        文2
本言語の仕様として、else は、直前の if に対応するものとして解釈されます。従って、
この場合、前者の方に解釈されます。後者の方に解釈させたい場合は、以下のように、波
括弧 { ... } で囲う必要があります。
    if( 条件式1 )
    {
        if( 条件式2 )
            文21
    }
    else
        文2
しかしながら、前者の場合でも、以下のように、波括弧 { ... } で囲った方が、明確に
なります。
    if( 条件式1 )
    {
        if( 条件式2 )
            文21
        else
            文22
    }
次に、「文2」に別の「if 文」を記述する場合ですが、以下のようになります。
    if( 条件式1 )
        文1
    else
        if( 条件式2 )
            文21
        else
            文22
この場合は、前述のような文法上の曖昧さはありません。むしろ、この場合は、多分岐の
制御用に活用できます。たとえば、
    if( 条件式1 )
        文1
    else if( 条件式2 )
        文2
    else if( 条件式3 )
        文3
    else
        文4
では、「条件式1」が真なら「文1」、「条件式2」が真なら「文2」、「条件式3」が
真なら「文3」、どの条件式も偽なら「文4」が、実行されます。

● switch 文
 「switch 文」は、多分岐を制御する構文で、書式は以下のようになります。
    switch( 式 )
    {
      case 定数式1:    文1
      case 定数式2:    文2
        ・
        ・
        ・
      default:          文0
    }
 「switch 文」では、まず「式」が評価されて、その結果に一致する「定数式n」が
あれば、その位置に対応する「文n」が実行されます。( ここで、n = 1,2, ... )
どの「定数式」にも一致しなければ、default に対応する「文0」が実行されます。

 「 case 定数式n: 」は、何個でも記述できますが、同じ値の定数式があってはいけま
せん。「 default: 」は、省略可能ですが、複数の記述はできません。これらの順序は、
任意です。

 「 default: 」が省略されていて、switch の「式」がどの定数式の値にも一致しない
場合は、何も実行されずに、制御は、switch ブロックの直後に移ります。

 「 case 定数式n: 」と「 default: 」は、文法的にはラベルの一種です。つまり、制
御の流れの分岐先を示すだけで、そこに制御が分岐した後は、特に効力はありません。そ
のため、たとえば、switch の「式」の値が「定数式1」になる場合、「文1」が実行さ
れますが、その後の制御の流れは「文2」に移行します。強制的に、switch のブロック
を抜けるには、「break 文」を実行する必要があります。
 また、通常のラベルと同様に、同一の文に対して、複数の「 case 定数式n: 」や
「 default: 」を割り当てることもできます。

 本言語では、「定数式n」の値は、整数、浮動小数点数、文字列、null のどれであっ
てもかまいません。例えば、以下のような switch 文も可能です。
    switch( v )
    {
      case 12:      print "整数値 12";      break;
      case 3.45:    print "実数値 3.45";    break;
      case "ABC":   print "文字列 ABC";     break;
      case null:    print "無効値 null";    break;
      default:      print "それ以外";       break;
    }
 本言語では、複数の case が同一の文に付く場合、その各定数式をコンマで区切って、
1つの case 内に記述できます。例えば、3つの定数式を1つの case 内にまとめて記述
する場合は、以下のようになります。
  case 定数式1, 定数式2, 定数式3:
次に、この例として、指定された西暦年の月の日数を求める関数を示します。
    function DaysPerMonth( year, month )
    {
        switch( month )
        {
          case 2:
            return (( year % 4 == 0 && year % 100 != 0 ) ||
                    ( year % 400 == 0 )) ?  29 : 28;
          case 4, 6, 9, 11:
            return 30;
          case 1, 3, 5, 7, 8, 10, 12:
            return 31;
        }
    }
 ちなみに、本言語では、switch の「式」の値と各 case の定数式の値を、上から順に
比較していくような実行の仕方ではないので、case が多数あっても、また、どの順位の 
case と一致する場合でも、分岐に要する時間はほぼ同じです。

● while 文
 「while 文」は、反復ループを制御する構文で、書式は以下の通りです。
    while( 条件式 )
        文
 この「while 文」では、「条件式」の評価値が真の間、「文」が反復して実行されます。
もっと厳密に言うと、まず、「条件式」が評価され、その結果が偽なら何も実行されませ
ん。つまり反復は行なわれません。真なら「文」が実行されます。次に再度、「条件式」
が評価されます。その結果が偽なら反復は終了します。真なら「文」が再度実行されます。
以降同様に「条件式」が偽になるまで、この反復が続きます。

 反復ループを途中で脱出するには、「break 文」か「quit 文」を使います。また、 途
中で次の反復ループに移行するには「continue 文」を使います。これらについては、後
述します。さらに「goto 文」を使えば、反復ループからの脱出も、反復ループの外から
内への突入も可能です。これについては、次章で説明します。なお、このようなループ制
御は、「for 文」「do-while 文」においても、同様です。

 「while 文」で「条件式」が常に真になるようにすると、無限ループになります。たと
えば、以下の通りです。
    while(1)
    {
        ・・・・・
    }
 このように、「while 文」の「条件式」が定数式の場合には、コンパイル時にその真偽
値を確定できるので、条件式を評価する中間コードはわざわざ生成されません。それに
よって、その分だけ中間コードが少なくなり、実行速度が速くなります。

● for 文
 「for 文」は、反復ループを制御する第2の構文で、書式は以下の通りです。
    for( 式1 ; 条件式 ; 式2 )
        文
 この「for 文」では、まず、「式1」が評価されます。次に「条件式」の評価値が真の
間、「文」と「式2」が反復して実行されます。もっと厳密に言うと、まず、「式1」が
評価されます。次に「条件式」が評価され、その結果が偽なら何も実行されません。つま
り反復は行なわれません。真なら「文」が実行されてから「式2」が評価されます。次に
再度、「条件式」が評価されます。その結果が偽なら反復は終了します。真なら再度「文」
が実行されてから「式2」が評価されます。以降同様に「条件式」が偽になるまで、この
反復が続きます。

 「for 文」は、以下の「while 文」と同じです。
    式1;
    while( 条件式 )
    {
        文
        式2;
    }
 次に、「for 文」の簡単な例を示します。この例は、1 〜 100 までの数字の合計をプ
リントします。
    sum = 0;
    for( i = 1 ; i <= 100 ; i++ )
    {
        sum += i;
    }
    print sum;
 「for 文」では、「式1」「条件式」「式2」は、省略可能です。「条件式」を省略す
ると、常に真と判断されて、無限ループになります。たとえば、以下の通りです。
    for( ;; )
    {
        ・・・・・
    }
 「for 文」の「式1」「条件式」「式2」は、「リスト式」にもできます。たとえば、
    for( A = 1, B = 2 ; A <= 100 ; A++, B-- )
    {
        ・・・・・
    }

● do-while 文
 「do-while 文」は、反復ループを制御する第3の構文で、書式は以下の通りです。
    do
        文
    while( 条件式 );
 この「do-while 文」では、まず「文」が実行されます。次に「条件式」が評価され、
その結果が偽ならそれで終わりです。真なら「文」が再度実行されます。次に「条件式」
が再度評価されて、その結果が偽なら反復は終了します。真なら「文」がまた実行されま
す。以降同様に「条件式」が偽になるまで、この反復が続きます。

 「do-while 文」は、「while 文」や「for 文」よりも、使用頻度は少ないと思われま
すが、条件式の結果如何に関わらず、少なくとも1回は「文」を実行する必要がある場合
に便利です。

● continue 文
 「continue 文」は、次の反復ループへの移行を行なう構文で、書式は以下の通りです。
        continue;
 「continue 文」を実行すると、制御の流れは、現在の反復ループを中断して、次の反
復ループに移行します。このとき、「while 文」と「do-while 文」では、「条件式」の
評価から始まり、「for 文」では、「式2」の評価が行なわれてから「条件式」の評価に
移ります。いずれの場合も「条件式」の評価結果が偽なら、反復ループは終了します。真
なら反復ループが継続します。

 「continue 文」の対象となるは、その「continue 文」を含む最も内側の反復ループだ
けです。つまり、反復ループの中で反復ループを実行している時に「continue 文」を実
行すると、その対象になるのは内側のループで、外側のループには関係しません。

 「continue 文」は、反復ループ内でしか使えません。もし使えれば、コンパイル時に
エラーになります。

● break 文
 「break 文」は、反復ループを無条件に終了する構文で、書式は以下の通りです。
        break;
 「break 文」を実行すると、制御の流れは、反復ループから抜けて、その反復ループの
直後に移行します。

 「break 文」の対象となるは、その「break 文」を含む最も内側の反復ループだけです。
つまり、反復ループの中で反復ループを実行している時に「break 文」を実行すると、そ
の対象になるのは内側のループで、外側のループには関係しません。

 「break 文」は、「switch 文」のブロック内でも使えますが、その場合は、機能が異
なります。これについては後述します。「break 文」は、それ以外の所では使えません。
もし使えれば、コンパイル時にエラーになります。

● quit 文
 本言語では、「quit 文」も、反復ループを無条件に終了する構文で、書式は以下の通
りです。
        quit;
 「quit 文」を実行すると、制御の流れは、反復ループから抜けて、その反復ループの
直後に移行します。

 「quit 文」は、「continue 文」がそうであるように、あくまでも、反復ループが対象
です。「break 文」のように「switch 文」のブロック内での特別な機能はありません。
これは、むしろ、反復ループ内にある「switch 文」のブロック内から、反復ループを抜
け出す時に便利です。

 「quit 文」の対象となるは、その「quit 文」を含む最も内側の反復ループだけです。
つまり、反復ループの中で反復ループを実行している時に「quit 文」を実行すると、そ
の対象になるのは内側のループで、外側のループには関係しません。

 「quit 文」は、反復ループ内でしか使えません。もし使えれば、コンパイル時にエ
ラーになります。

● do-with 式
 本言語では、「do-with 式」で、その直後の波括弧 { ... } で囲われた部分をブロッ
ク関数として、任意の関数に渡すことができます。「do-with 式」の書式は、以下の通り
です。
    do 関数コール with 引数並び
    {
        ブロック関数の実行文
    }
 ここで、「関数コール」で呼び出される関数に、with 以降で定義されたブロック関数
が渡されます。with の直後は、このブロック関数の引数で、波括弧 { ... } の中が、ブ
ロック関数の本体になります。

 以下に簡単な例を挙げます。この例では、「関数コール」の対象は、以下の関数です。
    function Times( N, F )  // 関数 F を N 回コールする
    {
        for( i = 1 ; i <= N ; i++ )
            F( i );     // 関数 F の引数は何回目のコールかを示す
        return N;       // 反復回数を返す
    }
この関数を使って、1 〜 100 までの数値の合計を求める処理を書くと、以下のようにな
ります。
    sum = 0;
    do Times( 100 ) with i {  sum += i;  };
    print sum;
 ここで、Times( 100 ) の関数コールの引数には関数の指定がありませんが、「do-with
 式」では、暗黙で、「関数コール」の最後の引数でブロック関数を渡します。
 「do-with 式」は、文ではないので、文にするには、上例のように、最後にセミコロン
を付ける必要があります。
 「do-with 式」の式値は、「関数コール」で呼び出した関数の返値になります。上例で
は、この式値は使っていませんが、上例の do Times( 100 ) with ... の式値は 100 に
なります。なお、「関数コール」で呼び出した関数の返値がなければ、「do-with 式」の
式値は、null になります。
 「do-with 式」のブロック関数内のローカルスコープは、それよりも外側にあるローカ
ルスコープを引き継ぎます。そのため、上例の sum は、ブロック関数外のローカルス
コープにありますが、ブロック関数内からでもアクセスできます。これが、一般の関数に
はない、ブロック関数の特徴の1つです。
 一方、ブロック関数内のローカルスコープは、ブロック関数外からは見えません。とい
うよりも、そのブロック関数を出る時に破棄されます。たとえば、上例の i は、ブロッ
ク関数外からはアクセスできません。

 次に、同じ Times 関数を使って、「do-with 式」を入れ子にする例を示します。
    do Times( 5 ) with i
    {
        do Times( 6 ) with j
        {
            print i, j;
        };
    };
この例でも、外側のブロック関数のローカルスコープを、内側のブロック関数のローカル
スコープで引き継ぐので、外側のブロック関数内の変数 i を、内側のブロック関数から
アクセスできます。

 次に、対象の箱 A 内の箱を比較関数 f の結果をもとに並べ替える、システム組み込み
のリレー型関数 'sort( A, f ) を使った例を示します。まず、対象の箱 A には、
    A = { 25, 84, -10, 36, -97 };
というような整数データ列が入っているとします。
これを昇順に並べ替えるには、以下のようにします。
    do A'sort with a, b { return a - b; };
また、降順に並べ替えるには、以下のようにします。
    do A'sort with a, b { return b - a; };
これらの結果を見るには、対象の箱 A 内の箱を順に指定関数 f へ列挙する、システム組
み込みのリレー型関数 'each( A, f ) を使って、以下のようにします。
    do A'each with x {  print x, -;  };
    print;
このプリント結果は、示すまでもありませんが、昇順と降順でそれぞれ、以下の通りです。
    -97, -10, 25, 36, 84, 
    84, 36, 25, -10, -97, 

 このように、「do-with 式」を使えば、関数をわざわざ別に定義しなくても、その場で
書いたものを直接渡せるので便利です。

 「do-with 式」では、複数個のブロック関数を使うこともできます。たとえば、3個使
う場合、以下のような書式になります。
    do  関数コール
    with  引数並び1  {  ブロック関数の実行文1  }
    with  引数並び2  {  ブロック関数の実行文2  }
    with  引数並び3  {  ブロック関数の実行文3  }
 この各ブロック関数は、この順に暗黙で、「関数コール」で呼び出される関数の最後の
3個の引数で渡されます。

●'times による反復制御
 本言語は、'times という反復制御を行なうリレー型関数を、予め組み込んでいます。
この関数は、do-with 式 の説明で例として用いた Times という関数と殆ど同じです。
この関数は、具体的には、次のような処理を行ないます。
    function 'times( n, f )   // 関数 f を n 回コールする
    {
        for( i = 0 ; i < n ; )
        {
            if( f( i++ ) == -1 )   // 中断する?
                break;
        }
        return i;       // 返値は指定関数のコール回数
    }
この関数は、do-with 式で、例えば、次のようにして使います。
    do 5'times with i {  print i, -;  };
ちなみに、これを実行すると、「0, 1, 2, 3, 4, 」がプリントされます。

●'each による列挙制御
 本言語は、'each という列挙制御を行なうリレー型関数を、予め組み込んでいます。
この関数は、具体的には、次のような処理を行ないます。
    function 'each( T, f )  // 対象の箱内(直下)の各箱を順に指定関数へ列挙する
    {
        n = 0;
        for( p := T'first ; p'ref? ; p := T'next )
        {
            n++;
            if( f( p ) == -1 )  // 中断?
                return -n;      // 中断時、返値は、指定関数のコール回数の負値
        }
        return n;   // 返値は、指定関数のコール回数(中断なし時)
    }
この関数は、do-with 式で、例えば、次のようにして使います。
    A = { 11, 22, 33, 44, 55  };
    do A'each with p {  print p, -;  };
ちなみに、これを実行すると、「11, 22, 33, 44, 55, 」がプリントされます。
次は、配列内に特定の要素があるかどうかを検索する例です。
    B = { 12, 34, 56, 78, 90  };
    i = do B'each with p {  if( p == 56 ) return -1;  };
    if( i < 0 )
        print "Found: ": B[ ~i ];
    else
        print "Not found!";
ちなみに、これを実行すると、「Found: 56」とプリントされます。
なお、2の補数表現の整数値の場合、-i == ~i + 1 なので、~i == -i - 1 になります。

 ところで、'each による列挙以外にも、いろいろな列挙の仕方があります。たとえば、
逆順に列挙を行なう場合もあるかもしれません。それには次のような関数を用意すれば
できます。
    function 'RevEach( T, f )  // 対象の箱内(直下)の各箱を逆順に指定関数へ列挙する
    {
        n = 0;
        for( p := T'last ; p'ref? ; p := T'prev )
        {
            n++;
            if( f( p ) == -1 )  // 中断?
                return -n;      // 中断時、返値は、指定関数のコール回数の負値
        }
        return n;   // 返値は、指定関数のコール回数(中断なし時)
    }
この関数を使って、下記を実行すると、「55, 44, 33, 22, 11, 」とプリントされます。
    A = { 11, 22, 33, 44, 55  };
    do A'RevEach with p {  print p, -;  };

●'enum による列挙制御
 本言語は、'enum という列挙制御を行なうリレー型関数を、予め組み込んでいます。
この関数は、'each と似ていますが、対象の箱内に「箱を入れる箱」があれば、それに
対しても再帰的に走査します。具体的には、次のような処理を行なっています。
    function 'enum( T, f )  // 対象箱内の箱を順に指定関数へ列挙する(ツリー走査)
    {
        n = 0;
        for( p := T'first ; p'ref? ; p := T'next )
        {
            n++;
            if( f( p ) == -1 )      // 中断?
                return -n;          // 中断時、返値は、指定関数のコール回数の負値
            if( p'cbox? )           //「箱を入れる箱」?
            {
                k = p'enum( f );    // 再帰呼び出し!
                if( k < 0 )         // 中断?
                    return -n + k;  // 中断時、返値は、指定関数のコール回数の負値
                n += k;
            }
        }
        return n;   // 返値は、指定関数のコール回数(中断なし時)
    }
この関数は、do-with 式で、例えば、次のようにして使います。
    T = {  1, { 21, 22 }, { 31, { 321, 322, 323 }, 33 }, 4  };
    do T'enum with p
    {
        if( ! p'cbox? )
            print " "'rep( p'level ) : p;
    };
    print;
ちなみに、これを実行すると、次のようにプリントされます。
    1
     21
     22
     31
      321
      322
      323
     33
    4
次は、木構造の連想配列内に特定の要素があるかどうかを検索する例です。
    T = {  1, { 21, 22 }, { 31, { 321, 322, 323 }, 33 }, 4  };
    V = null;
    do T'enum with p { if( p == 321 ) { [ V'ref ] = p;  return -1; } };
    if( V != null )
        print "Found: ": V;
    else
        print "Not found!";
ちなみに、これを実行すると、「Found: 321」とプリントされます。