⛓️

Rustで配列に対して一般の関数で正規表現風にマッチができるライブラリをつくった

2022/09/01に公開

vec-reg

https://crates.io/crates/vec-reg

https://docs.rs/vec-reg/0.7.0/vec_reg/

なに?

文字列ではなく、配列に対して1つ一つの要素のマッチを判定する関数やクロージャを使って正規表現風にマッチできるライブラリをつくりました

use vec_reg::{Regex, CompiledRegex, vec_reg};

let is_fizz = |x: &i32| x % 3 == 0;
let is_buzz = |x: &i32| x % 5 == 0;
let reg = vec_reg!(([is_fizz])((?:[is_buzz])(?P<"FizzBuzz">[|x| x % 15 == 0]))+).compile();   
// 部分マッチ
assert!(reg.is_match(&[1, 2, 3, 5, 15, 20]));
// 全体マッチ
assert!(reg.is_full_match(&[3, 5, 15, 10, 30]));

// マッチした範囲全体のみの取得
let find_result = reg.find(&[1,3,5,15]);
assert!(find_result.is_some());
assert_eq!(find_result.as_ref().unwrap().range(), 1..4);

let captures = reg.captures(&[1, 3, 5, 15, 10, 30, 2]);
assert!(captures.is_some());
// 0番目のキャプチャはマッチした範囲全体を指す
assert_eq!(captures.as_ref().unwrap().get(0).unwrap().range(), 1..6);

assert_eq!(captures.as_ref().unwrap().get(1).unwrap().range(), 1..2);
assert_eq!(captures.as_ref().unwrap().get(1).unwrap().values(), &[3]);

// 名前付きキャプチャは番号でも名前でも両方でアクセス可能
assert_eq!(captures.as_ref().unwrap().get(3).unwrap().values(), &[30]);
assert_eq!(captures.as_ref().unwrap().get(3).unwrap().range(), 5..6);

assert_eq!(captures.as_ref().unwrap().name("FizzBuzz").unwrap().values(), &[30]);
assert_eq!(captures.as_ref().unwrap().name("FizzBuzz").unwrap().range(), 5..6);

なんで?

特に理由はないです...
学生時代に正規表現とオートマトンを授業でならった時に、文字列ではなく関数で判定ができるような一般の値の列にたいして正規表現が書けるとおもしろいんじゃないかと思って、似たようなライブラリをHaskellで書いていたのですが
https://github.com/pocket7878/mini-reg

それをふと思いたって真面目にバックトラック無しのVirtual Machine型で書きなおしつつ、正規表現らしい構文をマクロで提供できるのではないかとおもって実装してみました。

対応している構文

Rustのregexライブラリや、google/re2を参考にしています。
バックトラックが必要になる後方参照は実装していませんが、それ以外の配列に対するマッチングとして意味のありそうな構文はほぼサポートしています。

Syntax Description
[function_name] Match any values that satisfied given function.
[|x| *x == 1] Match any values that satisfied given closure.
[^function_name] Match any values that not satisfied given function.
[^|x| *x == 1] Match any values that not satisfied given closure.
. Match any values.
^ a beginning of input
$ a end of input
(R) numbered capturing group (submatch)
(?:R) non-capturing group
(?P<"name">R) named & numbered capturing group (submatch)
RS R followed by S
R|S R or S (prefer R)
R? zero or one R, prefer one
R?? zero or one R, prefer zero
R* zero or more R, prefer more
R*? zero or more R, prefer fewer
R+ one or more R, prefer more
R+? one or more R, prefer fewer
R{n,m} n or n + 1 or ... or m, prefere more
R{n,m}? n or n + 1 or ... or m, prefere fewer
R{n,} n or more R, prefere more
R{n,}? n or more R, prefere fewer
R{n} exactly n R
R{n}? exactly n R

対応している機能

pub trait CompiledRegex<I> {
    fn is_match(&self, input: &[I]) -> bool;
    fn find(&self, input: &'t [I]) -> Option<Match<'t, I>>;
    fn captures(&self, input: &'t [I]) -> Option<Captures<'t, I>>;

    fn is_full_match(&self, input: &[I]) -> bool { ... }
}

正規表現による部分マッチと、完全マッチ、最左からのマッチした範囲、キャプチャしたグループの場所とスライスの取りだし等に対応しています。

今後

regexを参考にサポートしている検索機能等の拡充をもう少しだけしていきたいとおもっています。

Discussion