[njs] Introduced RegExp.prototype.sticky support.

Dmitry Volyntsev xeioex at nginx.com
Tue Jun 8 18:00:00 UTC 2021


details:   https://hg.nginx.org/njs/rev/913a69870b49
branches:  
changeset: 1654:913a69870b49
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Sat Jun 05 11:55:08 2021 +0000
description:
Introduced RegExp.prototype.sticky support.

diffstat:

 src/njs_regexp.c         |  60 +++++++++++++++++++++++++++++++++++++++++------
 src/njs_regexp.h         |   1 +
 src/njs_regexp_pattern.h |   1 +
 src/test/njs_unit_test.c |  33 +++++++++++++++++++++++---
 4 files changed, 83 insertions(+), 12 deletions(-)

diffs (252 lines):

diff -r 06204b343066 -r 913a69870b49 src/njs_regexp.c
--- a/src/njs_regexp.c	Tue Jun 08 12:43:13 2021 +0000
+++ b/src/njs_regexp.c	Sat Jun 05 11:55:08 2021 +0000
@@ -93,6 +93,10 @@ njs_regexp_value_flags(njs_vm_t *vm, con
         flags |= NJS_REGEXP_MULTILINE;
     }
 
+    if (pattern->sticky) {
+        flags |= NJS_REGEXP_STICKY;
+    }
+
     return flags;
 }
 
@@ -332,6 +336,10 @@ njs_regexp_flags(u_char **start, u_char 
             flag = NJS_REGEXP_MULTILINE;
             break;
 
+        case 'y':
+            flag = NJS_REGEXP_STICKY;
+            break;
+
         default:
             if (*p >= 'a' && *p <= 'z') {
                 goto invalid;
@@ -429,6 +437,11 @@ njs_regexp_pattern_create(njs_vm_t *vm, 
          options |= PCRE_MULTILINE;
     }
 
+    pattern->sticky = ((flags & NJS_REGEXP_STICKY) != 0);
+    if (pattern->sticky) {
+        options |= PCRE_ANCHORED;
+    }
+
     *p++ = '\0';
 
     ret = njs_regexp_pattern_compile(vm, &pattern->regex[0],
@@ -644,11 +657,12 @@ njs_regexp_prototype_flags(njs_vm_t *vm,
     u_char       *p;
     njs_int_t    ret;
     njs_value_t  *this, value;
-    u_char       dst[3];
+    u_char       dst[4];
 
     static const njs_value_t  string_global = njs_string("global");
     static const njs_value_t  string_ignore_case = njs_string("ignoreCase");
     static const njs_value_t  string_multiline = njs_string("multiline");
+    static const njs_value_t  string_sticky = njs_string("sticky");
 
     this = njs_argument(args, 0);
     if (njs_slow_path(!njs_is_object(this))) {
@@ -688,6 +702,16 @@ njs_regexp_prototype_flags(njs_vm_t *vm,
         *p++ = 'm';
     }
 
+    ret = njs_value_property(vm, this, njs_value_arg(&string_sticky),
+                             &value);
+    if (njs_slow_path(ret == NJS_ERROR)) {
+        return NJS_ERROR;
+    }
+
+    if (njs_bool(&value)) {
+        *p++ = 'y';
+    }
+
     return njs_string_new(vm, &vm->retval, dst, p - dst, p - dst);
 }
 
@@ -728,8 +752,12 @@ njs_regexp_prototype_flag(njs_vm_t *vm, 
         break;
 
     case NJS_REGEXP_MULTILINE:
+        yn = pattern->multiline;
+        break;
+
+    case NJS_REGEXP_STICKY:
     default:
-        yn = pattern->multiline;
+        yn = pattern->sticky;
         break;
     }
 
@@ -859,15 +887,19 @@ njs_regexp_to_string(njs_vm_t *vm, njs_v
     size = njs_strlen(source);
     length = njs_utf8_length(source, size);
 
-    length = (length >= 0) ? length: 0;
+    length = (length >= 0) ? (length + (pattern->sticky != 0)): 0;
 
-    p = njs_string_alloc(vm, retval, size, length);
+    p = njs_string_alloc(vm, retval, size + (pattern->sticky != 0), length);
     if (njs_slow_path(p == NULL)) {
         return NJS_ERROR;
     }
 
     p = njs_cpymem(p, source, size);
 
+    if (pattern->sticky) {
+        *p++ = 'y';
+    }
+
     return NJS_OK;
 }
 
@@ -905,7 +937,7 @@ njs_regexp_prototype_test(njs_vm_t *vm, 
 
 
 /**
- * TODO: sticky, unicode flags.
+ * TODO: unicode flags.
  */
 static njs_int_t
 njs_regexp_builtin_exec(njs_vm_t *vm, njs_value_t *r, njs_value_t *s,
@@ -937,7 +969,7 @@ njs_regexp_builtin_exec(njs_vm_t *vm, nj
         return NJS_ERROR;
     }
 
-    if (!pattern->global) {
+    if (!pattern->global && !pattern->sticky) {
         last_index = 0;
     }
 
@@ -995,7 +1027,7 @@ njs_regexp_builtin_exec(njs_vm_t *vm, nj
 
 not_found:
 
-    if (pattern->global) {
+    if (pattern->global || pattern->sticky) {
         njs_set_number(&value, 0);
         ret = njs_value_property_set(vm, r, njs_value_arg(&njs_string_lindex),
                                      &value);
@@ -1082,7 +1114,7 @@ njs_regexp_exec_result(njs_vm_t *vm, njs
 
     njs_set_number(&prop->value, index);
 
-    if (pattern->global) {
+    if (pattern->global || pattern->sticky) {
         if (type == NJS_REGEXP_UTF8) {
             index = njs_string_index(string, captures[1]);
 
@@ -1682,6 +1714,18 @@ static const njs_object_prop_t  njs_rege
 
     {
         .type = NJS_PROPERTY,
+        .name = njs_string("sticky"),
+        .value = njs_value(NJS_INVALID, 1, NAN),
+        .getter = njs_native_function2(njs_regexp_prototype_flag, 0,
+                                       NJS_REGEXP_STICKY),
+        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
+        .writable = NJS_ATTRIBUTE_UNSET,
+        .configurable = 1,
+        .enumerable = 0,
+    },
+
+    {
+        .type = NJS_PROPERTY,
         .name = njs_string("toString"),
         .value = njs_native_function(njs_regexp_prototype_to_string, 0),
         .writable = 1,
diff -r 06204b343066 -r 913a69870b49 src/njs_regexp.h
--- a/src/njs_regexp.h	Tue Jun 08 12:43:13 2021 +0000
+++ b/src/njs_regexp.h	Sat Jun 05 11:55:08 2021 +0000
@@ -14,6 +14,7 @@ typedef enum {
     NJS_REGEXP_GLOBAL       =  1,
     NJS_REGEXP_IGNORE_CASE  =  2,
     NJS_REGEXP_MULTILINE    =  4,
+    NJS_REGEXP_STICKY       =  8,
 } njs_regexp_flags_t;
 
 
diff -r 06204b343066 -r 913a69870b49 src/njs_regexp_pattern.h
--- a/src/njs_regexp_pattern.h	Tue Jun 08 12:43:13 2021 +0000
+++ b/src/njs_regexp_pattern.h	Sat Jun 05 11:55:08 2021 +0000
@@ -38,6 +38,7 @@ struct njs_regexp_pattern_s {
     uint8_t               global;       /* 1 bit */
     uint8_t               ignore_case;  /* 1 bit */
     uint8_t               multiline;    /* 1 bit */
+    uint8_t               sticky;       /* 1 bit */
 
     njs_regexp_group_t    *groups;
 };
diff -r 06204b343066 -r 913a69870b49 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Tue Jun 08 12:43:13 2021 +0000
+++ b/src/test/njs_unit_test.c	Sat Jun 05 11:55:08 2021 +0000
@@ -10631,6 +10631,12 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("/[A-Z/]/"),
       njs_str("/[A-Z/]/") },
 
+    { njs_str("/a/gim"),
+      njs_str("/a/gim") },
+
+    { njs_str("/a/y"),
+      njs_str("/a/y") },
+
     { njs_str("/[A-Z\n]/"),
       njs_str("SyntaxError: Unterminated RegExp \"/[A-Z\" in 1") },
 
@@ -10720,6 +10726,9 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("/α/.test('\\u00CE\\u00B1'.toBytes())"),
       njs_str("true") },
 
+    { njs_str("var r = /abc/y; r.test('abc'); r.lastIndex"),
+      njs_str("3") },
+
     { njs_str("/\\d/.exec('123')"),
       njs_str("1") },
 
@@ -10849,8 +10858,21 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var r = (/^.+$/mg), s = 'ab\\nc'; [r.exec(s), r.exec(s)]"),
       njs_str("ab,c") },
 
-    { njs_str("var r = (/^.+$/mg); [r.global, r.multiline, r.ignoreCase]"),
-      njs_str("true,true,false") },
+    { njs_str("var r = (/^.+$/mg); [r.global, r.multiline, r.ignoreCase, r.sticky]"),
+      njs_str("true,true,false,false") },
+
+    { njs_str("['global', 'ignoreCase', 'multiline', 'sticky']"
+              ".map(v => Object.getOwnPropertyDescriptor(RegExp.prototype, v))"
+              ".every(desc => (typeof desc.get === 'function' && typeof desc.set === 'undefined'))"),
+      njs_str("true") },
+
+    { njs_str("var re = /./, re2 = /./y; re.lastIndex = 1; re2.lastIndex = 1;"
+              "[re.exec('abc')[0], re2.exec('abc')[0]]"),
+      njs_str("a,b") },
+
+    { njs_str("var re = /c/, re2 = /c/y;"
+              "njs.dump([re.exec('abc')[0], re2.exec('abc')])"),
+      njs_str("['c',null]") },
 
     { njs_str("['global', 'ignoreCase', 'multiline']"
               ".map(v => Object.getOwnPropertyDescriptor(RegExp.prototype, v))"
@@ -10869,6 +10891,9 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("var r = new RegExp('.', 'igm'); r"),
       njs_str("/./gim") },
 
+    { njs_str("var r = new RegExp('.', 'y'); r"),
+      njs_str("/./y") },
+
     { njs_str("var r = new RegExp('abc'); r.test('00abc11')"),
       njs_str("true") },
 
@@ -17340,8 +17365,8 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("njs.dump({a:1, b:[1,,2,{c:new Boolean(1)}]})"),
       njs_str("{a:1,b:[1,<empty>,2,{c:[Boolean: true]}]}") },
 
-    { njs_str("njs.dump([InternalError(),TypeError('msg'), new RegExp(), /^undef$/m, new Date(0)])"),
-      njs_str("[InternalError,TypeError: msg,/(?:)/,/^undef$/m,1970-01-01T00:00:00.000Z]") },
+    { njs_str("njs.dump([InternalError(),TypeError('msg'), new RegExp(), /^undef$/my, new Date(0)])"),
+      njs_str("[InternalError,TypeError: msg,/(?:)/,/^undef$/my,1970-01-01T00:00:00.000Z]") },
 
     { njs_str("njs.dump(Array.prototype.slice.call({'1':'b', length:2}))"),
       njs_str("[<empty>,'b']") },


More information about the nginx-devel mailing list