diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.cpp b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.cpp index a38c28684f..930d253877 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.cpp +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.cpp @@ -20,6 +20,7 @@ #include "ecma-builtins.h" #include "ecma-conversion.h" #include "ecma-exceptions.h" +#include "ecma-function-object.h" #include "ecma-gc.h" #include "ecma-globals.h" #include "ecma-helpers.h" @@ -743,11 +744,675 @@ ecma_builtin_string_prototype_object_match (ecma_value_t this_arg, /**< this arg return ret_value; } /* ecma_builtin_string_prototype_object_match */ -#endif /* !CONFIG_ECMA_COMPACT_PROFILE_DISABLE_REGEXP_BUILTIN */ + +/** + * This structure is the context which represents + * the state of the ongoing string replace. + */ +typedef struct +{ + /* General part. */ + bool is_regexp; /**< whether we search a regexp or string */ + bool is_global; /**< global search or not */ + bool is_replace_callable; /**< replace part is callable or not */ + ecma_value_t input_string; /**< input string */ + ecma_length_t input_length; /**< input string length */ + ecma_value_t regexp_or_search_string; /**< regular expression or search string + * depending on the value of is_regexp */ + ecma_length_t match_start; /**< starting position of the match */ + ecma_length_t match_end; /**< end position of the match */ + + /* Replace value callable part. */ + ecma_object_t *replace_function_p; + + /* Replace value string part. */ + ecma_string_t *replace_string_p; /**< replace string */ + lit_utf8_iterator_t replace_iterator; /**< replace string iterator */ +} ecma_builtin_replace_search_ctx_t; + +/** + * Generic helper function to append a substring at the end of a base string + * + * The base string can be kept or freed + * + * @return the constructed string + */ +static ecma_string_t * +ecma_builtin_string_prototype_object_replace_append_substr (ecma_string_t *base_string_p, /**< base string */ + ecma_string_t *appended_string_p, /**< appended string */ + ecma_length_t start, /**< start position */ + ecma_length_t end, /**< end position */ + bool free_base_string) /**< free base string or not */ +{ + ecma_string_t *ret_string_p; + + JERRY_ASSERT (start <= end); + JERRY_ASSERT (end <= ecma_string_get_length (appended_string_p)); + + if (start < end) + { + ecma_string_t *substring_p = ecma_string_substr (appended_string_p, start, end); + ret_string_p = ecma_concat_ecma_strings (base_string_p, substring_p); + + ecma_deref_ecma_string (substring_p); + if (free_base_string) + { + ecma_deref_ecma_string (base_string_p); + } + } + else if (free_base_string) + { + ret_string_p = base_string_p; + } + else + { + ret_string_p = ecma_copy_or_ref_ecma_string (base_string_p); + } + + return ret_string_p; +} /* ecma_builtin_string_prototype_object_replace_append_substr */ + +/** + * Generic helper function to perform the find the next match + * + * @return completion value + * Returned value must be freed with ecma_free_completion_value. + */ +static ecma_completion_value_t +ecma_builtin_string_prototype_object_replace_match (ecma_builtin_replace_search_ctx_t *context_p) /**< search + * context */ +{ + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + + context_p->match_start = 0; + context_p->match_end = 0; + + if (context_p->is_regexp) + { + ecma_value_t exec_arguments[1] = { context_p->input_string }; + + ECMA_TRY_CATCH (match_value, + ecma_builtin_regexp_prototype_dispatch_routine (LIT_MAGIC_STRING_EXEC, + context_p->regexp_or_search_string, + exec_arguments, + 1), + ret_value); + + if (!ecma_is_value_null (match_value)) + { + JERRY_ASSERT (ecma_is_value_object (match_value)); + + ecma_object_t *match_object_p = ecma_get_object_from_value (match_value); + ecma_string_t *index_string_p = ecma_get_magic_string (LIT_MAGIC_STRING_INDEX); + ecma_string_t *zero_string_p = ecma_new_ecma_string_from_uint32 (0); + + ECMA_TRY_CATCH (index_value, + ecma_op_object_get (match_object_p, index_string_p), + ret_value); + + ECMA_TRY_CATCH (result_string_value, + ecma_op_object_get (match_object_p, zero_string_p), + ret_value); + + /* We directly call the built-in exec, so + * we can trust in the returned value. */ + + JERRY_ASSERT (ecma_is_value_number (index_value)); + JERRY_ASSERT (ecma_is_value_string (result_string_value)); + + /* We use the length of the result string to determine the + * match end. This works regardless the global flag is set. */ + ecma_string_t *result_string_p = ecma_get_string_from_value (result_string_value); + ecma_number_t *index_number_p = ecma_get_number_from_value (index_value); + + context_p->match_start = (ecma_length_t) (*index_number_p); + context_p->match_end = context_p->match_start + (ecma_length_t) ecma_string_get_length (result_string_p); + + JERRY_ASSERT ((ecma_length_t) ecma_number_to_uint32 (*index_number_p) == context_p->match_start); + + ret_value = ecma_make_normal_completion_value (ecma_copy_value (match_value, true)); + + ECMA_FINALIZE (result_string_value); + ECMA_FINALIZE (index_value); + ecma_deref_ecma_string (index_string_p); + ecma_deref_ecma_string (zero_string_p); + } + else + { + ret_value = ecma_make_simple_completion_value (ECMA_SIMPLE_VALUE_NULL); + } + + ECMA_FINALIZE (match_value); + } + else + { + JERRY_ASSERT (!context_p->is_global); + + ecma_string_t *search_string_p = ecma_get_string_from_value (context_p->regexp_or_search_string); + lit_utf8_size_t search_size = ecma_string_get_size (search_string_p); + + MEM_DEFINE_LOCAL_ARRAY (search_start_p, + search_size, + lit_utf8_byte_t); + + ecma_string_to_utf8_string (search_string_p, + search_start_p, + (ssize_t) (search_size)); + + ecma_string_t *input_string_p = ecma_get_string_from_value (context_p->input_string); + lit_utf8_size_t input_size = ecma_string_get_size (input_string_p); + + MEM_DEFINE_LOCAL_ARRAY (input_start_p, + input_size, + lit_utf8_byte_t); + + ecma_string_to_utf8_string (input_string_p, + input_start_p, + (ssize_t) (input_size)); + + lit_utf8_iterator_t search_iterator = lit_utf8_iterator_create (search_start_p, search_size); + lit_utf8_iterator_t input_iterator = lit_utf8_iterator_create (input_start_p, input_size); + + ecma_length_t match_start = 0; + ecma_length_t match_end = 0; + bool match_found = false; + + if (lit_utf8_iterator_is_eos (&search_iterator)) + { + /* Empty string, always matches. */ + match_found = true; + } + else + { + ecma_char_t first_char = lit_utf8_iterator_read_next (&search_iterator); + + while (!lit_utf8_iterator_is_eos (&input_iterator)) + { + if (lit_utf8_iterator_read_next (&input_iterator) == first_char) + { + /* Local copy to preserve the original value of the iterators. */ + lit_utf8_iterator_t nested_search_iterator = search_iterator; + lit_utf8_iterator_t nested_input_iterator = input_iterator; + match_end = match_start + 1; + + match_found = true; + while (!lit_utf8_iterator_is_eos (&nested_search_iterator)) + { + if (lit_utf8_iterator_is_eos (&nested_input_iterator)) + { + match_found = false; + break; + } + + ecma_char_t search_character = lit_utf8_iterator_read_next (&nested_search_iterator); + ecma_char_t input_character = lit_utf8_iterator_read_next (&nested_input_iterator); + + if (search_character != input_character) + { + match_found = false; + break; + } + match_end++; + } + + if (match_found) + { + break; + } + } + match_start++; + } + } + + if (match_found) + { + ecma_value_t arguments_list_p[1] = { context_p->regexp_or_search_string }; + ECMA_TRY_CATCH (new_array_value, + ecma_op_create_array_object (arguments_list_p, 1, false), + ret_value); + + context_p->match_start = match_start; + context_p->match_end = match_end; + + ret_value = ecma_make_normal_completion_value (ecma_copy_value (new_array_value, true)); + + ECMA_FINALIZE (new_array_value); + } + else + { + ret_value = ecma_make_simple_completion_value (ECMA_SIMPLE_VALUE_NULL); + } + + MEM_FINALIZE_LOCAL_ARRAY (input_start_p); + MEM_FINALIZE_LOCAL_ARRAY (search_start_p); + } + return ret_value; +} /* ecma_builtin_string_prototype_object_replace_match */ + +/** + * Generic helper function to construct the string which replaces the matched part + * + * @return completion value + * Returned value must be freed with ecma_free_completion_value. + */ +static ecma_completion_value_t +ecma_builtin_string_prototype_object_replace_get_string (ecma_builtin_replace_search_ctx_t *context_p, /**< search + * context */ + ecma_value_t match_value) /**< returned match value */ +{ + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + + if (context_p->is_replace_callable) + { + ecma_object_t *match_object_p = ecma_get_object_from_value (match_value); + ecma_string_t *length_string_p = ecma_get_magic_string (LIT_MAGIC_STRING_LENGTH); + + ECMA_TRY_CATCH (length_value, + ecma_op_object_get (match_object_p, length_string_p), + ret_value); + + JERRY_ASSERT (ecma_is_value_number (length_value)); + + ecma_number_t *length_number_p = ecma_get_number_from_value (length_value); + ecma_length_t length = (ecma_length_t) (*length_number_p); + + JERRY_ASSERT ((ecma_length_t) ecma_number_to_uint32 (*length_number_p) == length); + JERRY_ASSERT (length >= 1); + + MEM_DEFINE_LOCAL_ARRAY (arguments_list, + length + 2, + ecma_value_t); + + /* An error might occure during the array copy and + * uninitalized elements must not be freed. */ + ecma_length_t values_copied = 0; + + for (ecma_length_t i = 0; + (i < length) && ecma_is_completion_value_empty (ret_value); + i++) + { + ecma_string_t *index_p = ecma_new_ecma_string_from_uint32 (i); + ECMA_TRY_CATCH (current_value, + ecma_op_object_get (match_object_p, index_p), + ret_value); + + arguments_list[i] = ecma_copy_value (current_value, true); + values_copied++; + + ECMA_FINALIZE (current_value); + ecma_deref_ecma_string (index_p); + } + + if (ecma_is_completion_value_empty (ret_value)) + { + ecma_number_t *index_number_p = ecma_alloc_number (); + + *index_number_p = context_p->match_start; + arguments_list[length] = ecma_make_number_value (index_number_p); + arguments_list[length + 1] = ecma_copy_value (context_p->input_string, true); + + ECMA_TRY_CATCH (result_value, + ecma_op_function_call (context_p->replace_function_p, + context_p->regexp_or_search_string, + arguments_list, + length + 2), + ret_value); + + ECMA_TRY_CATCH (to_string_value, + ecma_op_to_string (result_value), + ret_value); + + ret_value = ecma_make_normal_completion_value (ecma_copy_value (to_string_value, true)); + + ECMA_FINALIZE (to_string_value); + ECMA_FINALIZE (result_value); + + ecma_free_value (arguments_list[length + 1], true); + ecma_dealloc_number (index_number_p); + } + + for (ecma_length_t i = 0; i < values_copied; i++) + { + ecma_free_value (arguments_list[i], true); + } + + MEM_FINALIZE_LOCAL_ARRAY (arguments_list); + ECMA_FINALIZE (length_value); + ecma_deref_ecma_string (length_string_p); + } + else + { + ecma_string_t *result_string_p = ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY); + + ecma_length_t previous_start = 0; + ecma_length_t current_position = 0; + + lit_utf8_iterator_t replace_iterator = context_p->replace_iterator; + + JERRY_ASSERT (lit_utf8_iterator_is_bos (&replace_iterator)); + + while (!lit_utf8_iterator_is_eos (&replace_iterator)) + { + ecma_char_t action = LIT_CHAR_NULL; + + if (lit_utf8_iterator_read_next (&replace_iterator) == LIT_CHAR_DOLLAR_SIGN) + { + if (!lit_utf8_iterator_is_eos (&replace_iterator)) + { + action = lit_utf8_iterator_peek_next (&replace_iterator); + + if (action == LIT_CHAR_DOLLAR_SIGN) + { + current_position++; + } + else if (action == LIT_CHAR_0) + { + lit_utf8_iterator_incr (&replace_iterator); + ecma_char_t next_character = lit_utf8_iterator_peek_next (&replace_iterator); + if (!(next_character >= LIT_CHAR_1 && next_character <= LIT_CHAR_9)) + { + action = LIT_CHAR_NULL; + } + lit_utf8_iterator_decr (&replace_iterator); + } + else if (action != LIT_CHAR_AMPERSAND + && action != LIT_CHAR_GRAVE_ACCENT + && action != LIT_CHAR_SINGLE_QUOTE + && !(action >= LIT_CHAR_1 && action <= LIT_CHAR_9)) + { + action = LIT_CHAR_NULL; + } + } + } + + if (action != LIT_CHAR_NULL) + { + result_string_p = ecma_builtin_string_prototype_object_replace_append_substr (result_string_p, + context_p->replace_string_p, + previous_start, + current_position, + true); + lit_utf8_iterator_incr (&replace_iterator); + + if (action == LIT_CHAR_DOLLAR_SIGN) + { + current_position--; + } + else if (action == LIT_CHAR_GRAVE_ACCENT) + { + ecma_string_t *input_string_p = ecma_get_string_from_value (context_p->input_string); + result_string_p = ecma_builtin_string_prototype_object_replace_append_substr (result_string_p, + input_string_p, + 0, + context_p->match_start, + true); + } + else if (action == LIT_CHAR_SINGLE_QUOTE) + { + ecma_string_t *input_string_p = ecma_get_string_from_value (context_p->input_string); + result_string_p = ecma_builtin_string_prototype_object_replace_append_substr (result_string_p, + input_string_p, + context_p->match_end, + context_p->input_length, + true); + } + else + { + /* Everything else is submatch reading. */ + uint32_t index = 0; + + JERRY_ASSERT (action == LIT_CHAR_AMPERSAND || (action >= LIT_CHAR_0 && action <= LIT_CHAR_9)); + + if (action >= LIT_CHAR_0 && action <= LIT_CHAR_9) + { + index = (uint32_t) (action - LIT_CHAR_0); + action = lit_utf8_iterator_peek_next (&replace_iterator); + if (action >= LIT_CHAR_0 && action <= LIT_CHAR_9) + { + index = index * 10 + (uint32_t) (action - LIT_CHAR_0); + lit_utf8_iterator_incr (&replace_iterator); + current_position++; + } + JERRY_ASSERT (index != 0); + } + + ecma_string_t *index_string_p = ecma_new_ecma_string_from_uint32 (index); + ecma_object_t *match_object_p = ecma_get_object_from_value (match_value); + + ECMA_TRY_CATCH (submatch_value, + ecma_op_object_get (match_object_p, index_string_p), + ret_value); + + /* Undefined values are converted to empty string. */ + if (!ecma_is_value_undefined (submatch_value)) + { + JERRY_ASSERT (ecma_is_value_string (submatch_value)); + ecma_string_t *submatch_string_p = ecma_get_string_from_value (submatch_value); + + ecma_string_t *appended_string_p = ecma_concat_ecma_strings (result_string_p, submatch_string_p); + ecma_deref_ecma_string (result_string_p); + result_string_p = appended_string_p; + } + + ECMA_FINALIZE (submatch_value); + ecma_deref_ecma_string (index_string_p); + + if (!ecma_is_completion_value_empty (ret_value)) + { + break; + } + } + + current_position++; + previous_start = current_position + 1; + } + + current_position++; + } + + if (ecma_is_completion_value_empty (ret_value)) + { + result_string_p = ecma_builtin_string_prototype_object_replace_append_substr (result_string_p, + context_p->replace_string_p, + previous_start, + current_position, + true); + + ret_value = ecma_make_normal_completion_value (ecma_make_string_value (result_string_p)); + } + } + + return ret_value; +} /* ecma_builtin_string_prototype_object_replace_get_string */ + +/** + * Generic helper function to do the string replace + * + * @return completion value + * Returned value must be freed with ecma_free_completion_value. + */ +static ecma_completion_value_t +ecma_builtin_string_prototype_object_replace_loop (ecma_builtin_replace_search_ctx_t *context_p) /**< search + * context */ +{ + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + ecma_length_t previous_start = 0; + bool continue_match = true; + + ecma_string_t *result_string_p = ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY); + ecma_string_t *input_string_p = ecma_get_string_from_value (context_p->input_string); + + while (continue_match) + { + continue_match = false; + ECMA_TRY_CATCH (match_value, + ecma_builtin_string_prototype_object_replace_match (context_p), + ret_value); + + if (!ecma_is_value_null (match_value)) + { + result_string_p = ecma_builtin_string_prototype_object_replace_append_substr (result_string_p, + input_string_p, + previous_start, + context_p->match_start, + true); + + ECMA_TRY_CATCH (string_value, + ecma_builtin_string_prototype_object_replace_get_string (context_p, match_value), + ret_value); + + JERRY_ASSERT (ecma_is_value_string (string_value)); + + ecma_string_t *appended_string_p = ecma_concat_ecma_strings (result_string_p, + ecma_get_string_from_value (string_value)); + + ecma_deref_ecma_string (result_string_p); + result_string_p = appended_string_p; + + ECMA_FINALIZE (string_value); + + previous_start = context_p->match_end; + + if (context_p->is_global + && ecma_is_completion_value_empty (ret_value) + && context_p->match_start == context_p->match_end) + { + JERRY_ASSERT (context_p->is_regexp); + + if (context_p->match_end == context_p->input_length) + { + /* Aborts the match. */ + context_p->is_global = false; + } + else + { + ecma_string_t *last_index_string_p = ecma_get_magic_string (LIT_MAGIC_STRING_LASTINDEX_UL); + ecma_object_t *regexp_obj_p = ecma_get_object_from_value (context_p->regexp_or_search_string); + ecma_number_t *zero_number_p = ecma_alloc_number (); + + *zero_number_p = context_p->match_end + 1; + + ECMA_TRY_CATCH (put_value, + ecma_op_object_put (regexp_obj_p, + last_index_string_p, + ecma_make_number_value (zero_number_p), + true), + ret_value); + + ECMA_FINALIZE (put_value); + ecma_dealloc_number (zero_number_p); + ecma_deref_ecma_string (last_index_string_p); + } + } + } + + if (ecma_is_completion_value_empty (ret_value)) + { + if (!context_p->is_global || ecma_is_value_null (match_value)) + { + /* No more matches */ + ecma_string_t *appended_string_p; + appended_string_p = ecma_builtin_string_prototype_object_replace_append_substr (result_string_p, + input_string_p, + previous_start, + context_p->input_length, + false); + + ret_value = ecma_make_normal_completion_value (ecma_make_string_value (appended_string_p)); + } + else + { + continue_match = true; + } + } + + ECMA_FINALIZE (match_value); + } + + ecma_deref_ecma_string (result_string_p); + return ret_value; +} /* ecma_builtin_string_prototype_object_replace_loop */ + +/** + * Generic helper function to check whether the search value is callable. + * If it is not, the function converts the search value to string. The + * appropriate fields of the context were filled as well and the search + * loop is run afterwards. + * + * @return completion value + * Returned value must be freed with ecma_free_completion_value. + */ +static ecma_completion_value_t +ecma_builtin_string_prototype_object_replace_main (ecma_builtin_replace_search_ctx_t *context_p, /**< search + * context */ + ecma_value_t replace_value) /**< replacement for a match */ +{ + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + + if (ecma_op_is_callable (replace_value)) + { + context_p->is_replace_callable = true; + context_p->replace_function_p = ecma_get_object_from_value (replace_value); + + ret_value = ecma_builtin_string_prototype_object_replace_loop (context_p); + } + else + { + context_p->is_replace_callable = false; + + ECMA_TRY_CATCH (to_string_replace_val, + ecma_op_to_string (replace_value), + ret_value); + + ecma_string_t *replace_string_p = ecma_get_string_from_value (to_string_replace_val); + lit_utf8_size_t replace_size = ecma_string_get_size (replace_string_p); + + MEM_DEFINE_LOCAL_ARRAY (replace_start_p, + replace_size, + lit_utf8_byte_t); + + ecma_string_to_utf8_string (replace_string_p, + replace_start_p, + (ssize_t) (replace_size)); + + context_p->replace_string_p = replace_string_p; + context_p->replace_iterator = lit_utf8_iterator_create (replace_start_p, replace_size); + + ret_value = ecma_builtin_string_prototype_object_replace_loop (context_p); + + MEM_FINALIZE_LOCAL_ARRAY (replace_start_p); + ECMA_FINALIZE (to_string_replace_val); + } + + return ret_value; +} /* ecma_builtin_string_prototype_object_replace_main */ /** * The String.prototype object's 'replace' routine * + * The replace algorithm is splitted into several helper functions. + * This allows using ECMA_TRY_CATCH macros and avoiding early returns. + * + * To share data between these helper functions, we created a + * structure called ecma_builtin_replace_search_ctx_t, which + * represents the current state of the replace. + * + * The helper functions are called in the following order: + * + * 1) ecma_builtin_string_prototype_object_replace is called + * it initialise the context depending on search_value (regexp or string) + * 2) ecma_builtin_string_prototype_object_replace_main is called + * it initialise the context depending on replace_value (callable or string) + * 3) ecma_builtin_string_prototype_object_replace_loop is called + * this function has a loop which repeatedly calls + * - ecma_builtin_string_prototype_object_replace_match + * which performs a match + * - ecma_builtin_string_prototype_object_replace_get_string + * which computes the replacement string + * + * The final string is created from several string fragments appended + * together by ecma_builtin_string_prototype_object_replace_append_substr. + * * See also: * ECMA-262 v5, 15.5.4.11 * @@ -756,11 +1421,90 @@ ecma_builtin_string_prototype_object_match (ecma_value_t this_arg, /**< this arg */ static ecma_completion_value_t ecma_builtin_string_prototype_object_replace (ecma_value_t this_arg, /**< this argument */ - ecma_value_t arg1, /**< routine's first argument */ - ecma_value_t arg2) /**< routine's second argument */ + ecma_value_t search_value, /**< routine's first argument */ + ecma_value_t replace_value) /**< routine's second argument */ { - ECMA_BUILTIN_CP_UNIMPLEMENTED (this_arg, arg1, arg2); + ecma_completion_value_t ret_value = ecma_make_empty_completion_value (); + + /* 1. */ + ECMA_TRY_CATCH (check_coercible_val, + ecma_op_check_object_coercible (this_arg), + ret_value); + + /* 2. */ + ECMA_TRY_CATCH (to_string_value, + ecma_op_to_string (this_arg), + ret_value); + + ecma_builtin_replace_search_ctx_t context; + + if (ecma_is_value_object (search_value) + && ecma_object_get_class_name (ecma_get_object_from_value (search_value)) == LIT_MAGIC_STRING_REGEXP_UL) + { + ecma_object_t *regexp_obj_p = ecma_get_object_from_value (search_value); + ecma_string_t *global_string_p = ecma_get_magic_string (LIT_MAGIC_STRING_GLOBAL); + + ECMA_TRY_CATCH (global_value, + ecma_op_object_get (regexp_obj_p, global_string_p), + ret_value); + + JERRY_ASSERT (ecma_is_value_boolean (global_value)); + + context.is_regexp = true; + context.is_global = ecma_is_value_true (global_value); + context.input_string = to_string_value; + context.input_length = ecma_string_get_length (ecma_get_string_from_value (to_string_value)); + context.regexp_or_search_string = search_value; + + if (context.is_global) + { + ecma_string_t *last_index_string_p = ecma_get_magic_string (LIT_MAGIC_STRING_LASTINDEX_UL); + ecma_number_t *zero_number_p = ecma_alloc_number (); + *zero_number_p = 0; + + ECMA_TRY_CATCH (put_value, + ecma_op_object_put (regexp_obj_p, + last_index_string_p, + ecma_make_number_value (zero_number_p), + true), + ret_value); + + ECMA_FINALIZE (put_value); + ecma_dealloc_number (zero_number_p); + ecma_deref_ecma_string (last_index_string_p); + } + + if (ecma_is_completion_value_empty (ret_value)) + { + ret_value = ecma_builtin_string_prototype_object_replace_main (&context, replace_value); + } + + ECMA_FINALIZE (global_value); + ecma_deref_ecma_string (global_string_p); + } + else + { + ECMA_TRY_CATCH (to_string_search_val, + ecma_op_to_string (search_value), + ret_value); + + context.is_regexp = false; + context.is_global = false; + context.input_string = to_string_value; + context.input_length = ecma_string_get_length (ecma_get_string_from_value (to_string_value)); + context.regexp_or_search_string = to_string_search_val; + + ret_value = ecma_builtin_string_prototype_object_replace_main (&context, replace_value); + + ECMA_FINALIZE (to_string_search_val); + } + + ECMA_FINALIZE (to_string_value); + ECMA_FINALIZE (check_coercible_val); + + return ret_value; } /* ecma_builtin_string_prototype_object_replace */ +#endif /* !CONFIG_ECMA_COMPACT_PROFILE_DISABLE_REGEXP_BUILTIN */ /** * The String.prototype object's 'search' routine diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.inc.h index 57114eeea8..26ac82de0f 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-string-prototype.inc.h @@ -70,9 +70,9 @@ ROUTINE (LIT_MAGIC_STRING_LOCALE_COMPARE_UL, ecma_builtin_string_prototype_objec #ifndef CONFIG_ECMA_COMPACT_PROFILE_DISABLE_REGEXP_BUILTIN ROUTINE (LIT_MAGIC_STRING_MATCH, ecma_builtin_string_prototype_object_match, 1, 1) +ROUTINE (LIT_MAGIC_STRING_REPLACE, ecma_builtin_string_prototype_object_replace, 2, 2) #endif /* CONFIG_ECMA_COMPACT_PROFILE_DISABLE_REGEXP_BUILTIN */ -ROUTINE (LIT_MAGIC_STRING_REPLACE, ecma_builtin_string_prototype_object_replace, 2, 2) ROUTINE (LIT_MAGIC_STRING_SEARCH, ecma_builtin_string_prototype_object_search, 1, 1) ROUTINE (LIT_MAGIC_STRING_SPLIT, ecma_builtin_string_prototype_object_split, 2, 2) ROUTINE (LIT_MAGIC_STRING_SUBSTRING, ecma_builtin_string_prototype_object_substring, 2, 2) diff --git a/jerry-core/lit/lit-char-helpers.h b/jerry-core/lit/lit-char-helpers.h index 66af629851..3e342d8c35 100644 --- a/jerry-core/lit/lit-char-helpers.h +++ b/jerry-core/lit/lit-char-helpers.h @@ -108,6 +108,11 @@ extern bool lit_char_is_unicode_connector_punctuation (ecma_char_t); #define LIT_CHAR_QUESTION ((ecma_char_t) '?') /* question mark */ #define LIT_CHAR_COLON ((ecma_char_t) ':') /* colon */ +/* + * Special characters for String.prototype.replace. + */ +#define LIT_CHAR_GRAVE_ACCENT ((ecma_char_t) '`') /* grave accent */ + /** * Uppercase ASCII letters */ diff --git a/tests/jerry/string-prototype-replace.js b/tests/jerry/string-prototype-replace.js new file mode 100644 index 0000000000..f646df50cd --- /dev/null +++ b/tests/jerry/string-prototype-replace.js @@ -0,0 +1,91 @@ +// Copyright 2015 University of Szeged +// Copyright 2015 Samsung Electronics Co., Ltd. +// +// 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. + +assert ("abcabc".replace("bc", ":") === "a:abc"); +assert ("hello".replace("", ":") === ":hello"); + +assert ("xabcxabcx".replace (/abc/g, "[$&][$`][$']") === "x[abc][x][xabcx]x[abc][xabcx][x]x"); +assert ("abc".replace (/a(b)c|d()/, "[$1][$01][$2][$02][$99][$123][$012]") === "[b][b][][][][3][b2]"); +assert ("abc".replace("abc", "$x$$5$0$00$" === "$x$5$0$00$")); + +assert ("a true true story".replace(true) === "a undefined true story"); +assert ("1234".replace(23, 32) === "1324"); + +assert ("abcabc".replace(/bc/, ":") === "a:abc"); +assert ("axbcxx".replace(/x*/g, ":") === ":a::b:c::"); + +assert (String.prototype.replace.call (12321, /2/g, ".") === "1.3.1"); + +try +{ + String.prototype.replace.call (null, "u", "."); + assert (false); +} +catch (e) +{ + assert (e instanceof TypeError); +} + +assert ("98765".replace(76, function () { return {}; }) === "98[object Object]5"); + +function concat_arguments() +{ + var str = ""; + for (var i = 0; i < arguments.length; i++) + { + str += "[" + arguments[i] + "]"; + } + return str; +} + +assert ("abcdabcd".replace("cd", concat_arguments) === "ab[cd][2][abcdabcd]abcd"); +assert ("abcdef".replace (/a((b)c)|d()/, concat_arguments) === "[abc][bc][b][undefined][0][abcdef]def"); + +try +{ + "x".replace("x", function() { throw "MyError"; }); + assert (false); +} +catch (e) +{ + assert (e === "MyError"); +} + +assert ("\ud801\udc00".replace("\ud801", "#") === "#\udc00"); +assert ("\ud801\udc00".replace("\udc00", "#") === "\ud801#"); + +var regexp = /r/g; + +Object.defineProperty(regexp, "lastIndex", { + configurable : false, + enumerable : false, + value : 0, + writable : false +}); + +try { + "r".replace (regexp, "x"); + assert (false); +} catch (e) { + assert (e instanceof TypeError); +} + +// The real "exec" never returns with a number. +Object.getPrototypeOf(/x/).exec = function () { return 1234; } + +assert (/y/.exec("y") === 1234); + +// Changing exec should not affect replace. +assert ("y".replace (/y/, "x") === "x");