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 | なし | 関数から復帰する。 | [戻り値] → 下記参照 |
上の表を見る、および処理系のソースを読むにあたり、必要な情報をいくつか。