1use std::collections::{BTreeMap, HashMap};
13
14use rkyv::Archive;
15use rkyv::Archived;
16use serde::{Deserialize, Serialize};
17
18#[derive(
19 Debug,
20 Clone,
21 Copy,
22 PartialEq,
23 Eq,
24 Hash,
25 PartialOrd,
26 Ord,
27 Serialize,
28 Deserialize,
29 Archive,
30 rkyv::Serialize,
31 rkyv::Deserialize,
32)]
33#[rkyv(derive(Hash, Eq, PartialEq, PartialOrd, Ord))]
34pub struct TokenId(u16);
35
36impl TokenId {
37 pub const fn new(raw: u16) -> Self {
38 Self(raw)
39 }
40
41 pub const fn raw(self) -> u16 {
42 self.0
43 }
44
45 pub const fn as_usize(self) -> usize {
46 self.0 as usize
47 }
48
49 pub const fn to_le_bytes(self) -> [u8; 2] {
50 self.0.to_le_bytes()
51 }
52}
53
54#[cfg(test)]
55pub const fn tid(raw: u16) -> TokenId {
56 TokenId::new(raw)
57}
58
59impl From<u16> for TokenId {
60 fn from(value: u16) -> Self {
61 Self(value)
62 }
63}
64
65impl From<TokenId> for u16 {
66 fn from(value: TokenId) -> Self {
67 value.0
68 }
69}
70
71impl PartialEq<u16> for TokenId {
72 fn eq(&self, other: &u16) -> bool {
73 self.0 == *other
74 }
75}
76
77impl PartialOrd<u16> for TokenId {
78 fn partial_cmp(&self, other: &u16) -> Option<std::cmp::Ordering> {
79 self.0.partial_cmp(other)
80 }
81}
82
83impl PartialEq<TokenId> for u16 {
84 fn eq(&self, other: &TokenId) -> bool {
85 *self == other.0
86 }
87}
88
89impl PartialOrd<TokenId> for u16 {
90 fn partial_cmp(&self, other: &TokenId) -> Option<std::cmp::Ordering> {
91 self.partial_cmp(&other.0)
92 }
93}
94
95#[derive(
96 Debug,
97 Clone,
98 Copy,
99 PartialEq,
100 Eq,
101 Hash,
102 Serialize,
103 Deserialize,
104 Archive,
105 rkyv::Serialize,
106 rkyv::Deserialize,
107)]
108pub enum TokenKind {
109 Legalese,
110 Regular,
111}
112
113#[derive(
114 Debug,
115 Clone,
116 Copy,
117 PartialEq,
118 Eq,
119 Hash,
120 Serialize,
121 Deserialize,
122 Archive,
123 rkyv::Serialize,
124 rkyv::Deserialize,
125)]
126pub struct KnownToken {
127 pub id: TokenId,
128 pub kind: TokenKind,
129 pub is_digit_only: bool,
130 pub is_short_or_digit: bool,
131}
132
133#[derive(
134 Debug,
135 Clone,
136 Copy,
137 PartialEq,
138 Eq,
139 Hash,
140 Serialize,
141 Deserialize,
142 Archive,
143 rkyv::Serialize,
144 rkyv::Deserialize,
145)]
146pub enum QueryToken {
147 Known(KnownToken),
148 Unknown,
149 Stopword,
150}
151
152#[derive(
153 Debug, Clone, Copy, Serialize, Deserialize, Archive, rkyv::Serialize, rkyv::Deserialize,
154)]
155pub struct TokenMetadata {
156 pub kind: TokenKind,
157 pub is_digit_only: bool,
158 pub is_short_or_digit: bool,
159}
160
161#[derive(Debug, Clone, Archive, rkyv::Serialize, rkyv::Deserialize)]
173pub struct TokenDictionary {
174 tokens_to_ids: HashMap<String, TokenId>,
176
177 token_metadata: Vec<Option<TokenMetadata>>,
178
179 len_legalese: usize,
181
182 next_id: TokenId,
184}
185
186impl TokenDictionary {
187 const DEFAULT_METADATA: TokenMetadata = TokenMetadata {
188 kind: TokenKind::Regular,
189 is_digit_only: false,
190 is_short_or_digit: false,
191 };
192
193 pub fn new_with_legalese(legalese: &Archived<BTreeMap<String, u16>>) -> Self {
206 let mut tokens_to_ids = HashMap::new();
207 let max_existing_id = legalese
208 .iter()
209 .map(|(_, id)| id.to_native() as usize)
210 .max()
211 .unwrap_or(0);
212 let mut token_metadata = vec![None; max_existing_id.saturating_add(1)];
213
214 for (word, id) in legalese.iter() {
215 let native_id = TokenId::new(id.to_native());
216 tokens_to_ids.insert(word.to_string(), native_id);
217 token_metadata[native_id.as_usize()] = Some(TokenMetadata {
218 kind: TokenKind::Legalese,
219 is_digit_only: word.chars().all(|c: char| c.is_ascii_digit()),
220 is_short_or_digit: word.len() == 1
221 || word.chars().all(|c: char| c.is_ascii_digit()),
222 });
223 }
224
225 let len_legalese = legalese.len();
226 let next_id = TokenId::new((max_existing_id + 1).max(len_legalese) as u16);
227
228 Self {
229 tokens_to_ids,
230 token_metadata,
231 len_legalese,
232 next_id,
233 }
234 }
235
236 pub fn new_with_legalese_pairs(legalese_entries: &[(&str, u16)]) -> Self {
240 let mut tokens_to_ids = HashMap::new();
241 let max_existing_id = legalese_entries
242 .iter()
243 .map(|(_, token_id)| *token_id as usize)
244 .max()
245 .unwrap_or(0);
246 let mut token_metadata = vec![None; max_existing_id.saturating_add(1)];
247
248 for (word, token_id) in legalese_entries {
249 let id = TokenId::from(*token_id);
250 tokens_to_ids.insert(word.to_string(), id);
251 token_metadata[id.as_usize()] = Some(TokenMetadata {
252 kind: TokenKind::Legalese,
253 is_digit_only: word.chars().all(|c| c.is_ascii_digit()),
254 is_short_or_digit: word.len() == 1 || word.chars().all(|c| c.is_ascii_digit()),
255 });
256 }
257
258 let len_legalese = legalese_entries.len();
259 let next_id = TokenId::new((max_existing_id + 1).max(len_legalese) as u16);
260
261 Self {
262 tokens_to_ids,
263 token_metadata,
264 len_legalese,
265 next_id,
266 }
267 }
268
269 pub fn new(legalese_count: usize) -> Self {
277 Self {
278 tokens_to_ids: HashMap::new(),
279 token_metadata: Vec::new(),
280 len_legalese: legalese_count,
281 next_id: TokenId::new(legalese_count as u16),
282 }
283 }
284
285 fn metadata_for(&self, id: TokenId) -> TokenMetadata {
286 self.token_metadata
287 .get(id.as_usize())
288 .and_then(|meta| *meta)
289 .unwrap_or(Self::DEFAULT_METADATA)
290 }
291
292 fn build_known_token(&self, id: TokenId) -> KnownToken {
293 let metadata = self.metadata_for(id);
294 KnownToken {
295 id,
296 kind: metadata.kind,
297 is_digit_only: metadata.is_digit_only,
298 is_short_or_digit: metadata.is_short_or_digit,
299 }
300 }
301
302 fn insert_metadata(&mut self, id: TokenId, kind: TokenKind, token: &str) {
303 let raw = id.as_usize();
304 if self.token_metadata.len() <= raw {
305 self.token_metadata.resize(raw + 1, None);
306 }
307 self.token_metadata[raw] = Some(TokenMetadata {
308 kind,
309 is_digit_only: token.chars().all(|c| c.is_ascii_digit()),
310 is_short_or_digit: token.len() == 1 || token.chars().all(|c| c.is_ascii_digit()),
311 });
312 }
313
314 pub fn intern(&mut self, token: &str) -> KnownToken {
315 if let Some(&id) = self.tokens_to_ids.get(token) {
316 return self.build_known_token(id);
317 }
318
319 let id = self.next_id;
320 self.next_id = TokenId::new(self.next_id.raw() + 1);
321 self.tokens_to_ids.insert(token.to_string(), id);
322 self.insert_metadata(id, TokenKind::Regular, token);
323 self.build_known_token(id)
324 }
325
326 pub fn lookup(&self, token: &str) -> Option<KnownToken> {
327 self.tokens_to_ids
328 .get(token)
329 .copied()
330 .map(|id| self.build_known_token(id))
331 }
332
333 pub fn classify_query_token(&self, token: &str) -> QueryToken {
334 self.lookup(token)
335 .map_or(QueryToken::Unknown, QueryToken::Known)
336 }
337
338 pub fn token_kind(&self, token_id: TokenId) -> TokenKind {
339 self.metadata_for(token_id).kind
340 }
341
342 pub fn is_digit_only_token(&self, token_id: TokenId) -> bool {
343 self.metadata_for(token_id).is_digit_only
344 }
345
346 #[cfg(test)]
347 pub fn get_or_assign(&mut self, token: &str) -> TokenId {
348 self.intern(token).id
349 }
350
351 pub fn get_token_id(&self, token: &str) -> Option<TokenId> {
359 self.lookup(token).map(|token| token.id)
360 }
361
362 #[inline]
364 pub fn get(&self, token: &str) -> Option<TokenId> {
365 self.get_token_id(token)
366 }
367
368 pub const fn legalese_count(&self) -> usize {
370 self.len_legalese
371 }
372
373 #[cfg(test)]
375 pub fn tokens_to_ids(&self) -> impl Iterator<Item = (&String, &TokenId)> {
376 self.tokens_to_ids.iter()
377 }
378
379 #[allow(dead_code)]
382 pub fn tokens_to_ids_len(&self) -> usize {
383 self.tokens_to_ids.len()
384 }
385}
386
387impl Default for TokenDictionary {
388 fn default() -> Self {
389 Self::new(0)
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 #[test]
398 fn test_token_dictionary_new() {
399 let dict = TokenDictionary::new(10);
400 assert_eq!(dict.legalese_count(), 10);
401 assert_eq!(dict.tokens_to_ids.len(), 0);
402 assert!(dict.tokens_to_ids.is_empty());
403 }
404
405 #[test]
406 fn test_new_with_legalese() {
407 let legalese = [
408 ("license".to_string(), 0u16),
409 ("copyright".to_string(), 1u16),
410 ("permission".to_string(), 2u16),
411 ];
412
413 let mut dict = TokenDictionary::new_with_legalese_pairs(
414 &legalese
415 .iter()
416 .map(|(s, i)| (s.as_str(), *i))
417 .collect::<Vec<_>>(),
418 );
419
420 assert_eq!(dict.legalese_count(), 3);
421 assert_eq!(dict.tokens_to_ids.len(), 3);
422 assert!(!dict.tokens_to_ids.is_empty());
423
424 assert_eq!(dict.get_token_id("license"), Some(tid(0)));
426 assert_eq!(dict.get_token_id("copyright"), Some(tid(1)));
427 assert_eq!(dict.get_token_id("permission"), Some(tid(2)));
428
429 let test_id = dict.get_or_assign("test");
431 assert_eq!(test_id, 3);
432 }
433
434 #[test]
435 fn test_new_with_legalese_sorted() {
436 let legalese = [
437 ("copyright".to_string(), 5u16),
438 ("license".to_string(), 0u16),
439 ("permission".to_string(), 10u16),
440 ];
441
442 let mut dict = TokenDictionary::new_with_legalese_pairs(
443 &legalese
444 .iter()
445 .map(|(s, i)| (s.as_str(), *i))
446 .collect::<Vec<_>>(),
447 );
448
449 assert_eq!(dict.legalese_count(), 3);
450 assert_eq!(dict.tokens_to_ids.len(), 3);
451
452 assert_eq!(dict.get_token_id("copyright"), Some(tid(5)));
454 assert_eq!(dict.get_token_id("license"), Some(tid(0)));
455 assert_eq!(dict.get_token_id("permission"), Some(tid(10)));
456
457 let test_id = dict.get_or_assign("test");
459 assert_eq!(test_id, tid(11));
460 }
461
462 #[test]
463 fn test_get_or_assign_new_token() {
464 let mut dict = TokenDictionary::new(5);
465
466 let id1 = dict.get_or_assign("hello");
467 let id2 = dict.get_or_assign("world");
468
469 assert_eq!(id1, 5);
471 assert_eq!(id2, 6);
472 assert_eq!(dict.tokens_to_ids.len(), 2);
473 }
474
475 #[test]
476 fn test_get_or_assign_existing_token() {
477 let mut dict = TokenDictionary::new(5);
478
479 let id1 = dict.get_or_assign("hello");
480 let id2 = dict.get_or_assign("hello");
481
482 assert_eq!(id1, id2);
484 assert_eq!(dict.tokens_to_ids.len(), 1);
485 }
486
487 #[test]
488 fn test_get_or_assign_with_preexisting_legalese() {
489 let legalese = [("license".to_string(), 0u16)];
490 let mut dict = TokenDictionary::new_with_legalese_pairs(
491 &legalese
492 .iter()
493 .map(|(s, i)| (s.as_str(), *i))
494 .collect::<Vec<_>>(),
495 );
496
497 let id = dict.get_or_assign("license");
499 assert_eq!(id, 0);
500 assert_eq!(dict.tokens_to_ids.len(), 1);
501
502 let new_id = dict.get_or_assign("new");
504 assert_eq!(new_id, 1);
505 assert_eq!(dict.tokens_to_ids.len(), 2);
506 }
507
508 #[test]
509 fn test_get_existing_token() {
510 let mut dict = TokenDictionary::new(5);
511
512 dict.get_or_assign("hello");
513 assert_eq!(dict.get_token_id("hello"), Some(tid(5)));
514 }
515
516 #[test]
517 fn test_get_nonexistent_token() {
518 let dict = TokenDictionary::new(5);
519 assert_eq!(dict.get_token_id("hello"), None);
520 }
521
522 #[test]
523 fn test_legalese_range() {
524 let dict = TokenDictionary::new(10);
525
526 assert!(0 < dict.legalese_count() as u16);
528 assert!(5 < dict.legalese_count() as u16);
529 assert!(9 < dict.legalese_count() as u16);
530
531 assert!(10 >= dict.legalese_count() as u16);
533 assert!(100 >= dict.legalese_count() as u16);
534 }
535
536 #[test]
537 fn test_legalese_range_with_actual_legalese() {
538 let legalese = [
539 ("license".to_string(), 0u16),
540 ("copyright".to_string(), 1u16),
541 ];
542
543 let mut dict = TokenDictionary::new_with_legalese_pairs(
544 &legalese
545 .iter()
546 .map(|(s, i)| (s.as_str(), *i))
547 .collect::<Vec<_>>(),
548 );
549
550 assert!(dict.get_token_id("license").unwrap() < dict.legalese_count() as u16);
552 assert!(dict.get_token_id("copyright").unwrap() < dict.legalese_count() as u16);
553
554 let regular_id = dict.get_or_assign("regular");
556 assert!(regular_id >= dict.legalese_count() as u16);
557 }
558
559 #[test]
560 fn test_token_dictionary_default() {
561 let dict = TokenDictionary::default();
562 assert_eq!(dict.legalese_count(), 0);
563 assert!(dict.tokens_to_ids.is_empty());
564 }
565
566 #[test]
567 fn test_get_alias() {
568 let mut dict = TokenDictionary::new(5);
569 dict.get_or_assign("hello");
570
571 assert_eq!(dict.get("hello"), dict.get_token_id("hello"));
573 }
574
575 #[test]
576 fn test_with_actual_legalese_module() {
577 use crate::license_detection::rules::legalese;
578
579 let legalese = legalese::archived_legalese();
580 assert!(!legalese.is_empty(), "Should have legalese words");
581
582 let mut dict = TokenDictionary::new_with_legalese(legalese);
583
584 assert_eq!(dict.legalese_count(), legalese.len());
586 assert_eq!(dict.tokens_to_ids.len(), legalese.len());
587
588 let license_id = dict.get_token_id("license");
590 assert!(license_id.is_some(), "License should be in dictionary");
591 assert!(
592 license_id.unwrap() < dict.legalese_count() as u16,
593 "License should be a legalese token"
594 );
595
596 let copyrighted_id = dict.get_token_id("copyrighted");
599 assert!(
600 copyrighted_id.is_some(),
601 "Copyrighted should be in dictionary"
602 );
603 assert!(
604 copyrighted_id.unwrap() < dict.legalese_count() as u16,
605 "Copyrighted should be a legalese token"
606 );
607
608 let hello_id = dict.get_or_assign("hello");
610 assert!(hello_id >= dict.legalese_count() as u16);
611 assert!(hello_id >= dict.legalese_count() as u16);
612 }
613}