utils/
ascii.rs

1//! ASCII helpers.
2
3use crate::bit::BitIterator;
4use std::fmt::{Debug, Display, Formatter};
5
6/// A set of ASCII characters.
7///
8/// # Examples
9/// ```
10/// # use utils::ascii::AsciiSet;
11/// let set1 = AsciiSet::new((1 << b'A') | (1 << b'B') | (1 << b'C'));
12/// assert_eq!(set1.len(), 3);
13/// assert_eq!(set1.to_string(), "'A', 'B', 'C'");
14/// assert_eq!(format!("{set1:?}"), "{'A', 'B', 'C'}");
15///
16/// let mut array = [false; 128];
17/// array[b'A' as usize] = true;
18/// array[b'B' as usize] = true;
19/// array[b'C' as usize] = true;
20/// assert_eq!(AsciiSet::from(array), set1);
21///
22/// assert_eq!(AsciiSet::from(|b| (b'A'..=b'C').contains(&b)), set1);
23/// ```
24#[derive(Copy, Clone, Eq, PartialEq, Default)]
25#[repr(transparent)]
26#[must_use]
27pub struct AsciiSet {
28    set: u128,
29}
30
31impl AsciiSet {
32    /// Creates a new `AsciiSet` from the specified bitset.
33    pub const fn new(set: u128) -> Self {
34        Self { set }
35    }
36
37    #[must_use]
38    pub const fn is_empty(&self) -> bool {
39        self.set == 0
40    }
41
42    #[must_use]
43    pub const fn len(&self) -> usize {
44        self.set.count_ones() as usize
45    }
46
47    #[expect(clippy::cast_possible_truncation)]
48    fn write_next_entry(&mut self, f: &mut Formatter<'_>) -> std::fmt::Result {
49        let b = self.set.trailing_zeros() as u8;
50        let run = (self.set >> b).trailing_ones() as u8;
51
52        let class_end = match b {
53            b'0'..=b'9' => b'9',
54            b'a'..=b'z' => b'z',
55            b'A'..=b'Z' => b'Z',
56            _ => b,
57        };
58        let range_len = (class_end - b + 1).min(run);
59
60        if range_len >= 4 {
61            self.set &= !(((1 << range_len) - 1) << b);
62            write!(f, "{:?}-{:?}", b as char, (b + range_len - 1) as char)
63        } else {
64            self.set &= !(1 << b);
65            write!(f, "{:?}", b as char)
66        }
67    }
68}
69
70/// Format the set for display, combining digit and letter ranges.
71///
72/// # Examples
73///
74/// ```
75/// # use utils::ascii::AsciiSet;
76/// assert_eq!(
77///     AsciiSet::from(|b: u8| b.is_ascii_lowercase()).to_string(),
78///     "'a'-'z'"
79/// );
80/// assert_eq!(
81///     AsciiSet::from(|b: u8| b.is_ascii_alphabetic()).to_string(),
82///     "'A'-'Z', 'a'-'z'"
83/// );
84/// assert_eq!(
85///     AsciiSet::from(|b: u8| matches!(b, b'.' | b'#' | b'0'..=b'9')).to_string(),
86///     "'#', '.', '0'-'9'"
87/// );
88/// assert_eq!(
89///     AsciiSet::from(|b: u8| b.is_ascii_graphic()).to_string(),
90///     concat!(
91///         "'!', '\"', '#', '$', '%', '&', '\\'', '(', ')', '*', '+', ',', '-', '.', '/', ",
92///         "'0'-'9', ':', ';', '<', '=', '>', '?', '@', 'A'-'Z', '[', '\\\\', ']', '^', '_', ",
93///         "'`', 'a'-'z', '{', '|', '}', '~'"
94///     )
95/// );
96/// ```
97impl Display for AsciiSet {
98    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99        if self.is_empty() {
100            return write!(f, "(empty)");
101        }
102        let mut set = *self;
103        set.write_next_entry(f)?;
104        while !set.is_empty() {
105            write!(f, ", ")?;
106            set.write_next_entry(f)?;
107        }
108        Ok(())
109    }
110}
111
112impl Debug for AsciiSet {
113    #[expect(clippy::cast_possible_truncation)]
114    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115        f.debug_set()
116            .entries(BitIterator::ones(self.set).map(|(c, _)| c as u8 as char))
117            .finish()
118    }
119}
120
121impl From<u128> for AsciiSet {
122    fn from(set: u128) -> Self {
123        Self { set }
124    }
125}
126
127impl From<[bool; 128]> for AsciiSet {
128    fn from(value: [bool; 128]) -> Self {
129        Self {
130            set: value
131                .iter()
132                .enumerate()
133                .fold(0, |s, (i, &b)| s | u128::from(b) << i),
134        }
135    }
136}
137
138impl<F: Fn(u8) -> bool> From<F> for AsciiSet {
139    fn from(value: F) -> Self {
140        Self {
141            set: (0u8..=127).fold(0, |s, i| s | u128::from(value(i)) << i),
142        }
143    }
144}