From 5664ea42d8defefba83f61fac4a17262cf54abd0 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Sat, 5 Jun 2021 11:55:08 +0000 Subject: [PATCH] Introduced RegExp.prototype.sticky support. --- 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(-) diff --git a/src/njs_regexp.c b/src/njs_regexp.c index 6caa1119..4709abc5 100644 --- a/src/njs_regexp.c +++ b/src/njs_regexp.c @@ -93,6 +93,10 @@ njs_regexp_value_flags(njs_vm_t *vm, const njs_value_t *regexp) 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 *end) 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, u_char *start, size_t length, 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, njs_value_t *args, 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, njs_value_t *args, *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,9 +752,13 @@ njs_regexp_prototype_flag(njs_vm_t *vm, njs_value_t *args, break; case NJS_REGEXP_MULTILINE: - default: yn = pattern->multiline; break; + + case NJS_REGEXP_STICKY: + default: + yn = pattern->sticky; + break; } njs_set_boolean(&vm->retval, yn); @@ -859,15 +887,19 @@ njs_regexp_to_string(njs_vm_t *vm, njs_value_t *retval, 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, njs_value_t *args, njs_uint_t nargs, /** - * 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, njs_value_t *r, njs_value_t *s, 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, njs_value_t *r, njs_value_t *s, 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_value_t *r, njs_regexp_utf8_t type, 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]); @@ -1680,6 +1712,18 @@ static const njs_object_prop_t njs_regexp_prototype_properties[] = .enumerable = 0, }, + { + .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"), diff --git a/src/njs_regexp.h b/src/njs_regexp.h index b9f4a922..202b65b7 100644 --- a/src/njs_regexp.h +++ b/src/njs_regexp.h @@ -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 --git a/src/njs_regexp_pattern.h b/src/njs_regexp_pattern.h index 5681453b..545a27d3 100644 --- a/src/njs_regexp_pattern.h +++ b/src/njs_regexp_pattern.h @@ -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 --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index c2ee363a..62a51626 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -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,,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("[,'b']") }, -- 2.47.3