MikoScript 言語仕様

 制御の流れ(ジャンプ系)

 プログラムの制御の流れは、ラベルへジャンプすることによっても変えることができま
す。ラベルジャンプは、制御の流れの構造化を乱すことになるので、乱用は禁物ですが、
本言語では、以下の理由で、ラベルジャンプを積極的に採用しています。

    ・制御の流れは、必ずしも構造化に適していない
    ・反復ループの深いネストの中から一気に脱出できる
    ・エラー対応処理を本来の処理と容易に分離できる
    ・インデントが深くなりすぎる場合を解消できる
    ・関数コールは通常、コール側のローカルスコープを引き継げない
    ・関数コールより、call文の方がはるかに速い(ローカルスコープも引き継ぐ)
    ・ラベルジャンプの方がプログラムがかえって見やすくなる場合がある
    ・プログラムの自動生成では、ラベルジャンプを使う方が容易になる場合が多い

●ラベル
 ラベルは、「文」に付ける名札で、制御の流れの移行先になれます。ラベルの書式は、
以下の通りです。
    ラベル名:
 この「ラベル名」には、「字句要素」の章で述べた「識別名」と同じ制約があります。

 複数の異なるラベルを同一の文に対応させることはできます。しかし、対応する文の無
いラベルは、記述できません。もし、記述すれば、コンパイル時にエラーになります。た
だし、空文(セミコロンだけの文)に、ラベルを付けることはできます。

 ラベルは、ある特定のブロック内では、設定できません。これについては、本章の最後
の節「注意事項」で説明します。

●連想ラベル
 本言語では、単一の識別名のラベル名だけではなく、「連想ラベル」が使えます。この
基本的な書式は、以下のようになります。
    ラベル名[ インデックス ]:
 この連想ラベルの表記は、連想配列の表記と同じですが、インデックスには定数式しか
使えません。以下に、連想ラベルの例を挙げます。
    A[1]:
    L[ "ABC" ]:
    XYZ[ -5+6*7, "いろは", 3.14 ]:
 連想ラベルは、連想配列と同様に、ラベル名を省略して、[ インデックス ] だけでも
かまいません。以下に、その例を示します。
    [12]:
    ["春"]:
 また、連想ラベルは、連想配列と同様に、[ インデックス ] を複数連ねて多次元にも
できます。以下に、その例を示します。
    Entry[3][4]:
    [ "Lucky", 7 ][2][3][2]:

● goto 文
 「goto 文」は、以下の書式になります。
    goto 行先ラベル;
 「goto 文」を実行すると、制御の流れは無条件に「行先ラベル」に対応する文へ移行
します。「行先ラベル」は、通常のラベルか、または、連想ラベルを特定する表記になり
ます。この連想ラベルの表記では、インデックスに変数が使えます。
 「goto 文」の「行先ラベル」には、「固定ラベル」と「可変ラベル」があります。
「固定ラベル」は、通常のラベルか、または、連想ラベルを特定する表記の引数並びに定
数式を使った場合です。「可変ラベル」は、連想ラベルを特定する表記の引数並びに変数
を使った場合です。

 以下に、連想ラベルに分岐する例を示します。
    function F( s )
    {
        goto [s];
      ["春"]:   print "春だ!";  return;
      ["夏"]:   print "夏だ!";  return;
      ["秋"]:   print "秋だ!";  return;
    }
 この関数を、F("春") で呼び出せば、「春だ!」 がプリントされます。また、F("夏")
 と F("秋") では、それぞれ、「夏だ!」と「秋だ!」がプリントされます。

 「goto 文」の「行先ラベル」に対応するラベルは、「goto 文」を含む関数内にないと
いけません。たとえば、先程の関数 F を使って、
    F("冬");
    ["冬"]:  print "冬だ!";    return;
を実行してもエラーになります。というよりも、この場合は、例外が発生します。例外に
ついては、「例外処理」の章で説明します。

 「行先ラベル」が「固定ラベル」の場合、コンパイル時にその行き先を確定できるので、
もしなければ、コンパイルエラーが出ます。「goto 文」の実行で、例外が発生するのは、
「行先ラベル」が「可変ラベル」でその行き先のラベルが見つからない場合です。

 基本的に、「goto 文」を使えば、その関数内でラベルが設定できる所であれば、どこ
にでも行けます。

 「goto 文」を実行しても、勿論、ローカルスコープは変わりません。これが重要な意
味を持つ場合があります。たとえば、「goto 文」の使用を回避するために、一部のルー
チンを関数にした場合、関連するローカル変数を引数で渡す必要があります。しかし、こ
れが多数あると、オーバーヘッドが増え、また、かっこの悪いプログラムになってしまい
ます。このような場合、わざわざ、「goto 文」の使用を避ける必要はありません。

 最後に、「goto 文」を使った方が、使わないよりも、便利で、見やすく、さらに、実
行ステップが減り、実行が速くなる場合を示します。

(1)反復ループから脱出する場合
    RETRY:
    for( ... )
    {
        for( ... )
        {
            for( ... )
            {
                if( ... )   // 中断要?
                    goto EXIT;
                if( ... )   // 最初からやり直し?
                    goto RETRY;
            }
        }
    }
    EXIT:   ;
(2)反復ループ内へ突入する場合
    goto ENTRY;     // 初回だけ、処理1を飛ばす
    do {
        処理1
      ENTRY:
        処理2
    }
    while( ... );
(3)エラーチェックの結果に応じてエラー処理する場合
    処理1
    if( エラー )  {
        エラー処理1
        goto ERR_FINISH;
    }
    処理2
    if( エラー )  {
        エラー処理2
        goto ERR_FINISH;
    }
    処理3
    if( エラー )  {
        エラー処理3
        goto ERR_FINISH;
    }
    正常終了処理
    return;
  ERR_FINISH:
    異常終了処理
    return;
 ちなみに、これを「goto 文」なしに書くと、以下のように、エラーチェックのたびに、
本来の処理のインデントが深くなっていってしまいます。また、余分なフラグが必要にな
ります。
    エラーフラグクリア
    処理1
    if( エラー )  {
        エラー処理1
        エラーフラグセット
    }
    else  {
        処理2
        if( エラー )
        {
            エラー処理2
            エラーフラグセット
        }
        else  {
            処理3
            if( エラー )
            {
                エラー処理3
                エラーフラグセット
            }
        }
    }
    if( エラーフラグ )
        異常終了処理
    else
        正常終了処理
 また、これを try 〜 throw 〜 catch で書けば、以下のようにすっきりした形になり
ます。しかし、この場合、本来の処理とエラー処理を必ず分離する必要があります。エ
ラー処理が僅かなら、エラーチェックの直後に書く方が便利で分かり易くなります。
    try {
        処理1
        if( エラー )  throw エラー1;
        処理2
        if( エラー )  throw エラー2;
        処理3
        if( エラー )  throw エラー2;
        正常終了処理
    }
    catch( エラー1 ) {  エラー処理1  }
    catch( エラー2 ) {  エラー処理2  }
    catch( エラー3 ) {  エラー処理3  }
 この処理形態は、「goto 文」を使っても書けます。次に示すのはその場合です。

(4)本来の処理とエラー処理を分離する場合
    処理1
    if( エラー )    goto ERR_1;
    処理2
    if( エラー )    goto ERR_2;
    処理3
    if( エラー )    goto ERR_3;
    return;

    ERR_1:  エラー処理1
    ERR_2:  エラー処理2
    ERR_3:  エラー処理3
 なお、このような処理の場合、現実行中の関数内だけのジャンプなら「goto 文」でも
充分ですが、後述の「warp 文」を使えば、コール先の関数でエラーが発生した時にでも、
コール元のエラー処理部に一気にジャンプすることができます。

(5)対応する if と else が離れ過ぎるのを解消する場合
    if( ... ) {
        処理1      // この部分がたとえば 200 行以上になる
    }
    else if( ... ) {
        処理2
    }
    ・・・・・・
 このように、対応する if と else が離れ過ぎて、プログラムが見にくくなる場合、
「goto 文」を使って以下のようにするのも1案です。
    if( ... )  goto TASK_1;
    if( ... )  goto TASK_2;
    ・・・・・・
    TASK_1:
        処理1
    TASK_2:
        処理2
(6)深くなりすぎるインデントを回避する場合
    if( ... ) {
        if( ... ) {
            if( ... ) {
                // ここでまだまだインデントが深くなる
                goto TASK_1;    // 一旦外に出る
            }
        }
    }
    ・・・・・・

    TASK_1:
        処理1
(7)インデント内に飛び込む場合
    if( ... ) {
        if( ... ) {
            if( ... ) {
              ENTRY_1:
                処理1
            }
        }
    }
    if( ... )   // また処理1に戻る必要あり?
        goto ENTRY_1;

● call 文と back 文
 「call 文」と「back 文」は、それぞれ、サブルーチンの呼び出しとサブルーチンから
の復帰を行なう構文です。
 「call 文」は、以下の書式になります。ここで、「行先ラベル」は、「goto 文」と同
様です。
    call 行先ラベル;
 「call 文」は、「goto 文」と同様に、「行先ラベル」にジャンプしますが、その際に、
「戻る場所(当「call 文」の直後)」を、専用のスタックに保存します。「call 文」の
「行先ラベル」は通常、サブルーチンのエントリーラベルになります。

 「back 文」は、以下の書式になります。
    back;
 「back 文」を実行すると、「call 文」の実行の際に保存された「戻る場所」を専用の
スタックから取り出し、そこに制御を移行します。つまり、サブルーチンから復帰(リ
ターン)して、そのサブルーチンを呼び出した「call 文」の直後に、制御を戻します。

 サブルーチンの呼び出し(コール)と復帰(リターン)の例を以下に示します。
    i = 0;
    call SUBR;  // サブルーチン呼び出し
    print ++i;  // 2 がプリントされる
    return;

  SUBR:
    print ++i;  // 1 がプリントされる
    back;       // 復帰
 サブルーチンでは、ローカルスコープは、変わりません。つまり、ローカルスコープ内
の変数は、サブルーチン内でもそのままアクセスできます。一方、関数のように、引数の
受け渡しはできません。また、返値もありません。

 サブルーチンコールは、関数コールよりも、遥かに高速です。そのため、特に関数にし
なくても良い場合や、関数コールのオーバーヘッドが問題になる場合は、サブルーチンの
方が効用があります。

 call は、goto とは違って、「行先ラベル」が、現実行中の関数内で見つからない場合
には、現実行中の関数が属するモジュール内の暗黙のメイン関数内を検索します。そこで
見つかれば、それがコール先になります。この時も、ローカルスコープは、変わりません。
一方、そこでも見つからなければ、例外が発生します。この例外については、「例外処理」
の章で説明します。
 call では、「行先ラベル」がこのように検索されるので、汎用性のあるサブルーチン
をまとめて、同モジュールの暗黙のメイン関数内に置いておくこともできます。

 以下に、「行先ラベル」がこのような場合の例を示します。
  // ここは、モジュールの暗黙のメイン関数内とする
    x = 1;
    Func();     // 関数を呼び出す(その中で以下のサブルーチンが呼ばれる)
    print x;    // 1 がプリントされる
    return;

  Subr:         // サブルーチンのエントリー
    print x;    // 3 がプリントされる
    back;       // 復帰

    function Func()
    {
        x = 3;
        call Subr;  // メイン関数内のサブルーチンの呼び出し
        // このサブルーチン実行中も、ローカルスコープは、この関数の
        // ローカルスコープのままなので、この関数内の変数 x は、
        // サブルーチン内でもアクセスできる
    }
 サブルーチン内で、別のサブルーチンを呼ぶことも、また、自分自身を再帰的に呼ぶ
こともできます。サブルーチンのネストの深さの最大は現状 16384 です。この制限は、
実用上特に支障ない程度で、かつ、サブルーチンのネストが際限なく深くなっていって
しまうようなプログラムミスを検知するために設けています。

● call 式とその返値
 上述の「call 文」は、あくまで「文」なので、その値はありません。つまり、その
値を使う構文が書けません。一方、ここで述べる「call 式」は、「back 文」が返した
値を使うことができます。
 「call 式」の書式は、ちょっと変わっていますが、以下のようになります。ここで、
「行先ラベル」は、「call 文」の場合と同じです。
    @ . 行先ラベル
 「back 文」は、値を返す場合、以下の書式になります。ここの「式」の評価値が、
「call 式」の値になります。
    back 式;
 「back 文」は、複数の値を返すこともできます。その場合は、以下の書式のように、
各「式」をコンマで区切って丸括弧で囲う必要があります。
    back ( 式, 式, ・・・ 式 );
 「call 式」と値を返す「back 文」を使った例を以下に示します。
    ( x, y ) = ( 1, 2 );
    N = 0;
    print N, @.Subr1;           // ⇒  0, 123
    print N, @.Subr2;           // ⇒  1, 12, -34
    print 10 * @.Subr1 + 4;     // ⇒  1234
    Func( @.Subr1, @.Subr2 );   // ⇒  123, 12, -34
    return;

  Subr1:
    back 100 * x + 10 * y + 3;

  Subr2:
    N++;
    back ( 12, -34 );

  function  Func( a, b, c )  {  print a, b, c;  }
 なお、「call 式」に対応する「back 文」が値を何も返さかった場合、その「call 式」
の値は、null になります。

● warp 文
 「warp」は、現関数内だけでなく、現関数を呼び出した関数や、その関数を呼び出した
関数等、現関数までの一連の呼び出し元の関数内にあるラベルへもジャンプできます。
 「warp 文」の書式は、以下のようになります。
    warp 行先ラベル;
ここで、「行先ラベル」には、ワープ先を指定します。この「行先ラベル」の表記は、前
述の「goto 文」と同様です。しかし、「warp 文」での「行先ラベル」は、ワープ先の
「固定ラベル」の指定だけでなく、ワープ先のラベル名を格納する「ラベル変数」の名前
の指定も兼ねています。つまり、ワープ先の検索対象は、「行先ラベル」の「固定ラベル」
と、「行先ラベル」の名前の「ラベル変数」の中身の文字列のラベル名の両方になります。
なお、「ラベル変数」というのは、実質的には文字列を格納する箱ですが、その文字列が
ラベル名として使われる変数の用途上の呼び名です。

 ワープ先の検索は、現関数から始まって、その呼び出し元の各関数を逆順に遡っていき
ます。その各関数内の検索においては、まず「ラベル変数」の「行先ラベル」の方が先に
検索されて、それが見つからなければ、「固定ラベル」の「行先ラベル」が検索されます。
つまり、「ラベル変数」の検索の方が、「固定ラベル」の検索よりも優先されます。
 「warp 文」の「行先ラベル」で指定された名前の「ラベル変数」は、次の各スコープ
内にあるものが対象になります。以下の番号は、その優先順を示します。
  (1)検索対象の各関数のローカルスコープ
  (2)スレッドローカルスコープ
  (3)モジュールローカルスコープ
  (4)グローバルスコープ
(1)のスコープ内にある「ラベル変数」の示すワープ先は、各関数内の検索時に、その
関数内とその関数の呼び出し元の各関数内で検索されます。(2)〜(4)のスコープ内
にある「ラベル変数」の示すワープ先は、各関数で共通になるので、各関数内の検索時に、
その関数内でのみ検索されます。また、「warp 文」の「行先ラベル」で指定された名前
の「固定ラベル」も、各関数内の検索時に、その関数内でのみ検索されます。
 これらの各検索において、最初に見つかったワープ先へ制御が移行します。もし、どこ
にもワープ先が見つからなければ、現スレッドは異常終了します。この場合、例外は発生
しません。

 例えば、
    warp Target; 
の実行では、まず、現関数内において、
    Target(関数ローカルスコープ内), $Target, ^Target, ::Target
の各ラベル変数があれば、それの示すワープ先がこの順に、現関数内とその呼び出し元の
各関数内で検索されます。もし見つからなければ、次に、Target という固定ラベルが、
現関数内で検索されます。もし見つからなければ、今度は、現関数の呼び出し元の関数内
において、その関数ローカルスコープ内の Target のラベル変数の示すワープ先が、その
関数とその呼び出し元の各関数内で検索されます。もし見つからなければ、次に、Target
という固定ラベルが、その関数内で検索されます。もしそれも見つからなければ、今度は、
その関数の呼び出し元の関数が同様に検索されます。これが呼び出し元の関数がなくなる
まで繰り返されます。最後までワープ先が見つからなければ、現実行スレッドはエラーで
終了します。さもなくて、このような順に行なった検索でワープ先が初めて見つかれば、
制御はそこへ移行します。

 以下に、まず、ワープ先が「固定ラベル」の場合の例を示します。
    F1();
    print "ワープは発生しませんでした!";
    return;

  WarpEntry:
    print "ワープでここに来ました!";
    return;

    function F1()   {   F2();   }
    function F2()   {  warp WarpEntry;  // ワープ起動  }
 これを実行すると、まず、関数 F1 が呼ばれます。その中で、関数 F2 が呼ばれます。
関数 F2 でワープを起動しますが、そのワープ先の WarpEntry は、関数 F2 内にはない
ので、関数 F2 を呼び出した関数 F1 内で検索されますが、そこにもないので、さらに、
その呼び出し元の暗黙のメイン関数内で検索されます。そこには、そのワープ先のラベル
があるので、現実行中の関数 F1 とその呼び出し元の関数 F2 を抜けて、そのワープ先の
ラベルへ制御を移行します。そこで「ワープでここに来ました!」とプリントされます。
もし、関数 F2 内でワープを起動しなければ、関数 F2 から関数 F1 へ復帰し、さらに
その呼び出し元まで復帰するので、「ワープは発生しませんでした!」とプリントされて
いるところです。

 次に、ワープ先が「ラベル変数」になる場合の例を示します、ここでは、WarpTarget 
というラベル変数に、ワープ先のラベル名を格納して、そのラベル変数名が、「warp 文」
の「行先ラベル」の指定になっています。
    WarpTarget = :WarpEntry;
    F1();
    return;      // ここには来ない

  WarpEntry:
    print "ワープでここに来ました!";
    return;

    function F1()   {   F2();   }
    function F2()   {  warp WarpTarget;  // ワープ起動  }
この実行結果は、前例と同様に、「ワープでここに来ました!」とプリントされますが、
その過程は次のようになります。関数 F2 内でワープが起動された時、そのワープ先の 
WarpTarget は、関数 F2 内にはないので、関数 F2 を呼び出した関数 F1 内で検索され
ますが、そこにもないので、さらにその呼び出し元の暗黙のメイン関数内で検索されます。
そのローカルスコープ内には、ラベル変数 WarpTarget があって、それの示すワープ先が
見つかるので、制御はそこへ移行します。

 ワープ先の固定ラベルは、プログラムの実行中には勿論変更できませんが、ワープ先の
ラベル変数の内容は、プログラムの実行中に変更することができます。また、ラベル変数
を削除する、或いは、ラベル変数にラベル名に該当しないデータを代入することによって、
ラベル変数によるワープを、無効にすることもできます。

 ワープの起動で、各関数を抜ける時、その関数のローカルスコープは、破棄されます。
その際、そのローカルスコープ直系の全ての箱が破棄されます。その中に、デストラクタ
が定義されているインスタンスがあれば、そのインスタンスのデストラクタが呼ばれます。
その呼ばれる順は、そのローカルスコープ内でインスタンスが生成された順とちょうど逆
の順になります。つまり、最後に生成されたインスタンスのデストラクタが最初に呼ばれ、
その前に生成されたインスタンスのデストラクタが次に呼ばれ、以降同様に、最初に生成
されたインスタンスのデストラクタが最後に呼ばれます。

 「warp 文」で「行先ラベル」が省略された場合、前回実行された「warp 文」と同じ
「行先ラベル」の示すワープ先が検索されます。但し、この場合、現関数内で検索される
と無限ループになる可能性があるので、現関数内では検索されずに、現関数の呼び出し元
の各関数が検索されます。この「行先ラベル」を省略した「warp 文」を使えば、例えば、
ある個別要因でワープが起動されて、制御があるところへ移行されて来た時に、そこでは
その個別要因に対応できなくても、同じ「行先ラベル」のある呼び出し元の関数へ制御を
移管することができます。

 ワープ先の検索中には、指定されたラベル以外にも、暗黙に「WARP_STOP」という特別
な名前の固定ラベルとラベル変数の示すワープ先が、同時に検索されます。このラベルの
示すワープ先の方が、指定のラベルよりも先に見つかると、そこが実ワープ先になって、
そこへ制御が移行します。この特別なラベルは、デフォールトのワープエントリーの設定
に使います。

●注意事項
 現状、以下の文のブロック内では、ラベルは設定できません。
  ・scope 文
  ・構造体設定文
  ・クラス設定文
従って、これらのブロック内へは、ジャンプ系の文( goto文、call文、warp文 )の実行
によって入ることはできません。しかし、これらのブロック内から外へは、ジャンプする
ことができます。goto文と warp文の実行で、これらのブロック内から外へ移行した場合、
これらのブロックを完全に抜けたことになります。一方、call文の実行では、いずれこれ
らのブロック内に戻って来ることになるので、これらのブロック外にあるサブルーチンを
実行している時でも、これらのブロック内にある状態になります。例えば、
    C.X = 1;
    scope C
    {
        .Y = 2;
        call SetMembers;
    }
    print C.X, C.Y, C.Z;
    return;

  SetMembers:
    .Z = 3;
    back;
を実行すると、以下の通りプリントされます。
    1, 2, 3