ググって出ないと思ったら、大石泉すき言語のWeb版実行環境が消えてる……!?
ぴにゃでも分かる!??大石泉すき言語プログラミング初級編 - あずいずの不定期報
でも紹介されているけど……
(アーカイブ:大石泉すき言語)
やだなあ……これでは大石泉すき言語を手軽に実行できないじゃないか……大石泉すきなのに……
というわけで、インタプリタを作ってみた。
大石泉すき言語
大石泉すき言語は、Brainf*ckの各命令を「大石泉」や「すき」に関係する言葉で表す言語である。
以下の命令が定義されている。
| 命令 | Brainf*ck | 動作 |
|---|---|---|
| 大石泉 | > |
ポインタをインクリメントする |
| 愛してる | < |
ポインタをデクリメントする |
| すき | + |
ポインタが指している値をインクリメントする |
| 好き | - |
ポインタが指している値をデクリメントする |
| すき! | . |
ポインタが指している値を出力する |
| すき? | , |
ポインタが指している場所に入力を読み込む |
| いずみん | [ |
ポインタが指している値が0なら、 対応する「イズミン」の直後に飛ぶ |
| イズミン | ] |
ポインタが指している値が0でないなら、 対応する「いずみん」の直後に飛ぶ |
「すき!」の感嘆符や「すき?」の疑問符は、全角である。
今回実装したインタプリタは、以下の仕様である。
- データメモリの初期値は、全要素 0
- データメモリの容量は、初期位置よりも「前」も含め、無制限 (上位の処理系などによる制限のみ)
- データメモリの要素は、32ビット符号付き整数
- 入出力の形式は、関数により指定可能
- 簡易版のAPIでは、UTF-16 のコードユニット単位で文字列を読み書きし、入力の最後まで読んだ後に入力を読もうとした際は
-1を返す
- 簡易版のAPIでは、UTF-16 のコードユニット単位で文字列を読み書きし、入力の最後まで読んだ後に入力を読もうとした際は
今回作成したインタプリタ
今回は、インタプリタ本体のモジュールと、それを呼び出すUIに分けてプログラムを作成した。
これにより、大石泉すき言語のインタプリタを他のプログラムに組み込んだり、より素敵なUIを開発したりしやすくなるはずである。
プログラム貯蔵庫へのリンク:
See the Pen nako3 oi-s-lang unofficial interpreter by MikeCAT (@mike_cat) on CodePen.
API
●(INPUTでSRCを)大石泉すき
文字列INPUTを入力としてプログラムを実行し、出力された文字列を返す。
- INPUT:入力文字列
- SRC:ソースコード
●(INPUTとOUTPUTでSRCを)カスタム入出力大石泉すき
入力関数と出力関数を指定して、プログラムを実行する。
- INPUT:入力関数 (引数なしで、1件 (1文字) 読み込んで数値を返す)
- OUTPUT:出力関数 (1引数で、引数として与えられる数値を (文字として) 出力する)
- SRC:ソースコード
実行してみる
過去に自分が書いたプログラム
あの言語で2進UTF-8文字列のデコーダを作ってみた|みけCAT
を実行してみると、筆者の環境では約3秒で「大石泉すき」が出力された。大石泉すき。
今回のインタプリタの仕組み
各命令を処理する関数を用意する
まず、実行状態 (命令列とデータメモリ) をグローバル変数で用意する。
そして、各命令を処理する関数を用意し、命令名をキーとした辞書に登録しておく。
計算結果や入力された値をデータメモリに格納する際、「OR」(ビットOR) を用いて32ビット符号付き整数に変換する。
ジャンプを行うかどうかを、命令を処理する関数の戻り値で表現する。
ソースコードを命令列に変換する
ソースコードを前から見ていき、命令の辞書を反復し、命令名がソースコードの見ている部分と一致するかをチェックする。
一致した中で、命令名が一番長いものを採用し、命令列に加える。
「すき!」や「すき?」を「すき」と認識しない、かつ汎用的な仕組みとして、「命令名が一番長いもの」を採用することにした。
一致した命令があれば、その採用した命令名の長さの分だけ見る位置を進める。
一致した命令がなければ、見る位置を1文字進める。
このとき、ジャンプ命令では飛び先の登録も行う。
「いずみん」([) が来たら、その命令の命令列内の位置をスタックにプッシュする。
「イズミン」(]) が来たら、スタックから位置をポップし、以下を行う。
- ポップした位置の命令の飛び先を、今の命令の位置の直後に設定する
- 今の命令の飛び先を、ポップした位置の直後に設定する
命令列を実行する
実行する命令の位置とデータメモリ・データポインタを初期化したあと、実行する命令の位置が命令列内の間、以下を繰り返す。
- 現在の位置の命令の処理を行う関数を実行する
- その返り値に応じて、実行する命令の位置を更新する (1進める、もしくは飛び先に設定する)
今回踏んだNG集
ここでは、今回のプログラムを作るにあたり、書いてみて上手く動かなかった書き方を紹介する。
ここで例に挙げたなでしこのプログラムの実行は、Web簡易エディタ (v3.7.11) で行った。
要素数未満の間、繰り返す
テストは[1, 2, 3]。
位置は0。
位置がテストの要素数未満の間、繰り返す
「テスト@{位置} = {テスト@位置}」を表示。
位置を1増やす。
ここまで。
これは、変数「位置」が配列「テスト」の要素数未満の間、繰り返しを行うことを意図したプログラムである。
JavaScript でいえば
const test = [1, 2, 3];
let pos = 0;
while (pos < test.length) {
console.log(`test[${pos}] = ${test[pos]}`);
pos++;
}
である。
しかし、これを実行すると、以下の出力しか得られなかった。
テスト@0 = 1
繰り返し文を、「テストの要素数」をカッコで括った
位置が(テストの要素数)未満の間、繰り返す
にすると、期待通りの出力
テスト@0 = 1
テスト@1 = 2
テスト@2 = 3
が得られた。
条件式全体をカッコで括って
(位置がテストの要素数未満)の間、繰り返す
とすると、以下のエラーになった。
[エラー][文法エラー]main.nako3(4行目): (...)の解析エラー。関数『要素数』の近く
条件式単体での真偽のチェック
以下のプログラムで、判定結果をチェックしてみた。
テストは[1, 2, 3]。
位置で0…5を繰り返す
位置がテストの要素数未満を表示。
ここまで。
結果は期待通りで、なぜ繰り返し文では上手くいかないのか謎が深まる。
true
true
true
false
false
false
条件分岐の場合
同様の書き方を条件分岐で行うと、エラーになった。
「ならば」はあるので、条件が複雑過ぎるのか……?
テストは[1, 2, 3]。
位置で0…5を繰り返す
もし、位置がテストの要素数未満ならば
「{位置}:そうだよ」を表示。
違えば
「{位置}:違うよ」を表示。
ここまで。
ここまで。
[エラー][文法エラー]main.nako3(4行目): 『もし』文の条件で次のエラーがあります。 [文法エラー]main.nako3(4行目): もし文で『ならば』がないか、条件が複雑過ぎます。関数『未満』の直前に『ならば』を書いてください。
同様に、「テストの要素数」をカッコで括ると、期待した結果が得られた。
もし、位置が(テストの要素数)未満ならば
0:そうだよ
1:そうだよ
2:そうだよ
3:違うよ
4:違うよ
5:違うよ
条件式全体をカッコで括ると、カッコが無いときとは別のエラーになった。
もし、(位置がテストの要素数未満)ならば
[エラー][文法エラー]main.nako3(4行目): 『もし』文の条件で次のエラーがあります。 [文法エラー]main.nako3(4行目): (...)の解析エラー。関数『要素数』の近く
オブジェクトのプロパティへの「代入」
テストは{}。
72をテスト$ホゲに代入。
テストをJSONエンコードして表示。
これは、オブジェクト「テスト」のプロパティ「ホゲ」に値「72」を代入することを意図したプログラムである。
これを実行すると、以下のエラーになってしまった。
[エラー][文法エラー]main.nako3(2行目): 代入文で代入先の変数が見当たりません。『(変数名)に(値)を代入』のように使います。
メッセージに従い、値を後に持ってきてみても、結果は変わらなかった。(同じエラーが出た)
テストは{}。
テスト$ホゲに72を代入。
テストをJSONエンコードして表示。
この場合は、「は」を使った代入はできるようである。
テストは{}。
テスト$ホゲは72。
テストをJSONエンコードして表示。
{"__setProp":null,"__getProp":null,"ホゲ":72}
配列の要素のプロパティへの代入
テストは[{}]。
(テスト@0)$ホゲはテストの要素数。
テストをJSONエンコードして表示。
これは、配列「テスト」の最初の要素として格納されているオブジェクトのプロパティ「ホゲ」に配列「テスト」の要素数 (すなわち、1) を代入することを意図したプログラムである。
これを実行すると、以下の実行結果が得られた。
[{"__setProp":null,"__getProp":null}]
プロパティ「ホゲ」が見当たらず、オブジェクトが直接格納された変数を用いた場合は代入に成功した「は」が、配列の要素には代入できていないことがわかる。
カッコを外してみると、別のエラーになった。
テストは[{}]。
テスト@0$ホゲはテストの要素数。
テストをJSONエンコードして表示。
[エラー][文法エラー]main.nako3(2行目): 不完全な文です。『ref_array』が解決していません。
「テストの要素数」のかわりに具体的な値を用いてみると、なぜか使っていない演算子「=」に関するエラーが出た。
テストは[{}]。
(テスト@0)$ホゲは72。
テストをJSONエンコードして表示。
[エラー][文法エラー]main.nako3(2行目): [文法エラー](2行目): 不完全な文です。演算子『=』が解決していません。演算子『=』は『『ref_array_value』と数値72が等しいかどうかの比較』として使われています。
オブジェクトを一旦変数に格納することで、代入に成功した。
テストは[{}]。
テストオブジェクトはテスト@0。
テストオブジェクト$ホゲはテストの要素数。
テストをJSONエンコードして表示。
[{"__setProp":null,"__getProp":null,"ホゲ":1}]
おわりに
なでしこで、大石泉すき言語のインタプリタを作ることができた。
誤って無限ループするプログラムを実行してしまった際に停止させるのが難しいなど、実用性は怪しいかもしれない。
そして、なでしこにはまだまだ謎がいっぱい……?
