use crate::ui::render::maths::{SHORT_BRACES, TALL_FENCES};
const BRACKET_PAIRS: [(char, char); 3] = [('(', ')'), ('[', ']'), ('{', '}')];
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum CharClass {
Word,
Punctuation,
Space,
}
impl CharClass {
pub fn of(character: char) -> Self {
if character.is_whitespace() {
Self::Space
} else if character.is_alphanumeric() || character == '_' || character == '\'' {
Self::Word
} else {
Self::Punctuation
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Bracket {
pub open: char,
pub close: char,
pub opening: bool,
}
impl Bracket {
pub fn of(character: char) -> Option<Self> {
BRACKET_PAIRS.iter().find_map(|&(open, close)| {
let opening = match character {
_ if character == open => true,
_ if character == close => false,
_ => return None,
};
Some(Self {
open,
close,
opening,
})
})
}
pub fn of_fence_top(glyph: &str) -> Option<Self> {
SHORT_BRACES
.iter()
.chain(&TALL_FENCES)
.find(|(_, [top, ..])| *top == glyph)
.and_then(|(fence, _)| Self::of(fence.chars().next()?))
}
pub fn is_fence_piece(glyph: &str) -> bool {
!glyph.is_empty()
&& SHORT_BRACES
.iter()
.chain(&TALL_FENCES)
.any(|(_, pieces)| pieces.contains(&glyph))
}
pub fn character(self) -> char {
match self.opening {
true => self.open,
false => self.close,
}
}
pub fn partner<P>(self, beyond: impl IntoIterator<Item = (P, char)>) -> Option<P> {
let (same, partner) = match self.opening {
true => (self.open, self.close),
false => (self.close, self.open),
};
let mut depth = 0usize;
for (position, character) in beyond {
if character == same {
depth += 1;
} else if character == partner {
match depth.checked_sub(1) {
Some(outer) => depth = outer,
None => return Some(position),
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_bracket_finds_its_partner_past_nested_pairs() {
let text = "f((a), [b]) + c";
let at = |index: usize| text[index..].chars().next().unwrap();
let open = Bracket::of(at(1)).unwrap();
let close = open.partner(text.char_indices().skip(2));
assert_eq!(close, Some(10));
let back = Bracket::of(at(10)).unwrap();
assert_eq!(back.partner(text[..10].char_indices().rev()), Some(1));
}
#[test]
fn every_tall_fence_reads_back_as_its_bracket() {
for (fence, pieces) in TALL_FENCES.iter().chain(&SHORT_BRACES) {
let Some(bracket) = Bracket::of(fence.chars().next().unwrap()) else {
continue;
};
assert_eq!(Bracket::of_fence_top(pieces[0]), Some(bracket), "{fence}");
assert!(pieces
.iter()
.filter(|piece| !piece.is_empty())
.all(|piece| Bracket::is_fence_piece(piece)));
}
}
}