はじめに
前回の記事から。シリーズ第一弾は普通に基本編。概要は以下の前回の記事を参照してください。
- スクリプト言語 KINX(ご紹介)
- Kinx 基本編(1) - プログラム基礎・データ型(今回)
前回お披露目してみたら★を付けてくださった方がいて、非常に浮かれています。ありがとうございました。温かいお気持ちで行く末を見届けてやってください。
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」でお届けする スクリプト言語 KINX。オブジェクト指向と C 系シンタックスで C 系プログラマになじむ触感 を目指して。
Kinx 基本編(1) - プログラム基礎・データ型
プログラム基礎
hello, world
プログラムはトップレベルから記述可能。
System.println("hello, world.");
hello.kx という名前で保存し、以下のように実行.
$ ./kinx hello.kx
hello, world.
コメント
コメントは C/C++ 形式と Perl のような # 形式と両方利用可能。
/* Comment */
// Comment
# Comment
変数宣言
変数宣言は var で宣言する。
var a = 10;
初期化子を使って初期化するが、初期化子を書かなかった場合は null となる。ちなみに Kinx においては null と undefined は同じ意味(賛否あると思うが)。どちらも a.isUndefined が true となる。
データ型一覧
Kinx は動的型付け言語だが、内部に型を持っている。
| Type | CheckProperty | Example | Meaning |
|---|---|---|---|
| Undefined | isUndefined |
null | 初期化されていない値。 |
| Integer |
isInteger, isBigInteger
|
100, 0x02 | 整数。演算では自動的に Big Integer と相互変換される。 |
| Double | isDouble |
1.5 | 実数。 |
| String | isString |
"aaa", 'bbb' | 文字列。 |
| Binary | isBinary |
<1,2,3> | バイナリ値。バイトの配列。要素は全て 0x00-0xFF に丸められる。 |
| Array |
isArray, isObject
|
[1,a,["aaa"]] | 配列。扱える型は全て保持可能。 |
| Object | isObject |
{ a: 1, b: x } | JSON のようなキーバリュー構造。 |
| Function | isFunction |
function(){}, &() => expr |
関数。 |
Undefined 以外であれば isDefined で true が返る。
尚、真偽値としての true、false は整数の 1、0 のエイリアスでしかないのに注意。
Boolean 型というのは特別に定義されていないが、オブジェクトとして真偽を表す True、False という定数が定義されているので、整数値とどうしても区別したい場合はそちらを使用する。
System.println(True ? 1 : 0); // 1
System.println(False ? 1 : 0); // 0
ブロックとスコープ
ブロックはスコープを持ち、内側で宣言された変数は外側のブロックからは参照不可。同じ名前で宣言した場合、外側ブロックの同名変数は隠蔽される。
var a = 10;
{
var a = 100;
System.println(a);
}
System.println(a);
この辺が「見た目は JavaScript」であっても中身は JavaScript ではない感じの部分。というか、JavaScript のスコープは変態過ぎて使いづらい。
式
式(エクスプレッション)は、以下の優先順位で四則演算、関数呼び出し、オブジェクト操作等が可能。
| # | 要素 | 演算子例 | 評価方向 |
|---|---|---|---|
| 1 | 要素 | 変数, 数値, 文字列, ... | - |
| 2 | 後置演算子 |
++, --, [], ., ()
|
左から右 |
| 3 | 前置演算子 |
!, +, -, ++, --
|
左から右 |
| 4 | パターンマッチ |
=~, !~
|
左から右 |
| 5 | べき乗 | ** |
右から左 |
| 6 | 乗除 |
*, /, %
|
左から右 |
| 7 | 加減 |
+, -
|
左から右 |
| 8 | ビットシフト |
<<, >>
|
左から右 |
| 9 | 大小比較 |
<, >, >=, <=
|
左から右 |
| 10 | 等値比較 |
==, !=
|
左から右 |
| 11 | ビットAND | & |
左から右 |
| 12 | ビットXOR | ^ |
左から右 |
| 13 | ビットOR | | |
左から右 |
| 14 | 論理AND | && |
左から右 |
| 15 | 論理OR | || |
左から右 |
| 16 | 三項演算子 |
? :, function(){}
|
左から右 |
| 17 | 代入演算子 |
=, +=, -=, *=. /=. %=, &=, |=, ^=, &&=, ||=
|
右から左 |
いくつか特徴を以下に示す。
演算
演算結果によって型が自動的に結果に適応していく。3/2 は 1.5 になる。
num = 3 + 2; // 5
num = 3 - 2; // 1
num = 3 * 2; // 6
num = 3 / 2; // 1.5
num = 3 % 2; // 1
インクリメント・デクリメント
前置形式・後置形式があり、C と同様。
var a = 10;
System.println(a++); // 10
System.println(++a); // 12
System.println(a--); // 12
System.println(--a); // 10
データ型
数値
整数、実数
整数、実数は以下の形式。整数では可読性向上のため任意の場所に _ を挿入可能。_ は単に無視される。
var i = 2;
var j = 100_000_000;
var num = 1.234;
文字列
基本
ダブルクォートとシングルクォートの両方が使えるが、エスケープしなければならないクォート文字が異なるだけでどちらも同じ意味になる。
var a = "\"aaa\", 'bbb'";
var b = '"aaa", \'bbb\'';
System.println(a == b ? "same" : "different"); // same
内部式
%{...} の形式で内部に式を持つことができる。
for (var i = 0; i < 10; ++i) {
System.println("i = %{i}, i * 2 = %{i * 2}");
}
// i = 0, i * 2 = 0
// i = 1, i * 2 = 2
// i = 2, i * 2 = 4
// i = 3, i * 2 = 6
// i = 4, i * 2 = 8
// i = 5, i * 2 = 10
// i = 6, i * 2 = 12
// i = 7, i * 2 = 14
// i = 8, i * 2 = 16
// i = 9, i * 2 = 18
フォーマッタ
文字列に対する % 演算子はフォーマッタ・オブジェクトを作成する。
var fmt = "This is %1%, I can do %2%.";
System.println(fmt % "Tom" % "cooking");
%1% の 1 はプレースホルダ番号を示し、% 演算子で適用した順に合わせて整形する。適用場所が順序通りであれば、C の printf と同様の指定の仕方も可能。さらに、C の printf と同じ指定子を使いながら同時にプレースホルダも指定したい場合は、$ の前に位置指定子を書き、'$' で区切って記述する。例えば、16進数で表示したい場合は以下のようにする。
var fmt = "This is %2%, I am 0x%1$02x years old in hex.";
System.println(fmt % 27 % "John");
// This is John, I am 0x1b years old in hex.
フォーマッタ・オブジェクトに後から値を適用していく場合は、%= 演算子で適用していく。
var fmt = "This is %1%, I can do %2%.";
fmt %= "Tom";
fmt %= "cooking";
System.println(fmt);
実際のフォーマット処理は、System.println 等で表示するとき、文字列との加算等が行われるとき、に実際に実行される。
Raw 文字列
文字列内部ではなく、%{...} で文字列を記載することで Raw 文字列を作成することが可能。%-{...} を使うと、先頭と末尾の改行文字をトリミングする。ヒアドキュメントのようにも使えるので、ヒアドキュメントはサポートしていない。また、%<...>、%(...)、%[...] を使うこともできる。
var a = 100;
var b = 10;
var str = %{
This is a string without escaping control characters.
New line is available in this area.
{ and } can be nested here.
};
System.println(str);
var str = %-{
This is a string without escaping control characters.
New line is available in this area.
But newlines at the beginning and the end are removed when starting with '%-'.
};
System.println(str);
\ でエスケープする必要があるのは、内部式を使う場合 %{ の % に対するものと、ネストした形にならないケースでのクォート文字である { や } に対するものだけとなる。
また、カッコは対応する閉じカッコでクォートするが、以下の文字を使ったクォートも可能である。その場合は、開始文字と終了文字は同じ文字となる。例えば、%|...| のような形で使用する。
-
|,!,^,~,_,.,,,+,*,@,&,$,:,;,?,',".
正規表現リテラル
正規表現リテラルは /.../ の形式で使う。リテラル内の / は \ でエスケープする必要がある。以下が例。
var a = "111/aaa/bbb/ccc/ddd";
while (group = (a =~ /\w+\//)) {
for (var i = 0, len = group.length(); i < len; ++i) {
System.println("found[%2d,%2d) = %s"
% group[i].begin
% group[i].end
% group[i].string);
}
}
/ を多用するような正規表現の場合、%m プレフィックスを付け、別のクォート文字を使うことで回避できる。例えば %m(...) といった記述が可能。これを使って上記を書き直すと、以下のようになる。
var a = "111/aaa/bbb/ccc/ddd";
while (group = (a =~ %m(\w+/))) {
for (var i = 0, len = group.length(); i < len; ++i) {
System.println("found[%2d,%2d) = %s"
% group[i].begin
% group[i].end
% group[i].string);
}
}
尚、正規表現リテラルを while 等の条件式に入れることができるが注意点があるので補足しておく。例えば以下のように記述した場合、str の文字列に対してマッチしなくなるまでループを回すことができる(group にはキャプチャ一覧が入る)。その際、最後のマッチまで実行せずに途中で break 等でループを抜けると正規表現リテラルの対象文字列が次回のループで正しくリセットされない、という状況が発生する。
while (group = (str =~ /ab+/)) {
/* block */
}
正規表現リテラルがリセットされるタイミングは以下の 2 パターン。
- 初回(前回のマッチが失敗して再度式が評価された場合を含む)。
-
strの内容が変化した場合。
将来改善を検討するかもしれないが、現在は上記の制約があることに注意。
配列
配列は任意の要素を保持するリスト。インデックスでアクセスできる。またインデックスに負の数を与えることで末尾からアクセスすることもできる。
var a = [1,2,3];
var b = [a, 1, 2];
System.println(b[0][2]); // 3
System.println(a[-1]); // 3
配列構造は左辺値で使用すると右辺値の配列を個々の変数に取り込むことが可能。これを使用して値のスワップも可能。
[a, b] = [b, a]; // Swap
スプレッド(レスト)演算子を使っての分割も可能。
[a, ...b] = [1, 2, 3, 4, 5];
// a = 1
// b = [2, 3, 4, 5]
尚、以下の書き方は できない できるようにしましたよ。変数は宣言なしで使用できる(最初に代入された時点で生成される)ため、var を付けない形で直接記述することは可能。ただしその際、外部のスコープで定義されていた場合、外側のスコープの変数の内容が書き換わってしまうことに注意。スコープで変数へのアクセス範囲を閉じたい場合は、予め var a, b; と宣言してから使うことで回避可能。
// var [a, ...b] = [1, 2, 3, 4, 5]; // okay.
var a = 3, b = [4], x = 3, y = [4];
{
var a, b;
[a, ...b] = [1, 2, 3, 4, 5];
// a = 1
// b = [2, 3, 4, 5]
[x, ...y] = [1, 2, 3, 4, 5];
// x = 1
// y = [2, 3, 4, 5]
[z] = [1, 2, 3, 4, 5];
// okay z = 1, but scoped out...
}
System.println("a = ", a); // 3
System.println("b = ", b[0]); // 4
System.println("x = ", x); // 1
System.println("y = ", y[0]); // 2
宣言と同時に使えると便利とは思うので、将来改善するかもしれない。
バイナリ
バイナリはバイト配列であり、<...> の形式で記述する。全ての要素は 0x00 ~ 0xFF の範囲にアジャストされ、配列のようにアクセス可能。
バイナリと配列は相互にスプレッド演算子で分割、結合することが可能。
var bin = <0x01, 0x02, 0x03, 0x04>;
var ary = [...bin];
// ary := [1, 2, 3, 4]
var ary = [10, 11, 12, 257];
var bin = <...ary>;
// bin := <0x0a, 0x0b, 0x0c, 0x01>
ただし、バイナリになった瞬間に 0x00-0xFF に丸められるので注意。
オブジェクト
いわゆる JSON。ただし、ソースコード上のキー文字列に対してクォートする必要は無い(しても良い)。
var a = { a: 100 };
a.b = 1_000;
a["c"] = 10_000;
System.println(a.a); // 100
System.println(a.b); // 1000
System.println(a.c); // 10000
内部的に実はオブジェクトと配列は同じであり、両方の値を同時に保持できる。
var a = { a: 100 };
a.b = 1_000;
a["c"] = 10_000;
a[1] = 10;
System.println(a[1]); // 10
System.println(a.a); // 100
System.println(a.b); // 1000
System.println(a.c); // 10000
おわりに
今回はここまで。結構疲れた。
非常に一般的な話ばかりで面白くないのだが、まずはこういうところに触れた上で色々な解説しとかないと土台が良く分からなくなりそうなので頑張ってみた。とりあえず、次は制御構造に触れて、一通りのプログラムが構築できるところまでを目指そう。そこまで行ったら関数とかクラスとかクロージャとか個別の解説をしてみる予定。
例によって★が増えるといいな、と宣伝しておく。
そして書きながらバグを見つけ修正するという...。