use crate::{Ast, Matcher};
#[derive(Debug, Clone, PartialEq)]
pub enum LookaroundType {
PositiveLookahead,
NegativeLookahead,
PositiveLookbehind,
NegativeLookbehind,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Lookaround {
pub lookaround_type: LookaroundType,
pub pattern: Box<Ast>,
}
impl Lookaround {
pub fn new(lookaround_type: LookaroundType, pattern: Ast) -> Self {
Self {
lookaround_type,
pattern: Box::new(pattern),
}
}
pub fn matches_at(&self, text: &str, pos: usize, matcher: &Matcher) -> bool {
match self.lookaround_type {
LookaroundType::PositiveLookahead => {
self.check_lookahead(text, pos, matcher, true)
}
LookaroundType::NegativeLookahead => {
self.check_lookahead(text, pos, matcher, false)
}
LookaroundType::PositiveLookbehind => {
self.check_lookbehind(text, pos, matcher, true)
}
LookaroundType::NegativeLookbehind => {
self.check_lookbehind(text, pos, matcher, false)
}
}
}
fn check_lookahead(&self, text: &str, pos: usize, matcher: &Matcher, positive: bool) -> bool {
if pos > text.len() {
return false;
}
let matches = self.pattern_matches_at(text, pos, matcher);
if positive {
matches
} else {
!matches
}
}
fn check_lookbehind(&self, text: &str, pos: usize, matcher: &Matcher, positive: bool) -> bool {
if pos > text.len() {
return false;
}
let matches = self.find_match_ending_at(text, pos, matcher);
if positive {
matches
} else {
!matches
}
}
fn pattern_matches_at(&self, text: &str, pos: usize, matcher: &Matcher) -> bool {
if pos > text.len() {
return false;
}
let remaining = &text[pos..];
if let Some((start, _end)) = matcher.find(remaining) {
start == 0
} else {
false
}
}
fn find_match_ending_at(&self, text: &str, pos: usize, matcher: &Matcher) -> bool {
for start in 0..=pos {
if self.check_match_span(text, start, pos, matcher) {
return true;
}
}
false
}
fn check_match_span(&self, text: &str, start: usize, end: usize, matcher: &Matcher) -> bool {
if start > text.len() || end > text.len() || start > end {
return false;
}
let span = &text[start..end];
if let Some((match_start, match_end)) = matcher.find(span) {
match_start == 0 && match_end == span.len()
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Matcher;
#[test]
fn test_positive_lookahead() {
let lookaround = Lookaround::new(
LookaroundType::PositiveLookahead,
Ast::Literal("bar".to_string()),
);
let matcher = Matcher::Literal("bar".to_string());
assert!(lookaround.matches_at("foobar", 3, &matcher));
assert!(!lookaround.matches_at("foobaz", 3, &matcher));
}
#[test]
fn test_negative_lookahead() {
let lookaround = Lookaround::new(
LookaroundType::NegativeLookahead,
Ast::Literal("bar".to_string()),
);
let matcher = Matcher::Literal("bar".to_string());
assert!(lookaround.matches_at("foobaz", 3, &matcher));
assert!(!lookaround.matches_at("foobar", 3, &matcher));
}
#[test]
fn test_positive_lookbehind() {
let lookaround = Lookaround::new(
LookaroundType::PositiveLookbehind,
Ast::Literal("foo".to_string()),
);
let matcher = Matcher::Literal("foo".to_string());
assert!(lookaround.matches_at("foobar", 3, &matcher));
assert!(!lookaround.matches_at("bazbar", 3, &matcher));
}
#[test]
fn test_negative_lookbehind() {
let lookaround = Lookaround::new(
LookaroundType::NegativeLookbehind,
Ast::Literal("foo".to_string()),
);
let matcher = Matcher::Literal("foo".to_string());
assert!(lookaround.matches_at("bazbar", 3, &matcher));
assert!(!lookaround.matches_at("foobar", 3, &matcher));
}
}