use crate::index::IndexError;
pub const ALL_PROPERTIES: &str = r"^[^\/]*$";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NamePattern {
text: String,
parent_path: String,
expression: Alternation,
}
impl NamePattern {
pub fn parse(definition_path: &str, text: &str) -> Result<Self, IndexError> {
let (parent_path, name) = if text == ALL_PROPERTIES {
(String::new(), text)
} else {
(parent_path_of(text), name_of(text))
};
let expression =
Parser::parse(name).map_err(|reason| IndexError::UnsupportedNamePattern {
definition_path: definition_path.to_owned(),
pattern: text.to_owned(),
reason,
})?;
Ok(Self {
text: text.to_owned(),
parent_path,
expression,
})
}
#[must_use]
pub fn text(&self) -> &str {
&self.text
}
#[must_use]
pub fn matches(&self, property_path: &str) -> bool {
if self.parent_path != parent_path_of(property_path) {
return false;
}
let name: Vec<char> = name_of(property_path).chars().collect();
self.expression.matches_whole(&name)
}
}
fn parent_path_of(path: &str) -> String {
match path.rfind('/') {
Some(0) => "/".to_owned(),
Some(at) => path[..at].to_owned(),
None => String::new(),
}
}
fn name_of(path: &str) -> &str {
match path.rfind('/') {
Some(at) => &path[at + 1..],
None => path,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Alternation(Vec<Sequence>);
#[derive(Clone, Debug, PartialEq, Eq)]
struct Sequence(Vec<Repeat>);
#[derive(Clone, Debug, PartialEq, Eq)]
struct Repeat {
atom: Atom,
quantifier: Quantifier,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Quantifier {
One,
ZeroOrMore,
OneOrMore,
ZeroOrOne,
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum Atom {
Literal(char),
Any,
Class(CharacterClass),
Group(Alternation),
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct CharacterClass {
negated: bool,
members: Vec<ClassMember>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ClassMember {
Single(char),
Range(char, char),
}
impl CharacterClass {
fn contains(&self, character: char) -> bool {
let held = self.members.iter().any(|member| match *member {
ClassMember::Single(one) => one == character,
ClassMember::Range(first, last) => (first..=last).contains(&character),
});
held != self.negated
}
}
struct Parser<'pattern> {
units: &'pattern [char],
at: usize,
}
impl Parser<'_> {
fn parse(text: &str) -> Result<Alternation, String> {
let units: Vec<char> = text.chars().collect();
let mut parser = Parser {
units: &units,
at: 0,
};
let parsed = parser.alternation(true)?;
if parser.at != units.len() {
return Err(format!("an unmatched `)` at {}", parser.at));
}
Ok(parsed)
}
fn peek(&self) -> Option<char> {
self.units.get(self.at).copied()
}
fn alternation(&mut self, outermost: bool) -> Result<Alternation, String> {
let mut branches = vec![self.sequence(outermost)?];
while self.peek() == Some('|') {
self.at += 1;
branches.push(self.sequence(outermost)?);
}
Ok(Alternation(branches))
}
fn sequence(&mut self, outermost: bool) -> Result<Sequence, String> {
let mut items = Vec::new();
loop {
match self.peek() {
None | Some('|' | ')') => break,
Some('^') if outermost && self.at == 0 => {
self.at += 1;
}
Some('$') if outermost && self.at + 1 == self.units.len() => {
self.at += 1;
}
Some('^' | '$') => {
return Err("an anchor anywhere but at the ends".to_owned());
}
Some(_) => {
let atom = self.atom()?;
let quantifier = self.quantifier()?;
if quantifier != Quantifier::One && contains_quantifier(&atom) {
return Err("a quantifier over a quantified group".to_owned());
}
items.push(Repeat { atom, quantifier });
}
}
}
Ok(Sequence(items))
}
fn quantifier(&mut self) -> Result<Quantifier, String> {
let quantifier = match self.peek() {
Some('*') => Quantifier::ZeroOrMore,
Some('+') => Quantifier::OneOrMore,
Some('?') => Quantifier::ZeroOrOne,
Some('{') => return Err("a counted quantifier `{…}`".to_owned()),
_ => return Ok(Quantifier::One),
};
self.at += 1;
match self.peek() {
Some('*' | '+' | '?') => Err("a doubled quantifier".to_owned()),
_ => Ok(quantifier),
}
}
fn atom(&mut self) -> Result<Atom, String> {
match self.peek() {
Some('(') => {
self.at += 1;
if self.peek() == Some('?') {
return Err("a group flag, a look-around or a non-capturing group".to_owned());
}
let inner = self.alternation(false)?;
if self.peek() != Some(')') {
return Err("an unclosed `(`".to_owned());
}
self.at += 1;
Ok(Atom::Group(inner))
}
Some('[') => {
self.at += 1;
Ok(Atom::Class(self.character_class()?))
}
Some('.') => {
self.at += 1;
Ok(Atom::Any)
}
Some('\\') => {
self.at += 1;
Ok(Atom::Literal(self.escape()?))
}
Some(']' | '}') => Err("an unmatched `]` or `}`".to_owned()),
Some(character) => {
self.at += 1;
Ok(Atom::Literal(character))
}
None => Err("an atom was expected".to_owned()),
}
}
fn escape(&mut self) -> Result<char, String> {
let Some(character) = self.peek() else {
return Err("a trailing `\\`".to_owned());
};
self.at += 1;
if character.is_ascii_alphanumeric() {
return Err(format!("the escape `\\{character}`"));
}
Ok(character)
}
fn character_class(&mut self) -> Result<CharacterClass, String> {
let negated = self.peek() == Some('^');
if negated {
self.at += 1;
}
let mut members = Vec::new();
loop {
let character = match self.peek() {
None => return Err("an unclosed `[`".to_owned()),
Some(']') if !members.is_empty() => {
self.at += 1;
return Ok(CharacterClass { negated, members });
}
Some('[') => return Err("a nested character class".to_owned()),
Some('&') => return Err("a class intersection".to_owned()),
Some('\\') => {
self.at += 1;
self.escape()?
}
Some(character) => {
self.at += 1;
character
}
};
if self.peek() == Some('-')
&& self.units.get(self.at + 1).is_some_and(|next| *next != ']')
{
self.at += 1;
let last = match self.peek() {
Some('\\') => {
self.at += 1;
self.escape()?
}
Some(character) => {
self.at += 1;
character
}
None => return Err("an unclosed `[`".to_owned()),
};
if last < character {
return Err("a range whose end precedes its start".to_owned());
}
members.push(ClassMember::Range(character, last));
} else {
members.push(ClassMember::Single(character));
}
}
}
}
fn contains_quantifier(atom: &Atom) -> bool {
let Atom::Group(alternation) = atom else {
return false;
};
alternation.0.iter().any(|sequence| {
sequence
.0
.iter()
.any(|repeat| repeat.quantifier != Quantifier::One || contains_quantifier(&repeat.atom))
})
}
impl Alternation {
fn matches_whole(&self, input: &[char]) -> bool {
self.matches_from(input, 0, &mut |at| at == input.len())
}
fn matches_from(&self, input: &[char], at: usize, rest: &mut dyn FnMut(usize) -> bool) -> bool {
self.0
.iter()
.any(|sequence| sequence.matches_from(input, at, rest))
}
}
impl Sequence {
fn matches_from(&self, input: &[char], at: usize, rest: &mut dyn FnMut(usize) -> bool) -> bool {
match self.0.split_first() {
None => rest(at),
Some((first, remainder)) => {
let remainder = Sequence(remainder.to_vec());
first.matches_from(input, at, &mut |next| {
remainder.matches_from(input, next, rest)
})
}
}
}
}
impl Repeat {
fn matches_from(&self, input: &[char], at: usize, rest: &mut dyn FnMut(usize) -> bool) -> bool {
match self.quantifier {
Quantifier::One => self.atom.matches_from(input, at, rest),
Quantifier::ZeroOrOne => self.atom.matches_from(input, at, rest) || rest(at),
Quantifier::ZeroOrMore => self.repeat_from(input, at, rest),
Quantifier::OneOrMore => self
.atom
.matches_from(input, at, &mut |next| self.repeat_from(input, next, rest)),
}
}
fn repeat_from(&self, input: &[char], at: usize, rest: &mut dyn FnMut(usize) -> bool) -> bool {
if self.atom.matches_from(input, at, &mut |next| {
next > at && self.repeat_from(input, next, rest)
}) {
return true;
}
rest(at)
}
}
impl Atom {
fn matches_from(&self, input: &[char], at: usize, rest: &mut dyn FnMut(usize) -> bool) -> bool {
match self {
Atom::Group(alternation) => alternation.matches_from(input, at, rest),
Atom::Literal(expected) => {
input.get(at).is_some_and(|found| found == expected) && rest(at + 1)
}
Atom::Any => at < input.len() && rest(at + 1),
Atom::Class(class) => {
input.get(at).is_some_and(|found| class.contains(*found)) && rest(at + 1)
}
}
}
}