Combineって?
Rustのパーサコンビネータライブラリ。
Haskellのparsecを参考に作ったらしい。
LL構文解析。
インストール
Cargo.tomlに
[dependencies]
combine = "ほしいバージョン"
みたいにして書く
ほしいバージョンは以下から確認して選んだらいい
https://github.com/Marwes/combine/releases
一応現時点の最新バージョンである3.3.0で説明していく。
早速使う
ドキュメントも載せておく。
extern crate combine;
use combine::char::char;
use combine::{Parser, many1};
fn main() {
let r = many1::<String, _>(char('a').or(char('b'))).parse("aabbaaaaaabababa");
match r {
Ok((value, _remaining_input)) => println!("{:?}", value),
Err(err) => println!("{}", err),
}
}
これは、任意個のaまたはbにマッチするパーサだ。
char('a')とかにするとaにマッチするパーサが作れる。
orで「または」を表現できる。
many1で複数回続くことを表現できる。
many1の<String,_>の部分の役割は、マッチした要素(今回だとchar型)の集合をどうやって表現するかを指定することだ。
String以外にもVec<char>とかも指定できる。
FromIterator<char>を実装している型であれば指定できるらしい。
作ったパーサはparse(パーサ対象)でパースし結果を得れる。
パーサ対象の型は&strだ。それ以外でもいけるがやり方よくわからん。
結果はResult型で、Okの場合、Ok(マッチしたもの,余ったもの)が返ってくる。
再帰したパーサを作る
パーサを再起するには、関数に包む必要がある。
それ用の便利なマクロがあるのでそれを使おう。
# [macro_use]
extern crate combine;
use combine::Parser;
use combine::char::char;
use combine::parser::choice::optional;
use combine::parser::item::value;
use combine::stream::Stream;
use std::option;
parser!{
fn paren[I]()(I) ->i32
where [I: Stream<Item=char>]
{
char('(').with(optional(paren())).skip(char(')')).then(
|op|
{
match op {
Option::Some(x)=> value(x+1),
Option::None=>value(1)
}
}
)
}
}
fn main() {
let r = paren().parse("((()))");
match r {
Ok((value, _remaining_input)) => println!("{:?}", value),
Err(err) => println!("{}", err),
}
}
これはネストしたカッコのネスト数を出力するパーサだ。
実行結果は3になった。
parser!がパーサを関数化するマクロだ。
fn paren[I]()(I) ->i32
where [I: Stream<Item=char>]
この部分について軽く説明。
Iは入力される値の型を示している。今回だと&strだ。
i32はパース結果の型だ。
それ以外はよくわからん。
parenの内部でparenを呼び出しているので無限再帰しそうだが、一旦関数ポインタに入れて工夫しているのだろう。無限ループしたりはしない。
マクロマジックだ。withやskipやvalue,optionalについては各自で調べてほしい。
参考URLだけ載せておく
https://docs.rs/combine/3.3.0/combine/parser/item/fn.value.html
https://docs.rs/combine/3.3.0/combine/parser/trait.Parser.html
https://docs.rs/combine/3.3.0/combine/parser/choice/fn.optional.html