Skip to main content

boa_engine/object/builtins/
jsregexp.rs

1//! A Rust API wrapper for Boa's `RegExp` Builtin ECMAScript Object
2use crate::{
3    Context, JsExpect, JsNativeError, JsResult, JsValue,
4    builtins::RegExp,
5    error::PanicError,
6    object::{JsArray, JsObject},
7    value::TryFromJs,
8};
9
10use boa_gc::{Finalize, Trace};
11use std::ops::Deref;
12
13/// `JsRegExp` provides a wrapper for Boa's implementation of the ECMAScript `RegExp` builtin object
14///
15/// # Examples
16///
17/// Create a `JsRegExp` and run RegExp.prototype.test( String )
18///
19/// ```
20/// # use boa_engine::{
21/// #  object::builtins::JsRegExp,
22/// #  Context, JsValue, JsResult,js_string
23/// # };
24/// # fn main() -> JsResult<()> {
25/// // Initialize the `Context`
26/// let context = &mut Context::default();
27///
28/// // Create a new RegExp with pattern and flags
29/// let regexp = JsRegExp::new(js_string!("foo"), js_string!("gi"), context)?;
30///
31/// let test_result = regexp.test(js_string!("football"), context)?;
32/// assert!(test_result);
33///
34/// let to_string = regexp.to_string(context)?;
35/// assert_eq!(to_string, String::from("/foo/gi"));
36/// # Ok(())
37/// # }
38/// ```
39#[derive(Debug, Clone, Trace, Finalize)]
40pub struct JsRegExp {
41    inner: JsObject,
42}
43
44impl JsRegExp {
45    /// Create a new `JsRegExp` object
46    /// ```
47    /// # use boa_engine::{
48    /// #  object::builtins::JsRegExp,
49    /// #  Context, JsValue, JsResult, js_string
50    /// # };
51    /// # fn main() -> JsResult<()> {
52    /// // Initialize the `Context`
53    /// let context = &mut Context::default();
54    ///
55    /// // Create a new RegExp with pattern and flags
56    /// let regexp = JsRegExp::new(js_string!("foo"), js_string!("gi"), context)?;
57    /// # Ok(())
58    /// # }
59    /// ```
60    pub fn new<S>(pattern: S, flags: S, context: &mut Context) -> JsResult<Self>
61    where
62        S: Into<JsValue>,
63    {
64        let regexp = RegExp::initialize(None, &pattern.into(), &flags.into(), context)?
65            .as_object()
66            .js_expect("RegExp::initialize must return a RegExp object")?
67            .clone();
68
69        Ok(Self { inner: regexp })
70    }
71
72    /// Create a `JsRegExp` from a regular expression `JsObject`
73    #[inline]
74    pub fn from_object(object: JsObject) -> JsResult<Self> {
75        if object.is::<RegExp>() {
76            Ok(Self { inner: object })
77        } else {
78            Err(JsNativeError::typ()
79                .with_message("object is not a RegExp")
80                .into())
81        }
82    }
83
84    /// Returns a boolean value for whether the `d` flag is present in `JsRegExp` flags
85    #[inline]
86    pub fn has_indices(&self, context: &mut Context) -> JsResult<bool> {
87        RegExp::get_has_indices(&self.inner.clone().into(), &[], context).and_then(|v| {
88            v.as_boolean()
89                .js_expect("value must be a bool")
90                .map_err(Into::into)
91        })
92    }
93
94    /// Returns a boolean value for whether the `g` flag is present in `JsRegExp` flags
95    #[inline]
96    pub fn global(&self, context: &mut Context) -> JsResult<bool> {
97        RegExp::get_global(&self.inner.clone().into(), &[], context).and_then(|v| {
98            v.as_boolean()
99                .js_expect("value must be a bool")
100                .map_err(Into::into)
101        })
102    }
103
104    /// Returns a boolean value for whether the `i` flag is present in `JsRegExp` flags
105    #[inline]
106    pub fn ignore_case(&self, context: &mut Context) -> JsResult<bool> {
107        RegExp::get_ignore_case(&self.inner.clone().into(), &[], context).and_then(|v| {
108            v.as_boolean()
109                .js_expect("value must be a bool")
110                .map_err(Into::into)
111        })
112    }
113
114    /// Returns a boolean value for whether the `m` flag is present in `JsRegExp` flags
115    #[inline]
116    pub fn multiline(&self, context: &mut Context) -> JsResult<bool> {
117        RegExp::get_multiline(&self.inner.clone().into(), &[], context).and_then(|v| {
118            v.as_boolean()
119                .js_expect("value must be a bool")
120                .map_err(Into::into)
121        })
122    }
123
124    /// Returns a boolean value for whether the `s` flag is present in `JsRegExp` flags
125    #[inline]
126    pub fn dot_all(&self, context: &mut Context) -> JsResult<bool> {
127        RegExp::get_dot_all(&self.inner.clone().into(), &[], context).and_then(|v| {
128            v.as_boolean()
129                .js_expect("value must be a bool")
130                .map_err(Into::into)
131        })
132    }
133
134    /// Returns a boolean value for whether the `u` flag is present in `JsRegExp` flags
135    #[inline]
136    pub fn unicode(&self, context: &mut Context) -> JsResult<bool> {
137        RegExp::get_unicode(&self.inner.clone().into(), &[], context).and_then(|v| {
138            v.as_boolean()
139                .js_expect("value must be a bool")
140                .map_err(Into::into)
141        })
142    }
143
144    /// Returns a boolean value for whether the `y` flag is present in `JsRegExp` flags
145    #[inline]
146    pub fn sticky(&self, context: &mut Context) -> JsResult<bool> {
147        RegExp::get_sticky(&self.inner.clone().into(), &[], context).and_then(|v| {
148            v.as_boolean()
149                .js_expect("value must be a bool")
150                .map_err(Into::into)
151        })
152    }
153
154    /// Returns the flags of `JsRegExp` as a string
155    /// ```
156    /// # use boa_engine::{
157    /// #  object::builtins::JsRegExp,
158    /// #  Context, JsValue, JsResult, js_string
159    /// # };
160    /// # fn main() -> JsResult<()> {
161    /// # let context = &mut Context::default();
162    /// let regexp = JsRegExp::new(js_string!("foo"), js_string!("gi"), context)?;
163    ///
164    /// let flags = regexp.flags(context)?;
165    /// assert_eq!(flags, String::from("gi"));
166    /// # Ok(())
167    /// # }
168    /// ```
169    #[inline]
170    pub fn flags(&self, context: &mut Context) -> JsResult<String> {
171        RegExp::get_flags(&self.inner.clone().into(), &[], context).and_then(|v| {
172            v.as_string()
173                .js_expect("value must be string")?
174                .to_std_string()
175                .map_err(|e| PanicError::new(e.to_string()).into())
176        })
177    }
178
179    /// Returns the source pattern of `JsRegExp` as a string
180    /// ```
181    /// # use boa_engine::{
182    /// #  object::builtins::JsRegExp,
183    /// #  Context, JsValue, JsResult, js_string
184    /// # };
185    /// # fn main() -> JsResult<()> {
186    /// # let context = &mut Context::default();
187    /// let regexp = JsRegExp::new(js_string!("foo"), js_string!("gi"), context)?;
188    ///
189    /// let src = regexp.source(context)?;
190    /// assert_eq!(src, String::from("foo"));
191    /// # Ok(())
192    /// # }
193    /// ```
194    #[inline]
195    pub fn source(&self, context: &mut Context) -> JsResult<String> {
196        RegExp::get_source(&self.inner.clone().into(), &[], context).and_then(|v| {
197            v.as_string()
198                .js_expect("value must be string")?
199                .to_std_string()
200                .map_err(|e| PanicError::new(e.to_string()).into())
201        })
202    }
203
204    /// Executes a search for a match between `JsRegExp` and the provided string
205    /// ```
206    /// # use boa_engine::{
207    /// #  object::builtins::JsRegExp,
208    /// #  Context, JsValue, JsResult, js_string
209    /// # };
210    /// # fn main() -> JsResult<()> {
211    /// # let context = &mut Context::default();
212    /// let regexp = JsRegExp::new(js_string!("foo"), js_string!("gi"), context)?;
213    ///
214    /// let test_result = regexp.test(js_string!("football"), context)?;
215    /// assert!(test_result);
216    /// # Ok(())
217    /// # }
218    /// ```
219    pub fn test<S>(&self, search_string: S, context: &mut Context) -> JsResult<bool>
220    where
221        S: Into<JsValue>,
222    {
223        RegExp::test(&self.inner.clone().into(), &[search_string.into()], context).and_then(|v| {
224            v.as_boolean()
225                .js_expect("value must be a bool")
226                .map_err(Into::into)
227        })
228    }
229
230    /// Executes a search for a match in a specified string
231    ///
232    /// Returns a `JsArray` containing matched value and updates the `lastIndex` property, or `None`
233    pub fn exec<S>(&self, search_string: S, context: &mut Context) -> JsResult<Option<JsArray>>
234    where
235        S: Into<JsValue>,
236    {
237        RegExp::exec(&self.inner.clone().into(), &[search_string.into()], context).and_then(|v| {
238            if v.is_null() {
239                Ok(None)
240            } else {
241                let obj = v.to_object(context).js_expect("value must be an array")?;
242                let array = JsArray::from_object(obj)
243                    .js_expect("from_object must not fail if value is an array object")?;
244                Ok(Some(array))
245            }
246        })
247    }
248
249    /// Return a string representing the regular expression.
250    /// ```
251    /// # use boa_engine::{
252    /// #  object::builtins::JsRegExp,
253    /// #  Context, JsValue, JsResult, js_string
254    /// # };
255    /// # fn main() -> JsResult<()> {
256    /// # let context = &mut Context::default();
257    /// let regexp = JsRegExp::new(js_string!("foo"), js_string!("gi"), context)?;
258    ///
259    /// let to_string = regexp.to_string(context)?;
260    /// assert_eq!(to_string, "/foo/gi");
261    /// # Ok(())
262    /// # }
263    /// ```
264    #[inline]
265    pub fn to_string(&self, context: &mut Context) -> JsResult<String> {
266        RegExp::to_string(&self.inner.clone().into(), &[], context).and_then(|v| {
267            v.as_string()
268                .js_expect("value must be a string")?
269                .to_std_string()
270                .map_err(|e| PanicError::new(e.to_string()).into())
271        })
272    }
273}
274
275impl From<JsRegExp> for JsObject {
276    #[inline]
277    fn from(o: JsRegExp) -> Self {
278        o.inner.clone()
279    }
280}
281
282impl From<JsRegExp> for JsValue {
283    #[inline]
284    fn from(o: JsRegExp) -> Self {
285        o.inner.clone().into()
286    }
287}
288
289impl Deref for JsRegExp {
290    type Target = JsObject;
291
292    #[inline]
293    fn deref(&self) -> &Self::Target {
294        &self.inner
295    }
296}
297
298impl TryFromJs for JsRegExp {
299    fn try_from_js(value: &JsValue, _context: &mut Context) -> JsResult<Self> {
300        if let Some(o) = value.as_object() {
301            Self::from_object(o.clone())
302        } else {
303            Err(JsNativeError::typ()
304                .with_message("value is not a RegExp object")
305                .into())
306        }
307    }
308}