artichoke_backend/extn/core/matchdata/
trampoline.rs

1use crate::convert::implicitly_convert_to_int;
2use crate::extn::core::array::Array;
3use crate::extn::core::matchdata::{Capture, CaptureAt, CaptureExtract, MatchData};
4use crate::extn::core::regexp::Regexp;
5use crate::extn::core::string::String;
6use crate::extn::core::symbol::Symbol;
7use crate::extn::prelude::*;
8use crate::sys::protect;
9
10pub fn begin(interp: &mut Artichoke, mut value: Value, mut at: Value) -> Result<Value, Error> {
11    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
12    let capture = match interp.try_convert_mut(&mut at)? {
13        CaptureExtract::GroupIndex(idx) => Capture::GroupIndex(idx),
14        CaptureExtract::GroupName(name) => Capture::GroupName(name),
15        CaptureExtract::Symbol(symbol) => Capture::GroupName(symbol.bytes(interp)),
16    };
17    let begin = data.begin(capture)?;
18    match begin.map(i64::try_from) {
19        Some(Ok(begin)) => Ok(interp.convert(begin)),
20        Some(Err(_)) => Err(ArgumentError::with_message("input string too long").into()),
21        None => Ok(Value::nil()),
22    }
23}
24
25pub fn captures(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
26    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
27    if let Some(captures) = data.captures()? {
28        interp.try_convert_mut(captures)
29    } else {
30        Ok(Value::nil())
31    }
32}
33
34pub fn element_reference(
35    interp: &mut Artichoke,
36    mut value: Value,
37    mut elem: Value,
38    len: Option<Value>,
39) -> Result<Value, Error> {
40    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
41
42    // ```
43    // [3.1.2] > class X; def to_str; "x"; end; end
44    // => :to_str
45    // [3.1.2] > class Y; def to_int; 1; end; end
46    // => :to_int
47    // [3.1.2] > m = /(?<x>abc) ./.match("abc xyz")
48    // => #<MatchData "abc x" x:"abc">
49    // [3.1.2] > m["x", 1]
50    // (irb):23:in `[]': no implicit conversion of String into Integer (TypeError)
51    //         from (irb):23:in `<main>'
52    //         from /usr/local/var/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
53    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `load'
54    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `<main>'
55    // [3.1.2] > m[1, 1]
56    // => ["abc"]
57    // [3.1.2] > m[1, -1]
58    // => nil
59    // [3.1.2] > m[X.new, X.new]
60    // (irb):26:in `[]': no implicit conversion of X into Integer (TypeError)
61    //         from (irb):26:in `<main>'
62    //         from /usr/local/var/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
63    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `load'
64    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `<main>'
65    // [3.1.2] > m[Y.new, Y.new]
66    // => ["abc"]
67    // ```
68    if let Some(len) = len {
69        let start = implicitly_convert_to_int(interp, elem)?;
70        let len = implicitly_convert_to_int(interp, len)?;
71        let at = CaptureAt::StartLen(start, len);
72        let matched = data.capture_at(at)?;
73        return interp.try_convert_mut(matched);
74    }
75
76    // ```
77    // [3.1.2] > m = /(?<x>abc) ./.match("abc xyz")
78    // => #<MatchData "abc x" x:"abc">
79    // [3.1.2] > m[:x]
80    // => "abc"
81    // [3.1.2] > m[:y]
82    // (irb):29:in `[]': undefined group name reference: y (IndexError)
83    //         from (irb):29:in `<main>'
84    //         from /usr/local/var/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
85    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `load'
86    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `<main>'
87    // ```
88    if let Ok(symbol) = unsafe { Symbol::unbox_from_value(&mut elem, interp) } {
89        let at = CaptureAt::GroupName(symbol.bytes(interp));
90        let matched = data.capture_at(at)?;
91        return interp.try_convert_mut(matched);
92    }
93
94    // ```
95    // [3.1.2] > class X; def to_str; "x"; end; end
96    // => :to_str
97    // [3.1.2] > m = /(?<x>abc) ./.match("abc xyz")
98    // => #<MatchData "abc x" x:"abc">
99    // [3.1.2] > m['x']
100    // => "abc"
101    // [3.1.2] > m['y']
102    // (irb):12:in `[]': undefined group name reference: y (IndexError)
103    //         from (irb):12:in `<main>'
104    //         from /usr/local/var/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
105    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `load'
106    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `<main>'
107    // [3.1.2] > m[1..2]
108    // => ["abc"]
109    // [3.1.2] > m[0..2]
110    // => ["abc x", "abc"]
111    // [3.1.2] > m[0..-1]
112    // => ["abc x", "abc"]
113    // [3.1.2] > m[X.new]
114    // (irb):17:in `[]': no implicit conversion of X into Integer (TypeError)
115    //         from (irb):17:in `<main>'
116    //         from /usr/local/var/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
117    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `load'
118    //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `<main>'
119    // ```
120    if let Ok(string) = unsafe { String::unbox_from_value(&mut elem, interp) } {
121        let at = CaptureAt::GroupName(string.as_slice());
122        let matched = data.capture_at(at)?;
123        return interp.try_convert_mut(matched);
124    }
125
126    // NOTE(lopopolo): Encapsulation is broken here by reaching into the
127    // inner regexp.
128    let captures_len = data.regexp.inner().captures_len(None)?;
129    let rangelen = i64::try_from(captures_len).map_err(|_| ArgumentError::with_message("input string too long"))?;
130    let at = match elem.is_range(interp, rangelen)? {
131        // ```
132        // [3.1.2] > class X; def to_str; "x"; end; end
133        // => :to_str
134        // [3.1.2] > class Y; def to_int; 1; end; end
135        // => :to_int
136        // [3.1.2] > m = /(?<x>abc) ./.match("abc xyz")
137        // => #<MatchData "abc x" x:"abc">
138        // [3.1.2] > m[0]
139        // => "abc x"
140        // [3.1.2] > m[1]
141        // => "abc"
142        // [3.1.2] > m[2]
143        // => nil
144        // [3.1.2] > m[-1]
145        // => "abc"
146        // [3.1.2] > m[-2]
147        // => nil
148        // [3.1.2] > m[X.new]
149        // (irb):17:in `[]': no implicit conversion of X into Integer (TypeError)
150        //         from (irb):17:in `<main>'
151        //         from /usr/local/var/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
152        //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `load'
153        //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `<main>'
154        // [3.1.2] > m[Y.new]
155        // => "abc"
156        // ```
157        None => {
158            let index = implicitly_convert_to_int(interp, elem)?;
159            CaptureAt::GroupIndex(index)
160        }
161        // ```
162        // [3.1.2] > m = /abc/.match("abc xyz")
163        // => #<MatchData "abc">
164        // [3.1.2] > m[-10..-5]
165        // => nil
166        // ```
167        Some(protect::Range::Out) => return Ok(Value::nil()),
168        // ```
169        // [3.1.2] > m = /(?<x>abc) ./.match("abc xyz")
170        // => #<MatchData "abc x" x:"abc">
171        // [3.1.2] > m[1..2]
172        // => ["abc"]
173        // [3.1.2] > m[0..2]
174        // => ["abc x", "abc"]
175        // [3.1.2] > m[0..-1]
176        // => ["abc x", "abc"]
177        // [3.1.2] > m[-10..-1]
178        // => nil
179        // [3.1.2] > m[-10..7]
180        // => nil
181        // [3.1.2] > m[-10..-5]
182        // => nil
183        // ```
184        Some(protect::Range::Valid { start, len }) => CaptureAt::StartLen(start, len),
185    };
186    let matched = data.capture_at(at)?;
187    interp.try_convert_mut(matched)
188}
189
190pub fn end(interp: &mut Artichoke, mut value: Value, mut at: Value) -> Result<Value, Error> {
191    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
192    let capture = match interp.try_convert_mut(&mut at)? {
193        CaptureExtract::GroupIndex(idx) => Capture::GroupIndex(idx),
194        CaptureExtract::GroupName(name) => Capture::GroupName(name),
195        CaptureExtract::Symbol(symbol) => Capture::GroupName(symbol.bytes(interp)),
196    };
197    let end = data.end(capture)?;
198    match end.map(i64::try_from) {
199        Some(Ok(end)) => Ok(interp.convert(end)),
200        Some(Err(_)) => Err(ArgumentError::with_message("input string too long").into()),
201        None => Ok(Value::nil()),
202    }
203}
204
205pub fn length(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
206    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
207    let len = data.len()?;
208    if let Ok(len) = i64::try_from(len) {
209        Ok(interp.convert(len))
210    } else {
211        Err(ArgumentError::with_message("input string too long").into())
212    }
213}
214
215pub fn named_captures(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
216    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
217    let named_captures = data.named_captures()?;
218    interp.try_convert_mut(named_captures)
219}
220
221pub fn names(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
222    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
223    let names = data.names();
224    interp.try_convert_mut(names)
225}
226
227pub fn offset(interp: &mut Artichoke, mut value: Value, mut at: Value) -> Result<Value, Error> {
228    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
229    let capture = match interp.try_convert_mut(&mut at)? {
230        CaptureExtract::GroupIndex(idx) => Capture::GroupIndex(idx),
231        CaptureExtract::GroupName(name) => Capture::GroupName(name),
232        CaptureExtract::Symbol(symbol) => Capture::GroupName(symbol.bytes(interp)),
233    };
234    if let Some([begin, end]) = data.offset(capture)? {
235        if let (Ok(begin), Ok(end)) = (i64::try_from(begin), i64::try_from(end)) {
236            let ary = Array::assoc(interp.convert(begin), interp.convert(end));
237            Array::alloc_value(ary, interp)
238        } else {
239            Err(ArgumentError::with_message("input string too long").into())
240        }
241    } else {
242        let ary = Array::assoc(Value::nil(), Value::nil());
243        Array::alloc_value(ary, interp)
244    }
245}
246
247pub fn post_match(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
248    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
249    let post = data.post();
250    interp.try_convert_mut(post)
251}
252
253pub fn pre_match(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
254    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
255    let pre = data.pre();
256    interp.try_convert_mut(pre)
257}
258
259pub fn regexp(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
260    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
261    let regexp = data.regexp();
262    // TODO(GH-614): `MatchData#regexp` needs to return an identical `Regexp` to
263    // the one used to create the match (same object ID).
264    //
265    // The `Regexp::alloc_value` here should be replaced with
266    // `Regexp::box_into_value`.
267    //
268    // See: <https://github.com/ruby/spec/pull/727>
269    let regexp = Regexp::alloc_value(regexp.clone(), interp)?;
270    Ok(regexp)
271}
272
273pub fn string(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
274    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
275    let mut string = interp.try_convert_mut(data.string())?;
276    string.freeze(interp)?;
277    Ok(string)
278}
279
280pub fn to_a(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
281    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
282    if let Some(ary) = data.to_a()? {
283        interp.try_convert_mut(ary)
284    } else {
285        Ok(Value::nil())
286    }
287}
288
289pub fn to_s(interp: &mut Artichoke, mut value: Value) -> Result<Value, Error> {
290    let data = unsafe { MatchData::unbox_from_value(&mut value, interp)? };
291    let display = data.to_s()?;
292    interp.try_convert_mut(display)
293}