Rustのパーサジェネレータの調査、少しツールを変えて、LALRPOPからPestに変えてみることにした。
PestもRustベースのパーサジェネレータで、文法ファイルの作り方も似ている気がする。別ファイルに文法ファイルを作ってそれを読み込ませることで解析を行うタイプだ。
さっそく使い方を確認してみる。サンプルの文法ファイルとして以下が定義されていた。
src/ident.pest
alpha = { 'a'..'z' | 'A'..'Z' } digit = { '0'..'9' } ident = { (alpha | digit)+ } ident_list = _{ !digit ~ ident ~ (" " ~ ident)+ } // ^ // ident_list rule is silent which means it produces no tokens
alpha
とdigit
の定義についてはすぐにわかると思う。ident
についても正規表現が示している通りだ。ident_list
については、
- 先頭は
digit
で始まらず、ident
に続く。 - 空白を置いて、
ident
を繰り返す。
ということを表現している。なるほど明快で分かりやすい。
これをParseするコードは以下のようになる。
src/main.rs
extern crate pest; #[macro_use] extern crate pest_derive; use pest::Parser; #[derive(Parser)] #[grammar = "ident.pest"] struct IdentParser; fn main() { let pairs = IdentParser::parse(Rule::ident_list, "a1 b2").unwrap_or_else(|e| panic!("{}", e)); // Because ident_list is silent, the iterator will contain idents for pair in pairs { // A pair is a combination of the rule which matched and a span of input println!("Rule: {:?}", pair.as_rule()); println!("Span: {:?}", pair.as_span()); println!("Text: {}", pair.as_str()); // A pair can be converted to an iterator of the tokens which make it up: for inner_pair in pair.into_inner() { match inner_pair.as_rule() { Rule::alpha => println!("Letter: {}", inner_pair.as_str()), Rule::digit => println!("Digit: {}", inner_pair.as_str()), _ => unreachable!() }; } }
IdentParser::parse(Rule::ident_list, "a1 b2")
で a1 b1
という文字列をParsingしている。それ以降のfor文はそれ以外をすべて確認しているだけである。Parsingの結果はどうもリストの形式で格納されているようで、さらにinner_pair
の内部をfor文で確認し、ルール別にmatch文を適用してprintln!
で表現している。
次に、前回問題になった文法をParseしてみる。
src/ident.pest
alpha = { 'a'..'z' | 'A'..'Z' } digit = { '0'..'9' } digit_head = { '1'..'9' } pos_int = { digit_head ~ (digit)+ } digit_int = { digit+ }
テストを作って実行してみる。
let pairs_pos_int = IdentParser::parse(Rule::pos_int, "99").unwrap_or_else(|e| panic!("{}", e)); println!("pairs_pos_int = {}", pairs_pos_int.as_str()); let pairs_digit_int = IdentParser::parse(Rule::digit_int, "0123").unwrap_or_else(|e| panic!("{}", e)); println!("pairs_digit_int = {}", pairs_digit_int.as_str()); let pairs_digit_int = IdentParser::parse(Rule::digit_int, "0123456").unwrap_or_else(|e| panic!("{}", e)); println!("pairs_digit_int = {}", pairs_digit_int.as_str());
$ cargo run
pairs_pos_int = 99 pairs_digit_int = 0123 pairs_digit_int = 0123456
問題なく動作しているようだ。