onepass_seed/expr/
chars.rs1use 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 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 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}