本ライブラリは、Rustで構築されたシンプルかつ強力な関数型パーサコンビネータ(Parser Combinator)ライブラリです。 モナド、アプリカティブファンクター、オルタナティブといった関数型プログラミングの概念をRustの型システム上に実現しており、豊富な演算子オーバーロードによって直感的かつ宣言的にパーサを記述できます。
すべてのパーサの基盤となる中心的な構造体です。内部的には文字列のスライス &str を受け取り、パース結果の型 A と「残りの文字列」のペアを返す関数を保持しています。
pub struct Parser<A: 'static>(Rc<dyn Fn(&str) -> Option<(A, &str)>>);- 安全な共有: 内部で
Rc(参照カウント)を使用しているため、パーサのクローンや組み合わせを安価に行うことができます。
本ライブラリの最大の特徴は、Rustの組み込み演算子をオーバーロードすることで、Haskellのパーサコンビネータに近い直感的な記述ができる点です。
| 演算子 | トレイト | 戻り値の型 | 説明 / 対応する概念 |
|---|---|---|---|
& |
BitAnd |
Parser<(A, B)> |
順次実行 (Sequence): 左のパースに成功した後、右を続けて実行し、両方の結果をタプルで返します。 |
| |
BitOr |
Parser<Either<A, B>> |
排他選択 (Either Choose): 左のパースを試し、失敗したら右を試します。結果は Either 型に包まれます。 |
+ |
Add |
Parser<A> |
選択 (Alternative): 左のパースを試し、失敗したら右を試します。両者の型 A は共通である必要があります。 |
* |
Mul |
Parser<Vec<A>> |
連結: パーサの結果、または結果のリスト(Vec<A>)に、次のパース結果を追加して要素をまとめます。 |
>> |
Shr |
Parser<B> |
右を流す: 左と右を順にパースしますが、左の結果を破棄して右の結果のみを返します。 |
<< |
Shl |
Parser<A> |
左を流す: 左と右を順にパースしますが、右の結果を破棄して左の結果のみを返します。 |
カスタムのパース関数から新しい Parser インスタンスを生成します。
パース処理を実行します。成功した場合は Some((結果, 残りの文字列)) を返し、失敗した場合は None を返します。
パースに成功した際の中身の結果に対して関数 f を適用し、別の型 B へ変換します(Functor)。
入力を一切消費せず、常に指定された値 x をパース結果として返すパーサを生成します(Applicative)。
関数を返すパーサ pf の結果を、自身のパーサの結果に適用します(Applicative)。
現在のパース結果を利用して、次に実行するパーサを動的に決定・接続します(Monad)。
既存のパーサを組み合わせて、より複雑なリピートや条件付けを行うためのメソッドです。
常にパースに失敗する空のパーサを返します。
自身が失敗した際、バックトラックして other を実行します(+ 演算子の実態)。
自身と other のいずれか成功した方の結果を Either 型で返します(| 演算子の実態)。
対象のパーサを0回以上繰り返し適用し、結果をリストとして収集します。
対象のパーサを1回以上繰り返し適用します。1回もマッチしない場合は失敗します。
パースが成功しても失敗しても全体のパースとしては成功扱いとし、結果を Option<A> で返します。
区切り文字(パーサ sep)で区切られた、0回以上の要素の並びをパースします。
区切り文字で区切られた、1回以上の要素の並びをパースします。
左結合の演算子(op)を挟んだ1回以上のリピートをパースします。数式の四則演算など、左優先の結合(例:1 - 2 - 3 は (1 - 2) - 3)を表現する際に使用します。
右結合の演算子(op)を挟んだ1回以上のリピートをパースします。
自身のパースに成功した後、続く末尾の空白文字(スペース、タブ、改行等)を自動的に消費します。
特定の文字やパターンをパースするための部品となる関数群です。
再帰的な文法(例:式の中にさらに括弧で囲まれた式が登場する等)を記述する際、評価を遅延させて無限再帰(スタックオーバーフロー)を防ぎます。
条件 f を満たす1文字を消費します。
指定された1文字 c と完全に一致する文字を消費します。
0 から 9 までの数字1文字にマッチします。
アルファベット1文字にマッチします。
アルファベット、または数字1文字にマッチします。
連続する数字をパースし、符号なし整数(i64)に変換して返します。
指定された文字列 expected と完全に一致する文字列を消費します。
任意の1文字を消費します。
与えられたリスト chars に含まれるいずれかの1文字にマッチします。
連続する空白類文字(スペース、タブ、改行など)を消費します。
入力を全く消費せず、何もせず成功(())を返します。
正規表現オブジェクト re にマッチする文字列を消費します。
一般的なプログラミング言語の識別子(例: x_1, total_score)にマッチします。(先頭はアルファベットかアンダースコア、2文字目以降は英数字・アンダースコア)
本ライブラリを使って、演算子の優先順位(足し引きより掛け割りが優先)や括弧を考慮した、本格的な数式パーサを構築するコード例です。
use your_crate_name::{Parser, number, charp, lazy, Either};
// 式 (足し算・引き算: 左結合)
fn expr() -> Parser<i64> {
term().chainl1(
(charp('+') | charp('-')).lexeme().map(|op| move |a, b|
match op {
either::Left(_) => a + b,
either::Right(_) => a - b
}
)
)
}
// 項 (掛け算・割り算: 左結合)
fn term() -> Parser<i64> {
primitive().chainl1(
(charp('*') | charp('/')).lexeme().map(|op| move |a, b|
match op {
either::Left(_) => a * b,
either::Right(_) => a / b
}
)
)
}
// 原子式 (数値、または括弧で囲まれた式)
fn primitive() -> Parser<i64> {
// 数値単体、または `(` + 式 + `)` の構造
number().lexeme() + (charp('(').lexeme() >> lazy(expr) << charp(')').lexeme())
}
fn main() {
let parser = expr();
// パース実行結果の検証
let result1 = parser.run("3 + 4 * 2");
assert_eq!(result1, Some((11, ""))); // 4 * 2 が優先される
let result2 = parser.run("(3 + 4) * 2");
assert_eq!(result2, Some((14, ""))); // 括弧内が優先される
}