Browse Source

implement RegExp.prototype [ @@search ] ( string ) (#1314)

* implement RegExp.prototype [ @@search ] ( string )

* implement String.prototype.search ( regexp )

* fix errors and refactor search implementations:
* RegExp.prototype [ @@search ] ( string )
* String.prototype.search ( regexp )

* add basic tests for search implementations:
* RegExp.prototype [ @@search ] ( string )
* String.prototype.search ( regexp )

* remove leftover comments

* Fix RegExp.prototype.search to accept all objects
* Add some more rust tests from the 262 suite
pull/1321/head
raskad 3 years ago committed by GitHub
parent
commit
fc3fbe26a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 64
      boa/src/builtins/regexp/mod.rs
  2. 86
      boa/src/builtins/regexp/tests.rs
  3. 46
      boa/src/builtins/string/mod.rs
  4. 10
      boa/src/builtins/string/tests.rs

64
boa/src/builtins/regexp/mod.rs

@ -14,6 +14,7 @@ use crate::{
gc::{empty_trace, Finalize, Trace}, gc::{empty_trace, Finalize, Trace},
object::{ConstructorBuilder, FunctionBuilder, GcObject, ObjectData, PROTOTYPE}, object::{ConstructorBuilder, FunctionBuilder, GcObject, ObjectData, PROTOTYPE},
property::{Attribute, DataDescriptor}, property::{Attribute, DataDescriptor},
symbol::WellKnownSymbols,
value::{RcString, Value}, value::{RcString, Value},
BoaProfiler, Context, Result, BoaProfiler, Context, Result,
}; };
@ -120,6 +121,11 @@ impl BuiltIn for RegExp {
.method(Self::test, "test", 1) .method(Self::test, "test", 1)
.method(Self::exec, "exec", 1) .method(Self::exec, "exec", 1)
.method(Self::to_string, "toString", 0) .method(Self::to_string, "toString", 0)
.method(
Self::search,
(WellKnownSymbols::search(), "[Symbol.search]"),
1,
)
.accessor("global", Some(get_global), None, flag_attributes) .accessor("global", Some(get_global), None, flag_attributes)
.accessor("ignoreCase", Some(get_ignore_case), None, flag_attributes) .accessor("ignoreCase", Some(get_ignore_case), None, flag_attributes)
.accessor("multiline", Some(get_multiline), None, flag_attributes) .accessor("multiline", Some(get_multiline), None, flag_attributes)
@ -714,4 +720,62 @@ impl RegExp {
Ok(result) Ok(result)
} }
/// `RegExp.prototype[ @@search ]( string )`
///
/// This method executes a search for a match between a this regular expression and a string.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype-@@search
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@search
pub(crate) fn search(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
// 1. Let rx be the this value.
// 2. If Type(rx) is not Object, throw a TypeError exception.
if !this.is_object() {
return context.throw_type_error(
"RegExp.prototype[Symbol.search] method called on incompatible value",
);
}
// 3. Let S be ? ToString(string).
let arg_str = args
.get(0)
.cloned()
.unwrap_or_default()
.to_string(context)?;
// 4. Let previousLastIndex be ? Get(rx, "lastIndex").
let previous_last_index = this.get_field("lastIndex", context)?.to_length(context)?;
// 5. If SameValue(previousLastIndex, +0𝔽) is false, then
if previous_last_index != 0 {
// a. Perform ? Set(rx, "lastIndex", +0𝔽, true).
this.set_field("lastIndex", 0, context)?;
}
// 6. Let result be ? RegExpExec(rx, S).
let result = Self::exec(this, &[Value::from(arg_str)], context)?;
// 7. Let currentLastIndex be ? Get(rx, "lastIndex").
let current_last_index = this.get_field("lastIndex", context)?.to_length(context)?;
// 8. If SameValue(currentLastIndex, previousLastIndex) is false, then
if current_last_index != previous_last_index {
// a. Perform ? Set(rx, "lastIndex", previousLastIndex, true).
this.set_field("lastIndex", previous_last_index, context)?;
}
// 9. If result is null, return -1𝔽.
// 10. Return ? Get(result, "index").
if result.is_null() {
Ok(Value::from(-1))
} else {
result
.get_field("index", context)
.map_err(|_| context.construct_type_error("Could not find property `index`"))
}
}
} }

86
boa/src/builtins/regexp/tests.rs

@ -107,3 +107,89 @@ fn no_panic_on_invalid_character_escape() {
// The line below should not cause Boa to panic // The line below should not cause Boa to panic
forward(&mut context, r"const a = /,\;/"); forward(&mut context, r"const a = /,\;/");
} }
#[test]
fn search() {
let mut context = Context::new();
// coerce-string
assert_eq!(
forward(
&mut context,
r#"
var obj = {
toString: function() {
return 'toString value';
}
};
/ring/[Symbol.search](obj)
"#
),
"4"
);
// failure-return-val
assert_eq!(forward(&mut context, "/z/[Symbol.search]('a')"), "-1");
// length
assert_eq!(
forward(&mut context, "RegExp.prototype[Symbol.search].length"),
"1"
);
let init =
"var obj = Object.getOwnPropertyDescriptor(RegExp.prototype[Symbol.search], \"length\")";
eprintln!("{}", forward(&mut context, init));
assert_eq!(forward(&mut context, "obj.enumerable"), "false");
assert_eq!(forward(&mut context, "obj.writable"), "false");
assert_eq!(forward(&mut context, "obj.configurable"), "true");
// name
assert_eq!(
forward(&mut context, "RegExp.prototype[Symbol.search].name"),
"\"[Symbol.search]\""
);
let init =
"var obj = Object.getOwnPropertyDescriptor(RegExp.prototype[Symbol.search], \"name\")";
eprintln!("{}", forward(&mut context, init));
assert_eq!(forward(&mut context, "obj.enumerable"), "false");
assert_eq!(forward(&mut context, "obj.writable"), "false");
assert_eq!(forward(&mut context, "obj.configurable"), "true");
// prop-desc
let init = "var obj = Object.getOwnPropertyDescriptor(RegExp.prototype, Symbol.search)";
eprintln!("{}", forward(&mut context, init));
assert_eq!(forward(&mut context, "obj.enumerable"), "false");
assert_eq!(forward(&mut context, "obj.writable"), "true");
assert_eq!(forward(&mut context, "obj.configurable"), "true");
// success-return-val
assert_eq!(forward(&mut context, "/a/[Symbol.search]('abc')"), "0");
assert_eq!(forward(&mut context, "/b/[Symbol.search]('abc')"), "1");
assert_eq!(forward(&mut context, "/c/[Symbol.search]('abc')"), "2");
// this-val-non-obj
let error = "Uncaught \"TypeError\": \"RegExp.prototype[Symbol.search] method called on incompatible value\"";
let init = "var search = RegExp.prototype[Symbol.search]";
eprintln!("{}", forward(&mut context, init));
assert_eq!(forward(&mut context, "search.call()"), error);
assert_eq!(forward(&mut context, "search.call(undefined)"), error);
assert_eq!(forward(&mut context, "search.call(null)"), error);
assert_eq!(forward(&mut context, "search.call(true)"), error);
assert_eq!(forward(&mut context, "search.call('string')"), error);
assert_eq!(forward(&mut context, "search.call(Symbol.search)"), error);
assert_eq!(forward(&mut context, "search.call(86)"), error);
// u-lastindex-advance
assert_eq!(
forward(&mut context, "/\\udf06/u[Symbol.search]('\\ud834\\udf06')"),
"-1"
);
assert_eq!(forward(&mut context, "/a/[Symbol.search](\"a\")"), "0");
assert_eq!(forward(&mut context, "/a/[Symbol.search](\"ba\")"), "1");
assert_eq!(forward(&mut context, "/a/[Symbol.search](\"bb\")"), "-1");
assert_eq!(forward(&mut context, "/u/[Symbol.search](null)"), "1");
assert_eq!(forward(&mut context, "/d/[Symbol.search](undefined)"), "2");
}

46
boa/src/builtins/string/mod.rs

@ -133,6 +133,7 @@ impl BuiltIn for String {
.method(Self::match_all, "matchAll", 1) .method(Self::match_all, "matchAll", 1)
.method(Self::replace, "replace", 2) .method(Self::replace, "replace", 2)
.method(Self::iterator, (symbol_iterator, "[Symbol.iterator]"), 0) .method(Self::iterator, (symbol_iterator, "[Symbol.iterator]"), 0)
.method(Self::search, "search", 1)
.build(); .build();
(Self::NAME, string_object.into(), Self::attribute()) (Self::NAME, string_object.into(), Self::attribute())
@ -1324,6 +1325,51 @@ impl String {
RegExp::match_all(&re, this.to_string(context)?.to_string(), context) RegExp::match_all(&re, this.to_string(context)?.to_string(), context)
} }
/// `String.prototype.search( regexp )`
///
/// The search() method executes a search for a match between a regular expression and this String object.
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.search
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/search
pub(crate) fn search(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> {
// 1. Let O be ? RequireObjectCoercible(this value).
let this = this.require_object_coercible(context)?;
// 2. If regexp is neither undefined nor null, then
let regexp = args.get(0).cloned().unwrap_or_default();
if !regexp.is_null_or_undefined() {
// a. Let searcher be ? GetMethod(regexp, @@search).
// b. If searcher is not undefined, then
if let Some(searcher) = regexp
.to_object(context)?
.get_method(context, WellKnownSymbols::search())?
{
// i. Return ? Call(searcher, regexp, « O »).
return searcher.call(&regexp, &[this.clone()], context);
}
}
// 3. Let string be ? ToString(O).
let s = this.to_string(context)?;
// 4. Let rx be ? RegExpCreate(regexp, undefined).
let rx = RegExp::constructor(&Value::from(Object::default()), &[regexp], context)?;
// 5. Return ? Invoke(rx, @@search, « string »).
if let Some(searcher) = rx
.to_object(context)?
.get_method(context, WellKnownSymbols::search())?
{
searcher.call(&rx, &[Value::from(s)], context)
} else {
context.throw_type_error("regexp[Symbol.search] is not a function")
}
}
pub(crate) fn iterator(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> { pub(crate) fn iterator(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
StringIterator::create_string_iterator(context, this.clone()) StringIterator::create_string_iterator(context, this.clone())
} }

10
boa/src/builtins/string/tests.rs

@ -1104,3 +1104,13 @@ fn string_get_property() {
assert_eq!(forward(&mut context, "'abc'['foo']"), "undefined"); assert_eq!(forward(&mut context, "'abc'['foo']"), "undefined");
assert_eq!(forward(&mut context, "'😀'[0]"), "\"\\ud83d\""); assert_eq!(forward(&mut context, "'😀'[0]"), "\"\\ud83d\"");
} }
#[test]
fn search() {
let mut context = Context::new();
assert_eq!(forward(&mut context, "'aa'.search(/b/)"), "-1");
assert_eq!(forward(&mut context, "'aa'.search(/a/)"), "0");
assert_eq!(forward(&mut context, "'aa'.search(/a/g)"), "0");
assert_eq!(forward(&mut context, "'ba'.search(/a/)"), "1");
}

Loading…
Cancel
Save