Skip to main content

onepass_seed/expr/
chars.rs

1use core::cmp::max;
2use std::io::{Result, Write};
3
4use crypto_bigint::{NonZero, U256};
5use secrecy::ExposeSecretMut;
6
7use super::{Eval, util::u256_to_word};
8
9#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct Chars(pub Box<[CharRange]>);
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub struct CharRange {
14    pub start: char,
15    pub end: char,
16}
17
18impl From<(char, char)> for CharRange {
19    fn from((start, end): (char, char)) -> Self {
20        CharRange { start, end }
21    }
22}
23
24impl Chars {
25    /// # Safety
26    /// This function is only safe for evaluation if the ranges are non-overlapping.
27    pub unsafe fn from_ranges_unchecked<T, V>(ranges: V) -> Self
28    where
29        V: IntoIterator<Item = T>,
30        T: Into<CharRange>,
31    {
32        Chars(ranges.into_iter().map(Into::into).collect())
33    }
34
35    pub fn from_ranges<T, V>(ranges: V) -> Self
36    where
37        V: IntoIterator<Item = T>,
38        T: Into<CharRange>,
39    {
40        let mut ranges = ranges.into_iter().map(Into::into).collect::<Vec<_>>();
41        ranges.sort_unstable_by_key(|a| a.start);
42        let mut i = 0;
43        let mut j = 1;
44        while j < ranges.len() {
45            let Some(next) = next_char(ranges[i].end) else {
46                break;
47            };
48            if next >= ranges[j].start {
49                ranges[i].end = max(ranges[i].end, ranges[j].end);
50                j += 1;
51                continue;
52            }
53            if j != i + 1 {
54                ranges.swap(i + 1, j);
55            }
56            i += 1;
57            j += 1;
58        }
59        ranges.drain(i + 1..);
60        Chars(ranges.into())
61    }
62
63    fn size(&self) -> u32 {
64        self.0.iter().map(|range| range.size()).sum()
65    }
66
67    fn nth(&self, mut n: u32) -> char {
68        for range in &self.0 {
69            let sz = range.size();
70            if n < sz {
71                return range.nth(n);
72            }
73            n -= sz;
74        }
75        unreachable!()
76    }
77}
78
79pub(super) fn next_char(c: char) -> Option<char> {
80    match c {
81        '\u{d7ff}' => Some(0xe000),
82        _ => u32::from(c).checked_add(1),
83    }
84    .and_then(char::from_u32)
85}
86
87impl CharRange {
88    // TODO(someday): replace these with `Step` methods once those are stabilized.
89
90    fn size(&self) -> u32 {
91        let start = self.start as u32;
92        let end = self.end as u32;
93        assert!(start <= end, "{:?} > {:?}", self.start, self.end);
94        let count = end - start + 1;
95        if start < 0xD800 && 0xE000 <= end {
96            count - 0x800
97        } else {
98            count
99        }
100    }
101
102    fn nth(&self, n: u32) -> char {
103        let start = self.start as u32;
104        let res = start + n;
105        let res = if start < 0xD800 && res >= 0xD800 {
106            res + 0x800
107        } else {
108            res
109        };
110        let res = char::from_u32(res).unwrap();
111        assert!(res <= self.end);
112        res
113    }
114}
115
116impl Eval for Chars {
117    fn size(&self) -> NonZero<U256> {
118        NonZero::new(Chars::size(self).into()).unwrap()
119    }
120
121    fn write_to(&self, w: &mut dyn Write, index: &mut dyn ExposeSecretMut<U256>) -> Result<()> {
122        let c = self.nth(u256_to_word(index.expose_secret_mut()).try_into().unwrap());
123        write!(w, "{}", c)
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use std::io::BufWriter;
130
131    use secrecy::SecretBox;
132
133    use super::*;
134
135    #[test]
136    fn test_from_ranges() {
137        let rs = Chars::from_ranges([('b', 'e'), ('a', 'c'), ('z', 'z')]);
138        assert_eq!(6, rs.size());
139        assert_eq!('a', rs.nth(0));
140        assert_eq!('z', rs.nth(5));
141
142        let rs = Chars::from_ranges(vec![('a', 'z')]);
143        assert_eq!(26, rs.size());
144    }
145
146    #[test]
147    fn test_eval_boundary() {
148        let cs = Chars::from_ranges([('\u{d7ff}', char::MAX)]);
149        let mut buf = BufWriter::new(Vec::new());
150        cs.write_to(&mut buf, &mut SecretBox::new(Box::new(U256::ONE)))
151            .unwrap();
152        assert_eq!(
153            "\u{e000}",
154            String::from_utf8(buf.into_inner().unwrap()).unwrap()
155        );
156        assert_eq!(char::MAX as u32 - 0xe000 + 2, cs.size());
157    }
158
159    #[test]
160    fn test_next_char_boundary() {
161        assert_eq!(Some('\u{e000}'), next_char('\u{d7ff}'));
162    }
163}