diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 0b40e238e2..4390d4bd5d 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -12,7 +12,7 @@ use crate::{ builtins::BuiltIn, gc::{empty_trace, Finalize, Trace}, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + object::{ConstructorBuilder, FunctionBuilder, GcObject, ObjectData, PROTOTYPE}, property::{Attribute, DataDescriptor}, value::{RcString, Value}, BoaProfiler, Context, Result, @@ -71,6 +71,44 @@ impl BuiltIn for RegExp { fn init(context: &mut Context) -> (&'static str, Value, Attribute) { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); + let flag_attributes = Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE; + + let get_global = FunctionBuilder::new(context, Self::get_global) + .name("get global") + .constructable(false) + .callable(true) + .build(); + let get_ignore_case = FunctionBuilder::new(context, Self::get_ignore_case) + .name("get ignoreCase") + .constructable(false) + .callable(true) + .build(); + let get_multiline = FunctionBuilder::new(context, Self::get_multiline) + .name("get multiline") + .constructable(false) + .callable(true) + .build(); + let get_dot_all = FunctionBuilder::new(context, Self::get_dot_all) + .name("get dotAll") + .constructable(false) + .callable(true) + .build(); + let get_unicode = FunctionBuilder::new(context, Self::get_unicode) + .name("get unicode") + .constructable(false) + .callable(true) + .build(); + let get_sticky = FunctionBuilder::new(context, Self::get_sticky) + .name("get sticky") + .constructable(false) + .callable(true) + .build(); + let get_flags = FunctionBuilder::new(context, Self::get_flags) + .name("get flags") + .constructable(false) + .callable(true) + .build(); + let regexp_object = ConstructorBuilder::with_standard_object( context, Self::constructor, @@ -82,6 +120,13 @@ impl BuiltIn for RegExp { .method(Self::test, "test", 1) .method(Self::exec, "exec", 1) .method(Self::to_string, "toString", 0) + .accessor("global", Some(get_global), None, flag_attributes) + .accessor("ignoreCase", Some(get_ignore_case), None, flag_attributes) + .accessor("multiline", Some(get_multiline), None, flag_attributes) + .accessor("dotAll", Some(get_dot_all), None, flag_attributes) + .accessor("unicode", Some(get_unicode), None, flag_attributes) + .accessor("sticky", Some(get_sticky), None, flag_attributes) + .accessor("flags", Some(get_flags), None, flag_attributes) .build(); // TODO: add them RegExp accessor properties @@ -207,76 +252,191 @@ impl RegExp { Ok(this) } - // /// `RegExp.prototype.dotAll` - // /// - // /// The `dotAll` property indicates whether or not the "`s`" flag is used with the regular expression. - // /// - // /// More information: - // /// - [ECMAScript reference][spec] - // /// - [MDN documentation][mdn] - // /// - // /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll - // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll - // fn get_dot_all(this: &Value, _: &[Value], _: &mut Context) -> Result { - // this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.dot_all))) - // } + #[inline] + fn regexp_has_flag(this: &Value, flag: char, context: &mut Context) -> Result { + if let Some(object) = this.as_object() { + if let Some(regexp) = object.borrow().as_regexp() { + return Ok(Value::boolean(match flag { + 'g' => regexp.global, + 'm' => regexp.multiline, + 's' => regexp.dot_all, + 'i' => regexp.ignore_case, + 'u' => regexp.unicode, + 'y' => regexp.sticky, + _ => unreachable!(), + })); + } - // /// `RegExp.prototype.flags` - // /// - // /// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object. - // /// - // /// More information: - // /// - [ECMAScript reference][spec] - // /// - [MDN documentation][mdn] - // /// - // /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.flags - // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags - // /// [flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2 - // fn get_flags(this: &Value, _: &[Value], _: &mut Context) -> Result { - // this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.flags.clone()))) - // } + if GcObject::equals( + &object, + &context.standard_objects().regexp_object().prototype, + ) { + return Ok(Value::undefined()); + } + } - // /// `RegExp.prototype.global` - // /// - // /// The `global` property indicates whether or not the "`g`" flag is used with the regular expression. - // /// - // /// More information: - // /// - [ECMAScript reference][spec] - // /// - [MDN documentation][mdn] - // /// - // /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.global - // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global - // fn get_global(this: &Value, _: &[Value], _: &mut Context) -> Result { - // this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.global))) - // } + let name = match flag { + 'g' => "global", + 'm' => "multiline", + 's' => "dotAll", + 'i' => "ignoreCase", + 'u' => "unicode", + 'y' => "sticky", + _ => unreachable!(), + }; - // /// `RegExp.prototype.ignoreCase` - // /// - // /// The `ignoreCase` property indicates whether or not the "`i`" flag is used with the regular expression. - // /// - // /// More information: - // /// - [ECMAScript reference][spec] - // /// - [MDN documentation][mdn] - // /// - // /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase - // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase - // fn get_ignore_case(this: &Value, _: &[Value], _: &mut Context) -> Result { - // this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.ignore_case))) - // } + context.throw_type_error(format!( + "RegExp.prototype.{} getter called on non-RegExp object", + name + )) + } - // /// `RegExp.prototype.multiline` - // /// - // /// The multiline property indicates whether or not the "m" flag is used with the regular expression. - // /// - // /// More information: - // /// - [ECMAScript reference][spec] - // /// - [MDN documentation][mdn] - // /// - // /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline - // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline - // fn get_multiline(this: &Value, _: &[Value], _: &mut Context) -> Result { - // this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.multiline))) - // } + /// `get RegExp.prototype.global` + /// + /// The `global` property indicates whether or not the "`g`" flag is used with the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.global + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global + pub(crate) fn get_global(this: &Value, _: &[Value], context: &mut Context) -> Result { + Self::regexp_has_flag(this, 'g', context) + } + + /// `get RegExp.prototype.ignoreCase` + /// + /// The `ignoreCase` property indicates whether or not the "`i`" flag is used with the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase + pub(crate) fn get_ignore_case( + this: &Value, + _: &[Value], + context: &mut Context, + ) -> Result { + Self::regexp_has_flag(this, 'i', context) + } + + /// `get RegExp.prototype.multiline` + /// + /// The multiline property indicates whether or not the "m" flag is used with the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline + pub(crate) fn get_multiline(this: &Value, _: &[Value], context: &mut Context) -> Result { + Self::regexp_has_flag(this, 'm', context) + } + + /// `get RegExp.prototype.dotAll` + /// + /// The `dotAll` property indicates whether or not the "`s`" flag is used with the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll + pub(crate) fn get_dot_all(this: &Value, _: &[Value], context: &mut Context) -> Result { + Self::regexp_has_flag(this, 's', context) + } + + /// `get RegExp.prototype.unicode` + /// + /// The unicode property indicates whether or not the "`u`" flag is used with a regular expression. + /// unicode is a read-only property of an individual regular expression instance. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode + pub(crate) fn get_unicode(this: &Value, _: &[Value], context: &mut Context) -> Result { + Self::regexp_has_flag(this, 'u', context) + } + + /// `get RegExp.prototype.sticky` + /// + /// This flag indicates that it matches only from the index indicated by the `lastIndex` property + /// of this regular expression in the target string (and does not attempt to match from any later indexes). + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky + pub(crate) fn get_sticky(this: &Value, _: &[Value], context: &mut Context) -> Result { + Self::regexp_has_flag(this, 'y', context) + } + + /// `get RegExp.prototype.flags` + /// + /// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.flags + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags + /// [flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2 + pub(crate) fn get_flags(this: &Value, _: &[Value], context: &mut Context) -> Result { + if let Some(object) = this.as_object() { + let mut result = String::new(); + if object + .get(&"global".into(), this.clone(), context)? + .to_boolean() + { + result.push('g'); + } + if object + .get(&"ignoreCase".into(), this.clone(), context)? + .to_boolean() + { + result.push('i'); + } + if object + .get(&"multiline".into(), this.clone(), context)? + .to_boolean() + { + result.push('m'); + } + if object + .get(&"dotAll".into(), this.clone(), context)? + .to_boolean() + { + result.push('s'); + } + if object + .get(&"unicode".into(), this.clone(), context)? + .to_boolean() + { + result.push('u'); + } + if object + .get(&"sticky".into(), this.clone(), context)? + .to_boolean() + { + result.push('y'); + } + + return Ok(result.into()); + } + + context.throw_type_error("RegExp.prototype.flags getter called on non-object") + } // /// `RegExp.prototype.source` // /// @@ -289,39 +449,10 @@ impl RegExp { // /// // /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.source // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source - // fn get_source(this: &Value, _: &[Value], _: &mut Context) -> Result { + // pub(crate) fn get_source(this: &Value, _: &[Value], _: &mut Context) -> Result { // Ok(this.get_internal_slot("OriginalSource")) // } - // /// `RegExp.prototype.sticky` - // /// - // /// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object. - // /// - // /// More information: - // /// - [ECMAScript reference][spec] - // /// - [MDN documentation][mdn] - // /// - // /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky - // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky - // fn get_sticky(this: &Value, _: &[Value], _: &mut Context) -> Result { - // this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.sticky))) - // } - - // /// `RegExp.prototype.unicode` - // /// - // /// The unicode property indicates whether or not the "`u`" flag is used with a regular expression. - // /// unicode is a read-only property of an individual regular expression instance. - // /// - // /// More information: - // /// - [ECMAScript reference][spec] - // /// - [MDN documentation][mdn] - // /// - // /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode - // /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode - // fn get_unicode(this: &Value, _: &[Value], _: &mut Context) -> Result { - // this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.unicode))) - // } - /// `RegExp.prototype.test( string )` /// /// The `test()` method executes a search for a match between a regular expression and a specified string.