samplanはJavaで実装された簡単なプログラミング言語です。
プログラミング言語として実用的に使う、という目的ではなく、プログラミング言語の作り方を示すこと、および私自身が「言語処理系を書きたくなった」という理由で作ったものですので、言語としては、まあ実用性はありません。なにしろ使用できる組み込み関数は「print()」しかないですし、配列もないですし。
ソースはGitHubにあります。
https://github.com/kmaebashi/samplan
言語の雰囲気的なものは、ここのサンプルプログラムを見ればつかめると思います。波括弧が出てくるCやJavaに似た言語ですが、以下のような違いがあります。
samplanは静的な型付けを持つバイトコード実行型の言語です。ソースからコンパイルしたバイトコード※1をメモリ上に保持し、それを実行します。よって、内部的にはコンパイルしていますが、利用者がそれを意識することはありません。
処理系自体はJavaで記述しています(JDK17を使用。そんなに新しい機能は使っていませんが)。
コンパイラは、1トークン先読みの再帰下降パーサで自作しています(yaccとかJavaCCのようなパーサジェネレータは使っていません)。バイトコードを実行する仮想機械(SVM: Samplan Virtual Machine)はありがちなスタックマシンです。プログラミング言語の作り方を学びたい、という人には、何かしら参考にはなるのではないでしょうか。
プログラミング言語の作り方については、以前こちらに書いています。
samplanも基本的な考え方は同じですが、上のページでは、コンパイラを作るのに、C向けの、レキシカルアナライザ生成プログラムであるlex(flex)、パーサジェネレータであるyacc(bison)を使っています。こういうツールは確かに便利なのですが、せっかくイチからプログラミング言語を作るなら、そういうツールも使わずにフルスクラッチで書いてみたい、という人もいることでしょう。また、今どきはCに詳しくない人も多いでしょうから、Javaで作ったことにも意味があると思っています。
「プログラミング言語を作る」という、マニアックな、でも楽しい趣味に目覚めた人の参考になりましたら幸いです。
samplanでは、「#」から改行までがコメントです。
samplanでは以下のデータ型が使用可能です。
| 型名 | 意味 |
|---|---|
| boolean | 論理型。trueまたはfalseの値を持つ。 |
| int | 整数型。表現できる範囲等はJavaのintと同じ。 |
| real | 実数型。表現できる範囲等はJavaのdoubleと同じ。 |
| string | 文字列型。samplanに参照型はないので、初期値は空文字になっている。 |
変数は以下のように宣言します。
var hoge int; # 初期化式なしの場合 var hoge int := 10; # 初期化式ありの場合
変数は、使うところより先に宣言されていなければいけません。
samplanでは以下の演算子が使用可能です。
| No. | 優先順位 | 演算子 | 機能 | 結合性 |
|---|---|---|---|---|
| 1 | 1 | - | 単項のマイナス。オペランドの符号を逆転させる。 | 右から左 |
| 2 | 1 | ++ | インクリメント。オペランドの値を1増やす。整数型の変数のみに適用可能。 | 右から左 |
| 3 | 1 | -- | デクリメント。オペランドの値を1減らす。整数型の変数のみに適用可能。 | 右から左 |
| 4 | 2 | * | 乗算。整数型と実数型に適用可能。 | 左から右 |
| 5 | 2 | / | 除算。整数型と実数型に適用可能。 | 左から右 |
| 6 | 3 | + | 加算。整数型と実数型と文字列型に適用可能。文字列は連結する。 | 左から右 |
| 7 | 3 | - | 減算。整数型と実数型に適用可能。 | 左から右 |
| 8 | 4 | = | 等値演算子。整数型と実数型と文字列型に適用可能。 | 左から右 |
| 9 | 4 | != | 非等値演算子。整数型と実数型と文字列型に適用可能。 | 左から右 |
| 10 | 4 | > | 大小比較演算子。整数型と実数型と文字列型に適用可能(文字列型は辞書順)。 | 左から右 |
| 11 | 4 | >= | 大小比較演算子。整数型と実数型と文字列型に適用可能(文字列型は辞書順)。 | 左から右 |
| 12 | 4 | < | 大小比較演算子。整数型と実数型と文字列型に適用可能(文字列型は辞書順)。 | 左から右 |
| 13 | 4 | <= | 大小比較演算子。整数型と実数型と文字列型に適用可能(文字列型は辞書順)。 | 左から右 |
| 14 | 5 | && | 論理AND。論理型のみに使用可能。 | 左から右 |
| 15 | 5 | || | 論理OR。論理型のみに使用可能。 | 左から右 |
| 16 | 6 | := | 代入。これ自体が値を持つので、「a := b := c := 0;」も可能 | 右から左 |
以下のように書くことで関数が定義できます。
# ふたつの引数の値を加算して返す関数
function add(a real, b real) real {
return a + b;
}
戻り値を持たない関数は、型名を省略してください。
# ふたつの引数の値を加算して表示する関数
function add2(a real, b real) {
print("result.." + (a + b));
}
呼び出すときは、「add(2, 3)」のように書きます。
関数内ではローカル変数も使用可能です。
function sum(value int) int {
var i int := 0;
var sum int := 0;
while (i <= value) {
sum := sum + i;
++i;
}
return sum;
}
関数は、使うところより先に宣言されている必要はありません。
ローカル変数は、{ }で囲まれたブロックごとに宣言できますが、外周のブロックの変数(グローバル変数を含む)と名前が衝突してはいけません。
条件分岐はif文です。{ }が省略不可なので、elsifを導入しています。
if a = 0 {
print("aは0だよ!");
} elsif a = 1 {
print("aは1だよ!");
} else {
print("aは0でも1でもないよ!");
}
ループはwhile文です。今のところfor文はありません。
var i int:= 0;
while i < 10 {
print("i.." + i);
++i;
}
関数から戻る際にはreturn文を使います。
function add(a real, b real) real {
return a + b;
}
戻り値がない場合は、式を省略します。
SVMのオペランドは、すべて整数値です。
以下の表の「スタック」欄は、インストラクション実行時のスタックの変化を示します。[ ]内には、スタックの上位にある値の型を表記しています。 右端がスタックのトップです。SVMにはbooleanはなく、内部的にはintなのですが、わかりやすくするため booleanと書いています。 交換則が効かない演算子では、順番に意味があるので、 [int1 int2]のように記述し、結果の方を[ (int - int2) ]のように記述しています。[ (int1 > int1) ]の型はbooleanです。
| No. | 命令 | オペランド | 意味 | スタック |
|---|---|---|---|---|
| 1 | NOP | なし | 何もしない(未使用)。 | - |
| 2 | PUSH_INT | pushする整数値 | 整数をスタックにpushする。 | [] → [int] |
| 3 | PUSH_REAL | pushする実数値のコンスタントプールインデックス | 実数をスタックにpushする。 | [] → [real] |
| 4 | PUSH_STRING | pushする文字列のコンスタントプールインデックス | 文字列をスタックにpushする。 | [] → [string] |
| 5 | PUSH_STACK_INT | ローカル変数のID | 整数型のローカル変数の値をスタックにpushする。 | [] → [int] |
| 6 | PUSH_STACK_REAL | ローカル変数のID | 実数型のローカル変数の値をスタックにpushする。 | [] → [real] |
| 7 | PUSH_STACK_STRING | ローカル変数のID | 文字列型のローカル変数の値をスタックにpushする。 | [] → [string] |
| 8 | POP_STACK_INT | ローカル変数のID | スタックトップの整数値をローカル変数にpopする。 | [int] → [] |
| 9 | POP_STACK_REAL | ローカル変数のID | スタックトップの実数値をローカル変数にpopする。 | [real] → [] |
| 10 | POP_STACK_STRING | ローカル変数のID | スタックトップの文字列をローカル変数にpopする。 | [string] → [] |
| 11 | PUSH_STATIC_INT | グローバル変数のID | 整数型のグローバル変数の値をスタックにpushする。 | [] → [int] |
| 12 | PUSH_STATIC_REAL | グローバル変数のID | 実数型のグローバル変数の値をスタックにpushする。 | [] → [real] |
| 13 | PUSH_STATIC_STRING | グローバル変数のID | 文字列型のグローバル変数の値をスタックにpushする。 | [] → [string] |
| 14 | POP_STATIC_INT | グローバル変数のID | スタックトップの整数値をグローバル変数にpopする。 | [int] → [] |
| 15 | POP_STATIC_REAL | グローバル変数のID | スタックトップの実数値をグローバル変数にpopする。 | [real] → [] |
| 16 | POP_STATIC_STRING | グローバル変数のID | スタックトップの文字列をグローバル変数にpopする。 | [string] → [] |
| 17 | ADD_INT | なし | スタックトップの二つの整数同士の加算を行い、結果をスタックに積む。 | [int int] → [int] |
| 18 | ADD_REAL | なし | スタックトップの二つの実数同士の加算を行い、結果をスタックに積む。 | [real real] → [real] |
| 19 | ADD_STRING | なし | スタックトップの二つの文字列同士の連結を行い、結果をスタックに積む。 | [string string] → [string] |
| 20 | SUB_INT | なし | スタックトップの二つの整数同士の減算を行い、結果をスタックに積む。 | [int1 int2] → [(int1 - int2)] |
| 21 | SUB_REAL | なし | スタックトップの二つの実数同士の減算を行い、結果をスタックに積む。 | [real1 real2] → [(real1 - real2)] |
| 22 | MUL_INT | なし | スタックトップの二つの整数同士の乗算を行い、結果をスタックに積む。 | [int int] → [int] |
| 23 | MUL_REAL | なし | スタックトップの二つの実数同士の乗算を行い、結果をスタックに積む。 | [real real] → [real] |
| 24 | DIV_INT | なし | スタックトップの二つの整数同士の除算を行い、結果をスタックに積む。 | [int1 int2] → [(int1 / int2)] |
| 25 | DIV_REAL | なし | スタックトップの二つの実数同士の除算を行い、結果をスタックに積む。 | [real1 real2] → [(real1 / real2)] |
| 26 | MINUS_INT | なし | スタックトップの整数値の符号を反転する。 | [int] → [int] |
| 27 | MINUS_REAL | なし | スタックトップの実数値の符号を反転する。 | [real] → [real] |
| 28 | INCREMENT | なし | スタックトップの整数値に1加算する。 | [int] → [int] |
| 29 | DECREMENT | なし | スタックトップの整数値から1減算する。 | [int] → [int] |
| 30 | CAST_INT_TO_REAL | なし | スタックトップの整数値を実数に変換する。 | [int] → [real] |
| 31 | CAST_REAL_TO_INT | なし | スタックトップの実数値を整数に変換する。 | [real] → [int] |
| 32 | CAST_BOOLEAN_TO_STRING | なし | スタックトップの論理値を文字列に変換する。 | [boolean] → [string] |
| 33 | CAST_INT_TO_STRING | なし | スタックトップの整数値を文字列に変換する。 | [int] → [string] |
| 34 | CAST_REAL_TO_STRING | なし | スタックトップの実数値を文字列に変換する。 | [real] → [string] |
| 35 | EQ_INT | なし | スタックトップの二つの整数同士の等値比較を行い、結果をスタックに積む。 | [int int] → [boolean] |
| 36 | EQ_REAL | なし | スタックトップの二つの実数同士の等値比較を行い、結果をスタックに積む。 | [real real] → [boolean] |
| 37 | EQ_STRING | なし | スタックトップの二つの文字列同士の等値比較を行い、結果をスタックに積む。 | [string string] → [boolean] |
| 38 | GT_INT | なし | スタックトップの二つの整数同士の大小比較を行い、結果をスタックに積む。 | [int1 int2] → [(int1 > int2)] |
| 39 | GT_REAL | なし | スタックトップの二つの実数同士の大小比較を行い、結果をスタックに積む。 | [real1 real2] → [(real1 > real2)] |
| 40 | GT_STRING | なし | スタックトップの二つの文字列同士の大小比較を行い、結果をスタックに積む。 | [string1 string2] → [(string1 > string2)] |
| 41 | GE_INT | なし | スタックトップの二つの整数同士の大小比較を行い、結果をスタックに積む。 | [int1 int2] → [(int1 >= int2)] |
| 42 | GE_REAL | なし | スタックトップの二つの実数同士の大小比較を行い、結果をスタックに積む。 | [real1 real2] → [(real1 >= real2)] |
| 43 | GE_STRING | なし | スタックトップの二つの文字列同士の大小比較を行い、結果をスタックに積む。 | [string1 string2] → [(string1 >= string2)] |
| 44 | LT_INT | なし | スタックトップの二つの整数同士の大小比較を行い、結果をスタックに積む。 | [int1 int2] → [(int1 < int2)] |
| 45 | LT_REAL | なし | スタックトップの二つの実数同士の大小比較を行い、結果をスタックに積む。 | [real1 real2] → [(real1 < real2)] |
| 46 | LT_STRING | なし | スタックトップの二つの文字列同士の大小比較を行い、結果をスタックに積む。 | [string1 string2] → [(string1 < string2)] |
| 47 | LE_INT | なし | スタックトップの二つの整数同士の大小比較を行い、結果をスタックに積む。 | [int1 int2] → [(int1 <= int2)] |
| 48 | LE_REAL | なし | スタックトップの二つの実数同士の大小比較を行い、結果をスタックに積む。 | [real1 real2] → [(real1 <= real2)] |
| 49 | LE_STRING | なし | スタックトップの二つの文字列同士の大小比較を行い、結果をスタックに積む。 | [string1 string2] → [(string1 <= string2)] |
| 50 | NE_INT | なし | スタックトップの二つの整数同士の非等値比較を行い、結果をスタックに積む。 | [int int] → [boolean] |
| 51 | NE_REAL | なし | スタックトップの二つの実数同士の非等値比較を行い、結果をスタックに積む。 | [real real] → [boolean] |
| 52 | NE_STRING | なし | スタックトップの二つの文字列同士の非等値比較を行い、結果をスタックに積む。 | [string string] → [boolean] |
| 53 | LOGICAL_AND | なし | スタックトップの二つの論理値同士の論理積を取り、結果をスタックに積む。 | [boolean boolean] → [boolean] |
| 54 | LOGICAL_OR | なし | スタックトップの二つの論理値同士の論理和を取り、結果をスタックに積む。 | [boolean boolean] → [boolean] |
| 55 | POP | なし | スタックトップの要素を捨てる。 | [T] → [] |
| 56 | DUPLICATE | なし | スタックトップの要素を複製してスタックに積む。 | [T] → [T T] |
| 57 | JUMP | ジャンプ先のアドレス | オペランドで指定したアドレスにジャンプする。 | [] → [] |
| 58 | JUMP_IF_TRUE | ジャンプ先のアドレス | スタックトップの値がtrueであれば、オペランドで指定したアドレスにジャンプする。 | [boolean] → [] |
| 59 | JUMP_IF_FALSE | ジャンプ先のアドレス | スタックトップの値がfalseであれば、オペランドで指定したアドレスにジャンプする。 | [boolean] → [] |
| 60 | INVOKE | 関数のID | オペランドで指定された関数を呼び出す。 | [] → 下記参照 |
| 61 | RETURN | なし | 関数から復帰する。 | [戻り値] → 下記参照 |
上の表を見る、および処理系のソースを読むにあたり、必要な情報をいくつか。
