use crate::ast::node::{Pattern, PatternKind};
pub fn inject_lookahead(patterns: Vec<Pattern>) -> Vec<Pattern> {
let mut optimized = Vec::with_capacity(patterns.len());
let mut pending_capture: Option<Pattern> = None;
for pattern in patterns {
match &pattern.kind {
PatternKind::Literal(keyword) => {
if let Some(Pattern {
kind: PatternKind::Capture(capture),
span,
meta,
}) = pending_capture
{
let mut optimized_capture = capture.clone();
optimized_capture.edge = Some(keyword.clone());
optimized.push(Pattern {
kind: PatternKind::Capture(optimized_capture),
span,
meta,
});
} else if let Some(other) = pending_capture {
optimized.push(other);
}
pending_capture = None;
optimized.push(pattern);
}
PatternKind::Capture(..) => {
if let Some(prev) = pending_capture {
optimized.push(prev); }
pending_capture = Some(pattern);
}
_ => {
if let Some(prev) = pending_capture {
optimized.push(prev);
}
pending_capture = None;
optimized.push(pattern);
}
}
}
if let Some(last) = pending_capture {
optimized.push(last);
}
optimized
}
#[cfg(test)]
mod tests {
use super::*;
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
parse::{ParseStream, Parser},
Result,
};
use crate::{
ast::{
capture::Capture,
keyword::Keyword,
node::{Pattern, PatternKind},
},
syntax::context::ParseContext,
};
fn parse_capture(input: TokenStream, ctx: &mut ParseContext) -> Result<Capture> {
let parser = move |input: ParseStream| Capture::parse(input, ctx);
parser.parse2(input)
}
#[test]
fn test_inject_lookahead() {
let ctx = &mut ParseContext::default();
let input = quote!(#(x: Ident));
let capture: Capture = parse_capture(input, ctx).unwrap();
let pattern_capture = PatternKind::Capture(Box::new(capture));
let pattern_literal = PatternKind::Literal(Keyword::Rust(",".to_string()));
let patterns = vec![
Pattern {
kind: pattern_capture.clone(),
span: Span::call_site(),
meta: None,
},
Pattern {
kind: pattern_literal.clone(),
span: Span::call_site(),
meta: None,
},
];
let optimized = inject_lookahead(patterns);
assert_eq!(optimized.len(), 2);
if let PatternKind::Capture(cap) = &optimized[0].kind {
let Capture {
edge: Some(Keyword::Rust(s)),
..
} = *cap.clone()
else {
panic!("Wrong lookahead type");
};
assert_eq!(s, ",");
} else {
panic!("Lookahead not injected");
}
let patterns_consecutive = vec![
Pattern {
kind: pattern_capture.clone(),
span: Span::call_site(),
meta: None,
},
Pattern {
kind: pattern_capture.clone(),
span: Span::call_site(),
meta: None,
},
];
let optimized_consecutive = inject_lookahead(patterns_consecutive);
if let PatternKind::Capture(cap) = &optimized_consecutive[0].kind {
if let Capture { edge: Some(_), .. } = **cap {
panic!("Should not inject lookahead when followed by another capture");
};
}
let patterns_end = vec![Pattern {
kind: pattern_capture.clone(),
span: Span::call_site(),
meta: None,
}];
let optimized_end = inject_lookahead(patterns_end);
if let PatternKind::Capture(cap) = &optimized_end[0].kind {
if let Capture { edge: Some(_), .. } = **cap {
panic!("Should not inject lookahead at end of stream");
};
}
}
#[test]
fn test_inject_lookahead_complex_sequence() {
let ctx = &mut ParseContext::default();
let cap_a = parse_capture(quote!(#(a: Ident)), ctx).unwrap();
let lit_comma = PatternKind::Literal(Keyword::Rust(",".to_string()));
let cap_b = parse_capture(quote!(#(b: Ident)), ctx).unwrap();
let lit_semi = PatternKind::Literal(Keyword::Rust(";".to_string()));
let patterns = vec![
Pattern {
kind: PatternKind::Capture(Box::new(cap_a)),
span: Span::call_site(),
meta: None,
},
Pattern {
kind: lit_comma,
span: Span::call_site(),
meta: None,
},
Pattern {
kind: PatternKind::Capture(Box::new(cap_b)),
span: Span::call_site(),
meta: None,
},
Pattern {
kind: lit_semi,
span: Span::call_site(),
meta: None,
},
];
let optimized = inject_lookahead(patterns);
assert_eq!(optimized.len(), 4);
if let PatternKind::Capture(c) = &optimized[0].kind {
assert_eq!(c.edge, Some(Keyword::Rust(",".to_string())));
} else {
panic!("Expected Capture at index 0");
}
if let PatternKind::Capture(c) = &optimized[2].kind {
assert_eq!(c.edge, Some(Keyword::Rust(";".to_string())));
} else {
panic!("Expected Capture at index 2");
}
}
#[test]
fn test_inject_lookahead_interrupted_by_group() {
let ctx = &mut ParseContext::default();
let cap_a = parse_capture(quote!(#(a: Ident)), ctx).unwrap();
let group = PatternKind::Group {
delimiter: proc_macro2::Delimiter::Parenthesis,
children: vec![],
};
let lit_comma = PatternKind::Literal(Keyword::Rust(",".to_string()));
let patterns = vec![
Pattern {
kind: PatternKind::Capture(Box::new(cap_a)),
span: Span::call_site(),
meta: None,
},
Pattern {
kind: group,
span: Span::call_site(),
meta: None,
},
Pattern {
kind: lit_comma,
span: Span::call_site(),
meta: None,
},
];
let optimized = inject_lookahead(patterns);
if let PatternKind::Capture(c) = &optimized[0].kind {
assert!(
c.edge.is_none(),
"Capture should not consume literal across a group"
);
}
}
}