パーサジェネレータについて調べていると、以下のようなGitHubリポジトリを見つけた。
LALRPOPという、Rust向けのパーサジェネレータらしい。結構☆が付いているのでかなり使い物になりそうな気がする。試してみよう。
チュートリアルは以下のページから参照できる。
基本的な考え方
LALRPOPでは、パーサの核になる記述をxxx.lalrpop
というファイルに格納する。これはふたを開けてみればRustのソースコードそのもので、例えば以下のようなものを記述する。
calculator1.lalrpop
use std::str::FromStr; grammar; pub Term: i32 = { <n:Num> => n, "(" <t:Term> ")" => t, }; Num: i32 = <s:r"[0-9]+"> => i32::from_str(s).unwrap();
次に、プロジェクト内のCargo.toml
を編集しよう。そのままチュートリアルに則って以下のような記述に変える。
[package] name = "calculator" version = "0.1.0" authors = ["msyksphinz <msyksphinz.dev@gmail.com>"] edition = "2018" [build-dependencies] lalrpop = "0.17.2" [dependencies] lalrpop-util = "0.17.2" regex = "0.2.1"
さらにbuild.rs
にも以下を追加することで準備完了だ。
extern crate lalrpop; fn main() { lalrpop::process_root().unwrap(); }
LALRPOP文法の読み方
それではさっそく上記のcalculator1.lalrpop
の読み方を勉強する。上記のcalculator1.lalrpop
は2つの文法を定義している。
Term
: i32を返す。Num
もしくは(Term)
となる。Num
: i32を返す。10進数の数値を示す。
LALRPOPの文法では、<変数名:文法名>
とすることでその変数名を後で参照することができるようになっている。
この文法は簡単なのですぐに理解できたが、例えば以下のような文法を受け入れることができる。
22
(22)
((22))
(((((22)))))
テストする
さて、main.rs
に以下のテストを追加して実行してみる。
#[macro_use] extern crate lalrpop_util; lalrpop_mod!(pub calculator1); // synthesized by LALRPOP #[test] fn calculator1() { assert!(calculator1::TermParser::new().parse("22").is_ok()); assert!(calculator1::TermParser::new().parse("(22)").is_ok()); assert!(calculator1::TermParser::new().parse("((((22))))").is_ok()); assert!(calculator1::TermParser::new().parse("((22)").is_err()); }
TermParser
というのは、Term
文法から始まるParserであることを示す。つまりNum
文法をテストしたい場合はNumParser
とすれば良い。
$ cargo test
Finished dev [unoptimized + debuginfo] target(s) in 2.02s Running target/debug/deps/calculator-06b0a7843f952790 running 1 test test calculator1 ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out