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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Rust bindings for the irregex regex engine.
//!
//! The API is the `regex` crate's shape - [`Regex::new`], [`Regex::is_match`],
//! [`Regex::find`], [`Regex::find_iter`], [`Regex::captures`],
//! [`Regex::split`], [`Regex::replace_all`] - because that is the API a Rust
//! programmer already knows. What is behind it is a Zig engine linked into your
//! process, reached through a small C ABI.
//!
//! ```
//! # fn main() -> Result<(), irgx::Error> {
//! let re = irgx::Regex::new(r"(\w+)@(\w+)")?;
//! let caps = re.captures("mail bob@host now").unwrap();
//! assert_eq!(&caps[1], "bob");
//! assert_eq!(caps.get(2).unwrap().as_str(), "host");
//! # Ok(())
//! # }
//! ```
//!
//! # Compiling, and the two ways a pattern is refused
//!
//! There are two grammars here, so a refused pattern splits into two facts with
//! two different repairs, and they are two variants rather than one string.
//!
//! [`Error::NeedsPcre`] means the pattern is fine and only the linear grammar
//! cannot express it - lookaround, a backreference, a flag letter it does not
//! have (`(?x)`, `(?U)`, `(?R)`). A *leading* `(?i)` is not in that list: it is
//! read as the flag it asks for, as `regex` reads it, and compiles. The
//! same pattern under [`RegexBuilder::pcre`] compiles, so the retry is a match
//! arm:
//!
//! ```
//! use irgx::{Error, Regex, RegexBuilder};
//!
//! fn compile(pattern: &str) -> Result<Regex, Error> {
//! match Regex::new(pattern) {
//! Err(Error::NeedsPcre { .. }) => RegexBuilder::new(pattern).pcre(true).build(),
//! other => other,
//! }
//! }
//!
//! assert_eq!(compile(r"(?<=\$)\d+")?.find("cost $42").unwrap().as_str(), "42");
//! # Ok::<(), Error>(())
//! ```
//!
//! It is not retried for you because the PCRE2 arm is not linear in the length
//! of the text, and a program compiling somebody else's patterns may want to
//! decline rather than accept that.
//!
//! [`Error::Syntax`] means the pattern is malformed, and carries the byte offset
//! the engine stopped at. `pcre` will not rescue it, so retrying only fails
//! twice. The offset is always a real index into the pattern - never past the
//! end, never mid-codepoint - so `&pattern[..at]` is what the engine got
//! through:
//!
//! ```
//! # use irgx::{Error, Regex};
//! let Err(Error::Syntax { at, .. }) = Regex::new("(unclosed") else { unreachable!() };
//! assert_eq!(at, 9);
//! ```
//!
//! # Threads
//!
//! [`Regex`] is `Send + Sync`, so the idiom works:
//!
//! ```
//! use std::sync::LazyLock;
//! use irgx::Regex;
//!
//! static WORD: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\w+").unwrap());
//!
//! let total: usize = std::thread::scope(|scope| {
//! let handles: Vec<_> = ["one two", "three", "four five six"]
//! .map(|text| scope.spawn(move || WORD.find_iter(text).count()))
//! .into_iter()
//! .collect();
//! handles.into_iter().map(|h| h.join().unwrap()).sum()
//! });
//! assert_eq!(total, 6);
//! ```
//!
//! The C handle underneath is single-threaded: it owns the scratch its searches
//! run in. So a `Regex` owns a pool of handles and leases one per search. The
//! cost is one extra compile the first time a given level of concurrency is
//! reached, an uncontended mutex per search, and handles freed when the `Regex`
//! drops. Nothing is thread-bound and nothing leaks into a thread that outlives
//! the pattern.
//!
//! # Offsets are bytes
//!
//! [`Match::start`] and [`Match::end`] are byte offsets into the `&str` you
//! searched, which is the engine's own coordinate system - `&text[m.range()]`
//! is the matched text, no translation involved. A pattern compiled with
//! [`RegexBuilder::unicode`] off matches bytes, so it can report a boundary
//! inside a codepoint; that is [`Error::NotCharBoundary`] rather than a panic in
//! your slicing code.
//!
//! # How this differs from the `regex` crate
//!
//! * **[`Regex::find_iter`] is eager**, and therefore knows its length and runs
//! backwards. The sequence itself is the `regex` crate's, empty matches
//! included — `a*` over `"abc"` is `(0,1), (2,2), (3,3)` in both — and the
//! differential in `tests/sequence.rs` holds it there over a corpus of
//! nullable patterns.
//! * **Lookaround and backreferences exist**, behind
//! [`RegexBuilder::pcre`]. The default engine is linear in the length of the
//! text; the PCRE2 arm is not. A pattern that needs the other arm is
//! [`Error::NeedsPcre`], not a syntax error, so `regex`'s single
//! `Error::Syntax(String)` becomes two variants here.
//! * **[`RegexBuilder::fixed`], [`RegexBuilder::word`] and
//! [`RegexBuilder::smart_case`] are first-class flags**, not things you build
//! by rewriting the pattern.
//! * **Faults are possible after compiling.** The `regex`-shaped verbs panic on
//! one; each has a `try_` sibling that returns [`Error`].
//! * **[`Munch`] has no `regex`-crate counterpart at all.** It answers the
//! question a tokenizer asks and a search cannot: starting at exactly this
//! offset, over these patterns, which reaches furthest? Maximal munch, with the
//! permitted set narrowed per call, which is what makes a state-directed lexer
//! possible without stepping the automaton by hand.
//!
//! # Linking
//!
//! The crate carries a prebuilt static archive per supported target, so the
//! usual build needs no Zig toolchain. `IRGX_LIB_DIR` points the build at a
//! library you built yourself instead. A target with no vendored archive falls
//! back to building the engine from source, and fails at build time with a
//! sentence if it cannot.
/// Count, locate and restore a text the index does not store.
/// Searching a TREE rather than a buffer you already hold: the `tree`, `walk`
/// and `sieve` planes, and the corpus that warms them.
/// The line grid: rows, bands, and the off-by-one that lives here instead of in
/// your host.
/// Many literals, one pass, with attribution.
/// What a pattern PROMISES about every byte sequence it can match.
/// The Unicode tables this engine folds and classifies with.
/// Shared contract mirrors — engine/analytic/kinship constants and row tables.
/// The unified `SearchRequest` → match stream for the exact plane.
/// Transports, the analytic ladder, and the substrate [`runtime::Error`].
///
/// Distinct from the crate-root [`Error`], which is the regex face's refusal
/// vocabulary. Analytic/search callers use [`runtime::Error`].
pub use crate;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
pub use crate;
/// The C-ABI version this crate speaks. The linked library must report the same
/// number or every [`Regex::new`] fails with [`Error::Abi`].
pub const ABI_VERSION: u32 = ABI_VERSION;
/// The linked engine's semantic version, e.g. `"1.0.0"`.
///
/// Distinct from this crate's version: one crate release can carry a newer
/// engine without an API change.
/// The vendored PCRE2 version the [`RegexBuilder::pcre`] arm runs on.