言語処理系を作っていたとき考えていたこと
はじめに
Rustで言語処理系を作ってみるという試み の続きである。 どこにこだわり、どういう理由でそんなプログラミング言語の文法及び実装にしたのかということについて記述する。
設計、こだわりポイント
- 言語
- 静的型付け言語
- syntax
- Go, Python風に改行で1文を区切る
- if/else/whileなどの条件の括弧
()
は省略したい- GoとかRust風
- 追加のトレードオフ:
if variable_name {
のvariable_name {
が構造体リテラルに誤認識してしまった。パーサーの追加修正あり
- if/elseには必ず
{}
を付ける- 追加のトレードオフ
- elifキーワード (else if)の導入
if x {} else { if y {} }
のようなコードを防ぐため
- 追加のトレードオフ
- 型推論
val
,var
の型宣言を省略可能。また、後続の変数(代入など)から型推論できる- Rustっぽいですね
- エラー処理
- 文法や型チェックでエラーが出たとき、エラーの位置を表示できる
- エラーレポートの表示(行番号、位置)
- 内部構造
- パーサーでは再帰を多用しているが、無限再帰に陥る可能性がある。無限再帰予防の措置(カウントしている)がある
- String interningの利用。ソースコード中には、変数や関数などのidentifier、文字列リテラルなどあらゆるところでキーワードトークン以外の文字列が出てくる。そこで、文字列はSymbol(int型とか)で表現することで格納・比較がやりやすくなる。メモリ消費量も抑える(可能性がある)ことができてお得だ。
- 将来
- エラー処理のためにトークン及びASTレベルでコードの位置情報を保持しているが、LSP対応とかlinterとか考えると保持する利点はありそうである。
- Frontendは並列化(マルチスレッド化)したいとかそういう野望もある。現状は、String interningや型チェック時のシンボル探索などをどう並列化するとよいか考え中でもある。しかしClaude Codeがサクッと答えを出してきそうなポイントでもある。
- Claude Codeによると、Rust (rustc), TypeScript Compiler (tsc), Go Compiler - パッケージレベル, Swift Compiler - ファイルレベル などで並列化をサポートしているらしい。
実装・コンポーネントレベルのはなし
Rustの実装レベル、コンポーネントレベルでどのようにしているか書く。
- Lexer
- 以下のトークンに分割するのがLexerの役目。
- キーワード、コメント、数値リテラル、文字列リテラル
- すべてのトークンは文字列の位置情報を持っている。たしか、ソースの文字列の開始位置とその長さを持っていたと思う。
- 以下のトークンに分割するのがLexerの役目。
- Parser
- 当初はすべて手書きだったが、気づいたらClaude Codeに書き換えられていた。ちょっとむなしい。
- 再帰下降パーサーであるが、再帰の箇所では上に書いたように再帰回数のカウントがあり、スタックオーバーフロー防止になっている。Claude Codeの実装になるのと、そんな難しいアイディアではないが初出はどこだろうね。
- 文法エラーの複数取得は意外と簡単なところもあり、次に出現するトークン(予定)と違ったらエラーではあるが、そこはエラーは無かったことにして後続の処理へ続けさせ、1個でもエラーが貯まっていれば文法エラーがあると判別できる(理論上は、、、もうちょっと難しかったかもしれない)。
- TypeChecker
- TypeCheckerという仰々しい名前ではあるが、以下の機能も行っている。
- 型チェック
- 変数の存在管理(寿命、スコープ)
- モジュールのインポートとそのシンボル管理
- ビルトインのモジュール・関数の提供
- TypeCheckerという仰々しい名前ではあるが、以下の機能も行っている。
- Backend
- Interpreter (走査=walkで、AST walk interpreterと言う分類らしい)
- バックエンドを実装したとき、モデルケースとなる正解のインタプリタがあると便利だろうと思い作成した。
- 実際のところ、自分では使わないがテストを作って実行するのに非常に役に立つ存在となった。また、ベンチマークにもよく使っている。
- Lua backen
- 作りかけ。luaファイル(テキスト)を生成する
- 特にLua JITではビット演算のオペレーターがなかった(たぶん)ため、ビット演算をLua JIT提供のライブラリを使うことができるようにした。もしくは、Lua標準の機能を使うか、コード生成時にを使うか切り替え可能なバックエンドにしている
- Interpreter (走査=walkで、AST walk interpreterと言う分類らしい)
まとめ
雑に整理してみたが、こんなことを考えたり試行錯誤しながら実装していた。