1use hashbrown::HashSet;
8use std::time::Instant;
9use unicode_segmentation::{UnicodeSegmentation, UnicodeWords};
10use whatlang::{
11 Lang, detect as lang_detect_all, detect_lang as lang_detect, detect_script as script_detect,
12};
13
14#[cfg(feature = "tokenizer-chinese")]
15use std::vec::IntoIter;
16
17use super::stopwords::LexerStopWord;
18use crate::config::ConfigNormalization;
19use crate::query::QueryGenericLang;
20use crate::store::identifiers::{StoreTermHash, StoreTermHashed};
21
22pub struct TokenLexerBuilder;
23
24pub struct TokenLexer<'a> {
25 mode: TokenLexerMode,
26 locale: Option<Lang>,
27 #[cfg(feature = "stemming")]
28 snowball_algorithm: Option<snowball::Algorithm>,
29 words: TokenLexerWords<'a>,
30 yields: HashSet<StoreTermHashed>,
31 config: ConfigNormalization,
32}
33
34#[derive(PartialEq)]
35pub enum TokenLexerMode {
36 NormalizeAndCleanup,
37 NormalizeOnly,
38}
39
40enum TokenLexerWords<'a> {
41 UAX29(UnicodeWords<'a>),
42
43 #[cfg(feature = "tokenizer-chinese")]
44 JieBa(IntoIter<&'a str>),
45
46 #[cfg(feature = "tokenizer-japanese")]
47 Lindera(IntoIter<lindera_tokenizer::token::Token<'a>>),
48}
49
50const TEXT_LANG_TRUNCATE_OVER_CHARS: usize = 200;
51const TEXT_LANG_DETECT_PROCEED_OVER_CHARS: usize = 20;
52const TEXT_LANG_DETECT_NGRAM_UNDER_CHARS: usize = 60;
53
54#[cfg(feature = "tokenizer-chinese")]
55lazy_static! {
56 static ref TOKENIZER_JIEBA: jieba_rs::Jieba = jieba_rs::Jieba::new();
57}
58
59#[cfg(feature = "tokenizer-japanese")]
60lazy_static! {
61 static ref TOKENIZER_LINDERA: lindera_tokenizer::tokenizer::Tokenizer =
62 lindera_tokenizer::tokenizer::Tokenizer::from_config(
63 lindera_tokenizer::tokenizer::TokenizerConfig {
64 dictionary: lindera_dictionary::DictionaryConfig {
65 kind: Some(lindera_dictionary::DictionaryKind::UniDic),
66 path: None
67 },
68 user_dictionary: None,
69 mode: lindera_core::mode::Mode::Normal,
70 }
71 )
72 .expect("unable to initialize japanese tokenizer");
73}
74
75impl TokenLexerBuilder {
76 pub fn from(
77 mode: TokenLexerMode,
78 lang: Option<Lang>,
79 text: &str,
80 config: ConfigNormalization,
81 ) -> Result<TokenLexer<'_>, ()> {
82 let locale = match lang {
83 Some(hinted_lang) => {
85 tracing::debug!(
87 "using hinted locale: {} from lexer text: {}",
88 hinted_lang,
89 text
90 );
91
92 lang
93 }
94
95 None => match mode {
96 TokenLexerMode::NormalizeAndCleanup => {
98 let locale = Self::detect_lang(text);
99 tracing::debug!("detected locale: {:?} from lexer text: {}", locale, text);
100 locale
101 }
102
103 #[cfg(feature = "stemming")]
105 TokenLexerMode::NormalizeOnly if config.stemming_enabled => {
106 let locale = Self::detect_lang(text);
107 tracing::debug!("detected locale: {:?} from lexer text: {}", locale, text);
108 locale
109 }
110
111 TokenLexerMode::NormalizeOnly => {
113 tracing::debug!("not detecting locale from lexer text: {}", text);
114
115 None
116 }
117 },
118 };
119
120 Ok(TokenLexer::new(mode, text, locale, config))
122 }
123
124 fn detect_lang(text: &str) -> Option<Lang> {
125 tracing::debug!("detecting locale from lexer text: {}", text);
126
127 if text.len() < TEXT_LANG_DETECT_PROCEED_OVER_CHARS {
130 return None;
131 }
132
133 let safe_text = if text.len() > TEXT_LANG_TRUNCATE_OVER_CHARS {
136 tracing::debug!(
137 "lexer text needs to be truncated, as it is too long ({}/{}): {}",
138 text.len(),
139 TEXT_LANG_TRUNCATE_OVER_CHARS,
140 text
141 );
142
143 text.char_indices()
153 .nth(TEXT_LANG_TRUNCATE_OVER_CHARS)
154 .map(|(end_index, _)| &text[0..end_index])
155 .unwrap_or(text)
156 } else {
157 text
158 };
159
160 tracing::debug!("will detect locale for lexer safe text: {}", safe_text);
161
162 if safe_text.len() < TEXT_LANG_DETECT_NGRAM_UNDER_CHARS {
172 tracing::debug!(
173 "lexer text is shorter than {} characters, using the slow method",
174 TEXT_LANG_DETECT_NGRAM_UNDER_CHARS
175 );
176
177 Self::detect_lang_slow(safe_text)
178 } else {
179 tracing::debug!(
180 "lexer text is equal or longer than {} characters, using the fast method",
181 TEXT_LANG_DETECT_NGRAM_UNDER_CHARS
182 );
183
184 Self::detect_lang_fast(safe_text)
185 }
186 }
187
188 fn detect_lang_slow(safe_text: &str) -> Option<Lang> {
189 let ngram_start = Instant::now();
190
191 match lang_detect_all(safe_text) {
192 Some(detector) => {
193 let ngram_took = ngram_start.elapsed();
194
195 let mut locale = detector.lang();
196
197 tracing::info!(
198 "[slow lexer] locale detected from text: {} ({} from {} at {}/1; {}s + {}ms)",
199 safe_text,
200 locale,
201 detector.script(),
202 detector.confidence(),
203 ngram_took.as_secs(),
204 ngram_took.subsec_millis()
205 );
206
207 if !detector.is_reliable() {
211 tracing::debug!("[slow lexer] trying to detect locale from stopwords instead");
212
213 if let Some(alternate_locale) =
215 LexerStopWord::guess_lang(safe_text, detector.script())
216 {
217 tracing::info!(
218 "[slow lexer] detected more accurate locale from stopwords: {}",
219 alternate_locale
220 );
221
222 locale = alternate_locale;
223 }
224 }
225
226 Some(locale)
227 }
228 None => {
229 tracing::info!(
230 "[slow lexer] no locale could be detected from text: {}",
231 safe_text
232 );
233
234 None
235 }
236 }
237 }
238
239 fn detect_lang_fast(safe_text: &str) -> Option<Lang> {
240 let stopwords_start = Instant::now();
241
242 match script_detect(safe_text) {
243 Some(script) => {
244 if let Some(locale) = LexerStopWord::guess_lang(safe_text, script) {
246 let stopwords_took = stopwords_start.elapsed();
247
248 tracing::info!(
249 "[fast lexer] locale detected from text: {} ({}; {}s + {}ms)",
250 safe_text,
251 locale,
252 stopwords_took.as_secs(),
253 stopwords_took.subsec_millis()
254 );
255
256 Some(locale)
257 } else {
258 tracing::debug!(
259 "[fast lexer] trying to detect locale from fallback ngram instead"
260 );
261
262 lang_detect(safe_text)
264 }
265 }
266 None => {
267 tracing::info!(
268 "[fast lexer] no script could be detected from text: {}",
269 safe_text
270 );
271
272 None
273 }
274 }
275 }
276}
277
278impl<'a> TokenLexer<'a> {
279 fn new(
280 mode: TokenLexerMode,
281 text: &'a str,
282 locale: Option<Lang>,
283 config: ConfigNormalization,
284 ) -> TokenLexer<'a> {
285 let words = match locale {
287 #[cfg(feature = "tokenizer-chinese")]
288 Some(Lang::Cmn) => TokenLexerWords::JieBa(TOKENIZER_JIEBA.cut(text, false).into_iter()),
289 #[cfg(feature = "tokenizer-japanese")]
290 Some(Lang::Jpn) => match TOKENIZER_LINDERA.tokenize(text) {
291 Ok(tokens) => TokenLexerWords::Lindera(tokens.into_iter()),
292 Err(err) => {
293 tracing::warn!("unable to tokenize japanese, falling back: {}", err);
294
295 TokenLexerWords::UAX29(text.unicode_words())
296 }
297 },
298 _ => TokenLexerWords::UAX29(text.unicode_words()),
299 };
300
301 #[cfg(feature = "stemming")]
303 let snowball_algorithm = match &locale {
304 Some(locale) => super::stemming::snowball_algorithm(locale),
305 None => None,
306 };
307
308 TokenLexer {
309 mode,
310 locale,
311 #[cfg(feature = "stemming")]
312 snowball_algorithm,
313 words,
314 yields: HashSet::new(),
315 config,
316 }
317 }
318}
319
320impl TokenLexerMode {
321 pub fn from_query_lang(lang: &Option<QueryGenericLang>) -> TokenLexerMode {
322 match lang {
323 Some(QueryGenericLang::Enabled(_)) => {
324 TokenLexerMode::NormalizeAndCleanup
326 }
327 Some(QueryGenericLang::Disabled) => {
328 TokenLexerMode::NormalizeOnly
330 }
331 None => {
332 TokenLexerMode::NormalizeAndCleanup
334 }
335 }
336 }
337}
338
339impl<'a> Iterator for TokenLexer<'a> {
340 type Item = (String, StoreTermHashed, usize);
341
342 fn next(&mut self) -> Option<Self::Item> {
349 for original_word in &mut self.words {
350 let original_len = original_word.len();
351
352 let word = {
353 #[cfg(debug_assertions)]
354 let mut current_word: String = original_word.to_owned();
355
356 let mut chars: Box<dyn Iterator<Item = char>> = Box::new(original_word.chars());
359
360 {
362 use caseless::Caseless as _;
363
364 chars = Box::new(chars.default_case_fold());
365
366 #[cfg(debug_assertions)]
367 {
368 let new_word = chars.collect();
369 tracing::trace!("Case folding: {current_word:?} -> {new_word:?}");
370 current_word = new_word;
371 chars = Box::new(current_word.chars());
372 }
373 }
374
375 if self.config.diacritic_folding_enabled {
377 use unicode_normalization::UnicodeNormalization as _;
378 use unicode_normalization::char::is_combining_mark;
379
380 chars = Box::new(chars.nfd().filter(|c| !is_combining_mark(*c)));
381
382 #[cfg(debug_assertions)]
383 {
384 let new_word = chars.collect();
385 tracing::trace!("Diacritic folding: {current_word:?} -> {new_word:?}");
386 current_word = new_word;
387 chars = Box::new(current_word.chars());
388 }
389 }
390
391 #[allow(unused_mut)]
394 let mut new_word: String = chars.collect();
395
396 #[cfg(feature = "stemming")]
398 if self.config.stemming_enabled {
399 if let Some(algo) = self.snowball_algorithm {
400 new_word = String::from(snowball::stem(algo, &new_word));
401
402 tracing::debug!(
403 "lexer stemmed word {original_word:?} into {new_word:?} \
404 using Snowball algorithm {algo:?}"
405 );
406
407 #[cfg(debug_assertions)]
408 {
409 tracing::trace!("Stemming: {current_word:?} -> {new_word:?}");
410 current_word = new_word.clone();
411 }
412 }
413 }
414
415 new_word
416 };
417
418 if self.mode == TokenLexerMode::NormalizeOnly || !LexerStopWord::is(&word, self.locale)
420 {
421 let term_hash = StoreTermHash::from(&word);
425
426 if !self.yields.contains(&term_hash) {
428 tracing::debug!("lexer yielded word: {}", word);
429
430 self.yields.insert(term_hash);
431
432 return Some((word, term_hash, original_len));
433 } else {
434 tracing::debug!(
435 "lexer did not yield word: {} because: word already yielded",
436 word
437 );
438 }
439 } else {
440 tracing::debug!(
441 "lexer did not yield word: {} because: word is a stop-word",
442 word
443 );
444 }
445 }
446
447 None
448 }
449}
450
451impl<'a> Iterator for TokenLexerWords<'a> {
452 type Item = &'a str;
453
454 fn next(&mut self) -> Option<Self::Item> {
455 match self {
456 TokenLexerWords::UAX29(token) => token.next(),
457
458 #[cfg(feature = "tokenizer-chinese")]
459 TokenLexerWords::JieBa(token) => token.next(),
460
461 #[cfg(feature = "tokenizer-japanese")]
462 TokenLexerWords::Lindera(token) => match token.next() {
463 Some(inner) => Some(inner.text),
464 None => None,
465 },
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 const NORMALIZATION_CONFIG: ConfigNormalization = ConfigNormalization {
475 diacritic_folding_enabled: false,
476 stemming_enabled: false,
477 };
478
479 #[test]
480 fn it_cleans_token_english() {
481 let mut token_cleaner = TokenLexerBuilder::from(
482 TokenLexerMode::NormalizeAndCleanup,
483 None,
484 "The quick brown fox jumps over the lazy dog!",
485 NORMALIZATION_CONFIG,
486 )
487 .unwrap();
488
489 assert_eq!(token_cleaner.locale, Some(Lang::Eng));
490 assert_eq!(
491 token_cleaner.next(),
492 Some(("quick".to_string(), 4179131656, 5))
493 );
494 assert_eq!(
495 token_cleaner.next(),
496 Some(("brown".to_string(), 1268820067, 5))
497 );
498 assert_eq!(
499 token_cleaner.next(),
500 Some(("fox".to_string(), 667256324, 3))
501 );
502 assert_eq!(
503 token_cleaner.next(),
504 Some(("jumps".to_string(), 633865164, 5))
505 );
506 assert_eq!(
507 token_cleaner.next(),
508 Some(("lazy".to_string(), 4130433347, 4))
509 );
510 assert_eq!(
511 token_cleaner.next(),
512 Some(("dog".to_string(), 2044924251, 3))
513 );
514 assert_eq!(token_cleaner.next(), None);
515 }
516
517 #[test]
518 fn it_cleans_token_french() {
519 let mut token_cleaner = TokenLexerBuilder::from(
520 TokenLexerMode::NormalizeAndCleanup,
521 None,
522 "Le vif renard brun saute par dessus le chien paresseux.",
523 NORMALIZATION_CONFIG,
524 )
525 .unwrap();
526
527 assert_eq!(token_cleaner.locale, Some(Lang::Fra));
528 assert_eq!(
529 token_cleaner.next(),
530 Some(("renard".to_string(), 1635186311, 6))
531 );
532 assert_eq!(
533 token_cleaner.next(),
534 Some(("brun".to_string(), 2763604928, 4))
535 );
536 assert_eq!(
537 token_cleaner.next(),
538 Some(("saute".to_string(), 1918158211, 5))
539 );
540 assert_eq!(
541 token_cleaner.next(),
542 Some(("chien".to_string(), 2177818351, 5))
543 );
544 assert_eq!(
545 token_cleaner.next(),
546 Some(("paresseux".to_string(), 1678693110, 9))
547 );
548 assert_eq!(token_cleaner.next(), None);
549 }
550
551 #[cfg(feature = "tokenizer-chinese")]
552 #[test]
553 fn it_cleans_token_chinese_jieba() {
554 let mut token_cleaner = TokenLexerBuilder::from(
555 TokenLexerMode::NormalizeAndCleanup,
556 None,
557 "我们中出了一个叛徒",
558 NORMALIZATION_CONFIG,
559 )
560 .unwrap();
561
562 assert_eq!(token_cleaner.locale, Some(Lang::Cmn));
563 assert_eq!(token_cleaner.next(), Some(("出".into(), 241978070, 3)));
564 assert_eq!(token_cleaner.next(), Some(("一个".into(), 2596274530, 6)));
565 assert_eq!(token_cleaner.next(), Some(("叛徒".into(), 3244183759, 6)));
566 assert_eq!(token_cleaner.next(), None);
567 }
568
569 #[cfg(not(feature = "tokenizer-chinese"))]
570 #[test]
571 fn it_cleans_token_chinese_naive() {
572 let mut token_cleaner = TokenLexerBuilder::from(
573 TokenLexerMode::NormalizeAndCleanup,
574 None,
575 "快狐跨懒狗快狐跨懒狗",
576 NORMALIZATION_CONFIG,
577 )
578 .unwrap();
579
580 assert_eq!(token_cleaner.locale, Some(Lang::Cmn));
581 assert_eq!(token_cleaner.next(), Some(("快".to_string(), 126546256, 3)));
582 assert_eq!(
583 token_cleaner.next(),
584 Some(("狐".to_string(), 2879689662, 3))
585 );
586 assert_eq!(
587 token_cleaner.next(),
588 Some(("跨".to_string(), 2913342670, 3))
589 );
590 assert_eq!(
591 token_cleaner.next(),
592 Some(("懒".to_string(), 3199935961, 3))
593 );
594 assert_eq!(
595 token_cleaner.next(),
596 Some(("狗".to_string(), 3360772096, 3))
597 );
598 assert_eq!(token_cleaner.next(), None);
599 }
600
601 #[cfg(feature = "tokenizer-japanese")]
602 #[test]
603 fn it_cleans_token_japanese_lindera_product() {
604 let mut token_cleaner = TokenLexerBuilder::from(
605 TokenLexerMode::NormalizeAndCleanup,
606 None,
607 "関西国際空港限定トートバッグ",
608 NORMALIZATION_CONFIG,
609 )
610 .unwrap();
611
612 assert_eq!(token_cleaner.locale, Some(Lang::Jpn));
613 assert_eq!(
614 token_cleaner.next(),
615 Some(("関西".to_string(), 1283572620, 6))
616 );
617 assert_eq!(
618 token_cleaner.next(),
619 Some(("国際".to_string(), 2132457693, 6))
620 );
621 assert_eq!(
622 token_cleaner.next(),
623 Some(("空港".to_string(), 865668138, 6))
624 );
625 assert_eq!(
626 token_cleaner.next(),
627 Some(("限定".to_string(), 3708465176, 6))
628 );
629 assert_eq!(
630 token_cleaner.next(),
631 Some(("トート".to_string(), 881444746, 9))
632 );
633 assert_eq!(
634 token_cleaner.next(),
635 Some(("バッグ".to_string(), 3515727814, 9))
636 );
637 assert_eq!(token_cleaner.next(), None);
638 }
639
640 #[cfg(feature = "tokenizer-japanese")]
641 #[test]
642 fn it_cleans_token_japanese_lindera_food() {
643 let token_cleaner = TokenLexerBuilder::from(
644 TokenLexerMode::NormalizeAndCleanup,
645 None,
646 "𠮷野家",
647 NORMALIZATION_CONFIG,
648 )
649 .unwrap();
650
651 assert_eq!(token_cleaner.locale, None);
652
653 let token_cleaner = TokenLexerBuilder::from(
654 TokenLexerMode::NormalizeAndCleanup,
655 None,
656 "ヱビスビール",
657 NORMALIZATION_CONFIG,
658 )
659 .unwrap();
660
661 assert_eq!(token_cleaner.locale, None);
662 }
663
664 #[cfg(feature = "tokenizer-japanese")]
665 #[test]
666 fn it_cleans_token_japanese_lindera_sentence() {
667 let mut token_cleaner = TokenLexerBuilder::from(
668 TokenLexerMode::NormalizeAndCleanup,
669 None,
670 "𠮷野家でヱビスビールを飲んだ",
671 NORMALIZATION_CONFIG,
672 )
673 .unwrap();
674
675 assert_eq!(token_cleaner.locale, Some(Lang::Jpn));
676 assert_eq!(
677 token_cleaner.next(),
678 Some(("𠮷".to_string(), 2866455824, 4))
679 );
680 assert_eq!(
681 token_cleaner.next(),
682 Some(("野家".to_string(), 1324395598, 6))
683 );
684 assert_eq!(
685 token_cleaner.next(),
686 Some(("ヱビス".to_string(), 1696836208, 9))
687 );
688 assert_eq!(
689 token_cleaner.next(),
690 Some(("ビール".to_string(), 3421909800, 9))
691 );
692 assert_eq!(
693 token_cleaner.next(),
694 Some(("飲ん".to_string(), 3196735184, 6))
695 );
696 assert_eq!(token_cleaner.next(), None);
697 }
698
699 #[test]
700 fn it_cleans_token_emojis() {
701 let mut token_cleaner = TokenLexerBuilder::from(
702 TokenLexerMode::NormalizeAndCleanup,
703 None,
704 "🚀 🙋♂️🙋♂️🙋♂️",
705 NORMALIZATION_CONFIG,
706 )
707 .unwrap();
708
709 assert_eq!(token_cleaner.locale, None);
710 assert_eq!(token_cleaner.next(), None);
711 }
712
713 #[test]
714 fn it_cleans_token_lang_hinted() {
715 let mut token_cleaner_right = TokenLexerBuilder::from(
716 TokenLexerMode::NormalizeAndCleanup,
717 Some(Lang::Eng),
718 "This will be cleaned properly, as English was hinted rightfully so.",
719 NORMALIZATION_CONFIG,
720 )
721 .unwrap();
722 let mut token_cleaner_wrong = TokenLexerBuilder::from(
723 TokenLexerMode::NormalizeAndCleanup,
724 Some(Lang::Fra),
725 "This will not be cleaned properly, as French was hinted but this is English.",
726 NORMALIZATION_CONFIG,
727 )
728 .unwrap();
729
730 assert_eq!(token_cleaner_right.locale, Some(Lang::Eng));
731 assert_eq!(token_cleaner_wrong.locale, Some(Lang::Fra));
732
733 assert_eq!(
734 token_cleaner_right.next(),
735 Some(("cleaned".to_string(), 3550382624, 7))
736 );
737 assert_eq!(
738 token_cleaner_wrong.next(),
739 Some(("this".to_string(), 493303710, 4))
740 );
741 }
742
743 #[test]
744 fn it_detects_lang_english_regular() {
745 assert_eq!(
746 TokenLexerBuilder::detect_lang("The quick brown fox jumps over the lazy dog!"),
747 Some(Lang::Eng)
748 );
749 }
750
751 #[test]
752 fn it_detects_lang_english_long() {
753 assert_eq!(
754 TokenLexerBuilder::detect_lang(
755 r#"Running an electrical current through water splits it into oxygen and hydrogen,
756 the latter of which can be used as a reliable, zero-emission fuel source. In the past,
757 the process of purifying water beforehand was too energy intensive for this process to
758 be useful — but now scientists have figured out how to skip the process altogether and
759 convert seawater into usable hydrogen"#
760 ),
761 Some(Lang::Eng)
762 );
763 }
764
765 #[test]
766 fn it_doesnt_detect_lang_english_tiny() {
767 assert_eq!(TokenLexerBuilder::detect_lang("The quick"), None);
768 }
769}
770
771#[cfg(all(feature = "benchmark", test))]
772mod benches {
773 extern crate test;
774
775 use super::*;
776 use test::Bencher;
777
778 #[bench]
779 fn bench_normalize_token_french_build(b: &mut Bencher) {
780 b.iter(|| {
781 TokenLexerBuilder::from(
782 TokenLexerMode::NormalizeOnly,
783 "Le vif renard brun saute par dessus le chien paresseux.",
784 NORMALIZATION_CONFIG,
785 )
786 });
787 }
788
789 #[bench]
790 fn bench_normalize_token_french_exhaust(b: &mut Bencher) {
791 b.iter(|| {
792 let token_cleaner = TokenLexerBuilder::from(
793 TokenLexerMode::NormalizeOnly,
794 "Le vif renard brun saute par dessus le chien paresseux.",
795 NORMALIZATION_CONFIG,
796 )
797 .unwrap();
798
799 token_cleaner.map(|value| value.1).collect::<Vec<u32>>()
800 });
801 }
802
803 #[bench]
804 fn bench_clean_token_english_regular_build(b: &mut Bencher) {
805 b.iter(|| {
806 TokenLexerBuilder::from(
807 TokenLexerMode::NormalizeAndCleanup,
808 None,
809 "The quick brown fox jumps over the lazy dog!",
810 NORMALIZATION_CONFIG,
811 )
812 });
813 }
814
815 #[bench]
816 fn bench_clean_token_english_regular_exhaust(b: &mut Bencher) {
817 b.iter(|| {
818 let token_cleaner = TokenLexerBuilder::from(
819 TokenLexerMode::NormalizeAndCleanup,
820 None,
821 "The quick brown fox jumps over the lazy dog!",
822 NORMALIZATION_CONFIG,
823 )
824 .unwrap();
825
826 token_cleaner.map(|value| value.1).collect::<Vec<u32>>()
827 });
828 }
829
830 #[bench]
831 fn bench_clean_token_english_long_exhaust(b: &mut Bencher) {
832 b.iter(|| {
833 let token_cleaner = TokenLexerBuilder::from(
834 TokenLexerMode::NormalizeAndCleanup,
835 None,
836 r#"Running an electrical current through water splits it into oxygen and hydrogen,
837 the latter of which can be used as a reliable, zero-emission fuel source. In the
838 past, the process of purifying water beforehand was too energy intensive for this
839 process to be useful — but now scientists have figured out how to skip the process
840 altogether and convert seawater into usable hydrogen"#,
841 NORMALIZATION_CONFIG,
842 )
843 .unwrap();
844
845 token_cleaner.map(|value| value.1).collect::<Vec<u32>>()
846 });
847 }
848
849 #[bench]
850 fn bench_clean_token_english_hinted_build(b: &mut Bencher) {
851 b.iter(|| {
852 TokenLexerBuilder::from(
853 TokenLexerMode::NormalizeAndCleanup(Some(Lang::Eng)),
854 "The quick brown fox jumps over the lazy dog!",
855 NORMALIZATION_CONFIG,
856 )
857 });
858 }
859
860 #[bench]
861 fn bench_clean_token_english_hinted_exhaust(b: &mut Bencher) {
862 b.iter(|| {
863 let token_cleaner = TokenLexerBuilder::from(
864 TokenLexerMode::NormalizeAndCleanup(Some(Lang::Eng)),
865 "The quick brown fox jumps over the lazy dog!",
866 NORMALIZATION_CONFIG,
867 )
868 .unwrap();
869
870 token_cleaner.map(|value| value.1).collect::<Vec<u32>>()
871 });
872 }
873
874 #[bench]
875 fn bench_clean_token_chinese_build(b: &mut Bencher) {
876 b.iter(|| {
877 TokenLexerBuilder::from(
878 TokenLexerMode::NormalizeAndCleanup,
879 None,
880 "我们中出了一个叛徒",
881 NORMALIZATION_CONFIG,
882 )
883 });
884 }
885
886 #[bench]
887 fn bench_clean_token_chinese_exhaust(b: &mut Bencher) {
888 b.iter(|| {
889 let token_cleaner = TokenLexerBuilder::from(
890 TokenLexerMode::NormalizeAndCleanup,
891 None,
892 "我们中出了一个叛徒",
893 NORMALIZATION_CONFIG,
894 )
895 .unwrap();
896
897 token_cleaner.map(|value| value.1).collect::<Vec<u32>>()
898 });
899 }
900
901 #[bench]
902 fn bench_clean_token_japanese_build(b: &mut Bencher) {
903 b.iter(|| {
904 TokenLexerBuilder::from(
905 TokenLexerMode::NormalizeAndCleanup,
906 None,
907 "関西国際空港限定トートバッグ",
908 NORMALIZATION_CONFIG,
909 )
910 });
911 }
912
913 #[bench]
914 fn bench_clean_token_japanese_exhaust(b: &mut Bencher) {
915 b.iter(|| {
916 let token_cleaner = TokenLexerBuilder::from(
917 TokenLexerMode::NormalizeAndCleanup,
918 None,
919 "関西国際空港限定トートバッグ",
920 NORMALIZATION_CONFIG,
921 )
922 .unwrap();
923
924 token_cleaner.map(|value| value.1).collect::<Vec<u32>>()
925 });
926 }
927
928 #[bench]
929 fn bench_detect_lang_english_short(b: &mut Bencher) {
930 b.iter(|| TokenLexerBuilder::detect_lang("The quick brown fox."));
931 }
932
933 #[bench]
934 fn bench_detect_lang_english_regular(b: &mut Bencher) {
935 b.iter(|| TokenLexerBuilder::detect_lang("The quick brown fox jumps over the lazy dog!"));
936 }
937
938 #[bench]
939 fn bench_detect_lang_english_long(b: &mut Bencher) {
940 b.iter(|| {
941 TokenLexerBuilder::detect_lang(
942 r#"Running an electrical current through water splits it into oxygen and hydrogen,
943 the latter of which can be used as a reliable, zero-emission fuel source. In the past,
944 the process of purifying water beforehand was too energy intensive for this process to
945 be useful — but now scientists have figured out how to skip the process altogether and
946 convert seawater into usable hydrogen"#,
947 )
948 });
949 }
950
951 #[bench]
952 fn bench_dont_detect_lang_english_tiny(b: &mut Bencher) {
953 b.iter(|| TokenLexerBuilder::detect_lang("The quick"));
954 }
955}