Rustで言語処理系を作ってみるという試み
はじめに
Rustでプログラミング言語の処理系(toylang:名前は未定のまま引きずっている)を作成した。 厳密には作成途中ではあるが、その経過や設計について書きたいと思う。
https://github.com/suma/toylang
最初に断っておくと、私はプログラミング言語や型システムの専門家ではない。アマチュアである。 また、これは趣味の活動によって開発された(厳密には、「されている」)ソフトウェアである。業務で開発した物ではない。
趣味とはいえ、私はソフトウェアエンジニアとしてのプロの経歴が10年以上になる。 実装レベルで手抜きをしてしまってる箇所も幾分あるが、今回作成した処理系の設計・デザインチョイスといった部分では、こだわり・挑戦してみたいことなどを優先している。といっても、試行錯誤のひとつなので特別お気に入りでない限り特筆しない。 また、こだわりだけではいっこうに開発が進まなかったため、実用レベルのソフトウェアを開発する気概を持ちつつも、途中からClaude Codeの利用が始まると妥協を多く入れていった。まぁ、何しろ言語処理系を作るのはかなり筋肉(と表現させてもらう)が要るからね…。
作った言語について
言語仕様は、Rust, Go, Pythonなどからいいとこ取り、気に入った文法や機能を試しに実装している段階にある。繰り返しになるが、まだ開発中である。
将来的にはLLVMバックエンドなど作りたいところであるが、いまはフロントエンドやインタプリタを作って動作確認・お試しをしている段階にある。
フロントエンドは何でもよいとか、LLMで生成するからなどという説があるかもしれないが、UXであるとか、DSLとしての使い方など、見た目(文法)はこだわれるポイントである。 LLM(コーディングエージェント)が存在するからといって、オフライン環境ではローカルLLMが必要になるだろうし、ネット上に存在しないハードウェアのソフトを実装するとしたらどうなるだろうか。
スタート地点 rflex
詳しくは Rustでコンパイラ作り part1に譲るが、自作のコンパイラを作りたいと2019年頃から考えていた。 その頃からパーサは手書きにすると決めていたため、せめてLexer生成器だけは自動生成にしようとrflexを作成した。
再帰下降パーサとFlatten AST
再帰下降パーサを書く時点からFlatten ASTな形式であると決めていた。 ふつうAST(抽象構文木)を木構造で表現するが、実装上は配列に格納する形としていた。木構造を表現しなくていいため、そのためのオーバーヘッドや、配列形式によるメモリキャッシュの活用が想定された。
再帰下降パーサで文法を記述するのに、約2,3年はかかった。 理由は難しかったからではなく、実装の手間が大きかったことによる。 文法が定まってない間は、よい子は余り真似しない方がいいかもしれない。
転換点 1. Claude Code
再帰下降パーサで文法をおおよそ定義出来てきた頃、Claude CodeなどのAIエージェント(コーディングエージェント)が流行りだした。 これは良いと私も採用を決定し、手書きのフロントエンド(再帰下降パーサ、型チェッカー)などの書き換えが進んでいった。 インタプリタもほぼClaude Codeを利用した気がするし、README.mdもである。
LLM(Claude Code)の利用テクニックはここで書いても仕方ないと思うが、気にしていた点として、以下の点があげられる。
- コンテキストは長く持たない(
/clear
コマンドを利用する) - コードが複雑になる前にリファクタリングをする
- サブエージェント の活用(git-add, git-commitを作成した)
- Enter連打(あるいは自動)でコードを入力させ、早く実行させたくなるが、Claude Codeが作成したコードの意味をなるべくたずねる、設計上の選択を聞く、オススメの選択を聞くといった行動をとること
転換点 2. Structure of Arrays
SNS上でData Oriented DesignとZig言語、その実装パターンであるStructure of Arraysに感銘を受け、ASTのデータの持ち方をFlatten ASTから変更した。
リファクタリングにもClaude Codeを使った。 フロントエンドのパース、型チェックの速度はベンチマーク上で約20倍にもなったので驚愕した。 正直なところ実用的なコードが無い以上、ここでベンチマークの結果についてあれこれ語っても仕方ない面もあるが、コンパイラという種類のソフトウェアの設計(実装パターン)でさえ10倍以上の速度差が出ることについて、やっぱりソフトウェア開発は面白いなと思った。
まとめ(現在・未来)
こだわりの機能としては、
- 配列のスライス (絶対欲しい人がいる、自分では使わないがそう思った)
お試しで作ってみたのは、
- Lua backend
- Generics
Claude Codeの甘言に乗せられてしまい、関数と構造体にGenerics機能を追加してしまった。型チェッカーが重くなり、型推論も時間がかかっているはずである。
本当はこだわりポイントとか語って気持ちよくなろうと思っていたが、表層をなぞるだけで自分がお腹いっぱいになってしまった。 本当にプログラミング言語として成功するには、FFIやツールチェインも必要そう。もしくは、ゲームエンジンに採用されるようなアプローチ(としてLua backendありかなと試しに作った)も考えられるかもしれない。
まとめると、まずは自分が心地よいコードを書けて、なおかつ近年の研究課題 などもアプローチできるとなお面白いんじゃないかと思う。
課題
- 言語組み込みの関数、クラスを作るのは課題である
- なるべく楽に言語処理系内に組み込みたいというモチベーション
- FFIを上げたが、外部ライブラリとの連携も肝である
- 研究っぽい難しい課題にチャレンジするとなお面白いかも
参考
ウェブ
書籍
他にもたくさん書籍はあるが、開発中に読んだものとして、
- Rustで作るプログラミング言語 —— コンパイラ/インタプリタの基礎からプログラミング言語の新潮流まで