LoginSignup
1
0

More than 3 years have passed since last update.

スクリプト言語 KINX/基本編(2) - 制御構造

Last updated at Posted at 2020-02-28

はじめに

前回の続き。過去の解説は以下を参照。概要は以下の初回記事「スクリプト言語 KINX(ご紹介)」を参照してください。

見た目は JavaScript頭脳(中身)は Ruby、(安定感は AC/DC)」なスクリプト言語 KINX。オブジェクト指向と C 系シンタックスで C 系プログラマになじむ触感 をお届け。

Kinx 基本編(2)- 制御構造

文・制御構文

文(ステートメント)として、宣言、代入、continuebreakreturnthrowyield、および制御構文として if-elsewhiledo-whileforswitch-casetry-catch-finally が、使用可能。if-else の接続はぶら下がり構文で使用する。

宣言文

var で宣言する。初期化子で初期化も可能、またカンマで区切って複数同時に宣言することも可能。

var a;
var a = 0, b = 100;

型を指定する場合は変数名の後に記述する。が、ここで指定された型は現時点で native でしか使用されていない。逆に native では指定しなければ全て int と見なされる。

var a:dbl;
var a:int = 0, b:dbl = 100.0;
native<dbl> test(a:dbl, b:dbl) {
    /* function body */
}

指定できる型は現状以下の範囲のみ。

  • int ... 整数。通常スクリプトの範囲では自動的に Big Integer へのプロモーションが行われるが、native 関数内では単にオーバーフローするので注意。
  • dbl ... 実数。intdbldblint へのキャストはサポートできていない。
  • native<type> ... type は復帰値のタイプ(int or dbl)。native< native<type> > とかは未サポート。

代入文

代入文は普通の式文。代入は右辺から評価される。

a = b = 10;

上記では b = 10 が先に評価され、その結果が a に代入される。

continue

ループ先頭に戻る。正確にはループ条件式の直前に戻る。ただし、for 文の場合は第三フィールド(カウンタ更新の部分、何て言うんだ?)の直前に戻る。

continue はラベル指定が可能。continue LABELLABEL の示すブロックの先頭(ブロックがループの場合は上記の場所)に制御が戻る。また、continue は if 修飾が可能。continue if (expression) の形で条件を指定することができる。

continue;
continue LABEL;

break

ループを抜ける。正確にはループ・ブロックの直後に進む。

break はラベル指定が可能。break LABELLABEL の示すブロックの末尾に制御が進む。また、break は if 修飾が可能。break if (expression) の形で条件を指定することができる。

break;
break LABEL;

return

関数を抜ける。正確にはスタックをクリアし、復帰値を設定して呼び出し元の次の命令に進む。

return のみを指定した場合は暗黙的に null が返る。また、return は if 修飾が可能。return expression if (expression) の形で条件を指定することができる。

また、関数が Fiber として定義されていたた場合、一旦リターンすると次の呼び出しで FiberException 例外が発生する。実は catch してもう一回呼ぶと再度最初から実行できるが、この仕様で良いのかはわからない。

return; // same as `return null;`
return expression;

throw

例外を送出する。例外システムは貧弱だが実用できないわけではない。

例外オブジェクトは type()what() というメソッドを持ち、型とメッセージを取得できる。がしかし型によって捕捉する例外を区別したりできない。キャッチしてから type() で確認する感じ。今のところ、SystemExceptionFiberExceptionRuntimeException というのがあるが、ユーザーが一般に投げられる例外は RuntimeException

型によって区別できた方が良いのかな。個人的には例外はあくまで「例外」であって、エラー処理が適切にできれば良いのだが、ご意見ご要望をお待ちしております。

また、throw も if 修飾が可能。throw expression if (expression) の形で条件を指定することができる。

ちなみに catch 節の中では throw 単独での利用が可能。この場合、catch した例外オブジェクトをそのまま再送出する。

throw;
throw expression;

yield

Fiber で一旦ホスト側に処理を戻すために使用。値を返すことも可能。ホスト側から再度 resume(args) 返ってきた値 args を受け取ることも可能。その際、引数は配列の形でまとまってくるので、個別に受信したい場合はスプレッド(レスト)演算子を使って以下のように受け取る。

[a, ...b] = yield;

上記の例では最初の引数を a で受け取り、残りの引数を配列として b が受け取る。また、yield も if 修飾が可能。yield expression if (expression) の形で条件を指定することができる。

通常は以下の形式。

yield;
yield expression;

Fiber#resume(args) の復帰値を受け取る場合は以下の形式。

var fiber = new Fiber(&{
    a = yield;                  // a = [10, 20, 30]
    [a1] = yield expression;    // a1 = 10
})
fiber.resume(); // first call.
fiber.resume(10, 20, 30);
fiber.resume(10, 20, 30);

尚、今後触れるつもりだが、&{...} はブロックを渡しているように見えて実際は &() => {...} と同じ意味。具体的には引数無しの無名関数オブジェクトを簡潔に表現できるようにしたもの。ブロックを渡しているように見えていいなと勝手に思ってこうしてみた。

if-else

if (expression) block else block の形で使用。複数条件を連続させる場合は以下のようにぶら下がり構文を使用する。

if (expression) {
    /* block */
} else if (expression) {
    /* block */
} else {
    /* block */
}

while

while は条件判断をループの最初で行うループ構造を示す。以下が例だが詳細は難しくないため省略。

while (expression) {
    /* block */
}

do-while

do-while は条件判断をループの最後で行うループ構造を示す。従って、必ず 1 度はループ・ブロックが処理される。詳細は省略。

do {
    /* block */
} while (expression);

for

for は「初期化」「条件式」「更新部」(それぞれ何て言うんだ?)の 3 つのフィールドを持つ制御構造。初期化部では var を指定して for ブロックのスコープ内だけで有効な変数の宣言が可能。詳細は省略。

for (initialize; condition; update) {
    /* block */
};

JavaScript には for-in というのがあり、サポートするか検討中。しなくていいかな(すぐには)。通常は Array.each() があるのでそれを使うのが良い。キー一式を取得する keySet() もある。

for-in 構文をサポートしました。詳しくは こちら をご参照ください。

switch-case

switch-case は悪名高いフォールスルーだ。だが、C プログラマとしてはフォールスルーじゃないと逆に変な感じでムズムズする。想像してみよう。break が無いと逆に 「次に行く感」 を感じてしまうところに根本原因があると思う。ここは馴染んだ道具に合わせてフォールスルーだ。ちゃんと break 書こうぜ。

ちなみに C 言語同様、default は最後に置かなくてもいいんだ。さらに数値以外も case に書ける。こんな感じ。

var array = [1,2,3,4,5,6,7,8,9,10];
function switchTest(n) {
    switch (n) {
    case 1:     System.println(n); break;
    case 2:     System.println(n); break;
    case 3:     System.println(n); break;
    case 4:     System.println(n); break;
    case 5:     System.println(n); break;
    case 6:     System.println(n); break;
    case 7:     System.print(n, ", "); /* fall through */
    case 8:     System.println(n); break;
    case 100:   System.println(n); break;

    default:
        System.println("default");
        break;
    case array.length():
        System.println("array-length:%{n}");
        break;
    case "aaa":
        System.println(n);
        break;
    case "bbb":
        System.println(n);
        break;
    }
}
0.upto(100, function(i) {
    System.print("%{i} => ");
    switchTest(i);
});

尚、native では switch-case をサポートしていない。これはやればできるので、やってないだけ...(優先順位の関係で)。

ただ、今利用している汎用アセンブラみたいなライブラリだとジャンプテーブル化はできなさそう。x64 だけとかならできるんだが。

try-catch-finally

try-catch-finally は例外を扱うための構文。以下のように使用する。だいたい分かってもらえそうなので、詳細は省略。尚、catch (e)(e) は省略できない。最近の JavaScript では省略できるっぽいので、できるようにするか検討中。

try {
    /* block */
} catch (e) {
    /* block */
} finally {
    /* block */
}

native でもサポートしたが、実際の例外オブジェクトを投げることができない制約がある(Type Mismatch とか Divide By Zero とかがスローされる可能性があるのでソレ用)のと、スタックトレースが保持されないという制約がある。これらは何とかなりそうな気もするので、今後の検討課題。

おわりに

誰かが期待してくれるのかはさっぱり不明だが、ご意見・ご要望は随時募集中。色々考えよう。

今回も地道に★が増えるといいな、と宣伝。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0