use itertools::Itertools as _;
use thiserror::Error;
use crate::glob::token::{self, Component, Token};
use crate::glob::{IteratorExt as _, SliceExt as _, Terminals};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RuleError {
#[error("invalid tree wildcard `**` in alternative")]
AlternativeTree,
#[error("invalid zero-or-more wildcard `*` or `$` in alternative")]
AlternativeZeroOrMore,
#[error("invalid separator `/`")]
SeparatorAdjacent,
}
pub fn check<'t, I>(tokens: I) -> Result<(), RuleError>
where
I: IntoIterator<Item = &'t Token<'t>>,
I::IntoIter: Clone,
{
let tokens = tokens.into_iter();
alternative(tokens.clone())?;
separator(tokens)?;
Ok(())
}
fn alternative<'t, I>(tokens: I) -> Result<(), RuleError>
where
I: IntoIterator<Item = &'t Token<'t>>,
I::IntoIter: Clone,
{
use crate::glob::token::Token::{Alternative, Wildcard};
use crate::glob::token::Wildcard::{Tree, ZeroOrMore};
use crate::glob::Terminals::{Only, StartEnd};
fn recurse<'t>(
components: impl Iterator<Item = Component<'t>>,
parent: (Option<&Token<'t>>, Option<&Token<'t>>),
) -> Result<(), RuleError> {
for component in components {
for (left, alternative, right) in
component
.tokens()
.iter()
.adjacent()
.filter_map(|adjacency| match adjacency.into_tuple() {
(left, Alternative(alternative), right) => Some((left, alternative, right)),
_ => None,
})
{
let left = left.cloned().or(parent.0);
let right = right.cloned().or(parent.1);
for tokens in alternative.branches() {
if let Some(terminals) = tokens.terminals() {
check(terminals, left, right)?;
}
recurse(token::components(tokens), (left, right))?;
}
}
}
Ok(())
}
fn check<'t>(
terminals: Terminals<&Token<'t>>,
left: Option<&Token<'t>>,
right: Option<&Token<'t>>,
) -> Result<(), RuleError> {
match terminals {
Only(Wildcard(Tree)) => {
Err(RuleError::AlternativeTree)
}
StartEnd(Wildcard(Tree), _) if left.is_some() => {
Err(RuleError::AlternativeTree)
}
StartEnd(_, Wildcard(Tree)) if right.is_some() => {
Err(RuleError::AlternativeTree)
}
Only(Wildcard(ZeroOrMore(_)))
if matches!(
(left, right),
(Some(Wildcard(ZeroOrMore(_))), _) | (_, Some(Wildcard(ZeroOrMore(_))))
) =>
{
Err(RuleError::AlternativeZeroOrMore)
}
StartEnd(Wildcard(ZeroOrMore(_)), _)
if matches!(left, Some(Wildcard(ZeroOrMore(_)))) =>
{
Err(RuleError::AlternativeZeroOrMore)
}
StartEnd(_, Wildcard(ZeroOrMore(_)))
if matches!(right, Some(Wildcard(ZeroOrMore(_)))) =>
{
Err(RuleError::AlternativeZeroOrMore)
}
_ => Ok(()),
}
}
recurse(token::components(tokens), (None, None))
}
fn separator<'t, I>(tokens: I) -> Result<(), RuleError>
where
I: IntoIterator<Item = &'t Token<'t>>,
I::IntoIter: Clone,
{
if tokens
.into_iter()
.tuple_windows::<(_, _)>()
.any(|adjacent| matches!(adjacent, (Token::Separator, Token::Separator)))
{
Err(RuleError::SeparatorAdjacent)
}
else {
Ok(())
}
}