1use rudb_common::{Error, Result};
24
25use crate::fsst::{ESCAPE, SymbolTable};
26
27const UNKNOWN: u8 = u8::MAX;
30
31#[derive(Debug, Clone)]
33pub struct Sequence {
34 next: Vec<u8>,
37 done: u8,
39 needs: u64,
41}
42
43#[inline]
45fn gram(a: u8, b: u8, c: u8) -> u64 {
46 let run = u32::from(a) << 16 | u32::from(b) << 8 | u32::from(c);
47 1 << (run.wrapping_mul(0x9E37_79B1) >> 26)
48}
49
50#[must_use]
55pub fn grams(text: &[u8]) -> u64 {
56 text.windows(3).fold(0, |bits, run| bits | gram(run[0], run[1], run[2]))
57}
58
59impl Sequence {
60 #[must_use]
66 pub fn new(pieces: &[&[u8]]) -> Option<Self> {
67 let pieces: Vec<&[u8]> = pieces.iter().copied().filter(|piece| !piece.is_empty()).collect();
68 let total: usize = pieces.iter().map(|piece| piece.len()).sum();
69 if pieces.is_empty() || total >= usize::from(UNKNOWN) {
70 return None;
71 }
72 let states = total + 1;
73 let mut next = vec![0_u8; states * 256];
74 let mut base = 0;
78 for piece in &pieces {
79 let first = usize::from(piece[0]);
80 for byte in 0..256 {
81 next[base * 256 + byte] = base as u8;
82 }
83 next[base * 256 + first] = (base + 1) as u8;
84 let mut restart = base;
85 for (offset, &byte) in piece.iter().enumerate().skip(1) {
86 let state = base + offset;
87 let (before, row) = next.split_at_mut(state * 256);
88 row[..256].copy_from_slice(&before[restart * 256..restart * 256 + 256]);
89 row[usize::from(byte)] = (state + 1) as u8;
90 restart = usize::from(next[restart * 256 + usize::from(byte)]);
91 }
92 base += piece.len();
93 }
94 for byte in 0..256 {
95 next[total * 256 + byte] = total as u8;
96 }
97 let needs = pieces.iter().fold(0, |bits, piece| bits | grams(piece));
98 Some(Self { next, done: total as u8, needs })
99 }
100
101 #[must_use]
106 pub fn needs(&self) -> u64 {
107 self.needs
108 }
109
110 fn states(&self) -> usize {
111 usize::from(self.done) + 1
112 }
113
114 #[must_use]
116 pub fn holds(&self, text: &[u8]) -> bool {
117 let mut state = 0_u8;
118 for &byte in text {
119 state = self.next[usize::from(state) * 256 + usize::from(byte)];
120 if state == self.done {
121 return true;
122 }
123 }
124 false
125 }
126
127 #[must_use]
129 pub fn over<'a>(&'a self, table: &'a SymbolTable) -> Coded<'a> {
130 Coded { sequence: self, table, steps: vec![UNKNOWN; self.states() * 256] }
131 }
132}
133
134#[derive(Debug)]
137pub struct Coded<'a> {
138 sequence: &'a Sequence,
139 table: &'a SymbolTable,
140 steps: Vec<u8>,
143}
144
145impl Coded<'_> {
146 pub fn holds(&mut self, codes: &[u8]) -> Result<bool> {
152 let done = self.sequence.done;
153 let mut state = 0_u8;
154 let mut at = 0;
155 while at < codes.len() {
156 let code = codes[at];
157 at += 1;
158 let slot = usize::from(state) * 256 + usize::from(code);
159 state = if code == ESCAPE {
160 let Some(&byte) = codes.get(at) else {
161 return Err(Error::internal("a compressed string ends in an escape"));
162 };
163 at += 1;
164 self.sequence.next[usize::from(state) * 256 + usize::from(byte)]
165 } else if self.steps[slot] != UNKNOWN {
166 self.steps[slot]
167 } else {
168 self.learn(state, code)?
169 };
170 if state == done {
171 return Ok(true);
172 }
173 }
174 Ok(false)
175 }
176
177 #[cold]
178 fn learn(&mut self, from: u8, code: u8) -> Result<u8> {
179 let Some((bytes, len)) = self.table.symbol(code) else {
180 return Err(Error::internal(format!("code {code} is not in the table")));
181 };
182 let mut state = from;
183 for &byte in &bytes[..len] {
184 state = self.sequence.next[usize::from(state) * 256 + usize::from(byte)];
185 }
186 self.steps[usize::from(from) * 256 + usize::from(code)] = state;
187 Ok(state)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 fn searched(text: &[u8], pieces: &[&[u8]]) -> bool {
197 let mut rest = text;
198 for piece in pieces {
199 if piece.is_empty() {
200 continue;
201 }
202 match rest.windows(piece.len()).position(|window| window == *piece) {
203 Some(at) => rest = &rest[at + piece.len()..],
204 None => return false,
205 }
206 }
207 true
208 }
209
210 #[test]
213 fn a_string_that_holds_the_pieces_has_every_bit_they_need() {
214 let cases: [&[&[u8]]; 4] =
215 [&[b"special", b"requests"], &[b"furiously"], &[b"ab"], &[b"aab", b"sts", b"\xc3\xa9"]];
216 for pieces in cases {
217 let sequence = Sequence::new(pieces).expect("an automaton");
218 let mut kept = 0;
219 for text in texts() {
220 let has = grams(&text) & sequence.needs() == sequence.needs();
221 if searched(&text, pieces) {
222 assert!(has, "{:?} holds {pieces:?} and its sketch says not", text);
223 kept += 1;
224 }
225 }
226 assert!(kept > 0, "{pieces:?} is held somewhere, so the test tests something");
227 }
228 assert_eq!(Sequence::new(&[b"ab"]).expect("an automaton").needs(), 0);
229 assert_eq!(grams(b"ab"), 0, "no run of three");
230 assert_ne!(grams(b"special") & grams(b"requests"), grams(b"special"));
231 }
232
233 fn texts() -> Vec<Vec<u8>> {
234 let words = [
235 "special",
236 "requests",
237 "spec",
238 "specia",
239 "ial",
240 "requ",
241 "sts",
242 "the",
243 "furiously",
244 "aaa",
245 "aab",
246 "ab",
247 "é",
248 "ü",
249 "",
250 "s",
251 ];
252 let mut texts = Vec::new();
253 let mut seed = 7_u64;
254 for _ in 0..3000 {
255 let mut text = Vec::new();
256 seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1);
257 for step in 0..(seed >> 60) {
258 let pick = (seed >> (step * 4 % 56)) as usize % words.len();
259 text.extend_from_slice(words[pick].as_bytes());
260 if (seed >> (step % 60)) & 1 == 1 {
261 text.push(b' ');
262 }
263 }
264 texts.push(text);
265 }
266 texts
267 }
268
269 #[test]
270 fn the_walk_over_bytes_and_the_walk_over_codes_agree_with_a_search() {
271 let texts = texts();
272 let samples: Vec<&[u8]> = texts.iter().map(Vec::as_slice).collect();
273 let table = SymbolTable::train(&samples);
274 let patterns: [&[&[u8]]; 7] = [
275 &[b"special", b"requests"],
276 &[b"aab"],
277 &[b"ab", b"ab", b"ab"],
278 &[b"s", b"s"],
279 &["é".as_bytes(), b"ial"],
280 &[b"", b"spec", b""],
281 &[b"furiously the", b"sts"],
282 ];
283 let mut found = 0;
284 for pieces in patterns {
285 let sequence = Sequence::new(pieces).expect("something to find");
286 let mut coded = sequence.over(&table);
287 for text in &texts {
288 let wanted = searched(text, pieces);
289 found += usize::from(wanted);
290 assert_eq!(sequence.holds(text), wanted, "{pieces:?} in {text:?}");
291 let mut codes = Vec::new();
292 table.compress(text, &mut codes);
293 assert_eq!(coded.holds(&codes).expect("codes"), wanted, "{pieces:?} in {text:?}");
294 }
295 }
296 assert!(found > 1000, "only {found} matches");
297 }
298
299 #[test]
300 fn nothing_to_find_or_too_much_is_no_automaton() {
301 assert!(Sequence::new(&[]).is_none());
302 assert!(Sequence::new(&[b"", b""]).is_none());
303 assert!(Sequence::new(&[&[b'x'; 255]]).is_none());
304 assert!(Sequence::new(&[&[b'x'; 254]]).is_some());
305 }
306}