mirror of
https://github.com/jerryscript-project/jerryscript.git
synced 2025-12-15 16:29:21 +00:00
Implement String.prototype.replace function.
JerryScript-DCO-1.0-Signed-off-by: Zoltan Herczeg zherczeg@inf.u-szeged.hu
This commit is contained in:
parent
39cf5aaa7c
commit
048e20925e
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
91
tests/jerry/string-prototype-replace.js
Normal file
91
tests/jerry/string-prototype-replace.js
Normal file
@ -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");
|
||||
Loading…
x
Reference in New Issue
Block a user