先日 Ruby にマージされた LALR(1)パーサジェネレータ Lrama を使ってみました。
参考: RubyにlramaがマージされてBison依存がなくなった(RubyKaigi 2023)|TechRacho by BPS株式会社
できたもの
https://github.com/sonota88/vm2gol-v2-c/tree/alt-parser-lrama
alt-parser-lrama ブランチに mrcl_parser_lrama.y
が入っています。
概要
Mini Ruccola は私がコンパイラ実装に入門するために作った自作言語とその処理系です。原始的だけどその分入門者(=私)視点では分かりやすい、という方向性のものです。私でも作れる簡単なコンパイラ。
作ったときに書いた備忘記事:
コンパイラのパーサ部分は元々手書きの再帰下降パーサだったのですが、他のパーサライブラリも試したくて Racc 版と Parslet 版のパーサを以前書きました。
今回の Lrama 版はこのシリーズの延長です。
Mini Ruccola コンパイラはレキサ・パーサ・コード生成器が独立しています。そのため、レキサとコード生成器は Ruby 製のものをそのまま使い、パーサだけ別の言語で書いて一緒に動かすなんてことも可能です。
可能なのですが、C言語への移植版
github.com/sonota88/vm2gol-v2-c
を使った方が楽なので今回はこっちを使いました。
Racc 版パーサ実装と C移植版がすでにありますから、あとは Racc 版パーサを Lrama + C 向けに書き直せば一丁あがり、という寸法です。
たとえば以下は関数定義の規則とアクションの記述の比較です。
# Racc
func_def : "func" IDENT "(" args ")" "{" stmts "}"
{
_, fn_name, _, args, _, _, stmts, _, = val
result = ["func", fn_name, args, stmts]
}
// Lrama
func_def : TS_KW_FUNC TS_IDENT TS_PAREN_L args TS_PAREN_R TS_SYM stmts TS_SYM
// "func" fn_name "(" args ")" "{" stmts "}"
{
NodeList* func_def = NodeList_new();
NodeList_add_str(func_def, "func");
NodeList_add_str(func_def, $2);
NodeList_add_list(func_def, $4);
NodeList_add_list(func_def, $7);
$$ = func_def;
}
こんな感じで読み換えていきました。どちらも Yacc から派生した LALRパーサジェネレータなのでよく似ていますね。
メモ
- 1日くらいでババッと書いたものなので雑だったり手抜きで済ませている部分があります。一応動いてる、という程度の出来です。
- 警告もひとまず放置
- Yacc, Bison もそのうち触ってみようと思いつつ結局今まで触らずじまいだった
- 今回ちょっとやってみて雰囲気が知れてよかった
- Rubyソースコード完全解説の 第9章 速習yacc を読んだらとりあえずなんとかなった
- Rubyには慣れているけどCは分からないという状態で LALR パーサジェネレータに入門したい人は、まず Racc で入門するのが良いのではないかと思います。Ruby の知識だけで使えるので、いきなり Yacc や Bison や Lrama に挑戦するよりはお手軽かと。
- Racc の作者(青木峰郎さん)による解説本『Rubyを256倍使うための本 無道編』もまだ中古で入手可能(Amazon)
この記事を読んだ人は(ひょっとしたら)こちらも読んでいます
Racc の気持ちが分かるように↓こういう図を描かせてみたもの。
メソッドの再帰呼び出しについてすでに知っていれば1時間もかからず書ける内容。比較的簡単で時間がかからないので、LALR パーサにこだわる事情がなければ先に再帰下降パーサで入門するのも良いと思います。