はじめに
前回の続き。過去の解説は以下を参照。概要は以下の初回記事「スクリプト言語 KINX(ご紹介)」を参照してください。
- スクリプト言語 KINX(ご紹介)
- Kinx 基本編(1) - プログラム基礎・データ型
- Kinx 基本編(2) - 制御構造(今回)
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」なスクリプト言語 KINX。オブジェクト指向と C 系シンタックスで C 系プログラマになじむ触感 をお届け。
Kinx 基本編(2)- 制御構造
文・制御構文
文(ステートメント)として、宣言、代入、continue
、break
、return
、throw
、yield
、および制御構文として if-else
、while
、do-while
、for
、switch-case
、try-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
... 実数。int
→dbl
、dbl
→int
へのキャストはサポートできていない。 -
native<type>
... type は復帰値のタイプ(int
ordbl
)。native< native<type> >
とかは未サポート。
代入文
代入文は普通の式文。代入は右辺から評価される。
a = b = 10;
上記では b = 10
が先に評価され、その結果が a
に代入される。
continue
ループ先頭に戻る。正確にはループ条件式の直前に戻る。ただし、for
文の場合は第三フィールド(カウンタ更新の部分、何て言うんだ?)の直前に戻る。
continue
はラベル指定が可能。continue LABEL
で LABEL
の示すブロックの先頭(ブロックがループの場合は上記の場所)に制御が戻る。また、continue
は if 修飾が可能。continue if (expression)
の形で条件を指定することができる。
continue;
continue LABEL;
break
ループを抜ける。正確にはループ・ブロックの直後に進む。
break
はラベル指定が可能。break LABEL
で LABEL
の示すブロックの末尾に制御が進む。また、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()
で確認する感じ。今のところ、SystemException
、FiberException
、RuntimeException
というのがあるが、ユーザーが一般に投げられる例外は 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 とかがスローされる可能性があるのでソレ用)のと、スタックトレースが保持されないという制約がある。これらは何とかなりそうな気もするので、今後の検討課題。
おわりに
誰かが期待してくれるのかはさっぱり不明だが、ご意見・ご要望は随時募集中。色々考えよう。
今回も地道に★が増えるといいな、と宣伝。