[njs] Fixed String.prototype.replace() according to the specification.

Dmitry Volyntsev xeioex at nginx.com
Thu Jul 2 14:01:44 UTC 2020


details:   https://hg.nginx.org/njs/rev/1c729f765cfb
branches:  
changeset: 1448:1c729f765cfb
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Thu Jul 02 14:00:16 2020 +0000
description:
Fixed String.prototype.replace() according to the specification.

This closes #308 issue on Github.

diffstat:

 src/njs_regexp.c         |   316 +++++++++++++
 src/njs_string.c         |  1055 ++++++++++-----------------------------------
 src/njs_string.h         |     3 +
 src/njs_value.c          |    25 +
 src/njs_value.h          |     3 +
 src/test/njs_unit_test.c |   365 ++++++++++-----
 6 files changed, 820 insertions(+), 947 deletions(-)

diffs (truncated from 1960 to 1000 lines):

diff -r f9082cd59ba6 -r 1c729f765cfb src/njs_regexp.c
--- a/src/njs_regexp.c	Thu Jul 02 13:59:33 2020 +0000
+++ b/src/njs_regexp.c	Thu Jul 02 14:00:16 2020 +0000
@@ -1161,6 +1161,314 @@ njs_regexp_string_create(njs_vm_t *vm, n
 }
 
 
+static njs_int_t
+njs_regexp_prototype_symbol_replace(njs_vm_t *vm, njs_value_t *args,
+    njs_uint_t nargs, njs_index_t unused)
+{
+    u_char             *p;
+    int64_t            n, last_index, ncaptures, pos, next_pos, size, length;
+    njs_str_t          rep, m;
+    njs_int_t          ret;
+    njs_arr_t          results;
+    njs_chb_t          chain;
+    njs_uint_t         i;
+    njs_bool_t         global;
+    njs_array_t        *array;
+    njs_value_t        *arguments, *r, *rx, *string, *replace;
+    njs_value_t        s_lvalue, r_lvalue, value, matched, groups, retval;
+    njs_function_t     *func_replace;
+    njs_string_prop_t  s;
+
+    static const njs_value_t  string_global = njs_string("global");
+    static const njs_value_t  string_groups = njs_string("groups");
+    static const njs_value_t  string_index = njs_string("index");
+    static const njs_value_t  string_lindex = njs_string("lastIndex");
+
+    rx = njs_argument(args, 0);
+
+    if (njs_slow_path(!njs_is_object(rx))) {
+        njs_type_error(vm, "\"this\" is not object");
+        return NJS_ERROR;
+    }
+
+    string = njs_lvalue_arg(&s_lvalue, args, nargs, 1);
+
+    ret = njs_value_to_string(vm, string, string);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    length = njs_string_prop(&s, string);
+
+    rep.start = NULL;
+    rep.length = 0;
+
+    replace = njs_lvalue_arg(&r_lvalue, args, nargs, 2);
+    func_replace = njs_is_function(replace) ? njs_function(replace) : NULL;
+
+    if (!func_replace) {
+        ret = njs_value_to_string(vm, replace, replace);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+    }
+
+    ret = njs_value_property(vm, rx, njs_value_arg(&string_global), &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    global = njs_bool(&value);
+
+    if (global) {
+        njs_set_number(&value, 0);
+        ret = njs_value_property_set(vm, rx, njs_value_arg(&string_lindex),
+                                     &value);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    njs_chb_init(&chain, vm->mem_pool);
+
+    results.separate = 0;
+    results.pointer = 0;
+
+    r = njs_arr_init(vm->mem_pool, &results, NULL, 4, sizeof(njs_value_t));
+    if (njs_slow_path(r == NULL)) {
+        return NJS_ERROR;
+    }
+
+    for ( ;; ) {
+        r = njs_arr_add(&results);
+        if (njs_slow_path(r == NULL)) {
+            ret = NJS_ERROR;
+            goto exception;
+        }
+
+        ret = njs_regexp_exec(vm, rx, string, r);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto exception;
+        }
+
+        if (njs_is_null(r) || !global) {
+            break;
+        }
+
+        if (njs_fast_path(njs_is_fast_array(r) && njs_array_len(r) != 0)) {
+            value = njs_array_start(r)[0];
+
+        } else {
+            ret = njs_value_property_i64(vm, r, 0, &value);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                goto exception;
+            }
+        }
+
+        ret = njs_value_to_string(vm, &value, &value);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto exception;
+        }
+
+        if (njs_string_length(&value) != 0) {
+            continue;
+        }
+
+        ret = njs_value_property(vm, rx, njs_value_arg(&string_lindex), &value);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto exception;
+        }
+
+        ret = njs_value_to_length(vm, &value, &last_index);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto exception;
+        }
+
+        njs_set_number(&value, last_index + 1);
+        ret = njs_value_property_set(vm, rx, njs_value_arg(&string_lindex),
+                                     &value);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto exception;
+        }
+    }
+
+    i = 0;
+    next_pos = 0;
+
+    while (i < results.items) {
+        r = njs_arr_item(&results, i++);
+
+        if (njs_slow_path(njs_is_null(r))) {
+            break;
+        }
+
+        ret = njs_value_property_i64(vm, r, 0, &matched);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto exception;
+        }
+
+        ret = njs_value_to_string(vm, &matched, &matched);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto exception;
+        }
+
+        ret = njs_value_property(vm, r, njs_value_arg(&string_index), &value);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto exception;
+        }
+
+        ret = njs_value_to_integer(vm, &value, &pos);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto exception;
+        }
+
+        if ((size_t) length != s.size) {
+            /* UTF-8 string. */
+            pos = njs_string_offset(s.start, s.start + s.size, pos) - s.start;
+        }
+
+        pos = njs_max(njs_min(pos, (int64_t) s.size), 0);
+
+        if (njs_fast_path(njs_is_fast_array(r))) {
+            array = njs_array(r);
+
+            arguments = array->start;
+            ncaptures = array->length;
+
+            for (n = 1; n < ncaptures; n++) {
+                if (njs_is_undefined(&arguments[n])) {
+                    continue;
+                }
+
+                ret = njs_value_to_string(vm, &arguments[n], &arguments[n]);
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    goto exception;
+                }
+            }
+
+        } else {
+            ret = njs_object_length(vm, r, &ncaptures);
+            if (njs_slow_path(ret != NJS_OK)) {
+                goto exception;
+            }
+
+            array = njs_array_alloc(vm, 0, ncaptures, 0);
+            if (njs_slow_path(array == NULL)) {
+                goto exception;
+            }
+
+            arguments = array->start;
+            arguments[0] = matched;
+
+            for (n = 1; n < ncaptures; n++) {
+                ret = njs_value_property_i64(vm, r, n, &arguments[n]);
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    goto exception;
+                }
+
+                if (njs_is_undefined(&arguments[n])) {
+                    continue;
+                }
+
+                ret = njs_value_to_string(vm, &arguments[n], &arguments[n]);
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    goto exception;
+                }
+            }
+        }
+
+        ret = njs_value_property(vm, r, njs_value_arg(&string_groups), &groups);
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            goto exception;
+        }
+
+        if (!func_replace) {
+            if (njs_is_defined(&groups)) {
+                ret = njs_value_to_object(vm, &groups);
+                if (njs_slow_path(ret != NJS_OK)) {
+                    return ret;
+                }
+            }
+
+            ret = njs_string_get_substitution(vm, &matched, string, pos,
+                                              arguments, ncaptures - 1, &groups,
+                                              replace, &retval);
+
+        } else {
+            ret = njs_array_expand(vm, array, 0,
+                                   njs_is_defined(&groups) ? 3 : 2);
+            if (njs_slow_path(ret != NJS_OK)) {
+                goto exception;
+            }
+
+            arguments = array->start;
+            njs_set_number(&arguments[n++], pos);
+            arguments[n++] = *string;
+
+            if (njs_is_defined(&groups)) {
+                arguments[n++] = groups;
+            }
+
+            ret = njs_function_call(vm, func_replace,
+                                    njs_value_arg(&njs_value_undefined),
+                                    arguments, n, &retval);
+        }
+
+        if (njs_slow_path(ret == NJS_ERROR)) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_value_to_string(vm, &retval, &retval);
+        if (njs_slow_path(ret != NJS_OK)) {
+            goto exception;
+        }
+
+        if (pos >= next_pos) {
+            njs_chb_append(&chain, &s.start[next_pos], pos - next_pos);
+
+            njs_string_get(&retval, &rep);
+            njs_chb_append_str(&chain, &rep);
+
+            njs_string_get(&matched, &m);
+
+            next_pos = pos + (int64_t) m.length;
+        }
+    }
+
+    if (next_pos < (int64_t) s.size) {
+        njs_chb_append(&chain, &s.start[next_pos], s.size - next_pos);
+    }
+
+    size = njs_chb_size(&chain);
+    if (njs_slow_path(size < 0)) {
+        njs_memory_error(vm);
+        ret = NJS_ERROR;
+        goto exception;
+    }
+
+    length = njs_chb_utf8_length(&chain);
+
+    p = njs_string_alloc(vm, &vm->retval, size, length);
+    if (njs_slow_path(p == NULL)) {
+        ret = NJS_ERROR;
+        goto exception;
+    }
+
+    njs_chb_join_to(&chain, p);
+
+    ret = NJS_OK;
+
+exception:
+
+    njs_chb_destroy(&chain);
+    njs_arr_destroy(&results);
+
+    return ret;
+}
+
+
+
+
 static const njs_object_prop_t  njs_regexp_constructor_properties[] =
 {
     {
@@ -1252,6 +1560,14 @@ static const njs_object_prop_t  njs_rege
         .writable = 1,
         .configurable = 1,
     },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_REPLACE),
+        .value = njs_native_function(njs_regexp_prototype_symbol_replace, 2),
+        .writable = 1,
+        .configurable = 1,
+    },
 };
 
 
diff -r f9082cd59ba6 -r 1c729f765cfb src/njs_string.c
--- a/src/njs_string.c	Thu Jul 02 13:59:33 2020 +0000
+++ b/src/njs_string.c	Thu Jul 02 14:00:16 2020 +0000
@@ -12,44 +12,6 @@
 #define NJS_TRIM_END    2
 
 
-typedef struct {
-    u_char                     *start;
-    size_t                     size;
-    njs_value_t                value;
-} njs_string_replace_part_t;
-
-
-#define NJS_SUBST_COPY        255
-#define NJS_SUBST_PRECEDING   254
-#define NJS_SUBST_FOLLOWING   253
-
-
-typedef struct {
-     uint32_t  type;
-     uint32_t  size;
-     u_char    *start;
-} njs_string_subst_t;
-
-
-typedef struct {
-    njs_value_t                retval;
-
-    njs_arr_t                  parts;
-    njs_string_replace_part_t  array[3];
-    njs_string_replace_part_t  *part;
-
-    njs_arr_t                  *substitutions;
-    njs_function_t             *function;
-
-    njs_regex_match_data_t     *match_data;
-
-    njs_bool_t                 empty;
-
-    njs_utf8_t                 utf8:8;
-    njs_regexp_utf8_t          type:8;
-} njs_string_replace_t;
-
-
 static void njs_encode_base64_core(njs_str_t *dst, const njs_str_t *src,
     const u_char *basis, njs_uint_t padding);
 static njs_int_t njs_decode_base64_core(njs_vm_t *vm,
@@ -72,25 +34,6 @@ static njs_int_t njs_string_match_multip
     njs_regexp_pattern_t *pattern);
 static njs_int_t njs_string_split_part_add(njs_vm_t *vm, njs_array_t *array,
     njs_utf8_t utf8, const u_char *start, size_t size);
-static njs_int_t njs_string_replace_regexp(njs_vm_t *vm, njs_value_t *this,
-    njs_value_t *regex, njs_string_replace_t *r);
-static njs_int_t njs_string_replace_regexp_function(njs_vm_t *vm,
-    njs_value_t *this, njs_value_t *regex, njs_string_replace_t *r,
-    int *captures, njs_uint_t n);
-static njs_int_t njs_string_replace_regexp_join(njs_vm_t *vm,
-    njs_string_replace_t *r);
-static njs_int_t njs_string_replace_search(njs_vm_t *vm, njs_value_t *this,
-    njs_value_t *search, njs_string_replace_t *r);
-static njs_int_t njs_string_replace_search_function(njs_vm_t *vm,
-        njs_value_t *this, njs_value_t *search, njs_string_replace_t *r);
-static njs_int_t njs_string_replace_parse(njs_vm_t *vm,
-    njs_string_replace_t *r, u_char *p, u_char *end, size_t size,
-    njs_uint_t ncaptures);
-static njs_int_t njs_string_replace_substitute(njs_vm_t *vm,
-    njs_string_replace_t *r, int *captures);
-static njs_int_t njs_string_replace_join(njs_vm_t *vm, njs_string_replace_t *r);
-static void njs_string_replacement_copy(njs_string_replace_part_t *string,
-    const njs_value_t *value);
 
 
 #define njs_base64_encoded_length(len)  (((len + 2) / 3) * 4)
@@ -3406,806 +3349,294 @@ njs_string_split_part_add(njs_vm_t *vm, 
 }
 
 
-static njs_int_t
-njs_string_prototype_replace(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
-    njs_index_t unused)
+njs_int_t
+njs_string_get_substitution(njs_vm_t *vm, njs_value_t *matched,
+    njs_value_t *string, int64_t pos, njs_value_t *captures, int64_t ncaptures,
+    njs_value_t *groups, njs_value_t *replacement, njs_value_t *retval)
 {
-    u_char                *p, *start, *end;
-    njs_int_t             ret;
-    njs_uint_t            ncaptures;
-    njs_value_t           *this, *search, *replace;
-    njs_value_t           search_lvalue, replace_lvalue;
-    njs_regex_t           *regex;
-    njs_string_prop_t     string;
-    njs_string_replace_t  *r, string_replace;
-
-    ret = njs_string_object_validate(vm, njs_arg(args, nargs, 0));
-    if (njs_slow_path(ret != NJS_OK)) {
-        return ret;
-    }
-
-    this = njs_argument(args, 0);
-
-    if (nargs == 1) {
-        goto original;
-    }
-
-    search = njs_lvalue_arg(&search_lvalue, args, nargs, 1);
-    replace = njs_lvalue_arg(&replace_lvalue, args, nargs, 2);
-
-    (void) njs_string_prop(&string, this);
-
-    if (string.size == 0) {
-        goto original;
-    }
-
-    r = &string_replace;
-
-    r->utf8 = NJS_STRING_BYTE;
-    r->type = NJS_REGEXP_BYTE;
-
-    if (string.length != 0) {
-        r->utf8 = NJS_STRING_ASCII;
-        r->type = NJS_REGEXP_UTF8;
-
-        if (string.length != string.size) {
-            r->utf8 = NJS_STRING_UTF8;
-        }
-    }
-
-    if (njs_is_regexp(search)) {
-        regex = &njs_regexp_pattern(search)->regex[r->type];
-
-        if (!njs_regex_is_valid(regex)) {
-            goto original;
+    int64_t      tail, size, length, n;
+    u_char       c, c2, *p, *r, *end;
+    njs_str_t    rep, m, str, cap;
+    njs_int_t    ret;
+    njs_chb_t    chain;
+    njs_value_t  name, value;
+
+    njs_string_get(replacement, &rep);
+    p = rep.start;
+    end = rep.start + rep.length;
+
+    njs_chb_init(&chain, vm->mem_pool);
+
+    while (p < end) {
+        r = njs_strlchr(p, end, '$');
+        if (r == NULL || r == &end[-1]) {
+            if (njs_fast_path(p == rep.start)) {
+                *retval = *replacement;
+                return NJS_OK;
+            }
+
+            njs_chb_append(&chain, p, end - p);
+            goto done;
         }
 
-        ncaptures = njs_regex_ncaptures(regex);
-
-    } else {
-        regex = NULL;
-        ncaptures = 1;
-
-        if (!njs_is_string(search)) {
-            ret = njs_value_to_string(vm, search, search);
-            if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
+        njs_chb_append(&chain, p, r - p);
+        p = r;
+
+        c = r[1];
+
+        switch (c) {
+        case '$':
+            njs_chb_append_literal(&chain, "$");
+            p += 2;
+            break;
+
+        case '&':
+            njs_string_get(matched, &m);
+            njs_chb_append_str(&chain, &m);
+            p += 2;
+            break;
+
+        case '`':
+            njs_string_get(string, &str);
+            njs_chb_append(&chain, str.start, pos);
+            p += 2;
+            break;
+
+        case '\'':
+            njs_string_get(matched, &m);
+            tail = pos + m.length;
+
+            njs_string_get(string, &str);
+            njs_chb_append(&chain, &str.start[tail],
+                           njs_max((int64_t) str.length - tail, 0));
+            p += 2;
+            break;
+
+        case '<':
+            r = njs_strlchr(p, end, '>');
+            if (r == NULL) {
+                njs_chb_append(&chain, p, 2);
+                p += 2;
+                break;
+            }
+
+            p += 2;
+
+            if (groups == NULL) {
+                break;
             }
-        }
-    }
-
-    /* This cannot fail. */
-    r->part = njs_arr_init(vm->mem_pool, &r->parts, &r->array,
-                           3, sizeof(njs_string_replace_part_t));
-
-    r->substitutions = NULL;
-    r->function = NULL;
-
-    /* A literal replacement is stored in the second part. */
-
-    if (nargs == 2) {
-        njs_string_replacement_copy(&r->part[1], &njs_string_undefined);
-
-    } else if (njs_is_function(replace)) {
-        r->function = njs_function(replace);
-
-    } else {
-        if (njs_slow_path(!njs_is_string(replace))) {
-            ret = njs_value_to_string(vm, replace, replace);
+
+            ret = njs_vm_value_string_set(vm, &name, p, r - p);
             if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
+                goto exception;
+            }
+
+            p = r + 1;
+
+            ret = njs_value_property(vm, groups, &name, &value);
+            if (njs_slow_path(ret == NJS_ERROR)) {
+                goto exception;
+            }
+
+            if (njs_is_defined(&value)) {
+                ret = njs_value_to_string(vm, &value, &value);
+                if (njs_slow_path(ret == NJS_ERROR)) {
+                    goto exception;
+                }
+
+                njs_string_get(&value, &str);
+                njs_chb_append_str(&chain, &str);
             }
-        }
-
-        njs_string_replacement_copy(&r->part[1], replace);
-
-        start = r->part[1].start;
-
-        if (start == NULL) {
-            start = r->part[1].value.short_string.start;
-        }
-
-        end = start + r->part[1].size;
-
-        for (p = start; p < end; p++) {
-            if (*p == '$') {
-                ret = njs_string_replace_parse(vm, r, p, end, p - start,
-                                               ncaptures);
-                if (njs_slow_path(ret != NJS_OK)) {
-                    return ret;
+
+            break;
+
+        default:
+            if (c >= '0' && c <= '9') {
+                n = c - '0';
+
+                c2 = (&r[2] < end) ? r[2] : 0;
+
+                if (c2 >= '0' && c2 <= '9'
+                    && (n * 10 + (c2 - '0')) <= ncaptures)
+                {
+                    n = n * 10 + (c2 - '0');
+
+                } else {
+                    c2 = 0;
                 }
 
-                /* Reset parts array to the subject string only. */
-                r->parts.items = 1;
+                if (n == 0 || n > ncaptures) {
+                    njs_chb_append(&chain, p, (c2 != 0) ? 3 : 2);
+                    p += (c2 != 0) ? 3 : 2;
+                    break;
+                }
+
+                p += (c2 != 0) ? 3 : 2;
+
+                if (njs_is_defined(&captures[n])) {
+                    njs_string_get(&captures[n], &cap);
+                    njs_chb_append_str(&chain, &cap);
+                }
 
                 break;
             }
-        }
-    }
-
-    r->part[0].start = string.start;
-    r->part[0].size = string.size;
-    njs_set_invalid(&r->part[0].value);
-
-    if (regex != NULL) {
-        r->match_data = njs_regex_match_data(regex, vm->regex_context);
-        if (njs_slow_path(r->match_data == NULL)) {
-            return NJS_ERROR;
-        }
-
-        return njs_string_replace_regexp(vm, this, search, r);
-    }
-
-    return njs_string_replace_search(vm, this, search, r);
-
-original:
-
-    njs_string_copy(&vm->retval, this);
-
-    return NJS_OK;
-}
-
-
-static njs_int_t
-njs_string_replace_regexp(njs_vm_t *vm, njs_value_t *this, njs_value_t *regex,
-    njs_string_replace_t *r)
-{
-    int                        *captures;
-    u_char                     *p, *start;
-    njs_int_t                  ret;
-    const u_char               *end;
-    njs_regexp_pattern_t       *pattern;
-    njs_string_replace_part_t  replace;
-
-    pattern = njs_regexp_pattern(regex);
-    end = r->part[0].start + r->part[0].size;
-
-    replace = r->part[1];
-
-    do {
-        ret = njs_regexp_match(vm, &pattern->regex[r->type],
-                               r->part[0].start, 0, r->part[0].size,
-                               r->match_data);
-
-        if (ret < 0) {
-            if (njs_slow_path(ret != NJS_REGEX_NOMATCH)) {
-                return NJS_ERROR;
-            }
-
+
+            njs_chb_append_literal(&chain, "$");
+            p += 1;
             break;
         }
-
-        captures = njs_regex_captures(r->match_data);
-
-        if (r->substitutions != NULL) {
-            ret = njs_string_replace_substitute(vm, r, captures);
-            if (njs_slow_path(ret != NJS_OK)) {
-                return ret;
-            }
-
-            if (!pattern->global) {
-                return njs_string_replace_regexp_join(vm, r);
-            }
-
-            continue;
-        }
-
-        if (r->part != r->parts.start) {
-            r->part = njs_arr_add(&r->parts);
-            if (njs_slow_path(r->part == NULL)) {
-                return NJS_ERROR;
-            }
-
-            r->part = njs_arr_add(&r->parts);
-            if (njs_slow_path(r->part == NULL)) {
-                return NJS_ERROR;
-            }
-
-            r->part -= 2;
-        }
-
-        if (captures[1] == 0) {
-
-            /* Empty match. */
-
-            start = r->part[0].start;
-
-            if (start < end) {
-                p = (r->utf8 != NJS_STRING_BYTE)
-                    ? (u_char *) njs_utf8_next(start, end) : start + 1;
-
-                r->part[1].start = start;
-                r->part[1].size = p - start;
-
-                r->part[2].start = p;
-                r->part[2].size = end - p;
-
-            } else {
-                r->part[1].size = 0;
-                r->part[2].size = 0;
-
-                /* To exit the loop. */
-                r->part[2].start = start + 1;
-            }
-
-            if (r->function != NULL) {
-                return njs_string_replace_regexp_function(vm, this, regex, r,
-                                                          captures, ret);
-            }
-
-            r->part[0] = replace;
-
-        } else {
-            r->part[2].start = r->part[0].start + captures[1];
-            r->part[2].size = r->part[0].size - captures[1];
-            njs_set_invalid(&r->part[2].value);
-
-            if (r->function != NULL) {
-                return njs_string_replace_regexp_function(vm, this, regex, r,
-                                                          captures, ret);
-            }
-
-            r->part[0].size = captures[0];
-
-            r->part[1] = replace;
-        }
-
-        if (!pattern->global) {
-            return njs_string_replace_regexp_join(vm, r);
-        }
-
-        r->part += 2;
-
-    } while (r->part[0].start <= end);
-
-    if (r->part != r->parts.start) {
-        return njs_string_replace_regexp_join(vm, r);
+    }
+
+done:
+
+    size = njs_chb_size(&chain);
+    if (njs_slow_path(size < 0)) {
+        njs_memory_error(vm);
+        ret = NJS_ERROR;
+        goto exception;
     }
 
-    njs_regex_match_data_free(r->match_data, vm->regex_context);
-
-    njs_arr_destroy(&r->parts);
-
-    njs_string_copy(&vm->retval, this);
+    length = njs_chb_utf8_length(&chain);
+
+    p = njs_string_alloc(vm, retval, size, length);
+    if (njs_slow_path(p == NULL)) {
+        ret = NJS_ERROR;
+        goto exception;
+    }
+
+    njs_chb_join_to(&chain, p);
+
+    ret = NJS_OK;
+
+exception:
+
+    njs_chb_destroy(&chain);
 
     return NJS_OK;
 }
 
 
 static njs_int_t
-njs_string_replace_regexp_function(njs_vm_t *vm, njs_value_t *this,
-    njs_value_t *regex, njs_string_replace_t *r, int *captures, njs_uint_t n)
+njs_string_prototype_replace(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused)
 {
-    u_char             *start;
-    size_t             size, length;
+    u_char             *r;
+    size_t             length, search_length, ret_length, size;
+    int64_t            pos;
     njs_int_t          ret;
-    njs_uint_t         i, k;
-    njs_value_t        *arguments;
-    njs_string_prop_t  string;
-
-    if (njs_slow_path((n + 3) >= UINT32_MAX / sizeof(njs_value_t))) {
-        njs_memory_error(vm);
+    njs_value_t        *this, *search, *replace;
+    njs_value_t        search_lvalue, replace_lvalue, replacer, retval,
+                       arguments[3];
+    const u_char       *p;
+    njs_function_t     *func_replace;
+    njs_string_prop_t  string, s, ret_string;
+
+    static const njs_value_t  replace_key =
+                                      njs_wellknown_symbol(NJS_SYMBOL_REPLACE);
+
+    this = njs_argument(args, 0);
+
+    if (njs_slow_path(njs_is_null_or_undefined(this))) {
+        njs_type_error(vm, "cannot convert \"%s\"to object",
+                       njs_type_string(this->type));
         return NJS_ERROR;
     }
 
-    njs_set_invalid(&r->retval);
-
-    arguments = njs_mp_alloc(vm->mem_pool, (n + 3) * sizeof(njs_value_t));
-    if (njs_slow_path(arguments == NULL)) {
-        njs_memory_error(vm);
-        return NJS_ERROR;
+    search = njs_lvalue_arg(&search_lvalue, args, nargs, 1);
+    replace = njs_lvalue_arg(&replace_lvalue, args, nargs, 2);
+
+    if (!njs_is_null_or_undefined(search)) {
+        ret = njs_value_method(vm, search, njs_value_arg(&replace_key),
+                               &replacer);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        if (njs_is_defined(&replacer)) {
+            arguments[0] = *this;
+            arguments[1] = *replace;
+
+            return njs_function_call(vm, njs_function(&replacer), search,
+                                     arguments, 2, &vm->retval);
+        }
+    }
+
+    ret = njs_value_to_string(vm, this, this);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    ret = njs_value_to_string(vm, search, search);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
     }
 
-    njs_set_undefined(&arguments[0]);
-
-    /* Matched substring and parenthesized submatch strings. */
-    for (k = 0, i = 1; i <= n; i++) {
-
-        start = r->part[0].start + captures[k];
-        size = captures[k + 1] - captures[k];
-        k += 2;
-
-        length = njs_string_calc_length(r->utf8, start, size);
-
-        ret = njs_string_new(vm, &arguments[i], start, size, length);
+    func_replace = njs_is_function(replace) ? njs_function(replace) : NULL;
+
+    if (func_replace == NULL) {
+        ret = njs_value_to_string(vm, replace, replace);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+    }
+
+    length = njs_string_prop(&string, this);
+    search_length = njs_string_prop(&s, search);
+
+    pos = njs_string_index_of(&string, &s, 0);
+    if (pos < 0) {
+        vm->retval = *this;
+        return NJS_OK;
+    }
+
+    if (func_replace == NULL) {
+        ret = njs_string_get_substitution(vm, search, this, pos, NULL, 0, NULL,
+                                          replace, &retval);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+    } else {
+        arguments[0] = *search;
+        njs_set_number(&arguments[1], pos);
+        arguments[2] = *this;
+
+        ret = njs_function_call(vm, func_replace,
+                                njs_value_arg(&njs_value_undefined),
+                                arguments, 3, &retval);
+
+        ret = njs_value_to_string(vm, &retval, &retval);
         if (njs_slow_path(ret != NJS_OK)) {
             return NJS_ERROR;
         }
     }
 
-    r->empty = (captures[0] == captures[1]);
-
-    /* The offset of the matched substring. */
-    njs_set_number(&arguments[n + 1], captures[0]);
-
-    /* The whole string being examined. */
-    length = njs_string_calc_length(r->utf8, r->part[0].start, r->part[0].size);
-
-    (void) njs_string_prop(&string, this);
-
-    ret = njs_string_new(vm, &arguments[n + 2], string.start, string.size,
-                         length);
-
-    if (njs_slow_path(ret != NJS_OK)) {
-        return NJS_ERROR;
-    }
-
-    r->part[0].size = captures[0];
-
-    ret = njs_function_apply(vm, r->function, arguments, n + 3, &r->retval);
-    if (njs_slow_path(ret != NJS_OK)) {
-        goto exception;
-    }
-
-    if (njs_slow_path(!njs_is_string(&r->retval))) {
-        ret = njs_value_to_string(vm, &r->retval, &r->retval);
-        if (njs_slow_path(ret != NJS_OK)) {
-            goto exception;
-        }
-    }
-
-    njs_string_replacement_copy(&r->part[r->empty ? 0 : 1], &r->retval);
-
-    if (njs_regexp_pattern(regex)->global) {
-        r->part += 2;
-
-        if (r->part[0].start > (string.start + string.size)) {
-            return njs_string_replace_regexp_join(vm, r);
-        }
-
-        return njs_string_replace_regexp(vm, this, regex, r);
-    }
-
-    return njs_string_replace_regexp_join(vm, r);
-
-exception:
-
-    njs_regex_match_data_free(r->match_data, vm->regex_context);
-
-    return NJS_ERROR;
-}
-
-


More information about the nginx-devel mailing list