Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement JsRegExp #2326

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(())
}