Skip to content

Commit

Permalink
Implement JsRegExp (#2326)
Browse files Browse the repository at this point in the history
<!---
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
information as you feel necessary.
--->

This Pull Request is related to #2098.

It changes the following:

- Implements `JsRegExp`
- Adds a brief `JsRegExp` example under `boa_examples`
  • Loading branch information
nekevss committed Oct 15, 2022
1 parent 9998a67 commit 13dcdd4
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 2 deletions.
4 changes: 2 additions & 2 deletions boa_engine/src/builtins/regexp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ impl RegExp {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpalloc
fn alloc(new_target: &JsValue, context: &mut Context) -> JsResult<JsObject> {
pub(crate) fn alloc(new_target: &JsValue, context: &mut Context) -> JsResult<JsObject> {
// 1. Let obj be ? OrdinaryCreateFromConstructor(newTarget, "%RegExp.prototype%", « [[RegExpMatcher]], [[OriginalSource]], [[OriginalFlags]] »).
let proto =
get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?;
Expand Down Expand Up @@ -259,7 +259,7 @@ impl RegExp {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexpinitialize
fn initialize(
pub(crate) fn initialize(
obj: JsObject,
pattern: &JsValue,
flags: &JsValue,
Expand Down
269 changes: 269 additions & 0 deletions boa_engine/src/object/builtins/jsregexp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
//! This module implements a wrapper for the `RegExp` Builtin Javascript Object
use crate::{
builtins::RegExp,
object::{JsArray, JsObject, JsObjectType},
Context, JsResult, JsValue,
};

use boa_gc::{Finalize, Trace};
use std::ops::Deref;

/// `JsRegExp` provides a wrapper for Boa's implementation of the JavaScript `RegExp` builtin object
///
/// # Examples
///
/// Create a `JsRegExp` and run RegExp.prototype.test( String )
///
/// ```
/// # use boa_engine::{
/// # object::builtins::JsRegExp,
/// # Context, JsValue,
/// # };
///
/// // Initialize the `Context`
/// let context = &mut Context::default();
///
/// // Create a new RegExp with pattern and flags
/// let regexp = JsRegExp::new("foo", "gi", context).unwrap();
///
/// let test_result = regexp.test("football", context).unwrap();
/// assert!(test_result);
///
/// let to_string = regexp.to_string(context).unwrap();
/// assert_eq!(to_string, String::from("/foo/gi"));
///
/// ```
///
#[derive(Debug, Clone, Trace, Finalize)]
pub struct JsRegExp {
inner: JsObject,
}

impl JsRegExp {
/// Create a new `JsRegExp` object
/// ```
/// # use boa_engine::{
/// # object::builtins::JsRegExp,
/// # Context, JsValue,
/// # };
/// // Initialize the `Context`
/// let context = &mut Context::default();
///
/// // Create a new RegExp with pattern and flags
/// let regexp = JsRegExp::new("foo", "gi", context).unwrap();
/// ```
#[inline]
pub fn new<S>(pattern: S, flags: S, context: &mut Context) -> JsResult<Self>
where
S: Into<JsValue>,
{
let constructor = &context
.intrinsics()
.constructors()
.regexp()
.constructor()
.into();
let obj = RegExp::alloc(constructor, context)?;

let regexp = RegExp::initialize(obj, &pattern.into(), &flags.into(), context)?
.as_object()
.expect("RegExp::initialize must return a RegExp object")
.clone();

Ok(Self { inner: regexp })
}

/// Create a `JsRegExp` from a regular expression `JsObject`
#[inline]
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> {
if object.borrow().is_regexp() {
Ok(Self { inner: object })
} else {
context.throw_type_error("object is not a RegExp")
}
}

/// Returns a boolean value for whether the `d` flag is present in `JsRegExp` flags
#[inline]
pub fn has_indices(&self, context: &mut Context) -> JsResult<bool> {
RegExp::get_has_indices(&self.inner.clone().into(), &[], context)
.map(|v| v.as_boolean().expect("value must be a bool"))
}

/// Returns a boolean value for whether the `g` flag is present in `JsRegExp` flags
#[inline]
pub fn global(&self, context: &mut Context) -> JsResult<bool> {
RegExp::get_global(&self.inner.clone().into(), &[], context)
.map(|v| v.as_boolean().expect("value must be a bool"))
}

/// Returns a boolean value for whether the `i` flag is present in `JsRegExp` flags
#[inline]
pub fn ignore_case(&self, context: &mut Context) -> JsResult<bool> {
RegExp::get_ignore_case(&self.inner.clone().into(), &[], context)
.map(|v| v.as_boolean().expect("value must be a bool"))
}

/// Returns a boolean value for whether the `m` flag is present in `JsRegExp` flags
#[inline]
pub fn multiline(&self, context: &mut Context) -> JsResult<bool> {
RegExp::get_multiline(&self.inner.clone().into(), &[], context)
.map(|v| v.as_boolean().expect("value must be a bool"))
}

/// Returns a boolean value for whether the `s` flag is present in `JsRegExp` flags
#[inline]
pub fn dot_all(&self, context: &mut Context) -> JsResult<bool> {
RegExp::get_dot_all(&self.inner.clone().into(), &[], context)
.map(|v| v.as_boolean().expect("value must be a bool"))
}

/// Returns a boolean value for whether the `u` flag is present in `JsRegExp` flags
#[inline]
pub fn unicode(&self, context: &mut Context) -> JsResult<bool> {
RegExp::get_unicode(&self.inner.clone().into(), &[], context)
.map(|v| v.as_boolean().expect("value must be a bool"))
}

/// Returns a boolean value for whether the `y` flag is present in `JsRegExp` flags
#[inline]
pub fn sticky(&self, context: &mut Context) -> JsResult<bool> {
RegExp::get_sticky(&self.inner.clone().into(), &[], context)
.map(|v| v.as_boolean().expect("value must be a bool"))
}

/// Returns the flags of `JsRegExp` as a string
/// ```
/// # use boa_engine::{
/// # object::builtins::JsRegExp,
/// # Context, JsValue,
/// # };
/// # let context = &mut Context::default();
/// let regexp = JsRegExp::new("foo", "gi", context).unwrap();
///
/// let flags = regexp.flags(context).unwrap();
/// assert_eq!(flags, String::from("gi"));
/// ```
#[inline]
pub fn flags(&self, context: &mut Context) -> JsResult<String> {
RegExp::get_flags(&self.inner.clone().into(), &[], context).map(|v| {
v.as_string()
.expect("value must be string")
.to_std_string()
.expect("flags must be a valid string")
})
}

/// Returns the source pattern of `JsRegExp` as a string
/// ```
/// # use boa_engine::{
/// # object::builtins::JsRegExp,
/// # Context, JsValue,
/// # };
/// # let context = &mut Context::default();
/// let regexp = JsRegExp::new("foo", "gi", context).unwrap();
///
/// let src = regexp.source(context).unwrap();
/// assert_eq!(src, String::from("foo"));
/// ```
#[inline]
pub fn source(&self, context: &mut Context) -> JsResult<String> {
RegExp::get_source(&self.inner.clone().into(), &[], context).map(|v| {
v.as_string()
.expect("value must be string")
.to_std_string()
.expect("source must be a valid string")
})
}

/// Executes a search for a match between `JsRegExp` and the provided string
/// ```
/// # use boa_engine::{
/// # object::builtins::JsRegExp,
/// # Context, JsValue,
/// # };
/// # let context = &mut Context::default();
/// let regexp = JsRegExp::new("foo", "gi", context).unwrap();
///
/// let test_result = regexp.test("football", context).unwrap();
/// assert!(test_result);
/// ```
#[inline]
pub fn test<S>(&self, search_string: S, context: &mut Context) -> JsResult<bool>
where
S: Into<JsValue>,
{
RegExp::test(&self.inner.clone().into(), &[search_string.into()], context)
.map(|v| v.as_boolean().expect("value must be a bool"))
}

/// Executes a search for a match in a specified string
///
/// Returns a `JsArray` containing matched value and updates the `lastIndex` property, or `None`
#[inline]
pub fn exec<S>(&self, search_string: S, context: &mut Context) -> JsResult<Option<JsArray>>
where
S: Into<JsValue>,
{
RegExp::exec(&self.inner.clone().into(), &[search_string.into()], context).map(|v| {
if v.is_null() {
None
} else {
Some(
JsArray::from_object(
v.to_object(context).expect("v must be an array"),
context,
)
.expect("from_object must not fail if v is an array object"),
)
}
})
}

/// Return a string representing the regular expression.
/// ```
/// # use boa_engine::{
/// # object::builtins::JsRegExp,
/// # Context, JsValue,
/// # };
/// # let context = &mut Context::default();
/// let regexp = JsRegExp::new("foo", "gi", context).unwrap();
///
/// let to_string = regexp.to_string(context).unwrap();
/// assert_eq!(to_string, String::from("/foo/gi"));
/// ```
#[inline]
pub fn to_string(&self, context: &mut Context) -> JsResult<String> {
RegExp::to_string(&self.inner.clone().into(), &[], context).map(|v| {
v.as_string()
.expect("value must be a string")
.to_std_string()
.expect("to_string value must be a valid string")
})
}
}

impl From<JsRegExp> for JsObject {
#[inline]
fn from(o: JsRegExp) -> Self {
o.inner.clone()
}
}

impl From<JsRegExp> for JsValue {
#[inline]
fn from(o: JsRegExp) -> Self {
o.inner.clone().into()
}
}

impl Deref for JsRegExp {
type Target = JsObject;

#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}

impl JsObjectType for JsRegExp {}
2 changes: 2 additions & 0 deletions boa_engine/src/object/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod jsfunction;
mod jsmap;
mod jsmap_iterator;
pub(crate) mod jsproxy;
mod jsregexp;
mod jsset;
mod jsset_iterator;
mod jstypedarray;
Expand All @@ -18,6 +19,7 @@ pub use jsfunction::*;
pub use jsmap::*;
pub use jsmap_iterator::*;
pub use jsproxy::{JsProxy, JsRevocableProxy};
pub use jsregexp::JsRegExp;
pub use jsset::*;
pub use jsset_iterator::*;
pub use jstypedarray::*;
21 changes: 21 additions & 0 deletions boa_examples/src/bin/jsregexp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use boa_engine::{object::builtins::JsRegExp, Context, JsResult};

fn main() -> JsResult<()> {
let context = &mut Context::default();

let regexp = JsRegExp::new("foo", "gi", context)?;

let test_result = regexp.test("football", context)?;
assert!(test_result);

let flags = regexp.flags(context)?;
assert_eq!(flags, String::from("gi"));

let src = regexp.source(context)?;
assert_eq!(src, String::from("foo"));

let to_string = regexp.to_string(context)?;
assert_eq!(to_string, String::from("/foo/gi"));

Ok(())
}

0 comments on commit 13dcdd4

Please sign in to comment.