1use crate::classify::{self, BLOCK, Backend, CAT_NONE, CATEGORY, Lanes, Rules};
2use crate::search::{self, NibbleSet};
3use crate::{Anchor, ByteSet, Finder, Memo, RunCache, RunHint, RunRule, Runs};
4use std::fmt;
5use std::mem::MaybeUninit;
6use std::ops::Range;
7
8const CL_DIGIT: u16 = 1 << 0;
9const CL_HEX_ALPHA: u16 = 1 << 1;
10const CL_ALPHA_OTHER: u16 = 1 << 2;
11const CL_AT: u16 = 1 << 3;
12const CL_DOLLAR: u16 = 1 << 4;
13const CL_HASH: u16 = 1 << 5;
14const CL_OPEN_BRACE: u16 = 1 << 6;
15const CL_OPEN_BRACKET: u16 = 1 << 7;
16const CL_COLON: u16 = 1 << 8;
17const CL_DOT: u16 = 1 << 9;
18const CL_SLASH: u16 = 1 << 10;
19const CL_TILDE: u16 = 1 << 11;
20const CL_PLUS: u16 = 1 << 12;
21const CL_OPEN_PAREN: u16 = 1 << 13;
22const CL_DASH: u16 = 1 << 14;
23
24const CL_ALL_USED: u16 = (1 << 15) - 1;
25const CL_HEX: u16 = CL_DIGIT | CL_HEX_ALPHA;
26
27const fn build_byte_class_table() -> [u16; 256] {
28 let mut table = [0u16; 256];
29 let mut i = 0u16;
30 while i < 256 {
31 let b = i as u8;
32 let mut c = 0u16;
33 if b >= b'0' && b <= b'9' {
34 c |= CL_DIGIT;
35 }
36 if (b >= b'a' && b <= b'f') || (b >= b'A' && b <= b'F') {
37 c |= CL_HEX_ALPHA;
38 }
39 if (b >= b'g' && b <= b'z') || (b >= b'G' && b <= b'Z') {
40 c |= CL_ALPHA_OTHER;
41 }
42 if b == b'@' {
43 c |= CL_AT;
44 }
45 if b == b'$' {
46 c |= CL_DOLLAR;
47 }
48 if b == b'#' {
49 c |= CL_HASH;
50 }
51 if b == b'{' {
52 c |= CL_OPEN_BRACE;
53 }
54 if b == b'[' {
55 c |= CL_OPEN_BRACKET;
56 }
57 if b == b':' {
58 c |= CL_COLON;
59 }
60 if b == b'.' {
61 c |= CL_DOT;
62 }
63 if b == b'/' {
64 c |= CL_SLASH;
65 }
66 if b == b'~' {
67 c |= CL_TILDE;
68 }
69 if b == b'+' {
70 c |= CL_PLUS;
71 }
72 if b == b'(' {
73 c |= CL_OPEN_PAREN;
74 }
75 if b == b'-' {
76 c |= CL_DASH;
77 }
78 table[i as usize] = c;
79 i += 1;
80 }
81 table
82}
83
84static BYTE_CLASSES: [u16; 256] = build_byte_class_table();
85
86const CTX_CLASSES: usize = 18;
90const CTX_NONE: usize = CTX_CLASSES - 1;
92
93const fn ctx_class(b: u8) -> usize {
94 match b {
95 b'0'..=b'9' => 0,
96 b'a'..=b'f' | b'A'..=b'F' => 1,
97 b'g' | b'G' => 2,
98 b's' | b'S' => 3,
99 b'y' | b'Y' => 4,
100 b'h'..=b'z' | b'H'..=b'Z' => 5,
101 b'.' => 6,
102 b':' => 7,
103 b'-' => 8,
104 b'_' => 9,
105 b'/' => 10,
106 b'@' => 11,
107 b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C => 12,
108 0x80..=0xBF => 13,
109 0xC0..=0xFF => 14,
110 b'(' | b'[' | b'{' | b'<' | b'"' | b'\'' | b'`' | b'=' | b'>' => 15,
111 _ => 16,
112 }
113}
114
115const fn build_ctx_class_table() -> [u8; 256] {
116 let mut table = [0u8; 256];
117 let mut i = 0;
118 while i < 256 {
119 table[i] = ctx_class(i as u8) as u8;
120 i += 1;
121 }
122 table
123}
124
125static CTX_CLASS: [u8; 256] = build_ctx_class_table();
126
127fn ctx_class_category(class: usize) -> u8 {
129 match class {
130 0 => classify::CAT_DIGIT,
131 1 => classify::CAT_HEX_ALPHA,
132 2 => classify::CAT_ALPHA_GO,
133 3 | 4 => classify::CAT_ALPHA_PZ,
134 5 => classify::CAT_OTHER_ALPHA,
135 6 => classify::CAT_DOT,
136 7 => classify::CAT_COLON,
137 13 | 14 => classify::CAT_HIGH,
138 _ => classify::CAT_OTHER,
139 }
140}
141
142#[inline]
143fn prescan(input: &[u8]) -> u16 {
144 let mut classes = 0u16;
145 let (chunks, remainder) = input.as_chunks::<8>();
146 for chunk in chunks {
147 classes |= BYTE_CLASSES[chunk[0] as usize]
148 | BYTE_CLASSES[chunk[1] as usize]
149 | BYTE_CLASSES[chunk[2] as usize]
150 | BYTE_CLASSES[chunk[3] as usize]
151 | BYTE_CLASSES[chunk[4] as usize]
152 | BYTE_CLASSES[chunk[5] as usize]
153 | BYTE_CLASSES[chunk[6] as usize]
154 | BYTE_CLASSES[chunk[7] as usize];
155 if classes == CL_ALL_USED {
156 return classes;
157 }
158 }
159 for &b in remainder {
160 classes |= BYTE_CLASSES[b as usize];
161 }
162 classes
163}
164
165fn required_classes(id: &str) -> &'static [(u16, bool)] {
166 match id {
167 "cidr" => &[(CL_DIGIT, true), (CL_SLASH, true)],
168 "codetag" => &[(CL_COLON, true)],
169 "color" => &[(CL_HASH | CL_ALPHA_OTHER | CL_HEX_ALPHA, false)],
170 "datetime" => &[(CL_DIGIT, true)],
171 "domain" => &[(CL_DOT, true), (CL_ALPHA_OTHER | CL_HEX_ALPHA, false)],
172 "email" => &[(CL_AT, true)],
173 "emoji" => &[],
174 "env" => &[(CL_DOLLAR, true)],
175 "handle" => &[(CL_AT, true)],
176 "hash" => &[(CL_HEX, false)],
177 "ip" => &[(CL_DIGIT | CL_HEX_ALPHA | CL_COLON | CL_OPEN_BRACKET, false)],
178 "json" => &[(CL_OPEN_BRACE | CL_OPEN_BRACKET, false)],
179 "jwt" => &[(CL_HEX_ALPHA, false)],
180 "mac" => &[(CL_HEX, false), (CL_COLON | CL_DASH | CL_DOT, false)],
181 "mirror" => &[],
182 "modeline" => &[(CL_COLON, true)],
183 "path" => &[(CL_SLASH | CL_DOT | CL_TILDE, false)],
184 "phone" => &[(CL_DIGIT | CL_PLUS | CL_OPEN_PAREN, false)],
185 "semver" => &[(CL_DIGIT, true), (CL_DOT, true)],
186 "uri" => &[(CL_COLON, true)],
187 "uuid" => &[(CL_HEX, false), (CL_DASH, true)],
188 _ => &[],
189 }
190}
191
192#[inline]
193fn can_skip_with_mask(cl: u16, required: &[(u16, bool)]) -> bool {
194 for &(mask, all_required) in required {
195 if all_required {
196 if cl & mask != mask {
197 return true;
198 }
199 } else if (cl & mask) == 0 {
200 return true;
201 }
202 }
203 false
204}
205
206const MAX_FINDERS: usize = 32;
207
208#[derive(Clone, Debug, PartialEq, Eq)]
209pub enum ScannerError {
210 TooManyFinders { len: usize, max: usize },
211}
212
213impl fmt::Display for ScannerError {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 match self {
216 ScannerError::TooManyFinders { len, max } => {
217 write!(f, "too many finders: got {}, max is {}", len, max)
218 }
219 }
220 }
221}
222
223#[derive(Clone, Debug, PartialEq, Eq)]
224pub struct Match {
225 pub finder_index: usize,
226 pub range: Range<usize>,
227}
228
229#[derive(Clone, Debug, Default, PartialEq, Eq)]
231pub struct FinderStats {
232 pub lines_run: u64,
234 pub find_calls: u64,
236 pub try_at_calls: u64,
238 pub try_trigger_calls: u64,
240 pub hits: u64,
242 pub matches: u64,
244}
245
246impl FinderStats {
247 pub fn calls(&self) -> u64 {
249 self.find_calls + self.try_at_calls + self.try_trigger_calls
250 }
251
252 fn add(&mut self, other: &FinderStats) {
253 self.lines_run += other.lines_run;
254 self.find_calls += other.find_calls;
255 self.try_at_calls += other.try_at_calls;
256 self.try_trigger_calls += other.try_trigger_calls;
257 self.hits += other.hits;
258 self.matches += other.matches;
259 }
260}
261
262#[derive(Clone, Debug, Default, PartialEq, Eq)]
269pub struct ScanStats {
270 pub lines: u64,
272 pub bytes: u64,
274 pub lines_skipped: u64,
276 pub finder_lines_skipped: u64,
278 pub positions: u64,
281 pub coarse_positions: u64,
284 pub candidate_positions: u64,
286 pub run_gated: u64,
288 pub coarse_rejected: Vec<u64>,
291 pub matches: u64,
293 pub sorts: u64,
295 pub finders: Vec<FinderStats>,
297}
298
299impl ScanStats {
300 pub fn for_scanner(scanner: &Scanner) -> Self {
302 ScanStats {
303 finders: vec![FinderStats::default(); scanner.finders.len()],
304 ..Default::default()
305 }
306 }
307
308 pub fn merge(&mut self, other: &ScanStats) {
311 self.lines += other.lines;
312 self.bytes += other.bytes;
313 self.lines_skipped += other.lines_skipped;
314 self.finder_lines_skipped += other.finder_lines_skipped;
315 self.positions += other.positions;
316 self.coarse_positions += other.coarse_positions;
317 self.candidate_positions += other.candidate_positions;
318 self.run_gated += other.run_gated;
319 if self.coarse_rejected.len() < other.coarse_rejected.len() {
320 self.coarse_rejected.resize(other.coarse_rejected.len(), 0);
321 }
322 for (i, n) in other.coarse_rejected.iter().enumerate() {
323 self.coarse_rejected[i] += n;
324 }
325 self.matches += other.matches;
326 self.sorts += other.sorts;
327 if self.finders.len() < other.finders.len() {
328 self.finders
329 .resize(other.finders.len(), FinderStats::default());
330 }
331 for (a, b) in self.finders.iter_mut().zip(&other.finders) {
332 a.add(b);
333 }
334 }
335
336 pub fn calls(&self) -> u64 {
338 self.finders.iter().map(FinderStats::calls).sum()
339 }
340
341 pub fn hits(&self) -> u64 {
343 self.finders.iter().map(|f| f.hits).sum()
344 }
345}
346
347trait Probe {
350 #[inline(always)]
351 fn line(&mut self, _bytes: usize) {}
352 #[inline(always)]
353 fn line_skipped(&mut self) {}
354 #[inline(always)]
355 fn finders_skipped(&mut self, _count: u32) {}
356 #[inline(always)]
357 fn finder_run(&mut self, _finder: usize) {}
358 #[inline(always)]
359 fn positions(&mut self, _count: usize) {}
360 #[inline(always)]
361 fn coarse_position(&mut self) {}
362 #[inline(always)]
363 fn candidate_position(&mut self) {}
364 #[inline(always)]
365 fn run_gated(&mut self) {}
366 #[inline(always)]
367 fn run_gated_n(&mut self, _n: u32) {}
368 #[inline(always)]
369 fn coarse_rejected(&mut self, _cur: u8) {}
370 #[inline(always)]
371 fn find_call(&mut self, _finder: usize, _hit: bool) {}
372 #[inline(always)]
373 fn try_at_call(&mut self, _finder: usize, _hit: bool) {}
374 #[inline(always)]
375 fn try_trigger_call(&mut self, _finder: usize, _hit: bool) {}
376 #[inline(always)]
377 fn matched(&mut self, _finder: usize) {}
378 #[inline(always)]
379 fn sorted(&mut self) {}
380}
381
382impl Probe for () {}
383
384impl Probe for ScanStats {
385 fn line(&mut self, bytes: usize) {
386 self.lines += 1;
387 self.bytes += bytes as u64;
388 }
389 fn line_skipped(&mut self) {
390 self.lines_skipped += 1;
391 }
392 fn finders_skipped(&mut self, count: u32) {
393 self.finder_lines_skipped += u64::from(count);
394 }
395 fn finder_run(&mut self, finder: usize) {
396 self.finders[finder].lines_run += 1;
397 }
398 fn positions(&mut self, count: usize) {
399 self.positions += count as u64;
400 }
401 fn coarse_position(&mut self) {
402 self.coarse_positions += 1;
403 }
404 fn candidate_position(&mut self) {
405 self.candidate_positions += 1;
406 }
407 fn run_gated(&mut self) {
408 self.run_gated += 1;
409 }
410 fn run_gated_n(&mut self, n: u32) {
411 self.run_gated += u64::from(n);
412 }
413 fn coarse_rejected(&mut self, cur: u8) {
414 if self.coarse_rejected.len() < 256 {
415 self.coarse_rejected.resize(256, 0);
416 }
417 self.coarse_rejected[cur as usize] += 1;
418 }
419 fn find_call(&mut self, finder: usize, hit: bool) {
420 let f = &mut self.finders[finder];
421 f.find_calls += 1;
422 f.hits += u64::from(hit);
423 }
424 fn try_at_call(&mut self, finder: usize, hit: bool) {
425 let f = &mut self.finders[finder];
426 f.try_at_calls += 1;
427 f.hits += u64::from(hit);
428 }
429 fn try_trigger_call(&mut self, finder: usize, hit: bool) {
430 let f = &mut self.finders[finder];
431 f.try_trigger_calls += 1;
432 f.hits += u64::from(hit);
433 }
434 fn matched(&mut self, finder: usize) {
435 self.finders[finder].matches += 1;
436 self.matches += 1;
437 }
438 fn sorted(&mut self) {
439 self.sorts += 1;
440 }
441}
442
443#[derive(Clone, Copy, Debug, PartialEq, Eq)]
447#[non_exhaustive]
448pub enum Strategy {
449 Legacy,
452 Gated,
455 Vector,
459}
460
461impl Strategy {
462 pub const ALL: &[Strategy] = &[Strategy::Legacy, Strategy::Gated, Strategy::Vector];
464
465 pub fn name(self) -> &'static str {
467 match self {
468 Strategy::Legacy => "legacy",
469 Strategy::Gated => "gated",
470 Strategy::Vector => "vector",
471 }
472 }
473
474 pub fn parse(name: &str) -> Option<Strategy> {
476 Strategy::ALL.iter().copied().find(|s| s.name() == name)
477 }
478}
479
480struct LineState {
485 finder_pos: [MaybeUninit<usize>; MAX_FINDERS],
486 memos: [MaybeUninit<Memo>; MAX_FINDERS],
487 tried: [MaybeUninit<usize>; MAX_FINDERS],
489 runs: RunCache,
490 len: usize,
492}
493
494impl LineState {
495 #[inline(always)]
496 fn new(len: usize) -> LineState {
497 let mut state = LineState {
498 finder_pos: [MaybeUninit::uninit(); MAX_FINDERS],
499 memos: [MaybeUninit::uninit(); MAX_FINDERS],
500 tried: [MaybeUninit::uninit(); MAX_FINDERS],
501 runs: RunCache::default(),
502 len,
503 };
504 for i in 0..len {
505 state.finder_pos[i].write(0);
506 state.memos[i].write(Memo::default());
507 state.tried[i].write(0);
508 }
509 state
510 }
511
512 #[inline(always)]
513 fn tried(&self, i: usize) -> usize {
514 debug_assert!(i < self.len);
515 unsafe { self.tried[i].assume_init() }
517 }
518
519 #[inline(always)]
520 fn set_tried(&mut self, i: usize, pos: usize) {
521 debug_assert!(i < self.len);
522 self.tried[i].write(pos);
523 }
524
525 #[inline(always)]
526 fn pos(&self, i: usize) -> usize {
527 debug_assert!(i < self.len);
528 unsafe { self.finder_pos[i].assume_init() }
531 }
532
533 #[inline(always)]
534 fn set_pos(&mut self, i: usize, pos: usize) {
535 debug_assert!(i < self.len);
536 self.finder_pos[i].write(pos);
537 }
538
539 #[inline(always)]
540 fn memo(&mut self, i: usize) -> &mut Memo {
541 debug_assert!(i < self.len);
542 unsafe { self.memos[i].assume_init_mut() }
544 }
545}
546
547trait Sink {
549 fn candidate(&mut self, scanner: &Scanner, pass: &Pass, pos: usize, hint: Option<&RunHint<'_>>);
550 fn stopped(&self) -> bool {
551 false
552 }
553}
554
555struct LineSink<'a, P: Probe> {
557 input: &'a [u8],
558 active_ctx: u32,
559 state: LineState,
560 matches: &'a mut Vec<Match>,
561 probe: &'a mut P,
562}
563
564impl<P: Probe> Sink for LineSink<'_, P> {
565 #[inline(always)]
566 fn candidate(
567 &mut self,
568 scanner: &Scanner,
569 pass: &Pass,
570 pos: usize,
571 hint: Option<&RunHint<'_>>,
572 ) {
573 self.probe.coarse_position();
574 scanner.probe_position(
575 pass,
576 self.input,
577 pos,
578 self.active_ctx,
579 &mut self.state,
580 self.matches,
581 self.probe,
582 hint,
583 );
584 }
585}
586
587trait LineOutput {
589 fn line(&mut self, start: usize, end: usize, matches: &mut Vec<Match>) -> bool;
592}
593
594struct Stream<'a, F: FnMut(usize, usize, &[Match]) -> bool>(&'a mut F);
596
597impl<F: FnMut(usize, usize, &[Match]) -> bool> LineOutput for Stream<'_, F> {
598 #[inline(always)]
599 fn line(&mut self, start: usize, end: usize, matches: &mut Vec<Match>) -> bool {
600 sort_matches(matches);
601 (self.0)(start, end, matches)
602 }
603}
604
605struct Collect<'a>(&'a mut Vec<Match>);
607
608impl LineOutput for Collect<'_> {
609 #[inline(always)]
610 fn line(&mut self, start: usize, _end: usize, matches: &mut Vec<Match>) -> bool {
611 for m in matches.drain(..) {
612 self.0.push(Match {
613 finder_index: m.finder_index,
614 range: m.range.start + start..m.range.end + start,
615 });
616 }
617 false
618 }
619}
620
621struct BufferSink<'a, O: LineOutput> {
625 data: &'a [u8],
626 line_start: usize,
627 line_end: usize,
629 line_next: usize,
631 active_ctx: u32,
632 state: LineState,
633 matches: Vec<Match>,
634 out: O,
635 stopped: bool,
636}
637
638impl<'a, O: LineOutput> BufferSink<'a, O> {
639 fn new(data: &'a [u8], scanner: &Scanner, out: O) -> Self {
640 BufferSink {
641 data,
642 line_start: 0,
643 line_end: 0,
644 line_next: 0,
645 active_ctx: 0,
646 state: LineState::new(scanner.finders.len()),
647 matches: Vec::new(),
648 out,
649 stopped: false,
650 }
651 }
652
653 #[inline(always)]
654 fn flush(&mut self) {
655 if self.matches.is_empty() || self.stopped {
656 return;
657 }
658 self.stopped = self
659 .out
660 .line(self.line_start, self.line_end, &mut self.matches);
661 self.matches.clear();
662 }
663
664 #[inline(always)]
666 fn enter_line(&mut self, scanner: &Scanner, pass: &Pass, pos: usize) {
667 let data = self.data;
668 self.line_start = memchr::memrchr(b'\n', &data[..pos]).map_or(0, |nl| nl + 1);
669 let nl = memchr::memchr(b'\n', &data[pos..]).map_or(data.len(), |i| pos + i);
670 self.line_next = (nl + 1).min(data.len());
671 let mut end = nl;
672 while end > self.line_start && data[end - 1] == b'\r' {
673 end -= 1;
674 }
675 self.line_end = end;
676 self.state = LineState::new(scanner.finders.len());
677 let line = &data[self.line_start..self.line_end];
678 self.active_ctx = scanner.buffer_active(pass, line);
679 }
680}
681
682impl<O: LineOutput> Sink for BufferSink<'_, O> {
683 #[inline(always)]
684 fn candidate(
685 &mut self,
686 scanner: &Scanner,
687 pass: &Pass,
688 pos: usize,
689 hint: Option<&RunHint<'_>>,
690 ) {
691 if pos >= self.line_next {
692 self.flush();
693 if self.stopped {
694 return;
695 }
696 self.enter_line(scanner, pass, pos);
697 }
698 if pos >= self.line_end || self.active_ctx == 0 {
699 return;
700 }
701 let line = &self.data[self.line_start..self.line_end];
702 scanner.probe_position(
703 pass,
704 line,
705 pos - self.line_start,
706 self.active_ctx,
707 &mut self.state,
708 &mut self.matches,
709 &mut (),
710 hint,
711 );
712 }
713
714 #[inline(always)]
715 fn stopped(&self) -> bool {
716 self.stopped
717 }
718}
719
720struct WholeSink<'a> {
724 data: &'a [u8],
725 state: LineState,
726 matches: &'a mut Vec<Match>,
727}
728
729impl Sink for WholeSink<'_> {
730 #[inline(always)]
731 fn candidate(
732 &mut self,
733 scanner: &Scanner,
734 pass: &Pass,
735 pos: usize,
736 hint: Option<&RunHint<'_>>,
737 ) {
738 let active_ctx = scanner.dispatch_mask | scanner.trigger_mask;
739 scanner.probe_position(
740 pass,
741 self.data,
742 pos,
743 active_ctx,
744 &mut self.state,
745 self.matches,
746 &mut (),
747 hint,
748 );
749 }
750}
751
752#[inline]
754fn sort_matches(matches: &mut [Match]) {
755 let sorted = matches
756 .windows(2)
757 .all(|w| (w[0].range.start, w[0].finder_index) <= (w[1].range.start, w[1].finder_index));
758 if !sorted {
759 matches.sort_by(|a, b| {
762 a.range
763 .start
764 .cmp(&b.range.start)
765 .then(a.finder_index.cmp(&b.finder_index))
766 });
767 }
768}
769
770fn merge_matches(mut lists: Vec<Vec<Match>>) -> Vec<Match> {
772 lists.retain(|list| !list.is_empty());
773 if lists.len() <= 1 {
774 return lists.pop().unwrap_or_default();
775 }
776 let total = lists.iter().map(Vec::len).sum();
777 let mut out = Vec::with_capacity(total);
778 let mut heads = vec![0usize; lists.len()];
779 loop {
780 let mut best: Option<usize> = None;
781 for (k, list) in lists.iter().enumerate() {
782 if heads[k] >= list.len() {
783 continue;
784 }
785 let m = &list[heads[k]];
786 let better = match best {
787 None => true,
788 Some(b) => {
789 let head = &lists[b][heads[b]];
790 (m.range.start, m.finder_index) < (head.range.start, head.finder_index)
791 }
792 };
793 if better {
794 best = Some(k);
795 }
796 }
797 let Some(b) = best else { break };
798 out.push(lists[b][heads[b]].clone());
799 heads[b] += 1;
800 }
801 out
802}
803
804fn emit_grouped(
807 data: &[u8],
808 matches: Vec<Match>,
809 emit: &mut impl FnMut(usize, usize, &[Match]) -> bool,
810) -> bool {
811 let mut line_start = 0;
812 let mut line_end = 0;
813 let mut line_next = 0;
814 let mut group: Vec<Match> = Vec::new();
815 for m in matches {
816 if m.range.start >= line_next || group.is_empty() {
817 if !group.is_empty() && emit(line_start, line_end, &group) {
818 return true;
819 }
820 group.clear();
821 line_start = memchr::memrchr(b'\n', &data[..m.range.start]).map_or(0, |nl| nl + 1);
822 let nl = memchr::memchr(b'\n', &data[m.range.start..])
823 .map_or(data.len(), |i| m.range.start + i);
824 line_next = (nl + 1).min(data.len());
825 line_end = nl;
826 while line_end > line_start && data[line_end - 1] == b'\r' {
827 line_end -= 1;
828 }
829 }
830 group.push(Match {
831 finder_index: m.finder_index,
832 range: m.range.start - line_start..m.range.end - line_start,
833 });
834 }
835 if !group.is_empty() && emit(line_start, line_end, &group) {
836 return true;
837 }
838 false
839}
840
841#[derive(Clone, Copy, Debug, PartialEq, Eq)]
843enum Search {
844 One(u8),
845 Two(u8, u8),
846 Three(u8, u8, u8),
847 Prefixes,
849 Blocks,
850}
851
852const PREFIX_RANK: u8 = 160;
856
857struct Pass {
862 search: Search,
863 finders: u32,
865 anchored: u32,
868 whole: bool,
871 rules: Option<Rules>,
873 prev: Option<NibbleSet>,
878 checks: search::Checks,
882 literals: Option<search::Literals>,
884 min_hex_run: usize,
889}
890
891impl Pass {
892 fn describe(&self) -> String {
895 let show = |list: &[u8]| -> String {
896 list.iter()
897 .map(|&b| {
898 if b.is_ascii_graphic() {
899 (b as char).to_string()
900 } else {
901 format!("\\x{b:02x}")
902 }
903 })
904 .collect()
905 };
906 let what = if self.anchored != 0 {
907 "anchors"
908 } else {
909 "start bytes"
910 };
911 match self.search {
912 Search::One(a) => format!("{what}({})", show(&[a])),
913 Search::Two(a, b) => format!("{what}({})", show(&[a, b])),
914 Search::Three(a, b, c) => format!("{what}({})", show(&[a, b, c])),
915 Search::Prefixes => "prefixes".to_string(),
916 Search::Blocks => "blocks".to_string(),
917 }
918 }
919}
920
921const MAX_MEMCHR_PASSES: usize = 3;
924
925const WORD_CAP: usize = 16;
928
929pub struct Scanner {
930 finders: Vec<Box<dyn Finder>>,
931 strategy: Strategy,
932 backend: classify::Kind,
933 passes: Vec<Pass>,
935 anchors: Vec<Option<Anchor>>,
936 starts: Vec<ByteSet>,
938 dispatch: [u32; 256],
940 trigger: [u32; 256],
942 gate_prev: Box<[[u32; 256]; CTX_CLASSES]>,
945 gate_next: Box<[[u32; CTX_CLASSES]; 256]>,
948 dispatch_mask: u32,
949 trigger_mask: u32,
950 context_mask: u32,
952 contexts: Vec<Option<(Box<[u64; 1024]>, u32)>>,
956 scan_mask: u32,
957 run_mask: u32,
959 run_rules: Vec<Vec<RunRule>>,
960 rule_cur: Box<[u32; 256]>,
962 after_digit: Box<[u32; 257]>,
967 after_hex: Box<[u32; 257]>,
968 len_digit: Box<[u32; 130]>,
971 len_hex: Box<[u32; 130]>,
972 digit_ruled: u32,
975 hex_ruled: u32,
976 run_cap: usize,
978 word_mask: u32,
981 word_cap: usize,
983 skip_requirements: Vec<&'static [(u16, bool)]>,
984}
985
986impl Scanner {
987 pub fn new(finders: Vec<Box<dyn Finder>>) -> Self {
988 Self::try_new(finders).expect("too many finders")
989 }
990
991 pub fn try_new(finders: Vec<Box<dyn Finder>>) -> Result<Self, ScannerError> {
992 if finders.len() > MAX_FINDERS {
993 return Err(ScannerError::TooManyFinders {
994 len: finders.len(),
995 max: MAX_FINDERS,
996 });
997 }
998
999 let mut dispatch = [0u32; 256];
1000 let mut trigger = [0u32; 256];
1001 let mut dispatch_mask = 0u32;
1002 let mut trigger_mask = 0u32;
1003 let mut scan_mask = 0u32;
1004
1005 let skip_requirements: Vec<&'static [(u16, bool)]> =
1006 finders.iter().map(|f| required_classes(f.id())).collect();
1007 let run_rules: Vec<Vec<RunRule>> = finders
1008 .iter()
1009 .map(|f| {
1010 if f.dispatchable() || f.triggerable() {
1011 f.run_rules()
1012 } else {
1013 Vec::new()
1014 }
1015 })
1016 .collect();
1017 for (i, rules) in run_rules.iter().enumerate() {
1021 let words = rules
1022 .iter()
1023 .filter(|r| r.class == crate::RunClass::Word)
1024 .count();
1025 assert!(
1026 words == 0 || words == rules.len(),
1027 "finder {} mixes word rules with digit or hex rules",
1028 finders[i].id()
1029 );
1030 }
1031 let run_mask = run_rules
1032 .iter()
1033 .enumerate()
1034 .filter(|(_, rules)| rules.iter().any(|r| r.class != crate::RunClass::Word))
1035 .fold(0u32, |mask, (i, _)| mask | (1u32 << i));
1036 let mut rule_cur = Box::new([0u32; 256]);
1037 let mut after_digit = Box::new([0u32; 257]);
1038 let mut after_hex = Box::new([0u32; 257]);
1039 let mut len_digit = Box::new([0u32; 130]);
1040 let mut len_hex = Box::new([0u32; 130]);
1041 let mut digit_ruled = 0u32;
1042 let mut hex_ruled = 0u32;
1043 for (i, rules) in run_rules.iter().enumerate() {
1044 let bit = 1u32 << i;
1045 for rule in rules {
1046 let (after, lens, ruled) = match rule.class {
1047 crate::RunClass::Digit => (&mut after_digit, &mut len_digit, &mut digit_ruled),
1048 crate::RunClass::Hex => (&mut after_hex, &mut len_hex, &mut hex_ruled),
1049 crate::RunClass::Word => continue,
1050 };
1051 *ruled |= bit;
1052 for len in rule.min..=rule.max.min(crate::RUN_CAP) {
1053 if rule.lengths == 0 || len >= 64 || rule.lengths & (1u64 << len) != 0 {
1054 lens[len as usize] |= bit;
1055 }
1056 }
1057 for b in 0..=255u8 {
1058 if rule.cur.contains(b) {
1059 rule_cur[b as usize] |= bit;
1060 }
1061 if rule.after.contains(b) {
1062 after[b as usize] |= bit;
1063 }
1064 }
1065 if rule.after_end {
1066 after[256] |= bit;
1067 }
1068 }
1069 }
1070 let run_cap = run_rules
1073 .iter()
1074 .flatten()
1075 .filter(|rule| rule.class != crate::RunClass::Word)
1076 .map(|rule| rule.max as usize + 1)
1077 .max()
1078 .unwrap_or(1)
1079 .min(crate::RUN_CAP as usize);
1080 let word_mask = run_rules
1081 .iter()
1082 .enumerate()
1083 .filter(|(_, rules)| rules.iter().any(|r| r.class == crate::RunClass::Word))
1084 .fold(0u32, |mask, (i, _)| mask | (1u32 << i));
1085 let word_cap = run_rules
1090 .iter()
1091 .flatten()
1092 .filter(|rule| rule.class == crate::RunClass::Word)
1093 .map(|rule| (rule.max as usize + 1).min(WORD_CAP).max(rule.min as usize))
1094 .max()
1095 .unwrap_or(1);
1096
1097 let mut gate_prev = Box::new([[0u32; 256]; CTX_CLASSES]);
1098 let mut gate_next = Box::new([[0u32; CTX_CLASSES]; 256]);
1099
1100 for (i, finder) in finders.iter().enumerate() {
1101 let bit = 1u32 << i;
1102 if finder.dispatchable() {
1103 dispatch_mask |= bit;
1104 for cur in 0..=255u8 {
1105 if !finder.could_start_at(cur) {
1106 continue;
1107 }
1108 dispatch[cur as usize] |= bit;
1109 gate_prev[CTX_NONE][cur as usize] |= bit;
1113 gate_next[cur as usize][CTX_NONE] |= bit;
1114 for other in 0..=255u8 {
1115 if finder.could_start_after(other, cur) {
1116 gate_prev[ctx_class(other)][cur as usize] |= bit;
1117 }
1118 if finder.could_continue_with(cur, other) {
1119 gate_next[cur as usize][ctx_class(other)] |= bit;
1120 }
1121 }
1122 }
1123 } else if finder.triggerable() {
1124 trigger_mask |= bit;
1125 for cur in 0..=255u8 {
1126 if !finder.could_trigger_at(cur) {
1127 continue;
1128 }
1129 trigger[cur as usize] |= bit;
1130 gate_prev[CTX_NONE][cur as usize] |= bit;
1131 gate_next[cur as usize][CTX_NONE] |= bit;
1132 for other in 0..=255u8 {
1133 if finder.could_start_after(other, cur) {
1134 gate_prev[ctx_class(other)][cur as usize] |= bit;
1135 }
1136 if finder.could_continue_with(cur, other) {
1137 gate_next[cur as usize][ctx_class(other)] |= bit;
1138 }
1139 }
1140 }
1141 } else {
1142 scan_mask |= bit;
1143 }
1144 }
1145
1146 let mut context_mask = 0u32;
1147 let contexts: Vec<Option<(Box<[u64; 1024]>, u32)>> = finders
1148 .iter()
1149 .enumerate()
1150 .map(|(i, f)| {
1151 if !f.triggerable() || !f.has_trigger_context() {
1152 return None;
1153 }
1154 context_mask |= 1u32 << i;
1155 let mut pairs = Box::new([0u64; 1024]);
1156 for prev2 in 0..=255u8 {
1157 for prev1 in 0..=255u8 {
1158 if f.trigger_context(prev2, prev1) {
1159 let index = usize::from(prev2) << 8 | usize::from(prev1);
1160 pairs[index >> 6] |= 1 << (index & 63);
1161 }
1162 }
1163 }
1164 let mut exempt = 0u32;
1167 for b in 0..=255u8 {
1168 if f.trigger_context_exempt(Some(b)) {
1169 exempt |= 1 << ctx_class(b);
1170 }
1171 }
1172 if f.trigger_context_exempt(None) {
1173 exempt |= 1 << CTX_NONE;
1174 }
1175 Some((pairs, exempt))
1176 })
1177 .collect();
1178 let anchors: Vec<Option<Anchor>> = finders
1179 .iter()
1180 .map(|f| if f.dispatchable() { f.anchor() } else { None })
1181 .collect();
1182 for (i, anchor) in anchors.iter().enumerate() {
1183 if let Some(anchor) = anchor
1187 && anchor.back.is_some()
1188 {
1189 assert!(
1190 (0..=255u8).all(|b| !(anchor.bytes.contains(b) && anchor.walk.contains(b))),
1191 "finder {} anchors on a walk byte with an exact offset",
1192 finders[i].id()
1193 );
1194 }
1195 }
1196 let starts: Vec<ByteSet> = finders
1197 .iter()
1198 .map(|f| ByteSet::from_fn(|b| f.dispatchable() && f.could_start_at(b)))
1199 .collect();
1200 let ctx_mask = dispatch_mask | trigger_mask;
1201
1202 let build_rules = |mask: u32| -> Rules {
1208 let mut items = Vec::new();
1209 for i in 0..finders.len() {
1210 let bit = 1u32 << i;
1211 if bit & mask == 0 {
1212 continue;
1213 }
1214 for b in 0..=255u8 {
1215 if gate_prev[CTX_NONE][b as usize] & bit == 0 {
1216 continue;
1217 }
1218 let mut prev = 0u8;
1219 let mut next = 0u8;
1220 for class in 0..CTX_NONE {
1221 if gate_prev[class][b as usize] & bit != 0 {
1222 prev |= ctx_class_category(class);
1223 }
1224 if gate_next[b as usize][class] & bit != 0 {
1225 next |= ctx_class_category(class);
1226 }
1227 }
1228 items.push((b, prev, next));
1229 }
1230 }
1231 Rules::build(items)
1232 };
1233 let min_hex_run = |mask: u32| -> usize {
1238 let mut min_hex_run = usize::MAX;
1239 for (i, finder) in finders.iter().enumerate() {
1240 if mask & (1u32 << i) == 0 || !finder.dispatchable() {
1241 continue;
1242 }
1243 let hex_starts: Vec<u8> = (0..=255u8)
1244 .filter(|&b| b.is_ascii_hexdigit() && finder.could_start_at(b))
1245 .collect();
1246 if hex_starts.is_empty() {
1247 continue;
1248 }
1249 let mut finder_min = usize::MAX;
1250 for &b in &hex_starts {
1251 let mut byte_min = usize::MAX;
1252 let mut covered = false;
1253 for rule in &run_rules[i] {
1254 if !rule.cur.contains(b) {
1255 continue;
1256 }
1257 covered = true;
1258 match rule.class {
1259 crate::RunClass::Hex => byte_min = byte_min.min(rule.min as usize),
1260 crate::RunClass::Digit | crate::RunClass::Word => byte_min = 1,
1263 }
1264 }
1265 if !covered {
1266 byte_min = 1;
1267 }
1268 finder_min = finder_min.min(byte_min);
1269 }
1270 min_hex_run = min_hex_run.min(finder_min);
1271 }
1272 if min_hex_run == usize::MAX || min_hex_run < 2 {
1273 0
1274 } else {
1275 min_hex_run
1276 }
1277 };
1278
1279 let bytes_of =
1288 |set: &dyn Fn(u8) -> bool| -> Vec<u8> { (0..=255u8).filter(|&b| set(b)).collect() };
1289 let few = |bytes: &[u8]| (1..=3).contains(&bytes.len());
1290 let search_bytes = |i: usize| -> Option<(Vec<u8>, bool)> {
1291 let finder = &finders[i];
1292 let bit = 1u32 << i;
1293 if trigger_mask & bit != 0 {
1294 let bytes = bytes_of(&|b| finder.could_trigger_at(b));
1295 return few(&bytes).then_some((bytes, false));
1296 }
1297 if dispatch_mask & bit == 0 {
1298 return None;
1299 }
1300 let bytes = bytes_of(&|b| starts[i].contains(b));
1303 let anchored = anchors[i]
1304 .map(|anchor| bytes_of(&|b| anchor.bytes.contains(b)))
1305 .filter(|bytes| few(bytes));
1306 match anchored {
1307 Some(anchor) if !few(&bytes) || search::rank(&anchor) < search::rank(&bytes) => {
1308 Some((anchor, true))
1309 }
1310 _ => few(&bytes).then_some((bytes, false)),
1311 }
1312 };
1313 let mut cheap: Vec<(usize, Vec<u8>, bool)> = Vec::new();
1314 let mut block_mask = 0u32;
1315 let mut prefix_mask = 0u32;
1318 let mut prefixes: Vec<Vec<u8>> = Vec::new();
1319 for (i, finder) in finders.iter().enumerate() {
1320 let bit = 1u32 << i;
1321 if ctx_mask & bit == 0 {
1322 continue;
1323 }
1324 let bytes = search_bytes(i);
1325 let own = match &bytes {
1328 Some((bytes, _)) if search::rank(bytes) < PREFIX_RANK => Vec::new(),
1329 _ if dispatch_mask & bit != 0 => finder.prefixes(),
1330 _ => Vec::new(),
1331 };
1332 let mut joined = prefixes.clone();
1333 joined.extend(own.iter().cloned());
1334 if !own.is_empty() && search::Literals::new(&joined).is_some() {
1335 prefix_mask |= bit;
1336 prefixes = joined;
1337 continue;
1338 }
1339 match bytes {
1340 Some((bytes, anchored)) => cheap.push((i, bytes, anchored)),
1341 None => block_mask |= bit,
1342 }
1343 }
1344 let literals = if block_mask != 0 {
1346 block_mask |= prefix_mask;
1347 prefix_mask = 0;
1348 None
1349 } else {
1350 search::Literals::new(&prefixes)
1351 };
1352 if block_mask != 0 {
1353 let base = min_hex_run(block_mask);
1354 let mut joined = block_mask;
1355 cheap.retain(|&(i, _, _)| {
1356 let bit = 1u32 << i;
1357 let weakens = base >= 2 && min_hex_run(joined | bit) < 2;
1358 if !weakens {
1359 joined |= bit;
1360 }
1361 weakens
1362 });
1363 block_mask = joined;
1364 }
1365 cheap.sort_by_key(|(_, bytes, _)| bytes.len());
1366 let mut groups: Vec<(Vec<u8>, u32, u32)> = Vec::new();
1367 for (i, bytes, anchored) in cheap {
1368 let bit = 1u32 << i;
1369 let anchored_bit = if anchored { bit } else { 0 };
1370 let fits = groups.iter_mut().find(|group| {
1371 let extra = bytes.iter().filter(|b| !group.0.contains(b)).count();
1372 group.0.len() + extra <= 3
1373 });
1374 if let Some(group) = fits {
1375 for &b in &bytes {
1376 if !group.0.contains(&b) {
1377 group.0.push(b);
1378 }
1379 }
1380 group.1 |= bit;
1381 group.2 |= anchored_bit;
1382 } else if groups.len() < MAX_MEMCHR_PASSES {
1383 groups.push((bytes, bit, anchored_bit));
1384 } else {
1385 block_mask |= bit;
1386 }
1387 }
1388 let whole_for = |mask: u32| {
1389 (0..finders.len())
1390 .filter(|&i| mask & (1u32 << i) != 0)
1391 .all(|i| finders[i].line_agnostic())
1392 };
1393 let prev_filter = |bytes: &[u8], mask: u32, anchored: u32| -> Option<NibbleSet> {
1399 let mut members = [false; 256];
1400 members[usize::from(b'\n')] = true;
1401 for (i, f) in finders.iter().enumerate() {
1402 if mask & (1u32 << i) == 0 {
1403 continue;
1404 }
1405 if anchored & (1u32 << i) != 0 {
1406 let anchor = anchors[i]
1407 .as_ref()
1408 .expect("an anchored finder has an anchor");
1409 if !anchor.back.is_some_and(|back| back >= 1) {
1410 return None;
1411 }
1412 for prev in 0..=255u8 {
1413 if anchor.walk.contains(prev) {
1414 members[usize::from(prev)] = true;
1415 }
1416 }
1417 continue;
1418 }
1419 let context = match &contexts[i] {
1420 Some((pairs, 0)) => {
1421 let mut allowed = [false; 256];
1422 for (word, &bits) in pairs.iter().enumerate() {
1423 for bit in 0..64 {
1424 if bits & (1 << bit) != 0 {
1425 allowed[(word * 64 + bit) & 255] = true;
1426 }
1427 }
1428 }
1429 Some(allowed)
1430 }
1431 _ => None,
1432 };
1433 for &cur in bytes {
1434 let starts = if f.dispatchable() {
1435 f.could_start_at(cur)
1436 } else {
1437 f.triggerable() && f.could_trigger_at(cur)
1438 };
1439 if !starts {
1440 continue;
1441 }
1442 for prev in 0..=255u8 {
1443 if f.could_start_after(prev, cur)
1444 && context.is_none_or(|allowed| allowed[usize::from(prev)])
1445 {
1446 members[usize::from(prev)] = true;
1447 }
1448 }
1449 }
1450 }
1451 NibbleSet::new(&members)
1452 };
1453 let search_checks = |bytes: &[u8], mask: u32, anchored: u32| -> search::Checks {
1454 let mut checks = search::Checks::default();
1455 let indices = |set: &crate::ByteSet| -> Option<u8> {
1457 if (0..=255u8).any(|b| set.contains(b) && !bytes.contains(&b)) {
1458 return None;
1459 }
1460 Some(
1461 (0..bytes.len())
1462 .filter(|&j| set.contains(bytes[j]))
1463 .fold(0u8, |m, j| m | 1 << j),
1464 )
1465 };
1466 for (k, &b) in bytes.iter().enumerate() {
1467 let (mut used, mut conditional) = (false, true);
1468 for (i, f) in finders.iter().enumerate() {
1469 let bit = 1u32 << i;
1470 if mask & bit == 0 {
1471 continue;
1472 }
1473 if anchored & bit == 0 {
1474 let starts = if f.dispatchable() {
1475 f.could_start_at(b)
1476 } else {
1477 f.triggerable() && f.could_trigger_at(b)
1478 };
1479 if starts {
1480 used = true;
1481 conditional = false;
1482 }
1483 continue;
1484 }
1485 let anchor = anchors[i]
1486 .as_ref()
1487 .expect("an anchored finder has an anchor");
1488 if !anchor.bytes.contains(b) {
1489 continue;
1490 }
1491 used = true;
1492 let Some((check, bytes_mask)) = anchor
1493 .checks
1494 .iter()
1495 .flatten()
1496 .find(|check| check.anchors.contains(b))
1497 .and_then(|check| Some((check, indices(&check.bytes)?)))
1498 .filter(|(check, _)| check.offsets().iter().all(|&o| o < 64))
1499 else {
1500 conditional = false;
1501 continue;
1502 };
1503 let entry = search::Check {
1504 hits: 1 << k,
1505 bytes: bytes_mask,
1506 offsets: check.offsets,
1507 count: check.offset_count,
1508 all: check.all,
1509 };
1510 let same = |e: &&mut search::Check| {
1511 (e.bytes, e.offsets, e.count, e.all)
1512 == (entry.bytes, entry.offsets, entry.count, entry.all)
1513 };
1514 if let Some(existing) = checks.list.iter_mut().flatten().find(same) {
1515 existing.hits |= 1 << k;
1516 } else if let Some(slot) = checks.list.iter_mut().find(|e| e.is_none()) {
1517 *slot = Some(entry);
1518 } else {
1519 conditional = false;
1520 }
1521 }
1522 if used && conditional {
1523 checks.conditional |= 1 << k;
1524 }
1525 }
1526 checks
1527 };
1528 let mut passes = Vec::new();
1529 for (mut bytes, mask, anchored) in groups {
1530 bytes.sort_unstable();
1531 let prev = prev_filter(&bytes, mask, anchored);
1532 let checks = search_checks(&bytes, mask, anchored);
1533 let search = match bytes.as_slice() {
1534 [a] => Search::One(*a),
1535 [a, b] => Search::Two(*a, *b),
1536 [a, b, c] => Search::Three(*a, *b, *c),
1537 _ => unreachable!("a memchr pass searches at most three bytes"),
1538 };
1539 passes.push(Pass {
1540 search,
1541 finders: mask,
1542 anchored,
1543 whole: whole_for(mask),
1544 rules: None,
1545 prev,
1546 checks,
1547 literals: None,
1548 min_hex_run: 0,
1549 });
1550 }
1551 if prefix_mask != 0 {
1552 passes.push(Pass {
1553 search: Search::Prefixes,
1554 finders: prefix_mask,
1555 anchored: 0,
1556 whole: whole_for(prefix_mask),
1557 rules: None,
1558 prev: None,
1559 checks: search::Checks::default(),
1560 literals,
1561 min_hex_run: 0,
1562 });
1563 }
1564 if block_mask != 0 {
1565 passes.push(Pass {
1566 search: Search::Blocks,
1567 finders: block_mask,
1568 anchored: 0,
1569 whole: whole_for(block_mask),
1570 rules: Some(build_rules(block_mask)),
1571 prev: None,
1572 checks: search::Checks::default(),
1573 literals: None,
1574 min_hex_run: min_hex_run(block_mask),
1575 });
1576 }
1577
1578 Ok(Scanner {
1579 finders,
1580 strategy: Strategy::Vector,
1581 backend: classify::detect(),
1582 passes,
1583 anchors,
1584 starts,
1585 dispatch,
1586 trigger,
1587 gate_prev,
1588 gate_next,
1589 dispatch_mask,
1590 trigger_mask,
1591 context_mask,
1592 contexts,
1593 scan_mask,
1594 run_mask,
1595 run_rules,
1596 rule_cur,
1597 after_digit,
1598 after_hex,
1599 len_digit,
1600 len_hex,
1601 digit_ruled,
1602 hex_ruled,
1603 run_cap,
1604 word_mask,
1605 word_cap,
1606 skip_requirements,
1607 })
1608 }
1609
1610 pub fn finders(&self) -> &[Box<dyn Finder>] {
1611 &self.finders
1612 }
1613
1614 pub fn strategy(&self) -> Strategy {
1616 self.strategy
1617 }
1618
1619 pub fn set_strategy(&mut self, strategy: Strategy) {
1621 self.strategy = strategy;
1622 }
1623
1624 pub fn backend(&self) -> &'static str {
1627 self.backend.name()
1628 }
1629
1630 pub fn plan(&self) -> String {
1634 if self.passes.is_empty() {
1635 return "none".to_string();
1636 }
1637 self.passes
1638 .iter()
1639 .map(Pass::describe)
1640 .collect::<Vec<_>>()
1641 .join(" + ")
1642 }
1643
1644 #[inline]
1647 fn all_memchr(&self) -> bool {
1648 !self.passes.is_empty() && self.passes.iter().all(|p| p.search != Search::Blocks)
1649 }
1650
1651 #[doc(hidden)]
1653 pub fn use_scalar_backend(&mut self) {
1654 self.backend = classify::Kind::Scalar;
1655 }
1656
1657 #[inline]
1658 fn compute_active(&self, line_classes: u16) -> u32 {
1659 let mut active = 0u32;
1660 for (i, req) in self.skip_requirements.iter().enumerate() {
1661 if !can_skip_with_mask(line_classes, req) {
1662 active |= 1u32 << i;
1663 }
1664 }
1665 active
1666 }
1667
1668 pub fn scan_line(&self, line: &str) -> Vec<Match> {
1669 let mut matches = Vec::new();
1670 self.scan_line_into(line, &mut matches);
1671 matches
1672 }
1673
1674 pub fn skip_to_candidate_line(&self, text: &[u8], from: usize) -> Option<usize> {
1682 let from = from.min(text.len());
1683 let search = match self.passes.as_slice() {
1685 [pass] => pass.search,
1686 _ => return Some(from),
1687 };
1688 let hay = &text[from..];
1689 let hit = match search {
1690 Search::One(a) => memchr::memchr(a, hay),
1691 Search::Two(a, b) => memchr::memchr2(a, b, hay),
1692 Search::Three(a, b, c) => memchr::memchr3(a, b, c, hay),
1693 Search::Prefixes | Search::Blocks => return Some(from),
1694 }?;
1695 let hit = from + hit;
1696 Some(
1697 memchr::memrchr(b'\n', &text[from..hit])
1698 .map(|nl| from + nl + 1)
1699 .unwrap_or(from),
1700 )
1701 }
1702
1703 pub fn scan_line_into(&self, line: &str, matches: &mut Vec<Match>) {
1704 self.scan_line_probe(line, matches, &mut ());
1705 }
1706
1707 pub fn scan_line_stats(&self, line: &str, matches: &mut Vec<Match>, stats: &mut ScanStats) {
1710 if stats.finders.len() < self.finders.len() {
1711 stats
1712 .finders
1713 .resize(self.finders.len(), FinderStats::default());
1714 }
1715 self.scan_line_probe(line, matches, stats);
1716 }
1717
1718 #[inline]
1722 fn buffer_active(&self, pass: &Pass, line: &[u8]) -> u32 {
1723 let ctx = pass.finders;
1724 let single_gated = self.finders.len() == 1 && ctx != 0;
1725 if single_gated || pass.search != Search::Blocks || line.is_empty() {
1726 return ctx;
1727 }
1728 self.compute_active(prescan(line)) & ctx
1729 }
1730
1731 #[inline]
1734 fn activate<P: Probe>(&self, input: &[u8], probe: &mut P) -> Option<u32> {
1735 probe.line(input.len());
1736 if input.is_empty() {
1737 probe.line_skipped();
1738 return None;
1739 }
1740 let all = if self.finders.is_empty() {
1741 0
1742 } else {
1743 u32::MAX >> (32 - self.finders.len())
1744 };
1745 let single_gated = self.finders.len() == 1 && (self.dispatch_mask | self.trigger_mask) != 0;
1751 if single_gated || (self.all_memchr() && self.strategy == Strategy::Vector) {
1752 if all == 0 {
1753 probe.line_skipped();
1754 return None;
1755 }
1756 let mut bits = all;
1757 while bits != 0 {
1758 probe.finder_run(bits.trailing_zeros() as usize);
1759 bits &= bits - 1;
1760 }
1761 return Some(all);
1762 }
1763 let line_classes = prescan(input);
1764 let active = self.compute_active(line_classes);
1765 probe.finders_skipped((all & !active).count_ones());
1766 if active == 0 {
1767 probe.line_skipped();
1768 return None;
1769 }
1770 let mut bits = active;
1771 while bits != 0 {
1772 probe.finder_run(bits.trailing_zeros() as usize);
1773 bits &= bits - 1;
1774 }
1775 Some(active)
1776 }
1777
1778 fn scan_line_probe<P: Probe>(&self, line: &str, matches: &mut Vec<Match>, probe: &mut P) {
1779 matches.clear();
1780
1781 let input = line.as_bytes();
1782 let Some(active) = self.activate(input, probe) else {
1783 return;
1784 };
1785
1786 let active_scan = active & self.scan_mask;
1787 if active_scan != 0 {
1788 let mut bits = active_scan;
1789 while bits != 0 {
1790 let i = bits.trailing_zeros() as usize;
1791 bits &= bits - 1;
1792 let finder = &self.finders[i];
1793 let mut idx = 0;
1794 while idx < line.len() {
1795 let found = finder.find(&line[idx..]);
1796 probe.find_call(i, found.is_some());
1797 if let Some(range) = found {
1798 probe.matched(i);
1799 matches.push(Match {
1800 finder_index: i,
1801 range: (idx + range.start)..(idx + range.end),
1802 });
1803 idx += range.end;
1804 } else {
1805 break;
1806 }
1807 }
1808 }
1809 }
1810
1811 match self.strategy {
1812 Strategy::Legacy => self.walk_legacy(input, active, matches, probe),
1813 Strategy::Gated => self.walk_gated(input, active, matches, probe),
1814 Strategy::Vector => self.walk_vector(input, active, matches, probe),
1815 }
1816
1817 let sorted = matches.windows(2).all(|w| {
1821 (w[0].range.start, w[0].finder_index) <= (w[1].range.start, w[1].finder_index)
1822 });
1823 if !sorted {
1824 probe.sorted();
1825 sort_matches(matches);
1826 }
1827 }
1828
1829 fn walk_legacy<P: Probe>(
1831 &self,
1832 input: &[u8],
1833 active: u32,
1834 matches: &mut Vec<Match>,
1835 probe: &mut P,
1836 ) {
1837 let active_dispatch = active & self.dispatch_mask;
1838 if active_dispatch != 0 {
1839 let mut finder_pos = [0usize; MAX_FINDERS];
1840 probe.positions(input.len());
1841
1842 for pos in 0..input.len() {
1843 let mut candidates = self.dispatch[input[pos] as usize] & active_dispatch;
1844 if candidates == 0 {
1845 continue;
1846 }
1847 probe.candidate_position();
1848 while candidates != 0 {
1849 let i = candidates.trailing_zeros() as usize;
1850 candidates &= candidates - 1;
1851
1852 if pos < finder_pos[i] {
1853 continue;
1854 }
1855 let found = self.finders[i].try_at(input, pos);
1856 probe.try_at_call(i, found.is_some());
1857 if let Some(range) = found {
1858 if range.start < finder_pos[i] {
1859 continue;
1860 }
1861 probe.matched(i);
1862 matches.push(Match {
1863 finder_index: i,
1864 range: range.clone(),
1865 });
1866 finder_pos[i] = range.end;
1867 }
1868 }
1869 }
1870 }
1871
1872 let active_trigger = active & self.trigger_mask;
1873 if active_trigger != 0 {
1874 let mut finder_pos = [0usize; MAX_FINDERS];
1875 probe.positions(input.len());
1876
1877 for pos in 0..input.len() {
1878 let mut candidates = self.trigger[input[pos] as usize] & active_trigger;
1879 if candidates == 0 {
1880 continue;
1881 }
1882 probe.candidate_position();
1883 while candidates != 0 {
1884 let i = candidates.trailing_zeros() as usize;
1885 candidates &= candidates - 1;
1886
1887 if pos < finder_pos[i] {
1888 continue;
1889 }
1890 let found = self.finders[i].try_trigger_at(input, pos);
1891 probe.try_trigger_call(i, found.is_some());
1892 if let Some(range) = found {
1893 if range.start < finder_pos[i] {
1894 continue;
1895 }
1896 finder_pos[i] = range.end;
1897 probe.matched(i);
1898 matches.push(Match {
1899 finder_index: i,
1900 range,
1901 });
1902 }
1903 }
1904 }
1905 }
1906 }
1907
1908 #[inline(always)]
1911 #[allow(clippy::too_many_arguments)]
1912 fn probe_position<P: Probe>(
1913 &self,
1914 pass: &Pass,
1915 input: &[u8],
1916 pos: usize,
1917 active_ctx: u32,
1918 state: &mut LineState,
1919 matches: &mut Vec<Match>,
1920 probe: &mut P,
1921 hint: Option<&RunHint<'_>>,
1922 ) {
1923 let active = active_ctx & pass.finders;
1924 if pass.anchored != 0 {
1925 self.probe_anchor(input, pos, active & pass.anchored, state, matches, probe);
1926 }
1927 let plain = active & !pass.anchored;
1928 if plain == 0 {
1929 return;
1930 }
1931 let cur = input[pos];
1932 let next_class = if pos + 1 < input.len() {
1933 CTX_CLASS[input[pos + 1] as usize] as usize
1934 } else {
1935 CTX_NONE
1936 };
1937 let mut plain = plain;
1938 let contextual = plain & self.context_mask & self.trigger[cur as usize];
1939 if contextual != 0 {
1940 let prev1 = if pos > 0 { input[pos - 1] } else { b' ' };
1944 let prev2 = if pos > 1 { input[pos - 2] } else { b' ' };
1945 let index = usize::from(prev2) << 8 | usize::from(prev1);
1946 let mut bits = contextual;
1947 while bits != 0 {
1948 let i = bits.trailing_zeros() as usize;
1949 bits &= bits - 1;
1950 let (pairs, exempt) = self.contexts[i]
1951 .as_ref()
1952 .expect("a context finder has a table");
1953 if exempt & (1 << next_class) == 0 && pairs[index >> 6] & (1 << (index & 63)) == 0 {
1954 plain &= !(1u32 << i);
1955 }
1956 }
1957 if plain == 0 {
1958 probe.coarse_rejected(cur);
1959 return;
1960 }
1961 }
1962 let prev_class = if pos > 0 {
1963 CTX_CLASS[input[pos - 1] as usize] as usize
1964 } else {
1965 CTX_NONE
1966 };
1967 let mut candidates = self.gate_prev[prev_class][cur as usize] & plain;
1968 if candidates == 0 {
1969 probe.coarse_rejected(cur);
1970 return;
1971 }
1972 candidates &= self.gate_next[cur as usize][next_class];
1973 if candidates == 0 {
1974 probe.coarse_rejected(cur);
1975 return;
1976 }
1977 probe.candidate_position();
1978 self.invoke(input, pos, candidates, state, matches, probe, hint);
1979 }
1980
1981 #[inline]
1987 fn probe_anchor<P: Probe>(
1988 &self,
1989 input: &[u8],
1990 pos: usize,
1991 mask: u32,
1992 state: &mut LineState,
1993 matches: &mut Vec<Match>,
1994 probe: &mut P,
1995 ) {
1996 let cur = input[pos];
1997 let mut bits = mask;
1998 while bits != 0 {
1999 let i = bits.trailing_zeros() as usize;
2000 bits &= bits - 1;
2001 let Some(anchor) = &self.anchors[i] else {
2004 continue;
2005 };
2006 if !anchor.bytes.contains(cur) || pos < state.pos(i) {
2007 continue;
2008 }
2009 let mut confirmed = false;
2012 if let Some(check) = anchor
2013 .checks
2014 .iter()
2015 .flatten()
2016 .find(|check| check.anchors.contains(cur))
2017 {
2018 if !check.passes(input, pos) {
2019 continue;
2020 }
2021 confirmed = true;
2022 }
2023 let tried = state.tried(i);
2024 if let Some(back) = anchor.back {
2025 let mut next_tried = pos + 1;
2027 if let Some(p) = pos.checked_sub(back as usize)
2028 && p >= tried
2029 && self.starts[i].contains(input[p])
2030 {
2031 let matched = if confirmed {
2035 self.call_finder_at(i, input, p, state, matches, probe)
2036 } else {
2037 self.try_finder_at(i, input, p, state, matches, probe)
2038 };
2039 if matched {
2040 next_tried = next_tried.max(state.pos(i));
2041 }
2042 }
2043 state.set_tried(i, next_tried);
2044 continue;
2045 }
2046 let mut start = pos;
2047 while start > tried && anchor.walk.contains(input[start - 1]) {
2048 start -= 1;
2049 }
2050 let mut p = start.max(tried);
2051 let mut next_tried = pos + 1;
2052 while p <= pos {
2053 if self.starts[i].contains(input[p])
2054 && self.try_finder_at(i, input, p, state, matches, probe)
2055 {
2056 next_tried = next_tried.max(state.pos(i));
2057 break;
2058 }
2059 p += 1;
2060 }
2061 state.set_tried(i, next_tried);
2062 }
2063 }
2064
2065 #[inline]
2069 fn call_finder_at<P: Probe>(
2070 &self,
2071 i: usize,
2072 input: &[u8],
2073 pos: usize,
2074 state: &mut LineState,
2075 matches: &mut Vec<Match>,
2076 probe: &mut P,
2077 ) -> bool {
2078 probe.candidate_position();
2079 let found = self.finders[i].try_at_memo(input, pos, state.memo(i));
2080 probe.try_at_call(i, found.is_some());
2081 match found {
2082 Some(range) if range.start >= state.pos(i) => {
2083 state.set_pos(i, range.end);
2084 probe.matched(i);
2085 matches.push(Match {
2086 finder_index: i,
2087 range,
2088 });
2089 true
2090 }
2091 _ => false,
2092 }
2093 }
2094
2095 #[inline]
2098 fn try_finder_at<P: Probe>(
2099 &self,
2100 i: usize,
2101 input: &[u8],
2102 pos: usize,
2103 state: &mut LineState,
2104 matches: &mut Vec<Match>,
2105 probe: &mut P,
2106 ) -> bool {
2107 let bit = 1u32 << i;
2108 let cur = input[pos];
2109 let prev_class = if pos > 0 {
2110 CTX_CLASS[input[pos - 1] as usize] as usize
2111 } else {
2112 CTX_NONE
2113 };
2114 let next_class = if pos + 1 < input.len() {
2115 CTX_CLASS[input[pos + 1] as usize] as usize
2116 } else {
2117 CTX_NONE
2118 };
2119 if self.gate_prev[prev_class][cur as usize] & self.gate_next[cur as usize][next_class] & bit
2120 == 0
2121 {
2122 return false;
2123 }
2124 probe.candidate_position();
2125 if self.word_mask & bit != 0 {
2126 let runs = Runs::only_word(Runs::word_at(input, pos, self.word_cap));
2127 if !RunRule::allow(&self.run_rules[i], cur, &runs, input, pos) {
2128 probe.run_gated();
2129 return false;
2130 }
2131 }
2132 if self.run_mask & self.rule_cur[cur as usize] & bit != 0 {
2133 let runs = Runs::at_cached(input, pos, &mut state.runs, self.run_cap);
2134 if self.may_accept(&runs) & bit == 0
2135 || !RunRule::allow(&self.run_rules[i], cur, &runs, input, pos)
2136 {
2137 probe.run_gated();
2138 return false;
2139 }
2140 }
2141 let found = self.finders[i].try_at_memo(input, pos, state.memo(i));
2142 probe.try_at_call(i, found.is_some());
2143 match found {
2144 Some(range) if range.start >= state.pos(i) => {
2145 state.set_pos(i, range.end);
2146 probe.matched(i);
2147 matches.push(Match {
2148 finder_index: i,
2149 range,
2150 });
2151 true
2152 }
2153 _ => false,
2154 }
2155 }
2156
2157 #[inline(always)]
2160 fn may_accept(&self, runs: &Runs) -> u32 {
2161 let index = |run: &crate::Run| run.after.map_or(256, |b| b as usize);
2162 let mut may = (self.after_digit[index(&runs.digit)]
2163 & self.len_digit[runs.digit.len as usize])
2164 | (self.after_hex[index(&runs.hex)] & self.len_hex[runs.hex.len as usize]);
2165 if runs.digit.capped {
2166 may |= self.digit_ruled & self.len_digit[runs.digit.len as usize];
2167 }
2168 if runs.hex.capped {
2169 may |= self.hex_ruled & self.len_hex[runs.hex.len as usize];
2170 }
2171 may
2172 }
2173
2174 #[inline(never)]
2178 fn word_gate<P: Probe>(
2179 &self,
2180 input: &[u8],
2181 pos: usize,
2182 mut candidates: u32,
2183 probe: &mut P,
2184 ) -> u32 {
2185 let runs = Runs::only_word(Runs::word_at(input, pos, self.word_cap));
2186 let mut gated = candidates & self.word_mask;
2187 while gated != 0 {
2188 let i = gated.trailing_zeros() as usize;
2189 gated &= gated - 1;
2190 if !RunRule::allow(&self.run_rules[i], input[pos], &runs, input, pos) {
2191 candidates &= !(1u32 << i);
2192 probe.run_gated();
2193 }
2194 }
2195 candidates
2196 }
2197
2198 #[inline(always)]
2200 #[allow(clippy::too_many_arguments)]
2201 fn invoke<P: Probe>(
2202 &self,
2203 input: &[u8],
2204 pos: usize,
2205 mut candidates: u32,
2206 state: &mut LineState,
2207 matches: &mut Vec<Match>,
2208 probe: &mut P,
2209 hint: Option<&RunHint<'_>>,
2210 ) {
2211 if candidates & self.word_mask != 0 {
2212 candidates = self.word_gate(input, pos, candidates, probe);
2213 }
2214 let ruled = candidates & self.run_mask & self.rule_cur[input[pos] as usize];
2215 if ruled != 0 {
2216 let runs = match hint {
2217 Some(hint) => Runs::at_hinted(input, pos, hint, &mut state.runs, self.run_cap),
2218 None => Runs::at_cached(input, pos, &mut state.runs, self.run_cap),
2219 };
2220 let may = self.may_accept(&runs);
2223 let rejected = ruled & !may;
2224 candidates &= !rejected;
2225 probe.run_gated_n(rejected.count_ones());
2226 let mut gated = ruled & may;
2227 while gated != 0 {
2228 let i = gated.trailing_zeros() as usize;
2229 gated &= gated - 1;
2230 if !RunRule::allow(&self.run_rules[i], input[pos], &runs, input, pos) {
2231 candidates &= !(1u32 << i);
2232 probe.run_gated();
2233 }
2234 }
2235 }
2236 while candidates != 0 {
2237 let i = candidates.trailing_zeros() as usize;
2238 candidates &= candidates - 1;
2239
2240 if pos < state.pos(i) {
2241 continue;
2242 }
2243 let found = if self.dispatch_mask & (1u32 << i) != 0 {
2244 let found = self.finders[i].try_at_memo(input, pos, state.memo(i));
2245 probe.try_at_call(i, found.is_some());
2246 found
2247 } else {
2248 let found = self.finders[i].try_trigger_at_memo(input, pos, state.memo(i));
2249 probe.try_trigger_call(i, found.is_some());
2250 found
2251 };
2252 if let Some(range) = found {
2253 if range.start < state.pos(i) {
2257 continue;
2258 }
2259 state.set_pos(i, range.end);
2260 probe.matched(i);
2261 matches.push(Match {
2262 finder_index: i,
2263 range,
2264 });
2265 }
2266 }
2267 }
2268
2269 fn walk_gated<P: Probe>(
2271 &self,
2272 input: &[u8],
2273 active: u32,
2274 matches: &mut Vec<Match>,
2275 probe: &mut P,
2276 ) {
2277 let active_ctx = active & (self.dispatch_mask | self.trigger_mask);
2278 if active_ctx == 0 {
2279 return;
2280 }
2281 let mut state = LineState::new(self.finders.len());
2282 probe.positions(input.len());
2283 let len = input.len();
2284 let mut prev_class = CTX_NONE;
2285
2286 for pos in 0..len {
2287 let cur = input[pos];
2288 let mut candidates = self.gate_prev[prev_class][cur as usize] & active_ctx;
2289 prev_class = CTX_CLASS[cur as usize] as usize;
2290 if candidates == 0 {
2291 continue;
2292 }
2293 let next_class = if pos + 1 < len {
2294 CTX_CLASS[input[pos + 1] as usize] as usize
2295 } else {
2296 CTX_NONE
2297 };
2298 candidates &= self.gate_next[cur as usize][next_class];
2299 if candidates == 0 {
2300 continue;
2301 }
2302 probe.candidate_position();
2303 self.invoke(input, pos, candidates, &mut state, matches, probe, None);
2304 }
2305 }
2306
2307 fn walk_vector<P: Probe>(
2309 &self,
2310 input: &[u8],
2311 active: u32,
2312 matches: &mut Vec<Match>,
2313 probe: &mut P,
2314 ) {
2315 let active_ctx = active & (self.dispatch_mask | self.trigger_mask);
2316 if active_ctx == 0 {
2317 return;
2318 }
2319 probe.positions(input.len());
2320 let mut sink = LineSink {
2321 input,
2322 active_ctx,
2323 state: LineState::new(self.finders.len()),
2324 matches,
2325 probe,
2326 };
2327 for pass in &self.passes {
2328 if active_ctx & pass.finders != 0 {
2329 self.walk_pass(pass, input, &mut sink);
2330 }
2331 }
2332 }
2333
2334 #[inline(always)]
2337 fn walk_pass<S: Sink>(&self, pass: &Pass, input: &[u8], sink: &mut S) {
2338 match pass.search {
2339 Search::One(_) | Search::Two(..) | Search::Three(..) => {
2340 let (bytes, count) = match pass.search {
2342 Search::One(a) => ([a, a, a], 1),
2343 Search::Two(a, b) => ([a, b, b], 2),
2344 Search::Three(a, b, c) => ([a, b, c], 3),
2345 Search::Prefixes | Search::Blocks => unreachable!("handled below"),
2346 };
2347 self.search(pass, &bytes[..count], input, sink);
2348 }
2349 Search::Prefixes => {
2350 let literals = pass.literals.as_ref().expect("a prefix pass has literals");
2351 let engine = search::Engine::of(self.backend);
2352 search::search_literals(engine, literals, input, |pos| {
2353 sink.candidate(self, pass, pos, None);
2354 sink.stopped()
2355 });
2356 }
2357 Search::Blocks => {
2358 if input.len() < BLOCK {
2359 let mut prev_class = CTX_NONE;
2361 for (pos, &cur) in input.iter().enumerate() {
2362 let candidates = self.gate_prev[prev_class][cur as usize] & pass.finders;
2363 prev_class = CTX_CLASS[cur as usize] as usize;
2364 if candidates != 0 {
2365 sink.candidate(self, pass, pos, None);
2366 if sink.stopped() {
2367 return;
2368 }
2369 }
2370 }
2371 return;
2372 }
2373 match self.backend {
2374 classify::Kind::Scalar => {
2375 self.walk_blocks::<classify::Scalar, S>(pass, input, sink)
2376 }
2377 #[cfg(target_arch = "aarch64")]
2378 classify::Kind::Neon => {
2379 self.walk_blocks::<classify::neon::Neon, S>(pass, input, sink)
2380 }
2381 #[cfg(target_arch = "x86_64")]
2382 classify::Kind::Ssse3 => {
2383 unsafe { self.walk_blocks_ssse3(pass, input, sink) }
2385 }
2386 }
2387 }
2388 }
2389 }
2390
2391 #[inline(always)]
2392 fn search<S: Sink>(&self, pass: &Pass, bytes: &[u8], input: &[u8], sink: &mut S) {
2393 let engine = search::Engine::of(self.backend);
2394 search::search(
2395 engine,
2396 bytes,
2397 pass.prev.as_ref(),
2398 &pass.checks,
2399 input,
2400 |pos| {
2401 sink.candidate(self, pass, pos, None);
2402 sink.stopped()
2403 },
2404 );
2405 }
2406
2407 #[cfg(target_arch = "x86_64")]
2408 #[target_feature(enable = "ssse3")]
2409 unsafe fn walk_blocks_ssse3<S: Sink>(&self, pass: &Pass, input: &[u8], sink: &mut S) {
2410 self.walk_blocks::<classify::ssse3::Ssse3, S>(pass, input, sink)
2411 }
2412
2413 #[inline(always)]
2414 fn walk_blocks<B: Backend, S: Sink>(&self, pass: &Pass, input: &[u8], sink: &mut S) {
2415 const GROUP: usize = 4;
2416 let rules = pass.rules.as_ref().expect("a block pass has rules");
2417 let tables = B::tables(rules);
2418 let len = input.len();
2419 debug_assert!(len >= BLOCK);
2420 let block_at =
2421 |at: usize| -> &[u8; BLOCK] { input[at..at + BLOCK].try_into().expect("a full block") };
2422 let cat_at =
2423 |at: usize| -> u8 { input.get(at).map_or(CAT_NONE, |&b| CATEGORY[b as usize]) };
2424
2425 let mut base = 0;
2426 while base + GROUP * BLOCK <= len {
2429 let prev = if base == 0 {
2430 CAT_NONE
2431 } else {
2432 cat_at(base - 1)
2433 };
2434 let l0 = B::block(&tables, rules, block_at(base), prev, cat_at(base + BLOCK));
2435 let l1 = B::block(
2436 &tables,
2437 rules,
2438 block_at(base + BLOCK),
2439 cat_at(base + BLOCK - 1),
2440 cat_at(base + 2 * BLOCK),
2441 );
2442 let l2 = B::block(
2443 &tables,
2444 rules,
2445 block_at(base + 2 * BLOCK),
2446 cat_at(base + 2 * BLOCK - 1),
2447 cat_at(base + 3 * BLOCK),
2448 );
2449 let l3 = B::block(
2450 &tables,
2451 rules,
2452 block_at(base + 3 * BLOCK),
2453 cat_at(base + 3 * BLOCK - 1),
2454 cat_at(base + 4 * BLOCK),
2455 );
2456 let mut lanes = [l0, l1, l2, l3];
2457 if pass.min_hex_run >= 2 {
2458 classify::drop_short_hex_runs::<B>(&mut lanes, pass.min_hex_run);
2459 }
2460 let any = B::or(
2461 B::or(lanes[0].cand, lanes[1].cand),
2462 B::or(lanes[2].cand, lanes[3].cand),
2463 );
2464 if !B::is_zero(any) {
2465 self.probe_group::<B, S>(pass, base, &lanes, 0, sink);
2466 if sink.stopped() {
2467 return;
2468 }
2469 }
2470 base += GROUP * BLOCK;
2471 }
2472 while base + BLOCK <= len {
2474 let prev = if base == 0 {
2475 CAT_NONE
2476 } else {
2477 cat_at(base - 1)
2478 };
2479 let mut lanes = [B::block(
2480 &tables,
2481 rules,
2482 block_at(base),
2483 prev,
2484 cat_at(base + BLOCK),
2485 )];
2486 if pass.min_hex_run >= 2 {
2487 classify::drop_short_hex_runs::<B>(&mut lanes, pass.min_hex_run);
2488 }
2489 if !B::is_zero(lanes[0].cand) {
2490 self.probe_group::<B, S>(pass, base, &lanes, 0, sink);
2491 if sink.stopped() {
2492 return;
2493 }
2494 }
2495 base += BLOCK;
2496 }
2497 if base < len {
2500 let at = len - BLOCK;
2501 let mut lanes = [B::block(
2502 &tables,
2503 rules,
2504 block_at(at),
2505 cat_at(at.wrapping_sub(1)),
2506 CAT_NONE,
2507 )];
2508 if pass.min_hex_run >= 2 {
2509 classify::drop_short_hex_runs::<B>(&mut lanes, pass.min_hex_run);
2510 }
2511 if !B::is_zero(lanes[0].cand) {
2512 self.probe_group::<B, S>(pass, at, &lanes, base - at, sink);
2513 }
2514 }
2515 }
2516
2517 #[inline(always)]
2520 fn probe_group<B: Backend, S: Sink>(
2521 &self,
2522 pass: &Pass,
2523 base: usize,
2524 lanes: &[Lanes<B::Vec>],
2525 skip: usize,
2526 sink: &mut S,
2527 ) {
2528 let mut hex = [0u64; 4];
2529 let mut digit = [0u64; 4];
2530 let mut cand = [0u64; 4];
2531 let count = lanes.len().min(4);
2532 for (i, l) in lanes.iter().take(count).enumerate() {
2533 cand[i] = B::mask(l.cand);
2534 hex[i] = B::mask(l.hex);
2535 digit[i] = B::mask(l.digit);
2536 }
2537 cand[0] &= !B::lanes(skip);
2538 for i in 0..count {
2539 let mut mask = cand[i];
2540 while mask != 0 {
2541 let lane = B::lane(mask);
2542 mask = B::clear(mask, lane);
2543 let hint = RunHint {
2544 hex: &hex[i..count],
2545 digit: &digit[i..count],
2546 lane,
2547 stride: B::STRIDE,
2548 };
2549 sink.candidate(self, pass, base + i * BLOCK + lane, Some(&hint));
2550 if sink.stopped() {
2551 return;
2552 }
2553 }
2554 }
2555 }
2556
2557 pub fn scan_buffer(
2568 &self,
2569 text: &str,
2570 mut emit: impl FnMut(usize, usize, &[Match]) -> bool,
2571 ) -> bool {
2572 let data = text.as_bytes();
2573 if self.strategy != Strategy::Vector || self.scan_mask != 0 || self.passes.is_empty() {
2574 return self.scan_lines(text, &mut emit);
2575 }
2576 if let [pass] = self.passes.as_slice() {
2577 if pass.whole {
2578 let mut matches = Vec::new();
2579 let mut sink = WholeSink {
2580 data,
2581 state: LineState::new(self.finders.len()),
2582 matches: &mut matches,
2583 };
2584 self.walk_pass(pass, data, &mut sink);
2585 sort_matches(&mut matches);
2586 return emit_grouped(data, matches, &mut emit);
2587 }
2588 let mut sink = BufferSink::new(data, self, Stream(&mut emit));
2589 self.walk_pass(pass, data, &mut sink);
2590 sink.flush();
2591 return sink.stopped;
2592 }
2593 let mut lists = Vec::with_capacity(self.passes.len());
2596 for pass in &self.passes {
2597 let mut matches = Vec::new();
2598 if pass.whole {
2599 let mut sink = WholeSink {
2600 data,
2601 state: LineState::new(self.finders.len()),
2602 matches: &mut matches,
2603 };
2604 self.walk_pass(pass, data, &mut sink);
2605 } else {
2606 let mut sink = BufferSink::new(data, self, Collect(&mut matches));
2607 self.walk_pass(pass, data, &mut sink);
2608 sink.flush();
2609 }
2610 sort_matches(&mut matches);
2611 lists.push(matches);
2612 }
2613 emit_grouped(data, merge_matches(lists), &mut emit)
2614 }
2615
2616 pub fn scan_buffer_matches(
2622 &self,
2623 text: &str,
2624 mut emit: impl FnMut(usize, Range<usize>) -> bool,
2625 ) -> bool {
2626 let data = text.as_bytes();
2627 if self.strategy != Strategy::Vector || self.scan_mask != 0 || self.passes.is_empty() {
2628 return self.scan_lines(text, &mut |start, _end, matches: &[Match]| {
2629 matches
2630 .iter()
2631 .any(|m| emit(m.finder_index, m.range.start + start..m.range.end + start))
2632 });
2633 }
2634 let mut lists = Vec::with_capacity(self.passes.len());
2635 for pass in &self.passes {
2636 let mut matches = Vec::new();
2637 if pass.whole {
2638 let mut sink = WholeSink {
2639 data,
2640 state: LineState::new(self.finders.len()),
2641 matches: &mut matches,
2642 };
2643 self.walk_pass(pass, data, &mut sink);
2644 } else {
2645 let mut sink = BufferSink::new(data, self, Collect(&mut matches));
2646 self.walk_pass(pass, data, &mut sink);
2647 sink.flush();
2648 }
2649 sort_matches(&mut matches);
2650 lists.push(matches);
2651 }
2652 merge_matches(lists)
2653 .into_iter()
2654 .any(|m| emit(m.finder_index, m.range))
2655 }
2656
2657 fn scan_lines(
2659 &self,
2660 text: &str,
2661 emit: &mut impl FnMut(usize, usize, &[Match]) -> bool,
2662 ) -> bool {
2663 let data = text.as_bytes();
2664 let mut matches = Vec::new();
2665 let mut pos = 0;
2666 while pos < data.len() {
2667 let nl = memchr::memchr(b'\n', &data[pos..]).map_or(data.len(), |i| pos + i);
2668 let mut end = nl;
2669 while end > pos && data[end - 1] == b'\r' {
2670 end -= 1;
2671 }
2672 self.scan_line_into(&text[pos..end], &mut matches);
2673 if !matches.is_empty() && emit(pos, end, &matches) {
2674 return true;
2675 }
2676 pos = nl + 1;
2677 }
2678 false
2679 }
2680
2681 pub fn scan_line_first(&self, line: &str) -> Option<Match> {
2684 let input = line.as_bytes();
2685 let active = self.activate(input, &mut ())?;
2686
2687 let mut best: Option<Match> = None;
2688 let beats = |best: &Option<Match>, start: usize, index: usize| match best {
2690 None => true,
2691 Some(b) => (start, index) < (b.range.start, b.finder_index),
2692 };
2693
2694 let active_scan = active & self.scan_mask;
2695 if active_scan != 0 {
2696 let mut bits = active_scan;
2697 while bits != 0 {
2698 let i = bits.trailing_zeros() as usize;
2699 bits &= bits - 1;
2700 if let Some(range) = self.finders[i].find(line)
2701 && beats(&best, range.start, i)
2702 {
2703 best = Some(Match {
2704 finder_index: i,
2705 range,
2706 });
2707 }
2708 }
2709 }
2710
2711 let active_ctx = active & (self.dispatch_mask | self.trigger_mask);
2712 if active_ctx != 0 {
2713 let mut state = LineState::new(self.finders.len());
2714 let len = input.len();
2715 let mut prev_class = CTX_NONE;
2716 let mut allowed = active_ctx;
2717
2718 for pos in 0..len {
2719 if allowed & self.dispatch_mask != 0
2724 && let Some(b) = &best
2725 && pos > b.range.start
2726 {
2727 allowed &= self.trigger_mask;
2728 if allowed == 0 {
2729 break;
2730 }
2731 }
2732 let cur = input[pos];
2733 let mut candidates = self.gate_prev[prev_class][cur as usize] & allowed;
2734 prev_class = CTX_CLASS[cur as usize] as usize;
2735 if candidates == 0 {
2736 continue;
2737 }
2738 let next_class = if pos + 1 < len {
2739 CTX_CLASS[input[pos + 1] as usize] as usize
2740 } else {
2741 CTX_NONE
2742 };
2743 candidates &= self.gate_next[cur as usize][next_class];
2744 if candidates & self.word_mask != 0 {
2745 let runs = Runs::only_word(Runs::word_at(input, pos, self.word_cap));
2746 let mut gated = candidates & self.word_mask;
2747 while gated != 0 {
2748 let i = gated.trailing_zeros() as usize;
2749 gated &= gated - 1;
2750 if !RunRule::allow(&self.run_rules[i], cur, &runs, input, pos) {
2751 candidates &= !(1u32 << i);
2752 }
2753 }
2754 }
2755 let ruled = candidates & self.run_mask & self.rule_cur[cur as usize];
2756 if ruled != 0 {
2757 let runs = Runs::at_cached(input, pos, &mut state.runs, self.run_cap);
2758 let may = self.may_accept(&runs);
2759 candidates &= !(ruled & !may);
2760 let mut gated = ruled & may;
2761 while gated != 0 {
2762 let i = gated.trailing_zeros() as usize;
2763 gated &= gated - 1;
2764 if !RunRule::allow(&self.run_rules[i], cur, &runs, input, pos) {
2765 candidates &= !(1u32 << i);
2766 }
2767 }
2768 }
2769 while candidates != 0 {
2770 let i = candidates.trailing_zeros() as usize;
2771 candidates &= candidates - 1;
2772
2773 if pos < state.pos(i) {
2774 continue;
2775 }
2776 let found = if self.dispatch_mask & (1u32 << i) != 0 {
2777 self.finders[i].try_at_memo(input, pos, state.memo(i))
2778 } else {
2779 self.finders[i].try_trigger_at_memo(input, pos, state.memo(i))
2780 };
2781 if let Some(range) = found {
2782 if range.start < state.pos(i) {
2783 continue;
2784 }
2785 state.set_pos(i, range.end);
2786 if beats(&best, range.start, i) {
2787 best = Some(Match {
2788 finder_index: i,
2789 range,
2790 });
2791 }
2792 }
2793 }
2794 }
2795 }
2796
2797 best
2798 }
2799}
2800
2801#[cfg(test)]
2802mod tests {
2803 use super::*;
2804
2805 #[test]
2806 fn prescan_empty_input() {
2807 assert_eq!(prescan(b""), 0);
2808 }
2809
2810 #[test]
2811 fn prescan_detects_digit() {
2812 assert_ne!(prescan(b"abc123") & CL_DIGIT, 0);
2813 }
2814
2815 #[test]
2816 fn prescan_detects_at() {
2817 assert_ne!(prescan(b"user@host") & CL_AT, 0);
2818 }
2819
2820 #[test]
2821 fn prescan_detects_dollar() {
2822 assert_ne!(prescan(b"$HOME") & CL_DOLLAR, 0);
2823 }
2824
2825 #[test]
2826 fn prescan_no_false_positives() {
2827 let cl = prescan(b"hello world");
2828 assert_eq!(cl & CL_AT, 0);
2829 assert_eq!(cl & CL_DOLLAR, 0);
2830 assert_eq!(cl & CL_DIGIT, 0);
2831 }
2832
2833 #[test]
2834 fn can_skip_email_without_at() {
2835 let cl = prescan(b"hello world");
2836 assert!(can_skip_with_mask(cl, required_classes("email")));
2837 }
2838
2839 #[test]
2840 fn cannot_skip_email_with_at() {
2841 let cl = prescan(b"user@example.com");
2842 assert!(!can_skip_with_mask(cl, required_classes("email")));
2843 }
2844
2845 #[test]
2846 fn can_skip_env_without_dollar() {
2847 let cl = prescan(b"no vars here");
2848 assert!(can_skip_with_mask(cl, required_classes("env")));
2849 }
2850
2851 #[test]
2852 fn can_skip_json_without_brackets() {
2853 let cl = prescan(b"no json here");
2854 assert!(can_skip_with_mask(cl, required_classes("json")));
2855 }
2856
2857 #[test]
2858 fn can_skip_uri_without_colon() {
2859 let cl = prescan(b"no uris here");
2860 assert!(can_skip_with_mask(cl, required_classes("uri")));
2861 }
2862
2863 #[test]
2864 fn can_skip_uuid_without_dash() {
2865 let cl = prescan(b"abcdef1234567890");
2866 assert!(can_skip_with_mask(cl, required_classes("uuid")));
2867 }
2868
2869 #[test]
2870 fn cannot_skip_mirror() {
2871 let cl = prescan(b"anything");
2872 assert!(!can_skip_with_mask(cl, required_classes("mirror")));
2873 }
2874
2875 #[test]
2876 fn can_skip_hash_without_hex() {
2877 let cl = prescan(b"no hx zzz");
2878 assert!(can_skip_with_mask(cl, required_classes("hash")));
2879 }
2880
2881 #[test]
2882 fn can_skip_mac_without_separator() {
2883 let cl = prescan(b"aabbccddeeff");
2884 assert!(can_skip_with_mask(cl, required_classes("mac")));
2885 }
2886
2887 #[test]
2888 fn can_skip_semver_without_dot() {
2889 let cl = prescan(b"version 100");
2890 assert!(can_skip_with_mask(cl, required_classes("semver")));
2891 }
2892
2893 #[test]
2894 fn can_skip_cidr_without_slash() {
2895 let cl = prescan(b"192.168.1.0");
2896 assert!(can_skip_with_mask(cl, required_classes("cidr")));
2897 }
2898
2899 #[test]
2900 fn can_skip_path_without_prefix() {
2901 let cl = prescan(b"no paths here");
2902 assert!(can_skip_with_mask(cl, required_classes("path")));
2903 }
2904
2905 #[test]
2906 fn skip_to_candidate_line_jumps_over_lines_without_start_bytes() {
2907 let scanner = Scanner::new(vec![Box::new(crate::email::Email::default())]);
2908 let text = b"no mail\nstill none\nhi a@b.co there\ntail";
2909 assert_eq!(scanner.skip_to_candidate_line(text, 0), Some(19));
2910 assert_eq!(scanner.skip_to_candidate_line(text, 19), Some(19));
2911 assert_eq!(scanner.skip_to_candidate_line(text, 34), None);
2912 assert_eq!(scanner.skip_to_candidate_line(text, 100), None);
2913 let scanner = Scanner::new(vec![Box::new(crate::hash::Hash::default())]);
2914 assert_eq!(scanner.skip_to_candidate_line(text, 8), Some(8));
2915 }
2916
2917 #[test]
2918 fn sparse_scanner_matches_prescanned_results() {
2919 let text = "mail a@b.co, url http://x.y, none";
2920 let finders = || -> Vec<Box<dyn Finder>> {
2921 vec![
2922 Box::new(crate::email::Email::default()),
2923 Box::new(crate::uri::URI::default()),
2924 ]
2925 };
2926 let mut gated = Scanner::new(finders());
2927 gated.set_strategy(Strategy::Gated);
2928 let vector = Scanner::new(finders());
2929 assert_eq!(gated.scan_line(text), vector.scan_line(text));
2930 assert_eq!(gated.scan_line("nothing"), vector.scan_line("nothing"));
2931 }
2932
2933 #[test]
2934 fn scanner_empty_finders() {
2935 let scanner = Scanner::new(Vec::new());
2936 assert!(scanner.scan_line("hello").is_empty());
2937 }
2938
2939 #[test]
2940 fn scanner_try_new_rejects_too_many_finders() {
2941 let finders: Vec<Box<dyn Finder>> = (0..=MAX_FINDERS)
2942 .map(|_| Box::new(crate::mirror::Mirror::default()) as Box<dyn Finder>)
2943 .collect();
2944 assert!(matches!(
2945 Scanner::try_new(finders),
2946 Err(ScannerError::TooManyFinders {
2947 len,
2948 max,
2949 })
2950 if len == MAX_FINDERS + 1 && max == MAX_FINDERS
2951 ));
2952 }
2953
2954 #[test]
2955 fn scanner_empty_line() {
2956 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::mirror::Mirror::default())];
2957 let scanner = Scanner::new(finders);
2958 assert!(scanner.scan_line("").is_empty());
2959 }
2960
2961 #[test]
2962 fn scanner_mirror_matches_everything() {
2963 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::mirror::Mirror::default())];
2964 let scanner = Scanner::new(finders);
2965 let matches = scanner.scan_line("hello world");
2966 assert_eq!(matches.len(), 1);
2967 assert_eq!(matches[0].range, 0..11);
2968 }
2969
2970 #[test]
2971 fn scanner_prescan_skips_impossible_finders() {
2972 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::email::Email::default())];
2973 let scanner = Scanner::new(finders);
2974 let matches = scanner.scan_line("no at sign here");
2975 assert!(matches.is_empty());
2976 }
2977
2978 #[test]
2979 fn scanner_dispatch_finds_hash() {
2980 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::hash::Hash::default())];
2981 let scanner = Scanner::new(finders);
2982 let input = "md5: 5d41402abc4b2a76b9719d911017c592";
2983 let matches = scanner.scan_line(input);
2984 assert_eq!(matches.len(), 1);
2985 assert_eq!(
2986 &input[matches[0].range.clone()],
2987 "5d41402abc4b2a76b9719d911017c592"
2988 );
2989 }
2990
2991 #[test]
2992 fn scanner_dispatch_finds_multiple_hashes() {
2993 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::hash::Hash::default())];
2994 let scanner = Scanner::new(finders);
2995 let input = "5d41402abc4b2a76b9719d911017c592 and 2aae6c35c94fcfb415dbe95f408b9ce91ee846ed";
2996 let matches = scanner.scan_line(input);
2997 assert_eq!(matches.len(), 2);
2998 assert_eq!(
2999 &input[matches[0].range.clone()],
3000 "5d41402abc4b2a76b9719d911017c592"
3001 );
3002 assert_eq!(
3003 &input[matches[1].range.clone()],
3004 "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"
3005 );
3006 }
3007
3008 #[test]
3009 fn scanner_mixed_dispatch_and_scan() {
3010 let finders: Vec<Box<dyn Finder>> = vec![
3011 Box::new(crate::hash::Hash::default()),
3012 Box::new(crate::email::Email::default()),
3013 ];
3014 let scanner = Scanner::new(finders);
3015 let input = "user@example.com 5d41402abc4b2a76b9719d911017c592";
3016 let matches = scanner.scan_line(input);
3017 assert_eq!(matches.len(), 2);
3018 assert_eq!(&input[matches[0].range.clone()], "user@example.com");
3019 assert_eq!(
3020 &input[matches[1].range.clone()],
3021 "5d41402abc4b2a76b9719d911017c592"
3022 );
3023 }
3024
3025 #[test]
3026 fn scanner_position_ordered_output() {
3027 let finders: Vec<Box<dyn Finder>> = vec![
3028 Box::new(crate::env::Env::default()),
3029 Box::new(crate::hash::Hash::default()),
3030 ];
3031 let scanner = Scanner::new(finders);
3032 let input = "5d41402abc4b2a76b9719d911017c592 $HOME";
3033 let matches = scanner.scan_line(input);
3034 assert_eq!(matches.len(), 2);
3035 assert!(matches[0].range.start < matches[1].range.start);
3036 }
3037
3038 #[test]
3039 fn scanner_first_returns_earliest() {
3040 let finders: Vec<Box<dyn Finder>> = vec![
3041 Box::new(crate::env::Env::default()),
3042 Box::new(crate::hash::Hash::default()),
3043 ];
3044 let scanner = Scanner::new(finders);
3045 let input = "$HOME then 5d41402abc4b2a76b9719d911017c592";
3046 let m = scanner.scan_line_first(input).unwrap();
3047 assert_eq!(&input[m.range], "$HOME");
3048 }
3049
3050 #[test]
3051 fn scanner_dispatch_finds_env() {
3052 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::env::Env::default())];
3053 let scanner = Scanner::new(finders);
3054 let input = "use $HOME and ${PATH}";
3055 let matches = scanner.scan_line(input);
3056 assert_eq!(matches.len(), 2);
3057 assert_eq!(&input[matches[0].range.clone()], "$HOME");
3058 assert_eq!(&input[matches[1].range.clone()], "${PATH}");
3059 }
3060
3061 #[test]
3062 fn scanner_dispatch_finds_json() {
3063 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::json::Json::default())];
3064 let scanner = Scanner::new(finders);
3065 let input = r#"data: {"key": "value"} end"#;
3066 let matches = scanner.scan_line(input);
3067 assert_eq!(matches.len(), 1);
3068 assert_eq!(&input[matches[0].range.clone()], r#"{"key": "value"}"#);
3069 }
3070
3071 #[test]
3072 fn scanner_dispatch_finds_uuid() {
3073 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::uuid::Uuid::default())];
3074 let scanner = Scanner::new(finders);
3075 let input = "id: 550e8400-e29b-41d4-a716-446655440000 end";
3076 let matches = scanner.scan_line(input);
3077 assert_eq!(matches.len(), 1);
3078 assert_eq!(
3079 &input[matches[0].range.clone()],
3080 "550e8400-e29b-41d4-a716-446655440000"
3081 );
3082 }
3083
3084 #[test]
3085 fn scanner_dispatch_finds_color() {
3086 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::color::Color::default())];
3087 let scanner = Scanner::new(finders);
3088 let input = "color: #ff00aa and rgb(0, 255, 0)";
3089 let matches = scanner.scan_line(input);
3090 assert_eq!(matches.len(), 2);
3091 assert_eq!(&input[matches[0].range.clone()], "#ff00aa");
3092 assert_eq!(&input[matches[1].range.clone()], "rgb(0, 255, 0)");
3093 }
3094
3095 #[test]
3096 fn scanner_dispatch_finds_ip() {
3097 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::ip::Ip::default())];
3098 let scanner = Scanner::new(finders);
3099 let input = "connect to 192.168.1.1 now";
3100 let matches = scanner.scan_line(input);
3101 assert_eq!(matches.len(), 1);
3102 assert_eq!(&input[matches[0].range.clone()], "192.168.1.1");
3103 }
3104
3105 #[test]
3106 fn scanner_dispatch_finds_datetime() {
3107 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::datetime::Datetime::default())];
3108 let scanner = Scanner::new(finders);
3109 let input = "at 2024-01-15T10:30:00Z end";
3110 let matches = scanner.scan_line(input);
3111 assert_eq!(matches.len(), 1);
3112 assert_eq!(&input[matches[0].range.clone()], "2024-01-15T10:30:00Z");
3113 }
3114
3115 #[test]
3116 fn scanner_many_finders() {
3117 let finders: Vec<Box<dyn Finder>> = vec![
3118 Box::new(crate::hash::Hash::default()),
3119 Box::new(crate::email::Email::default()),
3120 Box::new(crate::env::Env::default()),
3121 Box::new(crate::json::Json::default()),
3122 Box::new(crate::ip::Ip::default()),
3123 ];
3124 let scanner = Scanner::new(finders);
3125 let input =
3126 r#"192.168.1.1 user@example.com $HOME {"key": "val"} 5d41402abc4b2a76b9719d911017c592"#;
3127 let matches = scanner.scan_line(input);
3128 assert_eq!(matches.len(), 5);
3129 assert_eq!(&input[matches[0].range.clone()], "192.168.1.1");
3130 assert_eq!(&input[matches[1].range.clone()], "user@example.com");
3131 assert_eq!(&input[matches[2].range.clone()], "$HOME");
3132 assert_eq!(&input[matches[3].range.clone()], r#"{"key": "val"}"#);
3133 assert_eq!(
3134 &input[matches[4].range.clone()],
3135 "5d41402abc4b2a76b9719d911017c592"
3136 );
3137 }
3138
3139 #[test]
3142 fn scanner_single_byte_inputs() {
3143 let finders: Vec<Box<dyn Finder>> = vec![
3144 Box::new(crate::hash::Hash::default()),
3145 Box::new(crate::email::Email::default()),
3146 Box::new(crate::env::Env::default()),
3147 Box::new(crate::ip::Ip::default()),
3148 Box::new(crate::json::Json::default()),
3149 Box::new(crate::color::Color::default()),
3150 Box::new(crate::uuid::Uuid::default()),
3151 ];
3152 let scanner = Scanner::new(finders);
3153 for b in 0..=127u8 {
3154 let s = String::from(b as char);
3155 let _ = scanner.scan_line(&s);
3156 }
3157 }
3158
3159 #[test]
3160 fn scanner_two_byte_combinations() {
3161 let finders: Vec<Box<dyn Finder>> = vec![
3162 Box::new(crate::env::Env::default()),
3163 Box::new(crate::json::Json::default()),
3164 Box::new(crate::color::Color::default()),
3165 ];
3166 let scanner = Scanner::new(finders);
3167 let interesting = b"${}[]#rgb()0aA@:/.~+-\"\\";
3168 for &a in interesting {
3169 for &b in interesting {
3170 let s = String::from_utf8(vec![a, b]).unwrap();
3171 let _ = scanner.scan_line(&s);
3172 }
3173 }
3174 }
3175
3176 #[test]
3179 fn scanner_repeated_dollars() {
3180 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::env::Env::default())];
3181 let scanner = Scanner::new(finders);
3182 assert!(scanner.scan_line("$$$").is_empty());
3183 assert!(scanner.scan_line("$$$$").is_empty());
3184 }
3185
3186 #[test]
3187 fn scanner_repeated_hashes() {
3188 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::color::Color::default())];
3189 let scanner = Scanner::new(finders);
3190 assert!(scanner.scan_line("###").is_empty());
3191 }
3192
3193 #[test]
3194 fn scanner_repeated_brackets() {
3195 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::json::Json::default())];
3196 let scanner = Scanner::new(finders);
3197 let matches = scanner.scan_line("{}{}{}");
3198 assert_eq!(matches.len(), 3);
3199 }
3200
3201 #[test]
3202 fn scanner_repeated_dots() {
3203 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::ip::Ip::default())];
3204 let scanner = Scanner::new(finders);
3205 assert!(scanner.scan_line("....").is_empty());
3206 }
3207
3208 #[test]
3211 fn scanner_whitespace() {
3212 let finders: Vec<Box<dyn Finder>> = vec![
3213 Box::new(crate::hash::Hash::default()),
3214 Box::new(crate::email::Email::default()),
3215 ];
3216 let scanner = Scanner::new(finders);
3217 assert!(scanner.scan_line(" \t\n ").is_empty());
3218 }
3219
3220 #[test]
3223 fn scanner_long_hex_run() {
3224 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::hash::Hash::default())];
3225 let scanner = Scanner::new(finders);
3226 let long_hex = "a".repeat(10000);
3227 assert!(scanner.scan_line(&long_hex).is_empty());
3228 }
3229
3230 #[test]
3231 fn scanner_many_matches_in_line() {
3232 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::env::Env::default())];
3233 let scanner = Scanner::new(finders);
3234 let input = (0..100)
3235 .map(|i| format!("$VAR{}", i))
3236 .collect::<Vec<_>>()
3237 .join(" ");
3238 let matches = scanner.scan_line(&input);
3239 assert_eq!(matches.len(), 100);
3240 }
3241
3242 #[test]
3245 fn scanner_adjacent_env_vars() {
3246 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::env::Env::default())];
3247 let scanner = Scanner::new(finders);
3248 let input = "$A$B$C";
3249 let matches = scanner.scan_line(input);
3250 let texts: Vec<&str> = matches.iter().map(|m| &input[m.range.clone()]).collect();
3251 assert_eq!(texts, vec!["$A", "$B", "$C"]);
3252 }
3253
3254 #[test]
3255 fn scanner_adjacent_json_objects() {
3256 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::json::Json::default())];
3257 let scanner = Scanner::new(finders);
3258 let input = r#"{"a":1}{"b":2}[3]"#;
3259 let matches = scanner.scan_line(input);
3260 let texts: Vec<&str> = matches.iter().map(|m| &input[m.range.clone()]).collect();
3261 assert_eq!(texts, vec![r#"{"a":1}"#, r#"{"b":2}"#, "[3]"]);
3262 }
3263
3264 #[test]
3267 fn prescan_all_byte_classes() {
3268 let input = b"09afAF gZ@$#{}[]:./~+(- ";
3269 let cl = prescan(input);
3270 assert_ne!(cl & CL_DIGIT, 0);
3271 assert_ne!(cl & CL_HEX_ALPHA, 0);
3272 assert_ne!(cl & CL_ALPHA_OTHER, 0);
3273 assert_ne!(cl & CL_AT, 0);
3274 assert_ne!(cl & CL_DOLLAR, 0);
3275 assert_ne!(cl & CL_HASH, 0);
3276 assert_ne!(cl & CL_OPEN_BRACE, 0);
3277 assert_ne!(cl & CL_OPEN_BRACKET, 0);
3278 assert_ne!(cl & CL_COLON, 0);
3279 assert_ne!(cl & CL_DOT, 0);
3280 assert_ne!(cl & CL_SLASH, 0);
3281 assert_ne!(cl & CL_TILDE, 0);
3282 assert_ne!(cl & CL_PLUS, 0);
3283 assert_ne!(cl & CL_OPEN_PAREN, 0);
3284 assert_ne!(cl & CL_DASH, 0);
3285 }
3286
3287 #[test]
3288 fn prescan_high_bytes_have_no_class() {
3289 for b in 128..=255u8 {
3290 assert_eq!(
3291 BYTE_CLASSES[b as usize], 0,
3292 "byte {} should have no class",
3293 b
3294 );
3295 }
3296 }
3297
3298 #[test]
3301 fn dispatch_table_env_only_dollar() {
3302 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::env::Env::default())];
3303 let scanner = Scanner::new(finders);
3304 for b in 0..=255u8 {
3305 if b == b'$' {
3306 assert_ne!(scanner.gate_prev[CTX_NONE][b as usize], 0);
3307 } else {
3308 assert_eq!(scanner.gate_prev[CTX_NONE][b as usize], 0);
3309 }
3310 }
3311 }
3312
3313 #[test]
3314 fn dispatch_table_color_selective() {
3315 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::color::Color::default())];
3316 let scanner = Scanner::new(finders);
3317 let start = &scanner.gate_prev[CTX_NONE];
3318 assert_ne!(start[b'#' as usize], 0);
3319 assert_ne!(start[b'r' as usize], 0);
3320 assert_ne!(start[b'R' as usize], 0);
3321 assert_ne!(start[b'h' as usize], 0);
3322 assert_ne!(start[b'H' as usize], 0);
3323 assert_eq!(start[b'x' as usize], 0);
3324 assert_eq!(start[b' ' as usize], 0);
3325 assert_ne!(scanner.gate_prev[ctx_class(b'x')][b'#' as usize], 0);
3327 assert_eq!(scanner.gate_prev[ctx_class(b'x')][b'r' as usize], 0);
3328 assert_ne!(scanner.gate_prev[ctx_class(b' ')][b'r' as usize], 0);
3329 assert_ne!(scanner.gate_next[b'r' as usize][ctx_class(b'g')], 0);
3331 assert_eq!(scanner.gate_next[b'r' as usize][ctx_class(b'x')], 0);
3332 assert_eq!(scanner.gate_next[b'#' as usize][ctx_class(b'x')], 0);
3333 }
3334
3335 #[test]
3336 fn scanner_scan_line_first_with_no_matches() {
3337 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::email::Email::default())];
3338 let scanner = Scanner::new(finders);
3339 assert!(scanner.scan_line_first("no at sign").is_none());
3340 }
3341
3342 #[test]
3343 fn scanner_scan_line_first_prescan_skip() {
3344 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::email::Email::default())];
3345 let scanner = Scanner::new(finders);
3346 assert!(scanner.scan_line_first("no matches possible").is_none());
3347 }
3348
3349 #[test]
3352 fn prescan_early_exit_fires_with_all_classes() {
3353 let input = b"09afAF gZ@$#{}[]:./~+(- ";
3354 let cl = prescan(input);
3355 assert_eq!(cl, CL_ALL_USED);
3356 }
3357
3358 #[test]
3359 fn prescan_early_exit_on_long_input() {
3360 let mut buf = Vec::new();
3361 buf.extend_from_slice(b"09afAF gZ@$#{}[]:./~+(- ");
3362 buf.extend_from_slice(&[b'x'; 10000]);
3363 let cl = prescan(&buf);
3364 assert_eq!(cl, CL_ALL_USED);
3365 }
3366
3367 #[test]
3370 fn can_skip_all_required_single_bit_present() {
3371 let cl = CL_DIGIT | CL_SLASH;
3372 assert!(!can_skip_with_mask(
3373 cl,
3374 &[(CL_DIGIT, true), (CL_SLASH, true)]
3375 ));
3376 }
3377
3378 #[test]
3379 fn can_skip_all_required_missing_one() {
3380 let cl = CL_DIGIT;
3381 assert!(can_skip_with_mask(
3382 cl,
3383 &[(CL_DIGIT, true), (CL_SLASH, true)]
3384 ));
3385 }
3386
3387 #[test]
3388 fn can_skip_any_required_at_least_one() {
3389 let cl = CL_HASH;
3390 assert!(!can_skip_with_mask(
3391 cl,
3392 &[(CL_HASH | CL_ALPHA_OTHER, false)]
3393 ));
3394 }
3395
3396 #[test]
3397 fn can_skip_any_required_none_present() {
3398 let cl = CL_DIGIT;
3399 assert!(can_skip_with_mask(cl, &[(CL_HASH | CL_ALPHA_OTHER, false)]));
3400 }
3401
3402 #[test]
3403 fn can_skip_cidr_with_digit_but_no_slash() {
3404 let cl = CL_DIGIT | CL_DOT;
3405 assert!(can_skip_with_mask(cl, required_classes("cidr")));
3406 }
3407
3408 #[test]
3409 fn can_skip_semver_with_digit_but_no_dot() {
3410 let cl = CL_DIGIT;
3411 assert!(can_skip_with_mask(cl, required_classes("semver")));
3412 }
3413
3414 #[test]
3415 fn cannot_skip_semver_with_digit_and_dot() {
3416 let cl = CL_DIGIT | CL_DOT;
3417 assert!(!can_skip_with_mask(cl, required_classes("semver")));
3418 }
3419
3420 #[test]
3423 fn scan_line_into_reuses_buffer() {
3424 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::env::Env::default())];
3425 let scanner = Scanner::new(finders);
3426 let mut buf = Vec::new();
3427
3428 scanner.scan_line_into("$HOME here", &mut buf);
3429 assert_eq!(buf.len(), 1);
3430 assert_eq!(buf[0].range, 0..5);
3431
3432 scanner.scan_line_into("$PATH and $USER", &mut buf);
3433 assert_eq!(buf.len(), 2);
3434
3435 scanner.scan_line_into("no vars", &mut buf);
3436 assert!(buf.is_empty());
3437 }
3438
3439 #[test]
3440 fn scan_line_into_matches_scan_line() {
3441 let finders: Vec<Box<dyn Finder>> = vec![
3442 Box::new(crate::hash::Hash::default()),
3443 Box::new(crate::email::Email::default()),
3444 Box::new(crate::env::Env::default()),
3445 ];
3446 let scanner = Scanner::new(finders);
3447 let input = "user@example.com $HOME 5d41402abc4b2a76b9719d911017c592";
3448
3449 let from_scan_line = scanner.scan_line(input);
3450 let mut buf = Vec::new();
3451 scanner.scan_line_into(input, &mut buf);
3452
3453 assert_eq!(from_scan_line.len(), buf.len());
3454 for (a, b) in from_scan_line.iter().zip(buf.iter()) {
3455 assert_eq!(a.range, b.range);
3456 assert_eq!(a.finder_index, b.finder_index);
3457 }
3458 }
3459
3460 #[test]
3463 fn trigger_matches_never_overlap_per_finder() {
3464 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::email::Email::default())];
3465 let scanner = Scanner::new(finders);
3466 for input in ["a@b.co@d.ef", "user@example.com@evil.org"] {
3467 let matches = scanner.scan_line(input);
3468 for w in matches.windows(2) {
3469 assert!(
3470 w[1].range.start >= w[0].range.end,
3471 "overlapping matches on {input:?}: {:?} vs {:?}",
3472 w[0].range,
3473 w[1].range
3474 );
3475 }
3476 }
3477 }
3478
3479 #[test]
3480 fn trigger_overlap_suppressed_keeps_first_match() {
3481 let finders: Vec<Box<dyn Finder>> = vec![Box::new(crate::email::Email::default())];
3482 let scanner = Scanner::new(finders);
3483 let input = "user@example.com@evil.org";
3484 let matches = scanner.scan_line(input);
3485 assert_eq!(matches.len(), 1);
3486 assert_eq!(&input[matches[0].range.clone()], "user@example.com");
3487 }
3488
3489 #[test]
3492 fn scan_line_first_ties_broken_by_finder_index() {
3493 let input = "5d41402abc4b2a76b9719d911017c592 tail";
3497 let finders: Vec<Box<dyn Finder>> = vec![
3498 Box::new(crate::hash::Hash::default()),
3499 Box::new(crate::mirror::Mirror::default()),
3500 ];
3501 let scanner = Scanner::new(finders);
3502 let all = scanner.scan_line(input);
3503 let first = scanner.scan_line_first(input).unwrap();
3504 assert_eq!(first.finder_index, all[0].finder_index);
3505 assert_eq!(first.range, all[0].range);
3506 assert_eq!(first.finder_index, 0);
3507 }
3508
3509 #[test]
3510 fn scan_line_first_agrees_with_scan_line_across_orders() {
3511 let inputs = [
3512 "2024-01-15.example.com",
3513 "x 2024-01-15.example.com",
3514 "$HOME 5d41402abc4b2a76b9719d911017c592",
3515 "5d41402abc4b2a76b9719d911017c592 $HOME",
3516 "user@example.com $HOME",
3517 ];
3518 let build_finders = |reversed: bool| -> Vec<Box<dyn Finder>> {
3519 let mut v: Vec<Box<dyn Finder>> = vec![
3520 Box::new(crate::datetime::Datetime::default()),
3521 Box::new(crate::hash::Hash::default()),
3522 Box::new(crate::env::Env::default()),
3523 Box::new(crate::email::Email::default()),
3524 Box::new(crate::mirror::Mirror::default()),
3525 ];
3526 if reversed {
3527 v.reverse();
3528 }
3529 v
3530 };
3531 for reversed in [false, true] {
3532 let scanner = Scanner::new(build_finders(reversed));
3533 for input in inputs {
3534 let all = scanner.scan_line(input);
3535 let first = scanner.scan_line_first(input);
3536 match (all.first(), first) {
3537 (None, None) => {}
3538 (Some(a), Some(f)) => {
3539 assert_eq!(a.range, f.range, "range mismatch on {input:?}");
3540 assert_eq!(
3541 a.finder_index, f.finder_index,
3542 "finder mismatch on {input:?}"
3543 );
3544 }
3545 (a, f) => panic!("presence mismatch on {input:?}: {a:?} vs {f:?}"),
3546 }
3547 }
3548 }
3549 }
3550
3551 #[test]
3554 fn scanner_all_finders_no_panic() {
3555 let mut codetag = crate::codetag::Codetag::default();
3556 codetag.build_mnemonics_regex().unwrap();
3557 let finders: Vec<Box<dyn Finder>> = vec![
3558 Box::new(crate::cidr::Cidr::default()),
3559 Box::new(codetag),
3560 Box::new(crate::color::Color::default()),
3561 Box::new(crate::datetime::Datetime::default()),
3562 Box::new(crate::email::Email::default()),
3563 Box::new(crate::emoji::Emoji::default()),
3564 Box::new(crate::env::Env::default()),
3565 Box::new(crate::hash::Hash::default()),
3566 Box::new(crate::ip::Ip::default()),
3567 Box::new(crate::json::Json::default()),
3568 Box::new(crate::jwt::Jwt::default()),
3569 Box::new(crate::mac::Mac::default()),
3570 Box::new(crate::path::Path::default()),
3571 Box::new(crate::phone::Phone::default()),
3572 Box::new(crate::semver::Semver::default()),
3573 Box::new(crate::uri::URI::default()),
3574 Box::new(crate::uuid::Uuid::default()),
3575 ];
3576 let scanner = Scanner::new(finders);
3577
3578 for input in [
3579 "",
3580 " ",
3581 "hello world",
3582 "user@example.com",
3583 "http://example.com",
3584 "$HOME /etc/hosts 192.168.1.0/24",
3585 "550e8400-e29b-41d4-a716-446655440000",
3586 r#"{"key": "value"} TODO: fix this"#,
3587 "v1.2.3 #ff00aa 2024-01-15T10:30:00Z",
3588 "00:1A:2B:3C:4D:5E +14155551234",
3589 "😀🎉 hello",
3590 ] {
3591 let _ = scanner.scan_line(input);
3592 let _ = scanner.scan_line_first(input);
3593 }
3594 }
3595}