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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use rust_jsc_sys::{JSObjectMakeRegExp, JSValueRef};

use crate::{JSContext, JSError, JSObject, JSRegExp, JSResult, JSValue};

impl JSRegExp {
    pub fn new(object: JSObject) -> Self {
        Self { object }
    }

    /// Creates a new `JSRegExp` object.
    ///
    /// # Arguments
    /// - `ctx`: The JavaScript context to create the regexp in.
    /// - `args`: The values to initialize the regexp with.
    ///
    /// # Example
    /// ```
    /// use rust_jsc::{JSContext, JSRegExp, JSValue};
    ///
    /// let ctx = JSContext::new();
    /// let regexp = JSRegExp::new_regexp(&ctx, &[JSValue::string(&ctx, "a")]).unwrap();
    /// let result = regexp.exec(&ctx, "abc").unwrap();
    /// assert_eq!(result.as_string().unwrap(), "a");
    /// ```
    ///
    /// # Errors
    /// If an exception is thrown while creating the regexp.
    /// A `JSError` will be returned.
    ///
    /// # Returns
    /// The new `JSRegExp` object.
    pub fn new_regexp(ctx: &JSContext, args: &[JSValue]) -> JSResult<Self> {
        let mut exception: JSValueRef = std::ptr::null_mut();
        let args: Vec<JSValueRef> = args.iter().map(|arg| arg.inner).collect();

        let result = unsafe {
            JSObjectMakeRegExp(ctx.inner, args.len(), args.as_ptr(), &mut exception)
        };

        if !exception.is_null() {
            let value = JSValue::new(exception, ctx.inner);
            return Err(JSError::from(value));
        }

        Ok(Self::new(JSObject::from_ref(result, ctx.inner)))
    }

    /// Executes a search for a match in a specified string.
    /// Returns the first match, or `null` if no match was found.
    /// This is equivalent to `regexp.exec(string)` in JavaScript.
    ///
    /// # Arguments
    /// - `ctx`: The JavaScript context to execute the search in.
    /// - `string`: The string to search for a match in.
    ///
    /// # Returns
    /// The first match, or `null` if no match was found.
    ///
    /// # Errors
    /// If an exception is thrown while executing the search.
    /// A `JSError` will be returned.
    ///
    /// # Example
    /// ```
    /// use rust_jsc::{JSContext, JSRegExp, JSValue};
    ///
    /// let ctx = JSContext::new();
    /// let regexp = JSRegExp::new_regexp(&ctx, &[JSValue::string(&ctx, "a")]).unwrap();
    /// let result = regexp.exec(&ctx, "abc").unwrap();
    /// assert_eq!(result.as_string().unwrap(), "a");
    /// ```
    pub fn exec(&self, ctx: &JSContext, string: &str) -> JSResult<JSValue> {
        let string = JSValue::string(ctx, string);
        self.object
            .get_property("exec")?
            .as_object()?
            .call(Some(&self.object), &[string])
    }

    /// Tests for a match in a specified string.
    /// Returns `true` if a match was found, otherwise `false`.
    /// This is equivalent to `regexp.test(string)` in JavaScript.
    ///
    /// # Arguments
    /// - `ctx`: The JavaScript context to execute the test in.
    /// - `string`: The string to test for a match in.
    ///
    /// # Example
    /// ```
    /// use rust_jsc::{JSContext, JSRegExp, JSValue};
    ///
    /// let ctx = JSContext::new();
    /// let regexp = JSRegExp::new_regexp(&ctx, &[JSValue::string(&ctx, "a")]).unwrap();
    /// let result = regexp.test(&ctx, "abc").unwrap();
    /// assert_eq!(result.as_boolean(), true);
    /// ```
    ///
    /// # Errors
    /// If an exception is thrown while executing the test.
    /// A `JSError` will be returned.
    ///
    /// # Returns
    /// `true` if a match was found, otherwise `false`.
    pub fn test(&self, ctx: &JSContext, string: &str) -> JSResult<JSValue> {
        let string = JSValue::string(ctx, string);
        self.object
            .get_property("test")?
            .as_object()?
            .call(Some(&self.object), &[string])
    }
}

impl From<JSRegExp> for JSObject {
    fn from(regexp: JSRegExp) -> Self {
        regexp.object
    }
}

impl From<JSRegExp> for JSValue {
    fn from(regexp: JSRegExp) -> Self {
        regexp.object.into()
    }
}

#[cfg(test)]
mod tests {
    use crate::{JSContext, JSRegExp, JSValue};

    #[test]
    fn test_regexp() {
        let ctx = JSContext::new();
        let regexp = JSRegExp::new_regexp(&ctx, &[JSValue::string(&ctx, "a")]).unwrap();
        let result = regexp.exec(&ctx, "abc").unwrap();
        assert_eq!(result.as_string().unwrap(), "a");

        let result = regexp.test(&ctx, "abc").unwrap();
        assert_eq!(result.as_boolean(), true);
    }
}