tre_regex/exec.rs
1// SPDX-License-Identifier: BSD-2-Clause
2// See LICENSE file in the project root for full license text.
3
4use crate::{
5 Regex,
6 err::{BindingErrorCode, ErrorKind, RegexError, Result},
7 flags::RegexecFlags,
8 tre,
9};
10
11/// Captures returned from a UTF-8 match.
12pub type RegMatchStr<'a> = Vec<Option<&'a str>>;
13/// Captures returned from a byte match.
14pub type RegMatchBytes<'a> = Vec<Option<&'a [u8]>>;
15
16/// Converts a nonnegative TRE match offset into a Rust slice offset.
17///
18/// # Errors
19/// Returns a [`RegexError`] if the offset cannot be represented by [`usize`].
20pub fn match_offset(offset: tre::regoff_t) -> Result<usize> {
21 usize::try_from(offset).map_err(|error| {
22 RegexError::new(
23 ErrorKind::Binding(BindingErrorCode::INVALID_MATCH_OFFSET),
24 &format!("Invalid match offset: {error}"),
25 )
26 })
27}
28
29impl Regex {
30 /// Returns whether `string` matches this regular expression.
31 ///
32 /// # Errors
33 /// Returns a [`RegexError`] for execution errors other than a normal non-match.
34 pub fn is_match(&self, string: &str, flags: RegexecFlags) -> Result<bool> {
35 match self.regexec(string, 0, flags) {
36 Ok(_) => Ok(true),
37 Err(RegexError {
38 kind: ErrorKind::Tre(tre::reg_errcode_t::REG_NOMATCH),
39 ..
40 }) => Ok(false),
41 Err(error) => Err(error),
42 }
43 }
44
45 /// Returns whether `data` matches this byte regular expression.
46 ///
47 /// # Errors
48 /// Returns a [`RegexError`] for execution errors other than a normal non-match.
49 pub fn is_match_bytes(&self, data: &[u8], flags: RegexecFlags) -> Result<bool> {
50 match self.regexec_bytes(data, 0, flags) {
51 Ok(_) => Ok(true),
52 Err(RegexError {
53 kind: ErrorKind::Tre(tre::reg_errcode_t::REG_NOMATCH),
54 ..
55 }) => Ok(false),
56 Err(error) => Err(error),
57 }
58 }
59
60 /// Returns capture groups from `string`.
61 ///
62 /// This is the idiomatically named equivalent of [`regexec`](Self::regexec).
63 ///
64 /// # Errors
65 /// Returns a [`RegexError`] if matching fails or a capture is not valid UTF-8.
66 pub fn captures<'a>(
67 &self,
68 string: &'a str,
69 capacity: usize,
70 flags: RegexecFlags,
71 ) -> Result<RegMatchStr<'a>> {
72 self.regexec(string, capacity, flags)
73 }
74
75 /// Returns capture groups from `data`.
76 ///
77 /// This is the idiomatically named equivalent of [`regexec_bytes`](Self::regexec_bytes).
78 ///
79 /// # Errors
80 /// Returns a [`RegexError`] if matching fails.
81 pub fn captures_bytes<'a>(
82 &self,
83 data: &'a [u8],
84 capacity: usize,
85 flags: RegexecFlags,
86 ) -> Result<RegMatchBytes<'a>> {
87 self.regexec_bytes(data, capacity, flags)
88 }
89
90 /// Performs a regex search on the passed string, returning `nmatches` results.
91 ///
92 /// Non-matching subexpressions or patterns will return `None` in the results.
93 ///
94 /// # Arguments
95 /// * `string`: string to match against `compiled_reg`
96 /// * `nmatches`: number of matches to return
97 /// * `flags`: [`RegexecFlags`] to pass to [`tre_regnexec`](tre_regex_sys::tre_regnexec).
98 ///
99 /// # Returns
100 /// If no error was found, a [`Vec`] of [`Option`]s will be returned.
101 ///
102 /// If a given match index is empty, its `Option` is `None`; otherwise it contains a borrowed
103 /// substring of the input.
104 ///
105 /// # Errors
106 /// Returns a [`RegexError`] if matching fails or TRE returns offsets that do not fall on UTF-8
107 /// character boundaries.
108 ///
109 /// # Caveats
110 /// Unless copied, the match results must live at least as long as `string`. This is because they are
111 /// slices into `string` under the hood, for efficiency.
112 ///
113 /// # Examples
114 /// ```
115 /// # use tre_regex::Result;
116 /// # fn main() -> Result<()> {
117 /// use tre_regex::{RegcompFlags, RegexecFlags, Regex};
118 ///
119 /// let regcomp_flags = RegcompFlags::new()
120 /// .add(RegcompFlags::EXTENDED)
121 /// .add(RegcompFlags::ICASE)
122 /// .add(RegcompFlags::UNGREEDY);
123 /// let regexec_flags = RegexecFlags::new().add(RegexecFlags::NONE);
124 ///
125 /// let compiled_reg = Regex::new_bytes(b"^(hello).*(world)$", regcomp_flags)?;
126 /// let matches = compiled_reg.regexec("hello world", 3, regexec_flags)?;
127 ///
128 /// for (i, matched) in matches.into_iter().enumerate() {
129 /// match matched {
130 /// Some(substr) => println!("Match {i}: '{substr}'"),
131 /// None => println!("Match {i}: <None>"),
132 /// }
133 /// }
134 /// # Ok(())
135 /// # }
136 /// ```
137 ///
138 /// [`RegexError`]: crate::RegexError
139 #[inline]
140 pub fn regexec<'a>(
141 &self,
142 string: &'a str,
143 nmatches: usize,
144 flags: RegexecFlags,
145 ) -> Result<RegMatchStr<'a>> {
146 let Some(compiled_reg_obj) = self.as_raw() else {
147 return Err(RegexError::new(
148 ErrorKind::Binding(BindingErrorCode::REGEX_VACANT),
149 "Attempted to unwrap a vacant Regex object",
150 ));
151 };
152 let data = string.as_bytes();
153 let mut match_vec = vec![tre::regmatch_t::default(); nmatches];
154
155 // SAFETY: the regex is initialised, data is valid for its supplied length, and match_vec
156 // contains nmatches writable entries.
157 let result = unsafe {
158 tre::tre_regnexec(
159 compiled_reg_obj,
160 data.as_ptr().cast(),
161 data.len(),
162 nmatches,
163 match_vec.as_mut_ptr(),
164 flags.bits(),
165 )
166 };
167 if result != 0 {
168 return Err(self.regerror(result));
169 }
170
171 let mut result = Vec::with_capacity(nmatches);
172 for pmatch in match_vec {
173 if pmatch.rm_so < 0 || pmatch.rm_eo < 0 {
174 result.push(None);
175 continue;
176 }
177
178 let start_offset = match_offset(pmatch.rm_so)?;
179 let end_offset = match_offset(pmatch.rm_eo)?;
180 let matched = string.get(start_offset..end_offset).ok_or_else(|| {
181 RegexError::new(
182 ErrorKind::Binding(BindingErrorCode::ENCODING),
183 "TRE returned match offsets that are not UTF-8 character boundaries",
184 )
185 })?;
186 result.push(Some(matched));
187 }
188
189 Ok(result)
190 }
191
192 /// Performs a regex search on the passed bytes, returning `nmatches` results.
193 ///
194 /// This function should only be used if you need to match raw bytes, or bytes which may not be
195 /// UTF-8 compliant. Otherwise, [`regexec`] is recommended instead.
196 ///
197 /// # Arguments
198 /// * `data`: [`u8`] slice to match against `compiled_reg`
199 /// * `nmatches`: number of matches to return
200 /// * `flags`: [`RegexecFlags`] to pass to [`tre_regnexecb`](tre_regex_sys::tre_regnexecb).
201 ///
202 /// # Returns
203 /// If no error was found, a [`Vec`] of [`Option`]s will be returned.
204 ///
205 /// If a given match index is empty, The `Option` will be `None`. Otherwise, [`u8`] slices will be
206 /// returned.
207 ///
208 /// # Errors
209 /// If an error is encountered during matching, it returns a [`RegexError`].
210 ///
211 /// # Caveats
212 /// Unless copied, the match results must live at least as long as `data`. This is because they are
213 /// slices into `data` under the hood, for efficiency.
214 ///
215 /// # Examples
216 /// ```
217 /// # use tre_regex::Result;
218 /// # fn main() -> Result<()> {
219 /// use tre_regex::{RegcompFlags, RegexecFlags, Regex};
220 ///
221 /// let regcomp_flags = RegcompFlags::new()
222 /// .add(RegcompFlags::EXTENDED)
223 /// .add(RegcompFlags::ICASE);
224 /// let regexec_flags = RegexecFlags::new().add(RegexecFlags::NONE);
225 ///
226 /// let compiled_reg = Regex::new("^(hello).*(world)$", regcomp_flags)?;
227 /// let matches = compiled_reg.regexec_bytes(b"hello world", 2, regexec_flags)?;
228 ///
229 /// for (i, matched) in matches.into_iter().enumerate() {
230 /// match matched {
231 /// Some(substr) => println!(
232 /// "Match {i}: {}",
233 /// std::str::from_utf8(substr.as_ref()).unwrap()
234 /// ),
235 /// None => println!("Match {i}: <None>"),
236 /// }
237 /// }
238 /// # Ok(())
239 /// # }
240 /// ```
241 pub fn regexec_bytes<'a>(
242 &self,
243 data: &'a [u8],
244 nmatches: usize,
245 flags: RegexecFlags,
246 ) -> Result<RegMatchBytes<'a>> {
247 let Some(compiled_reg_obj) = self.as_raw() else {
248 return Err(RegexError::new(
249 ErrorKind::Binding(BindingErrorCode::REGEX_VACANT),
250 "Attempted to unwrap a vacant Regex object",
251 ));
252 };
253 let mut match_vec: Vec<tre::regmatch_t> =
254 vec![tre::regmatch_t { rm_so: 0, rm_eo: 0 }; nmatches];
255
256 // SAFETY: compiled_reg is a wrapped type (see safety concerns for Regex). data is read-only.
257 // match_vec has enough room for everything. flags also cannot wrap around.
258 let result = unsafe {
259 tre::tre_regnexecb(
260 compiled_reg_obj,
261 data.as_ptr().cast::<std::ffi::c_char>(),
262 data.len(),
263 nmatches,
264 match_vec.as_mut_ptr(),
265 flags.bits(),
266 )
267 };
268 if result != 0 {
269 return Err(self.regerror(result));
270 }
271
272 let mut result = Vec::with_capacity(nmatches);
273 for pmatch in match_vec {
274 if pmatch.rm_so < 0 || pmatch.rm_eo < 0 {
275 result.push(None);
276 continue;
277 }
278
279 let start_offset = match_offset(pmatch.rm_so)?;
280 let end_offset = match_offset(pmatch.rm_eo)?;
281
282 result.push(Some(&data[start_offset..end_offset]));
283 }
284
285 Ok(result)
286 }
287}
288
289/// Performs a regex search on the passed string, returning `nmatches` results.
290///
291/// This is a thin wrapper around [`Regex::regexec`].
292///
293/// Non-matching subexpressions or patterns will return `None` in the results.
294///
295/// # Arguments
296/// * `compiled_reg`: the compiled [`Regex`] object.
297/// * `string`: string to match against `compiled_reg`
298/// * `nmatches`: number of matches to return
299/// * `flags`: [`RegexecFlags`] to pass to [`tre_regnexec`](tre_regex_sys::tre_regnexec).
300///
301/// # Returns
302/// If no error was found, a [`Vec`] of [`Option`]s will be returned.
303///
304/// If a given match index is empty, its `Option` is `None`; otherwise it contains a borrowed
305/// substring of the input.
306///
307/// # Errors
308/// Returns a [`RegexError`] if matching fails or TRE returns offsets that do not fall on UTF-8
309/// character boundaries.
310///
311/// # Caveats
312/// Unless copied, the match results must live at least as long as `string`. This is because they are
313/// slices into `string` under the hood, for efficiency.
314///
315/// # Examples
316/// ```
317/// # use tre_regex::Result;
318/// # fn main() -> Result<()> {
319/// use tre_regex::{RegcompFlags, RegexecFlags, regcomp, regexec};
320///
321/// let regcomp_flags = RegcompFlags::new()
322/// .add(RegcompFlags::EXTENDED)
323/// .add(RegcompFlags::ICASE)
324/// .add(RegcompFlags::UNGREEDY);
325/// let regexec_flags = RegexecFlags::new().add(RegexecFlags::NONE);
326///
327/// let compiled_reg = regcomp("^(hello).*(world)$", regcomp_flags)?;
328/// let matches = regexec(
329/// &compiled_reg, // Compiled regex
330/// "hello world", // String to match against
331/// 2, // Number of matches
332/// regexec_flags // Flags
333/// )?;
334///
335/// for (i, matched) in matches.into_iter().enumerate() {
336/// match matched {
337/// Some(substr) => println!("Match {i}: '{substr}'"),
338/// None => println!("Match {i}: <None>"),
339/// }
340/// }
341/// # Ok(())
342/// # }
343/// ```
344#[inline]
345pub fn regexec<'a>(
346 compiled_reg: &Regex,
347 string: &'a str,
348 nmatches: usize,
349 flags: RegexecFlags,
350) -> Result<RegMatchStr<'a>> {
351 compiled_reg.regexec(string, nmatches, flags)
352}
353
354/// Performs a regex search on the passed bytes, returning `nmatches` results.
355///
356/// This is a thin wrapper around [`Regex::regexec_bytes`].
357///
358/// This function should only be used if you need to match raw bytes, or bytes which may not be
359/// UTF-8 compliant. Otherwise, [`regexec`] is recommended instead.
360///
361/// # Arguments
362/// * `compiled_reg`: the compiled [`Regex`] object.
363/// * `data`: [`u8`] slice to match against `compiled_reg`
364/// * `nmatches`: number of matches to return
365/// * `flags`: [`RegexecFlags`] to pass to [`tre_regnexecb`](tre_regex_sys::tre_regnexecb).
366///
367/// # Returns
368/// If no error was found, a [`Vec`] of [`Option`]s will be returned.
369///
370/// If a given match index is empty, The `Option` will be `None`. Otherwise, [`u8`] slices will be
371/// returned.
372///
373/// # Errors
374/// If an error is encountered during matching, it returns a [`RegexError`].
375///
376/// # Caveats
377/// Unless copied, the match results must live at least as long as `data`. This is because they are
378/// slices into `data` under the hood, for efficiency.
379///
380/// # Examples
381/// ```
382/// # use tre_regex::Result;
383/// # fn main() -> Result<()> {
384/// use tre_regex::{RegcompFlags, RegexecFlags, regcomp_bytes, regexec_bytes};
385///
386/// let regcomp_flags = RegcompFlags::new()
387/// .add(RegcompFlags::EXTENDED)
388/// .add(RegcompFlags::ICASE)
389/// .add(RegcompFlags::UNGREEDY);
390/// let regexec_flags = RegexecFlags::new().add(RegexecFlags::NONE);
391///
392/// let compiled_reg = regcomp_bytes(b"^(hello).*(world)$", regcomp_flags)?;
393/// let matches = regexec_bytes(
394/// &compiled_reg, // Compiled regex
395/// b"hello world", // Bytes to match against
396/// 2, // Number of matches
397/// regexec_flags // Flags
398/// )?;
399///
400/// for (i, matched) in matches.into_iter().enumerate() {
401/// match matched {
402/// Some(substr) => println!(
403/// "Match {i}: {}",
404/// std::str::from_utf8(substr.as_ref()).unwrap()
405/// ),
406/// None => println!("Match {i}: <None>"),
407/// }
408/// }
409/// # Ok(())
410/// # }
411/// ```
412pub fn regexec_bytes<'a>(
413 compiled_reg: &Regex,
414 data: &'a [u8],
415 nmatches: usize,
416 flags: RegexecFlags,
417) -> Result<RegMatchBytes<'a>> {
418 compiled_reg.regexec_bytes(data, nmatches, flags)
419}