Skip to main content

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}