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
//! [`MatchData#end`](https://ruby-doc.org/core-2.6.3/MatchData.html#method-i-end)

use std::convert::TryFrom;
use std::mem;

use crate::convert::{FromMrb, RustBackedValue, TryFromMrb};
use crate::extn::core::matchdata::MatchData;
use crate::sys;
use crate::value::{Value, ValueLike};
use crate::Mrb;

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Error {
    Fatal,
    IndexType,
    NoGroup,
    NoMatch,
}

#[derive(Debug, Clone)]
pub enum Args {
    Index(i64),
    Name(String),
}

impl Args {
    const ARGSPEC: &'static [u8] = b"o\0";

    pub unsafe fn extract(interp: &Mrb) -> Result<Self, Error> {
        let mut first = <mem::MaybeUninit<sys::mrb_value>>::uninit();
        sys::mrb_get_args(
            interp.borrow().mrb,
            Self::ARGSPEC.as_ptr() as *const i8,
            first.as_mut_ptr(),
        );
        let first = first.assume_init();
        if let Ok(index) = i64::try_from_mrb(interp, Value::new(interp, first)) {
            Ok(Args::Index(index))
        } else if let Ok(name) = String::try_from_mrb(interp, Value::new(interp, first)) {
            Ok(Args::Name(name))
        } else if let Ok(index) = Value::new(interp, first).funcall::<i64, _, _>("to_int", &[]) {
            Ok(Args::Index(index))
        } else {
            Err(Error::IndexType)
        }
    }
}

pub fn method(interp: &Mrb, args: Args, value: &Value) -> Result<Value, Error> {
    let data = unsafe { MatchData::try_from_ruby(interp, value) }.map_err(|_| Error::Fatal)?;
    let borrow = data.borrow();
    let regex = (*borrow.regexp.regex).as_ref().ok_or(Error::Fatal)?;
    let match_against = &borrow.string[borrow.region.start..borrow.region.end];
    let captures = regex.captures(match_against).ok_or(Error::NoMatch)?;
    let index = match args {
        Args::Index(index) => {
            if index < 0 {
                // Positive i64 must be usize
                let index = usize::try_from(-index).map_err(|_| Error::Fatal)?;
                captures.len().checked_sub(index).ok_or(Error::Fatal)?
            } else {
                // Positive i64 must be usize
                usize::try_from(index).map_err(|_| Error::Fatal)?
            }
        }
        Args::Name(name) => {
            let index = regex
                .capture_names()
                .find(|capture| capture.0 == name)
                .ok_or(Error::NoGroup)?
                .1
                .last()
                .ok_or(Error::NoMatch)?;
            usize::try_from(*index).map_err(|_| Error::Fatal)?
        }
    };
    let end = captures.pos(index).ok_or(Error::NoMatch)?.1;
    let end = match_against[0..end].chars().count();
    let end = end + borrow.region.start;
    let end = i64::try_from(end).map_err(|_| Error::Fatal)?;
    Ok(Value::from_mrb(&interp, end))
}