Implement nested capture groups, iterators
This commit is contained in:
+72
-18
@@ -1,4 +1,7 @@
|
|||||||
use std::ops::{Range, RangeInclusive};
|
use std::{
|
||||||
|
num::NonZero,
|
||||||
|
ops::{Range, RangeInclusive},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Parser {
|
pub struct Parser {
|
||||||
input: String,
|
input: String,
|
||||||
@@ -27,7 +30,7 @@ impl Parser {
|
|||||||
self.captures.truncate(sp.num_captures);
|
self.captures.truncate(sp.num_captures);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> Captures {
|
pub fn iter<'a>(&'a self) -> Captures<'a> {
|
||||||
Captures {
|
Captures {
|
||||||
input: &self.input,
|
input: &self.input,
|
||||||
raw: &self.captures,
|
raw: &self.captures,
|
||||||
@@ -52,34 +55,41 @@ impl<'a> Iterator for Captures<'a> {
|
|||||||
type Item = Capture<'a>;
|
type Item = Capture<'a>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
while let Some(x) = self.raw.get(self.index) {
|
let head = self.raw.get(self.index)?;
|
||||||
self.index = x.sibling_index;
|
let subtree_len = head.subtree_len.unwrap().get();
|
||||||
return Some(Capture {
|
let subtree_slice = &self.raw[self.index..self.index + subtree_len];
|
||||||
input: &self.input,
|
self.index += subtree_len;
|
||||||
raw: x,
|
Some(Capture {
|
||||||
});
|
input: &self.input,
|
||||||
}
|
raw: subtree_slice,
|
||||||
None
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Capture<'a> {
|
pub struct Capture<'a> {
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
raw: &'a RawCapture,
|
raw: &'a [RawCapture],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Capture<'a> {
|
impl<'a> Capture<'a> {
|
||||||
|
pub fn children(&self) -> Captures<'a> {
|
||||||
|
Captures {
|
||||||
|
input: &self.input,
|
||||||
|
raw: &self.raw[1..],
|
||||||
|
index: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> &'static str {
|
pub fn kind(&self) -> &'static str {
|
||||||
&self.raw.kind
|
&self.raw[0].kind
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span(&self) -> Range<usize> {
|
pub fn span(&self) -> Range<usize> {
|
||||||
self.raw.span.clone()
|
self.raw[0].span.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(&self) -> &'a str {
|
pub fn text(&self) -> &'a str {
|
||||||
let span = self.raw.span.clone();
|
&self.input[self.span()]
|
||||||
&self.input[span]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +97,7 @@ impl<'a> Capture<'a> {
|
|||||||
struct RawCapture {
|
struct RawCapture {
|
||||||
kind: &'static str,
|
kind: &'static str,
|
||||||
span: Range<usize>,
|
span: Range<usize>,
|
||||||
sibling_index: usize,
|
subtree_len: Option<NonZero<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Rule {
|
pub trait Rule {
|
||||||
@@ -271,11 +281,12 @@ where
|
|||||||
p.captures.push(RawCapture {
|
p.captures.push(RawCapture {
|
||||||
kind: self.0,
|
kind: self.0,
|
||||||
span: 0..0,
|
span: 0..0,
|
||||||
sibling_index: 0,
|
subtree_len: None,
|
||||||
});
|
});
|
||||||
if self.1.parse(p) {
|
if self.1.parse(p) {
|
||||||
|
let subtree_len = NonZero::new(p.captures.len() - index).unwrap();
|
||||||
p.captures[index].span = start_offset..p.offset;
|
p.captures[index].span = start_offset..p.offset;
|
||||||
p.captures[index].sibling_index = p.captures.len();
|
p.captures[index].subtree_len = Some(subtree_len);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
p.restore(save);
|
p.restore(save);
|
||||||
@@ -417,4 +428,47 @@ mod test {
|
|||||||
]
|
]
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_captures() {
|
||||||
|
let letter = || 'a'..='z';
|
||||||
|
let user = Tag("user", (letter, star!(letter)));
|
||||||
|
let domain = Tag("domain", (letter, star!(Opt("."), letter)));
|
||||||
|
let email = Tag("email", (user, "@", domain));
|
||||||
|
let rule = star!(Alt((email, Dot)));
|
||||||
|
let mut p = Parser::new("Send to alice@foo.net or bob@bar.com.");
|
||||||
|
assert!(rule.parse(&mut p));
|
||||||
|
|
||||||
|
// Make sure the capture groups were correct
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for email in p.iter() {
|
||||||
|
assert_eq!(email.kind(), "email");
|
||||||
|
for group in email.children() {
|
||||||
|
results.push(format!("{}: {}", group.kind(), group.text()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_debug_snapshot!(results, @r#"
|
||||||
|
[
|
||||||
|
"user: alice",
|
||||||
|
"domain: foo.net",
|
||||||
|
"user: bob",
|
||||||
|
"domain: bar.com",
|
||||||
|
]
|
||||||
|
"#);
|
||||||
|
|
||||||
|
// Make sure &str lifetimes outlive the Capture structs
|
||||||
|
let slices: Vec<&str> = p
|
||||||
|
.iter()
|
||||||
|
.flat_map(|email| email.children())
|
||||||
|
.map(|group| group.text())
|
||||||
|
.collect();
|
||||||
|
assert_debug_snapshot!(slices, @r#"
|
||||||
|
[
|
||||||
|
"alice",
|
||||||
|
"foo.net",
|
||||||
|
"bob",
|
||||||
|
"bar.com",
|
||||||
|
]
|
||||||
|
"#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user