use super::Pattern;
use crate::error::MacroError;
pub fn normalize_pattern(pattern: &Pattern) -> Result<Vec<(Pattern, Pattern)>, MacroError> {
Ok(match pattern {
Pattern::Attribute(_) => vec![(pattern.clone(), Pattern::Empty)],
Pattern::Element(_) | Pattern::Text => {
vec![(Pattern::Empty, pattern.clone())]
}
Pattern::Empty => vec![(Pattern::Empty, Pattern::Empty)],
Pattern::NotAllowed => vec![],
Pattern::Group(patterns) | Pattern::Interleave(patterns) => {
let interleaved = matches!(pattern, Pattern::Interleave(_));
let mut variants = vec![(Pattern::Empty, Pattern::Empty)];
for pattern in patterns {
let others = normalize_pattern(pattern)?;
variants = variants
.iter()
.flat_map(|(attribute, content)| {
others.iter().map(|(other_attribute, other_content)| {
(
Pattern::interleave([attribute.clone(), other_attribute.clone()]),
if interleaved {
Pattern::interleave
} else {
Pattern::group
}([
content.clone(),
other_content.clone(),
]),
)
})
})
.collect();
}
variants
}
Pattern::Choice(patterns) => {
let variants = patterns
.iter()
.map(normalize_pattern)
.collect::<Result<Vec<_>, _>>()?;
if variants
.iter()
.flatten()
.all(|(_, content)| *content == Pattern::Empty)
{
vec![(
Pattern::choice(
variants
.into_iter()
.flatten()
.map(|(attribute, _)| attribute),
),
Pattern::Empty,
)]
} else if variants
.iter()
.flatten()
.all(|(attribute, _)| *attribute == Pattern::Empty)
{
vec![(
Pattern::Empty,
Pattern::choice(variants.into_iter().flatten().map(|(_, content)| content)),
)]
} else {
variants.into_iter().flatten().collect()
}
}
Pattern::Optional(pattern) => {
let variants = normalize_pattern(pattern)?;
match variants.as_slice() {
[(attribute, content)] if *content == Pattern::Empty => {
vec![(Pattern::optional(attribute.clone()), Pattern::Empty)]
}
[(attribute, content)] if *attribute == Pattern::Empty => {
vec![(Pattern::Empty, Pattern::optional(content.clone()))]
}
_ => [(Pattern::Empty, Pattern::Empty)]
.into_iter()
.chain(variants)
.collect(),
}
}
Pattern::Many0(operand) | Pattern::Many1(operand) => {
let variants = normalize_pattern(operand)?;
if !variants
.iter()
.all(|(attribute, _)| *attribute == Pattern::Empty)
{
return Err(MacroError::RncPattern("repeated attribute pattern"));
}
vec![(
Pattern::Empty,
if matches!(pattern, Pattern::Many1(_)) {
Pattern::many1
} else {
Pattern::many0
}(Pattern::choice(
variants.into_iter().map(|(_, content)| content),
)),
)]
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
fn attribute(name: &str) -> Pattern {
Pattern::Attribute([name.into()].into())
}
fn element(name: &str) -> Pattern {
Pattern::Element([name.into()].into())
}
#[test]
fn split_attribute_and_element() {
assert_eq!(
normalize_pattern(&Pattern::interleave([attribute("foo"), element("bar")])).unwrap(),
vec![(attribute("foo"), element("bar"))]
);
}
#[test]
fn keep_attribute_choice_in_one_alternative() {
assert_eq!(
normalize_pattern(&Pattern::choice([attribute("foo"), attribute("bar")])).unwrap(),
vec![(
Pattern::choice([attribute("foo"), attribute("bar")]),
Pattern::Empty
)]
);
}
#[test]
fn lift_mixed_choice_into_alternatives() {
assert_eq!(
normalize_pattern(&Pattern::choice([attribute("foo"), element("bar")])).unwrap(),
vec![
(attribute("foo"), Pattern::Empty),
(Pattern::Empty, element("bar")),
]
);
}
#[test]
fn split_optional_attribute() {
assert_eq!(
normalize_pattern(&Pattern::optional(attribute("foo"))).unwrap(),
vec![(Pattern::optional(attribute("foo")), Pattern::Empty)]
);
}
#[test]
fn split_element_repetition() {
assert_eq!(
normalize_pattern(&Pattern::many0(element("foo"))).unwrap(),
vec![(Pattern::Empty, Pattern::many0(element("foo")))]
);
}
#[test]
fn split_at_least_one_element_repetition() {
assert_eq!(
normalize_pattern(&Pattern::many1(element("foo"))).unwrap(),
vec![(Pattern::Empty, Pattern::many1(element("foo")))]
);
}
#[test]
fn fail_on_attribute_repetition() {
assert!(matches!(
normalize_pattern(&Pattern::many1(Pattern::choice([
attribute("foo"),
attribute("bar")
]))),
Err(MacroError::RncPattern(_))
));
}
#[test]
fn split_not_allowed_into_no_alternative() {
assert_eq!(normalize_pattern(&Pattern::NotAllowed).unwrap(), vec![]);
}
}