1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Trigger-byte scanner for the Aozora notation lexer.
//!
//! ## What it does
//!
//! Given a source buffer, finds the byte offsets of every Aozora
//! trigger character (`|《》[]#※〔〕「」`). Each is a 3-byte BMP
//! UTF-8 codepoint; the scanner streams trigram start offsets into a
//! caller-provided [`OffsetSink`], or returns a `Vec<u32>` via the
//! convenience entry [`scan_offsets`].
//!
//! ## Why `aho-corasick`, not a hand-rolled SIMD kernel
//!
//! This crate used to carry the tree's only `unsafe`: a bespoke Teddy
//! multi-pattern matcher with one SIMD inner kernel per ISA
//! (`pshufb` / `vqtbl1q_u8` / `i8x16_swizzle`) plus a scalar fallback.
//! It worked, but the candidate filter keyed only on the lead byte's
//! nibbles — and `0xE3` is the lead byte of *every hiragana and
//! katakana codepoint*, so on real Japanese prose the filter fired on
//! a large fraction of the text and paid a scalar trigram-verify to
//! reject each kana byte.
//!
//! [`aho_corasick`] solves the same problem with a safe, portable,
//! expertly-maintained packed matcher whose fingerprint spans more
//! than the lead byte, so it is *both* algorithmically more selective
//! (fewer false-positive verifies) and free of `unsafe`. On 8 MiB of
//! real prose it scanned ~24% faster than the hand-rolled SIMD while
//! producing byte-identical offsets, and it carries every platform —
//! the win is portable, not pinned to the dev machine's AVX2. The
//! crate is now `#![forbid(unsafe_code)]`.
//!
//! ## Output channel
//!
//! [`OffsetSink`] decouples the scanner from "where the offsets land".
//! `Vec<u32>` and `bumpalo::collections::Vec<'_, u32>` both implement
//! it, so callers with an arena (the lex pipeline) write offsets
//! directly into the arena.
//!
//! ## Naive reference
//!
//! `NaiveScanner` is the brute-force `O(n × classify)` walker — the
//! independent oracle the production scanner is differentially tested
//! against (`tests/property_backend_equiv.rs`), and the safe scanner
//! used directly on `no_std` builds (where `aho-corasick`'s packed
//! path — which needs runtime CPU detection — is unavailable).
pub use OffsetSink;
pub use NaiveScanner;
/// The process-wide trigger automaton, built once.
///
/// `aho-corasick`'s automaton construction (and its one-time runtime
/// CPU-feature detection for the packed backend) is amortised across
/// every parse via a `OnceLock`, mirroring the old per-process backend
/// detection. The patterns come straight from the canonical
/// [`crate::spec::trigger::ALL_TRIGGER_TRIGRAMS`] so the scanner and the
/// classifier can never disagree on the trigger set.
/// Force the one-time automaton build (and packed-backend CPU
/// detection) now, off the hot path. The first [`scan_offsets`] then
/// reuses the cached automaton. Idempotent and sub-microsecond.
///
/// `no_std` builds scan with `NaiveScanner` and have nothing to warm,
/// so this is a documented no-op there.
// mutants::skip — deleting the body only defers the idempotent one-time
// automaton build to the first `scan_into`; it produces no observable
// output difference (the automaton is identical whenever it is built), so
// there is nothing an assertion can pin.
pub
/// Push every trigger offset in `source` into `sink`, in ascending
/// order. Generic over the sink so the lex pipeline writes straight
/// into its arena with no heap round-trip.
/// Scan `source` and return every trigger byte offset.
///
/// Allocates a fresh `Vec<u32>`; the [`OffsetSink`] abstraction keeps the
/// scan loop generic over the buffer, but the lex pipeline drives it through
/// this heap entry.
pub