FPGA開発日記

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

RustのパーサジェネレータLALRPOPに入門する(1. チュートリアルを読む)

パーサジェネレータについて調べていると、以下のようなGitHubリポジトリを見つけた。

LALRPOPという、Rust向けのパーサジェネレータらしい。結構☆が付いているのでかなり使い物になりそうな気がする。試してみよう。

github.com

チュートリアルは以下のページから参照できる。

lalrpop.github.io

基本的な考え方

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