かんたんな自作言語のコンパイラをいろんな言語で書いてみるシリーズ 20番目の言語はなでしこ3です。
ライフゲームのコンパイルが通ればヨシ、という程度の雑なものです。
できたもの
移植元
なでしこ3版のベースになっているバージョンは tag:62 のあたり
<自作言語処理系の説明用テンプレ>
自分がコンパイラ実装に入門するために作った素朴なトイ言語とその処理系です。簡単に概要を書くと下記のような感じ。
- 小規模: コンパイラ部分は 1,000 行程度
- pure Ruby / 標準ライブラリ以外のライブラリ不要
- x86風の独自VM向けにコンパイルする
- ライフゲームのために必要な機能だけ
- 変数宣言、代入、反復、条件分岐、関数定義
- 演算子:
+
,*
,==
,!=
のみ(優先順位は(
)
で明示) - 型なし(値は符号付き整数のみ)
- 作ったときに書いた備忘記事
-
本体には含めていない後付けの機能など
- 真偽値リテラル / break / if/else / 単項マイナス / Racc などを使って書いたパーサの別実装
- Ruby 以外の言語への移植(コンパイラ部分のみ)
- セルフホスト版
- 製作過程を知りたい場合は製作メモを見てください
<説明用テンプレおわり>
メモ
なでしこ3版のサイズ(行数)はこれくらい。1,000行切ってますね。
$ wc -l *.nako3 lib/*.nako3
365 codegen.nako3
110 lexer.nako3
422 parser.nako3
49 lib/utils.nako3
946 合計
- そんなに苦労しなかった
- 書き味は普通にスクリプト言語っぽい感じ
- 標準でJSON読み書きできる、ネストした配列が作れる
-
[1, "a", []]
のように JavaScript っぽくリテラルが書ける
- ファイル分割できない?
-
ナデシコ命令を使って分割したファイルの取込み(というか読み込んだコードの評価)ができないか軽く試したのですが、期待した動きにできなかったので、諦めて適当なスクリプト(
preproc.rb
)で前処理することに
-
ナデシコ命令を使って分割したファイルの取込み(というか読み込んだコードの評価)ができないか軽く試したのですが、期待した動きにできなかったので、諦めて適当なスクリプト(
引数+助詞、引数+助詞、 ... 関数。
のように書くのだ、ということをまず最初に知ると基本的な読み書きができるようになります。
命令文の基本的な文形 *
そして、なでしこのプログラムの構造は、以下のような形式になっています。
引数+助詞、引数+助詞、 ... 関数。
『「こんにちは」と表示』という命令文で言えば、「こんにちは」が引数で、「表示」が関数となります。
「読んで」が 読
と んで
に別れる、みたいなのもあって最初はよく分からなかった気がするので、助詞一覧にも目を通しておくとよいです。
予約語や助詞はハイライトがあるといいなと思って、 Emacs の generic-mode で色を付けて書いていました。最初は助詞一覧が頭に入ってないので、エディタのサポートがあると入門がスムーズになるように思います。Qiita のコードブロックでもサポートされるといいですね。
最初は慣れてなくて試行錯誤が少しありました、という例をメモ。
# 「変数宣言パース」はトークン列をパースして変数宣言文オブジェクトを返す自作関数
# 「文リスト」は配列
変数宣言パースして文リストに配列追加
変数宣言パース
関数の戻り値を 文リスト
に追加したいのですが、これは文法エラーになります。
マニュアルの 「配列追加」命令 の説明に書かれているように、AにBを
または Aへ
の形で引数を渡さなければいけない(関数定義で定められた通りに助詞を添えなければいけない)からです。
ふーむ、なるほど、ということで次のように一時変数を補って2行(2文?)に分けると AにBを配列追加
の形になり、動くようになります。
変数宣言パースして文に代入
文リストに文を配列追加
1行で書けないでしょうか?
# () はなくてもOK
文リストに(変数宣言パース)を配列追加
1行で書けるようになりました。でもなんか微妙ですね……
こういう場合は、「それ」を使うと AにBを配列追加
の形に持ち込むことができます。
変数宣言パースして、文リストにそれを配列追加
# 「Aに」「Bを」の順番は入れ替えてもよい
変数宣言パースして、それを文リストに配列追加
自然になりました。なるほど、こう書けばいいのかー。
なでしこ3には敬語があります。
なでしこv3.1.14以降、なでしこで礼節をわきまえたプログラムを書くことができるようになりました。
使ってみました。なでしこ3では礼節のあるコンパイラを書くことができます。
# parser.nako3
パースして抽象構文木に代入してください。
抽象構文木をJSONエンコードして、それを表示してください。お願いします。
# codegen.nako3
抽象構文木からコード生成してください。お願いします。
この記事を読んだ人はこちらも(たぶん)読んでいます