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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! [`Regexp::union`](https://ruby-doc.org/core-2.6.3/Regexp.html#method-c-union)

use std::mem;

use crate::convert::{FromMrb, RustBackedValue};
use crate::extn::core::regexp::{syntax, Regexp};
use crate::sys;
use crate::value::types::Ruby;
use crate::value::{Value, ValueLike};
use crate::Mrb;

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

#[derive(Debug)]
pub struct Args {
    pub rest: Vec<Value>,
}

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

    pub unsafe fn extract(interp: &Mrb) -> Self {
        let mut args = <mem::MaybeUninit<*const sys::mrb_value>>::uninit();
        let mut count = <mem::MaybeUninit<usize>>::uninit();
        sys::mrb_get_args(
            interp.borrow().mrb,
            Self::ARGSPEC.as_ptr() as *const i8,
            args.as_mut_ptr(),
            count.as_mut_ptr(),
        );
        let args = std::slice::from_raw_parts(args.assume_init(), count.assume_init());
        let args = args
            .iter()
            .map(|value| Value::new(&interp, *value))
            .collect::<Vec<_>>();
        Self { rest: args }
    }
}

pub fn method(interp: &Mrb, args: Args, slf: sys::mrb_value) -> Result<Value, Error> {
    let mrb = interp.borrow().mrb;
    let pattern = if args.rest.is_empty() {
        "(?!)".to_owned()
    } else if args.rest.len() == 1 {
        let arg = args.rest.into_iter().nth(0).unwrap();
        if arg.ruby_type() == Ruby::Array {
            let mut patterns = vec![];
            for pattern in arg
                .itself::<Vec<Value>>()
                .map_err(|_| Error::NoImplicitConversionToString)?
            {
                if let Ok(regexp) = unsafe { Regexp::try_from_ruby(&interp, &pattern) } {
                    patterns.push(regexp.borrow().pattern.clone());
                } else if let Ok(pattern) = pattern.funcall::<String, _, _>("to_str", &[]) {
                    patterns.push(syntax::escape(pattern.as_str()));
                } else {
                    return Err(Error::NoImplicitConversionToString);
                }
            }
            patterns.join("|")
        } else {
            let pattern = arg;
            if let Ok(regexp) = unsafe { Regexp::try_from_ruby(&interp, &pattern) } {
                regexp.borrow().pattern.clone()
            } else if let Ok(pattern) = pattern.funcall::<String, _, _>("to_str", &[]) {
                syntax::escape(pattern.as_str())
            } else {
                return Err(Error::NoImplicitConversionToString);
            }
        }
    } else {
        let mut patterns = vec![];
        for pattern in args.rest {
            if let Ok(regexp) = unsafe { Regexp::try_from_ruby(&interp, &pattern) } {
                patterns.push(regexp.borrow().pattern.clone());
            } else if let Ok(pattern) = pattern.funcall::<String, _, _>("to_str", &[]) {
                patterns.push(syntax::escape(pattern.as_str()));
            } else {
                return Err(Error::NoImplicitConversionToString);
            }
        }
        patterns.join("|")
    };

    let value = unsafe {
        sys::mrb_obj_new(
            mrb,
            sys::mrb_sys_class_ptr(slf),
            1,
            [Value::from_mrb(interp, pattern).inner()].as_ptr() as *const sys::mrb_value,
        )
    };
    Ok(Value::new(interp, value))
}