magnus/r_match.rs
1use std::{fmt, os::raw::c_int};
2
3use rb_sys::{
4 rb_reg_backref_number, rb_reg_last_match, rb_reg_match_last, rb_reg_match_post,
5 rb_reg_match_pre, rb_reg_nth_defined, rb_reg_nth_match, ruby_value_type, VALUE,
6};
7
8use crate::{
9 error::{protect, Error},
10 into_value::IntoValue,
11 object::Object,
12 r_string::{IntoRString, RString},
13 try_convert::TryConvert,
14 value::{
15 private::{self, ReprValue as _},
16 NonZeroValue, ReprValue, Value,
17 },
18 Ruby,
19};
20
21/// A Value pointer to a RMatch struct, Ruby's internal representation of the
22/// MatchData returned from a regex match.
23///
24/// See the [`ReprValue`] and [`Object`] traits for additional methods
25/// available on this type.
26#[derive(Clone, Copy)]
27#[repr(transparent)]
28pub struct RMatch(NonZeroValue);
29
30impl RMatch {
31 /// Return `Some(RMatch)` if `val` is a `RMatch`, `None` otherwise.
32 ///
33 /// # Examples
34 ///
35 /// ```
36 /// use magnus::{eval, RMatch};
37 /// # let _cleanup = unsafe { magnus::embed::init() };
38 ///
39 /// assert!(RMatch::from_value(eval(r#""foo".match(/o/)"#).unwrap()).is_some());
40 /// assert!(RMatch::from_value(eval(r#""o""#).unwrap()).is_none());
41 /// ```
42 #[inline]
43 pub fn from_value(val: Value) -> Option<Self> {
44 unsafe {
45 (val.rb_type() == ruby_value_type::RUBY_T_MATCH)
46 .then(|| Self(NonZeroValue::new_unchecked(val)))
47 }
48 }
49
50 #[inline]
51 pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self {
52 Self(NonZeroValue::new_unchecked(Value::new(val)))
53 }
54
55 /// Returns whether the `n`th capture group is set.
56 ///
57 /// Returns `Some(true)` when there is an `n`th capture and it has a value.
58 /// Returns `Some(false)` when there is an `n`th capture but it is empty.
59 /// Returns `None` when there is no `n`th capture.
60 ///
61 /// This function is similar to [`nth_match`](Self::nth_match), but can be
62 /// used to avoid allocating a Ruby string if the value of the capture is
63 /// not needed.
64 ///
65 /// # Examples
66 ///
67 /// ```
68 /// use magnus::{Error, Ruby};
69 ///
70 /// fn example(ruby: &Ruby) -> Result<(), Error> {
71 /// let regexp = ruby.reg_new(".([a-z])([a-z]*)([0-9])?", Default::default())?;
72 /// regexp.reg_match("ex")?;
73 /// let match_data = ruby.backref_get().unwrap();
74 /// // 0th group is the whole match
75 /// assert_eq!(match_data.nth_defined(0), Some(true));
76 /// // the `([a-z])` group
77 /// assert_eq!(match_data.nth_defined(1), Some(true));
78 /// // the `([a-z]*)` group
79 /// assert_eq!(match_data.nth_defined(2), Some(true));
80 /// // the `([0-9])?` group
81 /// assert_eq!(match_data.nth_defined(3), Some(false));
82 /// // no 4th group
83 /// assert_eq!(match_data.nth_defined(4), None);
84 ///
85 /// Ok(())
86 /// }
87 /// # Ruby::init(example).unwrap()
88 /// ```
89 pub fn nth_defined(self, n: isize) -> Option<bool> {
90 let value = unsafe { Value::new(rb_reg_nth_defined(n as c_int, self.as_rb_value())) };
91 Option::<bool>::try_convert(value).unwrap() // infallible
92 }
93
94 /// Returns the string captured by the `n`th capture group.
95 ///
96 /// Returns `None` when there is no `n`th capture.
97 ///
98 /// # Examples
99 ///
100 /// ```
101 /// use magnus::{Error, Ruby};
102 ///
103 /// fn example(ruby: &Ruby) -> Result<(), Error> {
104 /// let regexp = ruby.reg_new(".([a-z])([a-z]*)([0-9])?", Default::default())?;
105 /// regexp.reg_match("ex")?;
106 /// let match_data = ruby.backref_get().unwrap();
107 /// // 0th group is the whole match
108 /// assert_eq!(
109 /// match_data.nth_match(0).map(|s| s.to_string().unwrap()),
110 /// Some(String::from("ex"))
111 /// );
112 /// // the `([a-z])` group
113 /// assert_eq!(
114 /// match_data.nth_match(1).map(|s| s.to_string().unwrap()),
115 /// Some(String::from("x"))
116 /// );
117 /// // the `([a-z]*)` group
118 /// assert_eq!(
119 /// match_data.nth_match(2).map(|s| s.to_string().unwrap()),
120 /// Some(String::from(""))
121 /// );
122 /// // the `([0-9])?` group
123 /// assert_eq!(
124 /// match_data.nth_match(3).map(|s| s.to_string().unwrap()),
125 /// None
126 /// );
127 /// // no 4th group
128 /// assert_eq!(
129 /// match_data.nth_match(4).map(|s| s.to_string().unwrap()),
130 /// None
131 /// );
132 ///
133 /// Ok(())
134 /// }
135 /// # Ruby::init(example).unwrap()
136 /// ```
137 pub fn nth_match(self, n: isize) -> Option<RString> {
138 let value = unsafe { Value::new(rb_reg_nth_match(n as c_int, self.as_rb_value())) };
139 (!value.is_nil()).then(|| unsafe { RString::from_rb_value_unchecked(value.as_rb_value()) })
140 }
141
142 /// Returns the index for the named capture group.
143 ///
144 /// Returns `Err` if there's is no named capture group with the given name.
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// use magnus::{Error, Ruby};
150 ///
151 /// fn example(ruby: &Ruby) -> Result<(), Error> {
152 /// let regexp = ruby.reg_new("Hello, (?<subject>.*)!", Default::default())?;
153 /// regexp.reg_match("Hello, World!")?;
154 /// let match_data = ruby.backref_get().unwrap();
155 /// assert_eq!(match_data.backref_number("subject")?, 1);
156 /// assert!(match_data.backref_number("foo").is_err());
157 ///
158 /// Ok(())
159 /// }
160 /// # Ruby::init(example).unwrap()
161 /// ```
162 pub fn backref_number<T>(self, name: T) -> Result<usize, Error>
163 where
164 T: IntoRString,
165 {
166 let handle = Ruby::get_with(self);
167 let name = name.into_r_string_with(&handle);
168 let mut n = 0;
169 protect(|| unsafe {
170 n = rb_reg_backref_number(self.as_rb_value(), name.as_rb_value()) as usize;
171 handle.qnil()
172 })?;
173 Ok(n)
174 }
175
176 /// Returns the string matched.
177 ///
178 /// # Examples
179 ///
180 /// ```
181 /// use magnus::{Error, Ruby};
182 ///
183 /// fn example(ruby: &Ruby) -> Result<(), Error> {
184 /// let regexp = ruby.reg_new("b(.)r", Default::default())?;
185 /// regexp.reg_match("foo bar baz")?;
186 ///
187 /// let match_data = ruby.backref_get().unwrap();
188 /// assert_eq!(match_data.matched().to_string()?, "bar");
189 ///
190 /// Ok(())
191 /// }
192 /// # Ruby::init(example).unwrap()
193 /// ```
194 pub fn matched(self) -> RString {
195 unsafe { RString::from_rb_value_unchecked(rb_reg_last_match(self.as_rb_value())) }
196 }
197
198 /// Returns the string before the segment matched.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// use magnus::{Error, Ruby};
204 ///
205 /// fn example(ruby: &Ruby) -> Result<(), Error> {
206 /// let regexp = ruby.reg_new("b(.)r", Default::default())?;
207 /// regexp.reg_match("foo bar baz")?;
208 ///
209 /// let match_data = ruby.backref_get().unwrap();
210 /// assert_eq!(match_data.pre().to_string()?, "foo ");
211 ///
212 /// Ok(())
213 /// }
214 /// # Ruby::init(example).unwrap()
215 /// ```
216 pub fn pre(self) -> RString {
217 unsafe { RString::from_rb_value_unchecked(rb_reg_match_pre(self.as_rb_value())) }
218 }
219
220 /// Returns the string after the segment matched.
221 ///
222 /// # Examples
223 ///
224 /// ```
225 /// use magnus::{Error, Ruby};
226 ///
227 /// fn example(ruby: &Ruby) -> Result<(), Error> {
228 /// let regexp = ruby.reg_new("b(.)r", Default::default())?;
229 /// regexp.reg_match("foo bar baz")?;
230 ///
231 /// let match_data = ruby.backref_get().unwrap();
232 /// assert_eq!(match_data.post().to_string()?, " baz");
233 ///
234 /// Ok(())
235 /// }
236 /// # Ruby::init(example).unwrap()
237 /// ```
238 pub fn post(self) -> RString {
239 unsafe { RString::from_rb_value_unchecked(rb_reg_match_post(self.as_rb_value())) }
240 }
241
242 /// Returns the last capture.
243 ///
244 /// Returns `None` if there are no capture groups.
245 ///
246 /// # Examples
247 ///
248 /// ```
249 /// use magnus::{Error, Ruby};
250 ///
251 /// fn example(ruby: &Ruby) -> Result<(), Error> {
252 /// let regexp = ruby.reg_new("(.)oo b(.)r ba(.)", Default::default())?;
253 /// regexp.reg_match("foo bar baz")?;
254 ///
255 /// let match_data = ruby.backref_get().unwrap();
256 /// assert_eq!(match_data.last().unwrap().to_string()?, "z");
257 ///
258 /// Ok(())
259 /// }
260 /// # Ruby::init(example).unwrap()
261 /// ```
262 pub fn last(self) -> Option<RString> {
263 let value = unsafe { Value::new(rb_reg_match_last(self.as_rb_value())) };
264 (!value.is_nil()).then(|| unsafe { RString::from_rb_value_unchecked(value.as_rb_value()) })
265 }
266}
267
268impl fmt::Display for RMatch {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 write!(f, "{}", unsafe { self.to_s_infallible() })
271 }
272}
273
274impl fmt::Debug for RMatch {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 write!(f, "{}", self.inspect())
277 }
278}
279
280impl IntoValue for RMatch {
281 #[inline]
282 fn into_value_with(self, _: &Ruby) -> Value {
283 self.0.get()
284 }
285}
286
287impl Object for RMatch {}
288
289unsafe impl private::ReprValue for RMatch {}
290
291impl ReprValue for RMatch {}
292
293impl TryConvert for RMatch {
294 fn try_convert(val: Value) -> Result<Self, Error> {
295 Self::from_value(val).ok_or_else(|| {
296 Error::new(
297 Ruby::get_with(val).exception_type_error(),
298 format!("no implicit conversion of {} into MatchData", unsafe {
299 val.classname()
300 },),
301 )
302 })
303 }
304}