use std::ops::{Add, AddAssign, Range};
use std::slice;
use crate::presentation::Presentation;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct NonCanonicality {
pub unsanctioned_selectors: usize,
pub modifier_defective_selectors: usize,
pub additional_defective_selectors: usize,
pub tag_conflicting_selectors: usize,
pub tag_forced_presentations: usize,
pub tag_redundant_selectors: usize,
pub policy_redundant_selectors: usize,
pub presentation_decisions: usize,
}
impl Default for NonCanonicality {
fn default() -> Self {
Self::EMPTY
}
}
impl NonCanonicality {
const EMPTY: Self = Self {
unsanctioned_selectors: 0,
modifier_defective_selectors: 0,
additional_defective_selectors: 0,
tag_conflicting_selectors: 0,
tag_forced_presentations: 0,
tag_redundant_selectors: 0,
policy_redundant_selectors: 0,
presentation_decisions: 0,
};
pub(super) const MODIFIER_DEFECTIVE_SELECTOR: Self = Self {
modifier_defective_selectors: 1,
..Self::EMPTY
};
pub(super) const ADDITIONAL_DEFECTIVE_SELECTOR: Self = Self {
additional_defective_selectors: 1,
..Self::EMPTY
};
pub(super) const TAG_CONFLICTING_SELECTOR: Self = Self {
tag_conflicting_selectors: 1,
..Self::EMPTY
};
pub(super) const TAG_FORCED_PRESENTATION: Self = Self {
tag_forced_presentations: 1,
..Self::EMPTY
};
pub(super) const TAG_REDUNDANT_SELECTOR: Self = Self {
tag_redundant_selectors: 1,
..Self::EMPTY
};
pub(super) const POLICY_REDUNDANT_SELECTOR: Self = Self {
policy_redundant_selectors: 1,
..Self::EMPTY
};
pub(super) const PRESENTATION_DECISION: Self = Self {
presentation_decisions: 1,
..Self::EMPTY
};
pub(super) const fn unsanctioned(count: usize) -> Self {
Self {
unsanctioned_selectors: count,
..Self::EMPTY
}
}
#[must_use]
pub const fn is_empty(&self) -> bool {
matches!(
self,
Self {
unsanctioned_selectors: 0,
modifier_defective_selectors: 0,
additional_defective_selectors: 0,
tag_conflicting_selectors: 0,
tag_forced_presentations: 0,
tag_redundant_selectors: 0,
policy_redundant_selectors: 0,
presentation_decisions: 0,
}
)
}
}
impl Add for NonCanonicality {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
unsanctioned_selectors: self.unsanctioned_selectors + rhs.unsanctioned_selectors,
modifier_defective_selectors: self.modifier_defective_selectors
+ rhs.modifier_defective_selectors,
additional_defective_selectors: self.additional_defective_selectors
+ rhs.additional_defective_selectors,
tag_conflicting_selectors: self.tag_conflicting_selectors
+ rhs.tag_conflicting_selectors,
tag_forced_presentations: self.tag_forced_presentations + rhs.tag_forced_presentations,
tag_redundant_selectors: self.tag_redundant_selectors + rhs.tag_redundant_selectors,
policy_redundant_selectors: self.policy_redundant_selectors
+ rhs.policy_redundant_selectors,
presentation_decisions: self.presentation_decisions + rhs.presentation_decisions,
}
}
}
impl AddAssign for NonCanonicality {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum ReplacementElement<D> {
Fixed(String),
Choice(ReplacementChoice<D>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ReplacementChoice<D> {
pub(super) default: D,
pub(super) options: Vec<ReplacementOption<D>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ReplacementOption<D> {
pub(super) decision: D,
pub(super) replacement: String,
}
impl<D: Copy> ReplacementChoice<D> {
pub(super) const fn default_decision(&self) -> D {
self.default
}
}
impl<D: PartialEq> ReplacementChoice<D> {
pub(super) fn new(default: D, options: Vec<ReplacementOption<D>>) -> Self {
assert!(
options.iter().any(|option| option.decision == default),
"replacement choice default decision must be one of the options"
);
Self { default, options }
}
pub(super) fn from_replacements<const N: usize>(
default: D,
replacements: [(D, String); N],
) -> Self {
Self::new(
default,
replacements
.into_iter()
.map(|(decision, replacement)| ReplacementOption {
decision,
replacement,
})
.collect(),
)
}
pub(super) fn replacement(&self, decision: &D) -> Option<&str> {
self.options
.iter()
.find(|option| option.decision == *decision)
.map(|option| option.replacement.as_str())
}
#[allow(clippy::expect_used)] fn default_canonical_replacement(&self) -> &str {
self.replacement(&self.default)
.expect("replacement choice constructor validates its default decision")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ReplacementAnalysis {
non_canonicality: NonCanonicality,
elements: Vec<ReplacementElement<Presentation>>,
}
impl ReplacementAnalysis {
pub(super) fn empty() -> Self {
Self {
non_canonicality: NonCanonicality::default(),
elements: Vec::new(),
}
}
pub(super) fn fixed(non_canonicality: NonCanonicality, replacement: String) -> Self {
let mut analysis = Self {
non_canonicality,
elements: Vec::new(),
};
analysis.push_fixed(replacement);
analysis
}
pub(super) fn choice(
non_canonicality: NonCanonicality,
choice: ReplacementChoice<Presentation>,
) -> Self {
Self {
non_canonicality,
elements: vec![ReplacementElement::Choice(choice)],
}
}
pub(super) const fn is_canonical(&self) -> bool {
self.non_canonicality.is_empty()
}
fn decision_count(&self) -> usize {
self.elements
.iter()
.filter(|element| matches!(element, ReplacementElement::Choice(_)))
.count()
}
pub(super) fn push_fixed(&mut self, text: String) {
self.elements.push(ReplacementElement::Fixed(text));
}
}
impl AddAssign for ReplacementAnalysis {
fn add_assign(&mut self, rhs: Self) {
self.non_canonicality += rhs.non_canonicality;
self.elements.extend(rhs.elements);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Finding<'a> {
pub span: Range<usize>,
pub raw: &'a str,
analysis: ReplacementAnalysis,
}
impl Finding<'_> {
#[must_use]
pub const fn non_canonicality(&self) -> NonCanonicality {
self.analysis.non_canonicality
}
#[must_use]
pub fn default_decisions(&self) -> impl ExactSizeIterator<Item = Presentation> + '_ {
DefaultDecisions {
elements: self.analysis.elements.iter(),
remaining: self.analysis.decision_count(),
}
}
#[must_use]
pub fn default_canonical_replacement(&self) -> String {
let mut out = String::new();
for element in &self.analysis.elements {
match element {
ReplacementElement::Fixed(text) => out.push_str(text),
ReplacementElement::Choice(choice) => {
out.push_str(choice.default_canonical_replacement());
}
}
}
out
}
#[must_use]
pub fn canonical_replacement_with_decisions(
&self,
decisions: &[Presentation],
) -> Option<String> {
let mut decisions = decisions.iter();
let mut out = String::new();
for element in &self.analysis.elements {
match element {
ReplacementElement::Fixed(text) => out.push_str(text),
ReplacementElement::Choice(choice) => {
let decision = decisions.next()?;
out.push_str(choice.replacement(decision)?);
}
}
}
if decisions.next().is_none() {
Some(out)
} else {
None
}
}
}
struct DefaultDecisions<'a> {
elements: slice::Iter<'a, ReplacementElement<Presentation>>,
remaining: usize,
}
impl Iterator for DefaultDecisions<'_> {
type Item = Presentation;
fn next(&mut self) -> Option<Self::Item> {
for element in self.elements.by_ref() {
if let ReplacementElement::Choice(choice) = element {
self.remaining -= 1;
return Some(choice.default_decision());
}
}
None
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl ExactSizeIterator for DefaultDecisions<'_> {
fn len(&self) -> usize {
self.remaining
}
}
impl<'a> Finding<'a> {
pub(super) fn new(item: &crate::scanner::ScanItem<'a>, analysis: ReplacementAnalysis) -> Self {
assert!(
!analysis.is_canonical(),
"finding construction requires non-empty non-canonicality"
);
assert_eq!(
analysis.decision_count(),
analysis.non_canonicality.presentation_decisions,
"finding decision count must match presentation_decisions"
);
Self {
span: item.span.clone(),
raw: item.raw,
analysis,
}
}
pub(super) fn fixed(
item: &crate::scanner::ScanItem<'a>,
non_canonicality: NonCanonicality,
replacement: String,
) -> Self {
Self::new(
item,
ReplacementAnalysis::fixed(non_canonicality, replacement),
)
}
}