FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RustのパーサジェネレータPestに入門する (1. チュートリアルを試してみる)

Rustのパーサジェネレータの調査、少しツールを変えて、LALRPOPからPestに変えてみることにした。

PestもRustベースのパーサジェネレータで、文法ファイルの作り方も似ている気がする。別ファイルに文法ファイルを作ってそれを読み込ませることで解析を行うタイプだ。

docs.rs

さっそく使い方を確認してみる。サンプルの文法ファイルとして以下が定義されていた。

  • 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

alphadigitの定義についてはすぐにわかると思う。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

問題なく動作しているようだ。