mirror of
https://github.com/jerryscript-project/jerryscript.git
synced 2025-12-15 16:29:21 +00:00
Implement and add support for RegExp.prototype[@@search] (#3436)
JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai@inf.u-szeged.hu
This commit is contained in:
parent
c525b1f10a
commit
d3b8bed2c1
@ -617,6 +617,22 @@ ecma_builtin_regexp_prototype_symbol_replace (ecma_value_t this_arg, /**< this a
|
||||
return ecma_regexp_replace_helper (this_arg, string_arg, replace_arg);
|
||||
} /* ecma_builtin_regexp_prototype_symbol_replace */
|
||||
|
||||
/**
|
||||
* The RegExp.prototype object's '@@search' routine
|
||||
*
|
||||
* See also:
|
||||
* ECMA-262 v6.0, 21.2.5.9
|
||||
*
|
||||
* @return ecma value
|
||||
* Returned value must be freed with ecma_free_value.
|
||||
*/
|
||||
static ecma_value_t
|
||||
ecma_builtin_regexp_prototype_symbol_search (ecma_value_t this_arg, /**< this argument */
|
||||
ecma_value_t string_arg) /**< string argument */
|
||||
{
|
||||
return ecma_regexp_search_helper (this_arg, string_arg);
|
||||
} /* ecma_builtin_regexp_prototype_symbol_search */
|
||||
|
||||
/**
|
||||
* The RegExp.prototype object's '@@match' routine
|
||||
*
|
||||
|
||||
@ -56,6 +56,7 @@ ACCESSOR_READ_ONLY (LIT_MAGIC_STRING_STICKY,
|
||||
ECMA_PROPERTY_FIXED)
|
||||
|
||||
ROUTINE (LIT_GLOBAL_SYMBOL_REPLACE, ecma_builtin_regexp_prototype_symbol_replace, 2, 2)
|
||||
ROUTINE (LIT_GLOBAL_SYMBOL_SEARCH, ecma_builtin_regexp_prototype_symbol_search, 1, 1)
|
||||
ROUTINE (LIT_GLOBAL_SYMBOL_MATCH, ecma_builtin_regexp_prototype_symbol_match, 1, 1)
|
||||
#else /* !ENABLED (JERRY_ES2015) */
|
||||
/* ECMA-262 v5, 15.10.7.1 */
|
||||
|
||||
@ -274,37 +274,6 @@ ecma_builtin_string_prototype_object_locale_compare (ecma_string_t *this_string_
|
||||
} /* ecma_builtin_string_prototype_object_locale_compare */
|
||||
|
||||
#if ENABLED (JERRY_BUILTIN_REGEXP)
|
||||
|
||||
/**
|
||||
* The common preparation code for 'search' and 'match' functions
|
||||
* of the String prototype.
|
||||
*
|
||||
* @return empty value on success, error value otherwise
|
||||
* Returned value must be freed with ecma_free_value.
|
||||
*/
|
||||
static ecma_value_t
|
||||
ecma_builtin_string_prepare_search (ecma_value_t regexp_arg, /**< regex argument */
|
||||
ecma_value_t *regexp_value) /**< [out] ptr to store the regexp object */
|
||||
{
|
||||
/* 3. */
|
||||
if (ecma_object_is_regexp_object (regexp_arg))
|
||||
{
|
||||
*regexp_value = ecma_copy_value (regexp_arg);
|
||||
return ECMA_VALUE_EMPTY;
|
||||
}
|
||||
|
||||
/* 4. */
|
||||
ecma_value_t regexp_arguments[1] = { regexp_arg };
|
||||
ecma_value_t new_regexp_value = ecma_builtin_regexp_dispatch_construct (regexp_arguments, 1);
|
||||
|
||||
if (!ECMA_IS_VALUE_ERROR (new_regexp_value))
|
||||
{
|
||||
*regexp_value = new_regexp_value;
|
||||
}
|
||||
|
||||
return new_regexp_value;
|
||||
} /* ecma_builtin_string_prepare_search */
|
||||
|
||||
/**
|
||||
* The String.prototype object's 'match' routine
|
||||
*
|
||||
@ -600,62 +569,99 @@ cleanup_search:
|
||||
*
|
||||
* See also:
|
||||
* ECMA-262 v5, 15.5.4.12
|
||||
* ECMA-262 v6, 21.1.3.15
|
||||
*
|
||||
* @return ecma value
|
||||
* Returned value must be freed with ecma_free_value.
|
||||
*/
|
||||
static ecma_value_t
|
||||
ecma_builtin_string_prototype_object_search (ecma_value_t to_string_value, /**< this argument */
|
||||
ecma_value_t regexp_arg) /**< routine's argument */
|
||||
ecma_builtin_string_prototype_object_search (ecma_value_t this_value, /**< this argument */
|
||||
ecma_value_t regexp_value) /**< routine's argument */
|
||||
{
|
||||
|
||||
ecma_value_t regexp_value = ECMA_VALUE_EMPTY;
|
||||
|
||||
if (ECMA_IS_VALUE_ERROR (ecma_builtin_string_prepare_search (regexp_arg, ®exp_value)))
|
||||
#if ENABLED (JERRY_ES2015)
|
||||
if (!(ecma_is_value_undefined (regexp_value) || ecma_is_value_null (regexp_value)))
|
||||
{
|
||||
return ECMA_VALUE_ERROR;
|
||||
ecma_object_t *obj_p = ecma_get_object_from_value (ecma_op_to_object (regexp_value));
|
||||
ecma_value_t search_symbol = ecma_op_object_get_by_symbol_id (obj_p, LIT_MAGIC_STRING_SEARCH);
|
||||
ecma_deref_object (obj_p);
|
||||
|
||||
if (ECMA_IS_VALUE_ERROR (search_symbol))
|
||||
{
|
||||
return search_symbol;
|
||||
}
|
||||
|
||||
if (!ecma_is_value_undefined (search_symbol) && !ecma_is_value_null (search_symbol))
|
||||
{
|
||||
if (!ecma_op_is_callable (search_symbol))
|
||||
{
|
||||
ecma_free_value (search_symbol);
|
||||
return ecma_raise_type_error (ECMA_ERR_MSG ("@@search is not callable"));
|
||||
}
|
||||
|
||||
ecma_object_t *search_method = ecma_get_object_from_value (search_symbol);
|
||||
ecma_value_t search_result = ecma_op_function_call (search_method, regexp_value, &this_value, 1);
|
||||
|
||||
ecma_deref_object (search_method);
|
||||
return search_result;
|
||||
}
|
||||
}
|
||||
#else /* !ENABLED (JERRY_ES2015) */
|
||||
if (ecma_object_is_regexp_object (regexp_value))
|
||||
{
|
||||
return ecma_regexp_search_helper (regexp_value, this_value);
|
||||
}
|
||||
#endif /* ENABLED (JERRY_ES2015) */
|
||||
|
||||
ecma_value_t result = ECMA_VALUE_ERROR;
|
||||
|
||||
ecma_string_t *string_p = ecma_op_to_string (this_value);
|
||||
if (string_p == NULL)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
/* 5. */
|
||||
ecma_string_t *this_string_p = ecma_get_string_from_value (to_string_value);
|
||||
ecma_ref_ecma_string (this_string_p);
|
||||
|
||||
ecma_value_t match_result = ecma_regexp_exec_helper (regexp_value, to_string_value, true);
|
||||
|
||||
ecma_value_t ret_value = ECMA_VALUE_ERROR;
|
||||
|
||||
if (ECMA_IS_VALUE_ERROR (match_result))
|
||||
ecma_string_t *pattern_p = ecma_regexp_read_pattern_str_helper (regexp_value);
|
||||
if (pattern_p == NULL)
|
||||
{
|
||||
goto cleanup;
|
||||
goto cleanup_string;
|
||||
}
|
||||
|
||||
ecma_number_t offset = -1;
|
||||
|
||||
if (!ecma_is_value_null (match_result))
|
||||
ecma_value_t new_regexp = ecma_op_create_regexp_object (pattern_p, 0);
|
||||
ecma_deref_ecma_string (pattern_p);
|
||||
if (ECMA_IS_VALUE_ERROR (new_regexp))
|
||||
{
|
||||
JERRY_ASSERT (ecma_is_value_object (match_result));
|
||||
|
||||
ecma_object_t *match_object_p = ecma_get_object_from_value (match_result);
|
||||
|
||||
ecma_value_t index_value = ecma_op_object_get_by_magic_id (match_object_p, LIT_MAGIC_STRING_INDEX);
|
||||
|
||||
JERRY_ASSERT (!ECMA_IS_VALUE_ERROR (index_value) && ecma_is_value_number (index_value));
|
||||
|
||||
offset = ecma_get_number_from_value (index_value);
|
||||
|
||||
ecma_free_number (index_value);
|
||||
ecma_free_value (match_result);
|
||||
goto cleanup_string;
|
||||
}
|
||||
|
||||
ret_value = ecma_make_number_value (offset);
|
||||
#if !ENABLED (JERRY_ES2015)
|
||||
result = ecma_regexp_search_helper (new_regexp, ecma_make_string_value (string_p));
|
||||
ecma_deref_object (ecma_get_object_from_value (new_regexp));
|
||||
#else /* ENABLED (JERRY_ES2015) */
|
||||
ecma_object_t *regexp_obj_p = ecma_get_object_from_value (new_regexp);
|
||||
ecma_value_t search_symbol = ecma_op_object_get_by_symbol_id (regexp_obj_p, LIT_MAGIC_STRING_SEARCH);
|
||||
if (ECMA_IS_VALUE_ERROR (search_symbol))
|
||||
{
|
||||
goto cleanup_regexp;
|
||||
}
|
||||
|
||||
if (!ecma_op_is_callable (search_symbol))
|
||||
{
|
||||
result = ecma_raise_type_error (ECMA_ERR_MSG ("@@search is not callable"));
|
||||
goto cleanup_regexp;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
ecma_free_value (regexp_value);
|
||||
ecma_deref_ecma_string (this_string_p);
|
||||
ecma_object_t *search_method_p = ecma_get_object_from_value (search_symbol);
|
||||
ecma_value_t arguments[] = { ecma_make_string_value (string_p) };
|
||||
result = ecma_op_function_call (search_method_p, new_regexp, arguments, 1);
|
||||
ecma_deref_object (search_method_p);
|
||||
|
||||
/* 6. */
|
||||
return ret_value;
|
||||
cleanup_regexp:
|
||||
ecma_deref_object (regexp_obj_p);
|
||||
#endif /* !ENABLED (JERRY_ES2015) */
|
||||
|
||||
cleanup_string:
|
||||
ecma_deref_ecma_string (string_p);
|
||||
return result;
|
||||
} /* ecma_builtin_string_prototype_object_search */
|
||||
|
||||
#endif /* ENABLED (JERRY_BUILTIN_REGEXP) */
|
||||
|
||||
@ -1524,6 +1524,88 @@ ecma_regexp_read_pattern_str_helper (ecma_value_t pattern_arg) /**< the RegExp p
|
||||
return ecma_get_magic_string (LIT_MAGIC_STRING_EMPTY_NON_CAPTURE_GROUP);
|
||||
} /* ecma_regexp_read_pattern_str_helper */
|
||||
|
||||
/**
|
||||
* Helper function for RegExp based string searches
|
||||
*
|
||||
* See also:
|
||||
* ECMA-262 v6, 21.2.5.9
|
||||
*
|
||||
* @return index of the match
|
||||
*/
|
||||
ecma_value_t
|
||||
ecma_regexp_search_helper (ecma_value_t regexp_arg, /**< regexp argument */
|
||||
ecma_value_t string_arg) /**< string argument */
|
||||
{
|
||||
/* 2. */
|
||||
if (!ecma_is_value_object (regexp_arg))
|
||||
{
|
||||
return ecma_raise_type_error (ECMA_ERR_MSG ("'this' is not an object."));
|
||||
}
|
||||
|
||||
ecma_value_t result = ECMA_VALUE_ERROR;
|
||||
|
||||
/* 3-4. */
|
||||
ecma_string_t *const string_p = ecma_op_to_string (string_arg);
|
||||
if (string_p == NULL)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
ecma_object_t *const regexp_object_p = ecma_get_object_from_value (regexp_arg);
|
||||
|
||||
/* 5-6. */
|
||||
ecma_string_t *const last_index_str_p = ecma_get_magic_string (LIT_MAGIC_STRING_LASTINDEX_UL);
|
||||
const ecma_value_t prev_last_index = ecma_op_object_get (regexp_object_p, last_index_str_p);
|
||||
if (ECMA_IS_VALUE_ERROR (prev_last_index))
|
||||
{
|
||||
goto cleanup_string;
|
||||
}
|
||||
|
||||
/* 7-8. */
|
||||
const ecma_value_t status = ecma_op_object_put (regexp_object_p, last_index_str_p, ecma_make_uint32_value (0), true);
|
||||
if (ECMA_IS_VALUE_ERROR (status))
|
||||
{
|
||||
ecma_free_value (prev_last_index);
|
||||
goto cleanup_string;
|
||||
}
|
||||
|
||||
JERRY_ASSERT (ecma_is_value_boolean (status));
|
||||
|
||||
/* 9-10. */
|
||||
const ecma_value_t match = ecma_op_regexp_exec (regexp_arg, string_p);
|
||||
if (ECMA_IS_VALUE_ERROR (match))
|
||||
{
|
||||
ecma_free_value (prev_last_index);
|
||||
goto cleanup_string;
|
||||
}
|
||||
|
||||
/* 11-12. */
|
||||
result = ecma_op_object_put (regexp_object_p, last_index_str_p, prev_last_index, true);
|
||||
ecma_free_value (prev_last_index);
|
||||
|
||||
if (ECMA_IS_VALUE_ERROR (result))
|
||||
{
|
||||
ecma_free_value (match);
|
||||
goto cleanup_string;
|
||||
}
|
||||
|
||||
/* 13-14. */
|
||||
if (ecma_is_value_null (match))
|
||||
{
|
||||
result = ecma_make_int32_value (-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
ecma_object_t *const match_p = ecma_get_object_from_value (match);
|
||||
result = ecma_op_object_get_by_magic_id (match_p, LIT_MAGIC_STRING_INDEX);
|
||||
ecma_deref_object (match_p);
|
||||
}
|
||||
|
||||
cleanup_string:
|
||||
ecma_deref_ecma_string (string_p);
|
||||
return result;
|
||||
} /* ecma_regexp_search_helper */
|
||||
|
||||
/**
|
||||
* Fast path for RegExp based replace operation
|
||||
*
|
||||
|
||||
@ -106,6 +106,7 @@ lit_code_point_t ecma_regexp_canonicalize_char (lit_code_point_t ch);
|
||||
ecma_value_t ecma_regexp_parse_flags (ecma_string_t *flags_str_p, uint16_t *flags_p);
|
||||
void ecma_regexp_initialize_props (ecma_object_t *re_obj_p, ecma_string_t *source_p, uint16_t flags);
|
||||
|
||||
ecma_value_t ecma_regexp_search_helper (ecma_value_t regexp_arg, ecma_value_t string_arg);
|
||||
ecma_value_t
|
||||
ecma_regexp_replace_helper (ecma_value_t this_arg,
|
||||
ecma_value_t string_arg,
|
||||
|
||||
228
tests/jerry/es2015/symbol-search.js
Normal file
228
tests/jerry/es2015/symbol-search.js
Normal file
@ -0,0 +1,228 @@
|
||||
// Copyright JS Foundation and other contributors, http://js.foundation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
var search = RegExp.prototype[Symbol.search];
|
||||
|
||||
try {
|
||||
search.call (0, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
try {
|
||||
search.call (new RegExp(), {
|
||||
toString: () => {
|
||||
throw "abrupt string"
|
||||
}
|
||||
});
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt string");
|
||||
}
|
||||
|
||||
try {
|
||||
search.call ({
|
||||
get lastIndex() {
|
||||
throw "abrupt get lastIndex"
|
||||
}
|
||||
}, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt get lastIndex");
|
||||
}
|
||||
|
||||
try {
|
||||
search.call ({
|
||||
get lastIndex() {
|
||||
return 3;
|
||||
},
|
||||
set lastIndex(idx) {
|
||||
throw "abrupt set lastIndex"
|
||||
}
|
||||
}, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt set lastIndex");
|
||||
}
|
||||
|
||||
try {
|
||||
search.call ({
|
||||
get exec() {
|
||||
throw "abrupt exec"
|
||||
}
|
||||
}, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt exec");
|
||||
}
|
||||
|
||||
try {
|
||||
search.call ({
|
||||
exec: RegExp.prototype.exec
|
||||
}, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
try {
|
||||
search.call ({
|
||||
exec: 42
|
||||
}, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
try {
|
||||
search.call ({
|
||||
exec: () => {
|
||||
throw "abrupt exec result"
|
||||
}
|
||||
}, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt exec result");
|
||||
}
|
||||
|
||||
try {
|
||||
search.call ({
|
||||
exec: () => {
|
||||
return 1
|
||||
}
|
||||
}, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
try {
|
||||
search.call ({
|
||||
exec: () => {
|
||||
return {
|
||||
get index() {
|
||||
throw "abrupt index"
|
||||
}
|
||||
}
|
||||
}
|
||||
}, "string");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt index");
|
||||
}
|
||||
|
||||
assert (search.call (/abc/, "abc") === 0);
|
||||
assert (search.call (/abc/, "strabc") === 3);
|
||||
assert (search.call (/abc/, "bcd") === -1);
|
||||
|
||||
class Regexplike {
|
||||
constructor() {
|
||||
this.index = 0;
|
||||
this.global = true;
|
||||
}
|
||||
|
||||
exec() {
|
||||
if (this.index > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.index = 42;
|
||||
var result = {
|
||||
length: 1,
|
||||
0: "Duck",
|
||||
index: this.index
|
||||
};
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
re = new Regexplike();
|
||||
assert (search.call (re, "str") === 42);
|
||||
|
||||
/* Object with custom @@search method */
|
||||
var o = {}
|
||||
o[Symbol.search] = function () {
|
||||
return 4;
|
||||
};
|
||||
assert ("string".search (o) === 4);
|
||||
|
||||
o[Symbol.search] = 42;
|
||||
try {
|
||||
"string".search (o);
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
Object.defineProperty (o, Symbol.search, {
|
||||
get: () => {
|
||||
throw "abrupt @@search get"
|
||||
},
|
||||
set: (v) => {}
|
||||
});
|
||||
|
||||
try {
|
||||
"string".search (o);
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt @@search get");
|
||||
}
|
||||
|
||||
o = {};
|
||||
o[Symbol.search] = function () {
|
||||
throw "abrupt @@search"
|
||||
};
|
||||
try {
|
||||
"string".search (o);
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e === "abrupt @@search")
|
||||
}
|
||||
|
||||
o = {
|
||||
exec: function () { return {index: "Duck"}; },
|
||||
};
|
||||
assert ("string".search (o) === 1);
|
||||
|
||||
o[Symbol.search] = RegExp.prototype[Symbol.search];
|
||||
assert ("string".search (o) === "Duck");
|
||||
|
||||
o = {
|
||||
lastIndex: "Duck",
|
||||
exec: () => {
|
||||
return "Duck";
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
RegExp.prototype[Symbol.search].call (o, "Duck");
|
||||
assert (false);
|
||||
} catch (e) {
|
||||
assert (e instanceof TypeError);
|
||||
}
|
||||
|
||||
o = {
|
||||
exec: () => {
|
||||
return { 0: "Duck", index: 0 };
|
||||
},
|
||||
get lastIndex () {
|
||||
return "Duck";
|
||||
},
|
||||
set lastIndex (v) {
|
||||
return "Duck";
|
||||
}
|
||||
}
|
||||
|
||||
assert (RegExp.prototype[Symbol.search].call (o, "str") === 0);
|
||||
@ -15,4 +15,6 @@
|
||||
// Changing exec should not affect replace.
|
||||
Object.getPrototypeOf(/x/).exec = function () { return 1234; }
|
||||
assert (/y/.exec("y") === 1234);
|
||||
|
||||
assert ("y".replace (/y/, "x") === "x");
|
||||
assert ("ay".search (/y/) === 1);
|
||||
@ -36,24 +36,5 @@ assert ("aaxbb".search (regexp) === 2);
|
||||
assert ("aabb".search (regexp) === -1);
|
||||
assert (regexp.lastIndex === "index");
|
||||
|
||||
Object.defineProperty(regexp, "lastIndex", {
|
||||
configurable : false,
|
||||
enumerable : false,
|
||||
value : "index2",
|
||||
writable : false
|
||||
});
|
||||
|
||||
assert ("axb".search (regexp) === 1);
|
||||
assert ("aabb".search (regexp) === -1);
|
||||
assert (regexp.lastIndex === "index2");
|
||||
|
||||
assert ("##\ud801\udc00".search ("\ud801") === 2);
|
||||
assert ("##\ud801\udc00".search ("\udc00") === 3);
|
||||
|
||||
// The real "exec" never returns with a number.
|
||||
Object.getPrototypeOf(/x/).exec = function () { return "???"; }
|
||||
|
||||
assert (/y/.exec("y") === "???");
|
||||
|
||||
// Changing exec should not affect search.
|
||||
assert ("ay".search (/y/) === 1);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user