blob: 8e1dec05ce42684d313a81e85b4e5d58b20976a2 [file] [log] [blame]
/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <njs_main.h>
struct njs_regexp_group_s {
njs_str_t name;
uint32_t hash;
uint32_t capture;
};
static void *njs_regexp_malloc(size_t size, void *memory_data);
static void njs_regexp_free(void *p, void *memory_data);
static njs_regexp_flags_t njs_regexp_flags(u_char **start, u_char *end,
njs_bool_t bound);
static njs_int_t njs_regexp_prototype_source(njs_vm_t *vm,
njs_object_prop_t *prop, njs_value_t *value, njs_value_t *setval,
njs_value_t *retval);
static int njs_regexp_pattern_compile(njs_vm_t *vm, njs_regex_t *regex,
u_char *source, int options);
static u_char *njs_regexp_compile_trace_handler(njs_trace_t *trace,
njs_trace_data_t *td, u_char *start);
static u_char *njs_regexp_match_trace_handler(njs_trace_t *trace,
njs_trace_data_t *td, u_char *start);
static njs_int_t njs_regexp_exec_result(njs_vm_t *vm, njs_regexp_t *regexp,
njs_utf8_t utf8, u_char *string, njs_regex_match_data_t *match_data,
uint32_t last_index);
static njs_int_t njs_regexp_string_create(njs_vm_t *vm, njs_value_t *value,
u_char *start, uint32_t size, int32_t length);
njs_int_t
njs_regexp_init(njs_vm_t *vm)
{
vm->regex_context = njs_regex_context_create(njs_regexp_malloc,
njs_regexp_free, vm->mem_pool);
if (njs_slow_path(vm->regex_context == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
vm->single_match_data = njs_regex_match_data(NULL, vm->regex_context);
if (njs_slow_path(vm->single_match_data == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
vm->regex_context->trace = &vm->trace;
return NJS_OK;
}
static void *
njs_regexp_malloc(size_t size, void *memory_data)
{
return njs_mp_alloc(memory_data, size);
}
static void
njs_regexp_free(void *p, void *memory_data)
{
njs_mp_free(memory_data, p);
}
static njs_regexp_flags_t
njs_regexp_value_flags(njs_vm_t *vm, const njs_value_t *regexp)
{
njs_regexp_flags_t flags;
njs_regexp_pattern_t *pattern;
flags = 0;
pattern = njs_regexp_pattern(regexp);
if (pattern->global) {
flags |= NJS_REGEXP_GLOBAL;
}
if (pattern->ignore_case) {
flags |= NJS_REGEXP_IGNORE_CASE;
}
if (pattern->multiline) {
flags |= NJS_REGEXP_MULTILINE;
}
return flags;
}
static njs_int_t
njs_regexp_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
u_char *start;
njs_int_t ret;
njs_str_t string;
njs_value_t source, *pattern, *flags;
njs_regexp_flags_t re_flags;
pattern = njs_arg(args, nargs, 1);
if (njs_is_regexp(pattern)) {
ret = njs_regexp_prototype_source(vm, NULL, pattern, NULL, &source);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
re_flags = njs_regexp_value_flags(vm, pattern);
pattern = &source;
} else {
if (njs_is_defined(pattern)) {
ret = njs_value_to_string(vm, pattern, pattern);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
} else {
pattern = njs_value_arg(&njs_string_empty);
}
re_flags = 0;
}
flags = njs_arg(args, nargs, 2);
if (njs_is_defined(flags)) {
ret = njs_value_to_string(vm, flags, flags);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
njs_string_get(flags, &string);
start = string.start;
re_flags = njs_regexp_flags(&start, start + string.length, 1);
if (njs_slow_path(re_flags < 0)) {
njs_syntax_error(vm, "Invalid RegExp flags \"%V\"", &string);
return NJS_ERROR;
}
}
njs_string_get(pattern, &string);
return njs_regexp_create(vm, &vm->retval, string.start, string.length,
re_flags);
}
njs_int_t
njs_regexp_create(njs_vm_t *vm, njs_value_t *value, u_char *start,
size_t length, njs_regexp_flags_t flags)
{
njs_regexp_t *regexp;
njs_regexp_pattern_t *pattern;
if (length != 0 || flags != 0) {
if (length == 0) {
start = (u_char *) "(?:)";
length = njs_length("(?:)");
}
pattern = njs_regexp_pattern_create(vm, start, length, flags);
if (njs_slow_path(pattern == NULL)) {
return NJS_ERROR;
}
} else {
pattern = vm->shared->empty_regexp_pattern;
}
regexp = njs_regexp_alloc(vm, pattern);
if (njs_fast_path(regexp != NULL)) {
njs_set_regexp(value, regexp);
return NJS_OK;
}
return NJS_ERROR;
}
/*
* 1) PCRE with PCRE_JAVASCRIPT_COMPAT flag rejects regexps with
* lone closing square brackets as invalid. Whereas according
* to ES6: 11.8.5 it is a valid regexp expression.
*
* 2) escaping zero byte characters as "\u0000".
*
* Escaping it here as a workaround.
*/
njs_inline njs_int_t
njs_regexp_escape(njs_vm_t *vm, njs_str_t *text)
{
size_t brackets, zeros;
u_char *p, *dst, *start, *end;
njs_bool_t in;
start = text->start;
end = text->start + text->length;
in = 0;
zeros = 0;
brackets = 0;
for (p = start; p < end; p++) {
switch (*p) {
case '[':
in = 1;
break;
case ']':
if (!in) {
brackets++;
}
in = 0;
break;
case '\\':
p++;
if (p == end || *p != '\0') {
break;
}
/* Fall through. */
case '\0':
zeros++;
break;
}
}
if (!brackets && !zeros) {
return NJS_OK;
}
text->length = text->length + brackets + zeros * njs_length("\\u0000");
text->start = njs_mp_alloc(vm->mem_pool, text->length);
if (njs_slow_path(text->start == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
in = 0;
dst = text->start;
for (p = start; p < end; p++) {
switch (*p) {
case '[':
in = 1;
break;
case ']':
if (!in) {
*dst++ = '\\';
}
in = 0;
break;
case '\\':
*dst++ = *p++;
if (p == end) {
goto done;
}
if (*p != '\0') {
break;
}
/* Fall through. */
case '\0':
dst = njs_cpymem(dst, "\\u0000", 6);
continue;
}
*dst++ = *p;
}
done:
text->length = dst - text->start;
return NJS_OK;
}
njs_token_type_t
njs_regexp_literal(njs_vm_t *vm, njs_parser_t *parser, njs_value_t *value)
{
u_char *p;
njs_str_t text;
njs_lexer_t *lexer;
njs_regexp_flags_t flags;
njs_regexp_pattern_t *pattern;
lexer = parser->lexer;
for (p = lexer->start; p < lexer->end; p++) {
switch (*p) {
case '\n':
case '\r':
goto failed;
case '[':
while (1) {
if (++p >= lexer->end) {
goto failed;
}
if (*p == ']') {
break;
}
switch (*p) {
case '\n':
case '\r':
goto failed;
case '\\':
if (++p >= lexer->end || *p == '\n' || *p == '\r') {
goto failed;
}
break;
}
}
break;
case '\\':
if (++p >= lexer->end || *p == '\n' || *p == '\r') {
goto failed;
}
break;
case '/':
text.start = lexer->start;
text.length = p - text.start;
p++;
lexer->start = p;
flags = njs_regexp_flags(&p, lexer->end, 0);
if (njs_slow_path(flags < 0)) {
njs_parser_syntax_error(vm, parser,
"Invalid RegExp flags \"%*s\"",
p - lexer->start, lexer->start);
return NJS_TOKEN_ILLEGAL;
}
lexer->start = p;
pattern = njs_regexp_pattern_create(vm, text.start, text.length,
flags);
if (njs_slow_path(pattern == NULL)) {
return NJS_TOKEN_ILLEGAL;
}
value->data.u.data = pattern;
return NJS_TOKEN_REGEXP;
}
}
failed:
njs_parser_syntax_error(vm, parser, "Unterminated RegExp \"%*s\"",
p - (lexer->start - 1), lexer->start - 1);
return NJS_TOKEN_ILLEGAL;
}
static njs_regexp_flags_t
njs_regexp_flags(u_char **start, u_char *end, njs_bool_t bound)
{
u_char *p;
njs_regexp_flags_t flags, flag;
flags = 0;
for (p = *start; p < end; p++) {
switch (*p) {
case 'g':
flag = NJS_REGEXP_GLOBAL;
break;
case 'i':
flag = NJS_REGEXP_IGNORE_CASE;
break;
case 'm':
flag = NJS_REGEXP_MULTILINE;
break;
case ';':
case ' ':
case '\t':
case '\r':
case '\n':
case ',':
case ')':
case ']':
case '}':
case '.':
if (!bound) {
goto done;
}
/* Fall through. */
default:
goto invalid;
}
if (njs_slow_path((flags & flag) != 0)) {
goto invalid;
}
flags |= flag;
}
done:
*start = p;
return flags;
invalid:
*start = p + 1;
return NJS_REGEXP_INVALID_FLAG;
}
njs_regexp_pattern_t *
njs_regexp_pattern_create(njs_vm_t *vm, u_char *start, size_t length,
njs_regexp_flags_t flags)
{
int options, ret;
u_char *p, *end;
size_t size;
njs_str_t text;
njs_uint_t n;
njs_regex_t *regex;
njs_regexp_group_t *group;
njs_regexp_pattern_t *pattern;
size = 1; /* A trailing "/". */
size += ((flags & NJS_REGEXP_GLOBAL) != 0);
size += ((flags & NJS_REGEXP_IGNORE_CASE) != 0);
size += ((flags & NJS_REGEXP_MULTILINE) != 0);
text.start = start;
text.length = length;
ret = njs_regexp_escape(vm, &text);
if (njs_slow_path(ret != NJS_OK)) {
return NULL;
}
pattern = njs_mp_zalloc(vm->mem_pool, sizeof(njs_regexp_pattern_t) + 1
+ text.length + size + 1);
if (njs_slow_path(pattern == NULL)) {
njs_memory_error(vm);
return NULL;
}
pattern->flags = size;
p = (u_char *) pattern + sizeof(njs_regexp_pattern_t);
pattern->source = p;
*p++ = '/';
p = memcpy(p, text.start, text.length);
p += text.length;
end = p;
*p++ = '\0';
pattern->global = ((flags & NJS_REGEXP_GLOBAL) != 0);
if (pattern->global) {
*p++ = 'g';
}
#ifdef PCRE_JAVASCRIPT_COMPAT
/* JavaScript compatibility has been introduced in PCRE-7.7. */
options = PCRE_JAVASCRIPT_COMPAT;
#else
options = 0;
#endif
pattern->ignore_case = ((flags & NJS_REGEXP_IGNORE_CASE) != 0);
if (pattern->ignore_case) {
*p++ = 'i';
options |= PCRE_CASELESS;
}
pattern->multiline = ((flags & NJS_REGEXP_MULTILINE) != 0);
if (pattern->multiline) {
*p++ = 'm';
options |= PCRE_MULTILINE;
}
*p++ = '\0';
ret = njs_regexp_pattern_compile(vm, &pattern->regex[0],
&pattern->source[1], options);
if (njs_fast_path(ret >= 0)) {
pattern->ncaptures = ret;
} else if (ret < 0 && ret != NJS_DECLINED) {
goto fail;
}
ret = njs_regexp_pattern_compile(vm, &pattern->regex[1],
&pattern->source[1], options | PCRE_UTF8);
if (njs_fast_path(ret >= 0)) {
if (njs_slow_path(njs_regex_is_valid(&pattern->regex[0])
&& (u_int) ret != pattern->ncaptures))
{
njs_internal_error(vm, "regexp pattern compile failed");
goto fail;
}
pattern->ncaptures = ret;
} else if (ret != NJS_DECLINED) {
goto fail;
}
if (njs_regex_is_valid(&pattern->regex[0])) {
regex = &pattern->regex[0];
} else if (njs_regex_is_valid(&pattern->regex[1])) {
regex = &pattern->regex[1];
} else {
goto fail;
}
*end = '/';
pattern->ngroups = njs_regex_named_captures(regex, NULL, 0);
if (pattern->ngroups != 0) {
size = sizeof(njs_regexp_group_t) * pattern->ngroups;
pattern->groups = njs_mp_alloc(vm->mem_pool, size);
if (njs_slow_path(pattern->groups == NULL)) {
njs_memory_error(vm);
return NULL;
}
n = 0;
do {
group = &pattern->groups[n];
group->capture = njs_regex_named_captures(regex, &group->name, n);
group->hash = njs_djb_hash(group->name.start, group->name.length);
n++;
} while (n != pattern->ngroups);
}
njs_set_undefined(&vm->retval);
return pattern;
fail:
njs_mp_free(vm->mem_pool, pattern);
return NULL;
}
static int
njs_regexp_pattern_compile(njs_vm_t *vm, njs_regex_t *regex, u_char *source,
int options)
{
njs_int_t ret;
njs_trace_handler_t handler;
handler = vm->trace.handler;
vm->trace.handler = njs_regexp_compile_trace_handler;
/* Zero length means a zero-terminated string. */
ret = njs_regex_compile(regex, source, 0, options, vm->regex_context);
vm->trace.handler = handler;
if (njs_fast_path(ret == NJS_OK)) {
return regex->ncaptures;
}
return ret;
}
static u_char *
njs_regexp_compile_trace_handler(njs_trace_t *trace, njs_trace_data_t *td,
u_char *start)
{
u_char *p;
njs_vm_t *vm;
vm = trace->data;
trace = trace->next;
p = trace->handler(trace, td, start);
if (vm->parser != NULL && vm->parser->lexer != NULL) {
njs_syntax_error(vm, "%*s in %uD", p - start, start,
vm->parser->lexer->line);
} else {
njs_syntax_error(vm, "%*s", p - start, start);
}
return p;
}
njs_int_t
njs_regexp_match(njs_vm_t *vm, njs_regex_t *regex, const u_char *subject,
size_t len, njs_regex_match_data_t *match_data)
{
njs_int_t ret;
njs_trace_handler_t handler;
handler = vm->trace.handler;
vm->trace.handler = njs_regexp_match_trace_handler;
ret = njs_regex_match(regex, subject, len, match_data, vm->regex_context);
vm->trace.handler = handler;
return ret;
}
static u_char *
njs_regexp_match_trace_handler(njs_trace_t *trace, njs_trace_data_t *td,
u_char *start)
{
u_char *p;
njs_vm_t *vm;
vm = trace->data;
trace = trace->next;
p = trace->handler(trace, td, start);
njs_internal_error(vm, (const char *) start);
return p;
}
njs_regexp_t *
njs_regexp_alloc(njs_vm_t *vm, njs_regexp_pattern_t *pattern)
{
njs_regexp_t *regexp;
regexp = njs_mp_alloc(vm->mem_pool, sizeof(njs_regexp_t));
if (njs_fast_path(regexp != NULL)) {
njs_lvlhsh_init(&regexp->object.hash);
regexp->object.shared_hash = vm->shared->regexp_instance_hash;
regexp->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_REGEXP].object;
regexp->object.slots = NULL;
regexp->object.type = NJS_REGEXP;
regexp->object.shared = 0;
regexp->object.extensible = 1;
regexp->object.fast_array = 0;
regexp->object.error_data = 0;
njs_set_number(&regexp->last_index, 0);
regexp->pattern = pattern;
njs_string_short_set(&regexp->string, 0, 0);
return regexp;
}
njs_memory_error(vm);
return NULL;
}
static njs_int_t
njs_regexp_prototype_last_index(njs_vm_t *vm, njs_object_prop_t *unused,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
uint32_t index, last_index;
njs_regexp_t *regexp;
njs_string_prop_t string;
regexp = njs_object_proto_lookup(njs_object(value), NJS_REGEXP,
njs_regexp_t);
if (njs_slow_path(regexp == NULL)) {
njs_set_undefined(retval);
return NJS_DECLINED;
}
if (setval != NULL) {
regexp->last_index = *setval;
*retval = *setval;
return NJS_OK;
}
if (njs_slow_path(!njs_is_number(&regexp->last_index))) {
*retval = regexp->last_index;
return NJS_OK;
}
(void) njs_string_prop(&string, &regexp->string);
last_index = njs_number(&regexp->last_index);
if (njs_slow_path(string.size < last_index)) {
*retval = regexp->last_index;
return NJS_OK;
}
index = njs_string_index(&string, last_index);
njs_set_number(retval, index);
return NJS_OK;
}
static njs_int_t
njs_regexp_prototype_global(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
njs_regexp_pattern_t *pattern;
pattern = njs_regexp_pattern(value);
*retval = pattern->global ? njs_value_true : njs_value_false;
njs_release(vm, value);
return NJS_OK;
}
static njs_int_t
njs_regexp_prototype_ignore_case(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
njs_regexp_pattern_t *pattern;
pattern = njs_regexp_pattern(value);
*retval = pattern->ignore_case ? njs_value_true : njs_value_false;
njs_release(vm, value);
return NJS_OK;
}
static njs_int_t
njs_regexp_prototype_multiline(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
njs_regexp_pattern_t *pattern;
pattern = njs_regexp_pattern(value);
*retval = pattern->multiline ? njs_value_true : njs_value_false;
njs_release(vm, value);
return NJS_OK;
}
static njs_int_t
njs_regexp_prototype_source(njs_vm_t *vm, njs_object_prop_t *prop,
njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
u_char *source;
int32_t length;
uint32_t size;
njs_regexp_pattern_t *pattern;
pattern = njs_regexp_pattern(value);
/* Skip starting "/". */
source = pattern->source + 1;
size = njs_strlen(source) - pattern->flags;
length = njs_utf8_length(source, size);
return njs_regexp_string_create(vm, retval, source, size, length);
}
static njs_int_t
njs_regexp_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
njs_uint_t nargs, njs_index_t unused)
{
if (njs_is_regexp(njs_arg(args, nargs, 0))) {
return njs_regexp_to_string(vm, &vm->retval, &args[0]);
}
njs_type_error(vm, "\"this\" argument is not a regexp");
return NJS_ERROR;
}
njs_int_t
njs_regexp_to_string(njs_vm_t *vm, njs_value_t *retval,
const njs_value_t *value)
{
u_char *source;
int32_t length;
uint32_t size;
njs_regexp_pattern_t *pattern;
pattern = njs_regexp_pattern(value);
source = pattern->source;
size = njs_strlen(source);
length = njs_utf8_length(source, size);
return njs_regexp_string_create(vm, retval, source, size, length);
}
static njs_int_t
njs_regexp_prototype_test(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int *captures;
int64_t last_index;
njs_int_t ret, match;
njs_uint_t n;
njs_regex_t *regex;
njs_regexp_t *regexp;
njs_value_t *value, lvalue;
const njs_value_t *retval;
njs_string_prop_t string;
njs_regexp_pattern_t *pattern;
njs_regex_match_data_t *match_data;
if (!njs_is_regexp(njs_arg(args, nargs, 0))) {
njs_type_error(vm, "\"this\" argument is not a regexp");
return NJS_ERROR;
}
retval = &njs_value_false;
value = njs_lvalue_arg(&lvalue, args, nargs, 1);
if (!njs_is_string(value)) {
ret = njs_value_to_string(vm, value, value);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
(void) njs_string_prop(&string, value);
n = (string.length != 0);
regexp = njs_regexp(njs_argument(args, 0));
pattern = njs_regexp_pattern(&args[0]);
regex = &pattern->regex[n];
match_data = vm->single_match_data;
if (njs_regex_is_valid(regex)) {
if (njs_regex_backrefs(regex) != 0) {
match_data = njs_regex_match_data(regex, vm->regex_context);
if (njs_slow_path(match_data == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
}
match = njs_regexp_match(vm, regex, string.start, string.size,
match_data);
if (match >= 0) {
retval = &njs_value_true;
} else if (match != NJS_REGEX_NOMATCH) {
ret = NJS_ERROR;
goto done;
}
if (pattern->global) {
ret = njs_value_to_length(vm, &regexp->last_index, &last_index);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
if (match >= 0) {
captures = njs_regex_captures(match_data);
last_index += captures[1];
} else {
last_index = 0;
}
njs_set_number(&regexp->last_index, last_index);
}
}
ret = NJS_OK;
vm->retval = *retval;
done:
if (match_data != vm->single_match_data) {
njs_regex_match_data_free(match_data, vm->regex_context);
}
return ret;
}
njs_int_t
njs_regexp_prototype_exec(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
njs_index_t unused)
{
int64_t last_index;
njs_int_t ret;
njs_utf8_t utf8;
njs_value_t *value, lvalue;
njs_regexp_t *regexp;
njs_string_prop_t string;
njs_regexp_utf8_t type;
njs_regexp_pattern_t *pattern;
njs_regex_match_data_t *match_data;
if (!njs_is_regexp(njs_arg(args, nargs, 0))) {
njs_type_error(vm, "\"this\" argument is not a regexp");
return NJS_ERROR;
}
value = njs_lvalue_arg(&lvalue, args, nargs, 1);
if (!njs_is_string(value)) {
ret = njs_value_to_string(vm, value, value);
if (njs_slow_path(ret != NJS_OK)) {
return ret;
}
}
regexp = njs_regexp(&args[0]);
regexp->string = *value;
pattern = regexp->pattern;
ret = njs_value_to_length(vm, &regexp->last_index, &last_index);
if (njs_slow_path(ret != NJS_OK)) {
return NJS_ERROR;
}
if (!pattern->global) {
last_index = 0;
}
(void) njs_string_prop(&string, value);
if (string.size >= (size_t) last_index) {
utf8 = NJS_STRING_BYTE;
type = NJS_REGEXP_BYTE;
if (string.length != 0) {
utf8 = NJS_STRING_ASCII;
type = NJS_REGEXP_UTF8;
if (string.length != string.size) {
utf8 = NJS_STRING_UTF8;
}
}
pattern = regexp->pattern;
if (njs_regex_is_valid(&pattern->regex[type])) {
string.start += last_index;
string.size -= last_index;
match_data = njs_regex_match_data(&pattern->regex[type],
vm->regex_context);
if (njs_slow_path(match_data == NULL)) {
njs_memory_error(vm);
return NJS_ERROR;
}
ret = njs_regexp_match(vm, &pattern->regex[type], string.start,
string.size, match_data);
if (ret >= 0) {
return njs_regexp_exec_result(vm, regexp, utf8, string.start,
match_data, last_index);
}
if (njs_slow_path(ret != NJS_REGEX_NOMATCH)) {
njs_regex_match_data_free(match_data, vm->regex_context);
return NJS_ERROR;
}
}
}
if (pattern->global) {
njs_set_number(&regexp->last_index, 0);
}
vm->retval = njs_value_null;
return NJS_OK;
}
static njs_int_t
njs_regexp_exec_result(njs_vm_t *vm, njs_regexp_t *regexp, njs_utf8_t utf8,
u_char *string, njs_regex_match_data_t *match_data, uint32_t last_index)
{
int *captures;
u_char *start;
int32_t size, length;
njs_int_t ret;
njs_uint_t i, n;
njs_array_t *array;
njs_value_t name;
njs_object_t *groups;
njs_object_prop_t *prop;
njs_regexp_group_t *group;
njs_lvlhsh_query_t lhq;
static const njs_value_t string_index = njs_string("index");
static const njs_value_t string_input = njs_string("input");
static const njs_value_t string_groups = njs_string("groups");
array = njs_array_alloc(vm, 0, regexp->pattern->ncaptures, 0);
if (njs_slow_path(array == NULL)) {
goto fail;
}
captures = njs_regex_captures(match_data);
for (i = 0; i < regexp->pattern->ncaptures; i++) {
n = 2 * i;
if (captures[n] != -1) {
start = &string[captures[n]];
size = captures[n + 1] - captures[n];
length = njs_string_calc_length(utf8, start, size);
ret = njs_regexp_string_create(vm, &array->start[i], start, size,
length);
if (njs_slow_path(ret != NJS_OK)) {
goto fail;
}
} else {
njs_set_undefined(&array->start[i]);
}
}
prop = njs_object_prop_alloc(vm, &string_index, &njs_value_undefined, 1);
if (njs_slow_path(prop == NULL)) {
goto fail;
}
/* TODO: Non UTF-8 position */
njs_set_number(&prop->value, last_index + captures[0]);
if (regexp->pattern->global) {
njs_set_number(&regexp->last_index, last_index + captures[1]);
}
lhq.key_hash = NJS_INDEX_HASH;
lhq.key = njs_str_value("index");
lhq.replace = 0;
lhq.value = prop;
lhq.pool = vm->mem_pool;
lhq.proto = &njs_object_hash_proto;
ret = njs_lvlhsh_insert(&array->object.hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
goto insert_fail;
}
prop = njs_object_prop_alloc(vm, &string_input, &regexp->string, 1);
if (njs_slow_path(prop == NULL)) {
goto fail;
}
lhq.key_hash = NJS_INPUT_HASH;
lhq.key = njs_str_value("input");
lhq.value = prop;
ret = njs_lvlhsh_insert(&array->object.hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
goto insert_fail;
}
prop = njs_object_prop_alloc(vm, &string_groups, &njs_value_undefined, 1);
if (njs_slow_path(prop == NULL)) {
goto fail;
}
lhq.key_hash = NJS_GROUPS_HASH;
lhq.key = njs_str_value("groups");
lhq.value = prop;
ret = njs_lvlhsh_insert(&array->object.hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
goto insert_fail;
}
if (regexp->pattern->ngroups != 0) {
groups = njs_object_alloc(vm);
if (njs_slow_path(groups == NULL)) {
goto fail;
}
njs_set_object(&prop->value, groups);
i = 0;
do {
group = &regexp->pattern->groups[i];
ret = njs_string_set(vm, &name, group->name.start,
group->name.length);
if (njs_slow_path(ret != NJS_OK)) {
goto fail;
}
prop = njs_object_prop_alloc(vm, &name,
&array->start[group->capture], 1);
if (njs_slow_path(prop == NULL)) {
goto fail;
}
lhq.key_hash = group->hash;
lhq.key = group->name;
lhq.value = prop;
ret = njs_lvlhsh_insert(&groups->hash, &lhq);
if (njs_slow_path(ret != NJS_OK)) {
goto insert_fail;
}
i++;
} while (i < regexp->pattern->ngroups);
}
njs_set_array(&vm->retval, array);
ret = NJS_OK;
goto done;
insert_fail:
njs_internal_error(vm, "lvlhsh insert failed");
fail:
ret = NJS_ERROR;
done:
njs_regex_match_data_free(match_data, vm->regex_context);
return ret;
}
static njs_int_t
njs_regexp_string_create(njs_vm_t *vm, njs_value_t *value, u_char *start,
uint32_t size, int32_t length)
{
length = (length >= 0) ? length : 0;
return njs_string_new(vm, value, start, size, length);
}
static const njs_object_prop_t njs_regexp_constructor_properties[] =
{
{
.type = NJS_PROPERTY,
.name = njs_string("name"),
.value = njs_string("RegExp"),
.configurable = 1,
},
{
.type = NJS_PROPERTY,
.name = njs_string("length"),
.value = njs_value(NJS_NUMBER, 1, 2.0),
.configurable = 1,
},
{
.type = NJS_PROPERTY_HANDLER,
.name = njs_string("prototype"),
.value = njs_prop_handler(njs_object_prototype_create),
},
};
const njs_object_init_t njs_regexp_constructor_init = {
njs_regexp_constructor_properties,
njs_nitems(njs_regexp_constructor_properties),
};
static const njs_object_prop_t njs_regexp_prototype_properties[] =
{
{
.type = NJS_PROPERTY_HANDLER,
.name = njs_string("constructor"),
.value = njs_prop_handler(njs_object_prototype_create_constructor),
.writable = 1,
.configurable = 1,
},
{
.type = NJS_PROPERTY_HANDLER,
.name = njs_string("global"),
.value = njs_prop_handler(njs_regexp_prototype_global),
.configurable = 1,
},
{
.type = NJS_PROPERTY_HANDLER,
.name = njs_string("ignoreCase"),
.value = njs_prop_handler(njs_regexp_prototype_ignore_case),
.configurable = 1,
},
{
.type = NJS_PROPERTY_HANDLER,
.name = njs_string("multiline"),
.value = njs_prop_handler(njs_regexp_prototype_multiline),
.configurable = 1,
},
{
.type = NJS_PROPERTY_HANDLER,
.name = njs_string("source"),
.value = njs_prop_handler(njs_regexp_prototype_source),
.configurable = 1,
},
{
.type = NJS_PROPERTY,
.name = njs_string("toString"),
.value = njs_native_function(njs_regexp_prototype_to_string, 0),
.writable = 1,
.configurable = 1,
},
{
.type = NJS_PROPERTY,
.name = njs_string("test"),
.value = njs_native_function(njs_regexp_prototype_test, 1),
.writable = 1,
.configurable = 1,
},
{
.type = NJS_PROPERTY,
.name = njs_string("exec"),
.value = njs_native_function(njs_regexp_prototype_exec, 1),
.writable = 1,
.configurable = 1,
},
};
const njs_object_prop_t njs_regexp_instance_properties[] =
{
{
.type = NJS_PROPERTY_HANDLER,
.name = njs_string("lastIndex"),
.value = njs_prop_handler(njs_regexp_prototype_last_index),
.writable = 1,
},
};
const njs_object_init_t njs_regexp_instance_init = {
njs_regexp_instance_properties,
njs_nitems(njs_regexp_instance_properties),
};
const njs_object_init_t njs_regexp_prototype_init = {
njs_regexp_prototype_properties,
njs_nitems(njs_regexp_prototype_properties),
};
const njs_object_type_init_t njs_regexp_type_init = {
.constructor = njs_native_ctor(njs_regexp_constructor, 2, 0),
.constructor_props = &njs_regexp_constructor_init,
.prototype_props = &njs_regexp_prototype_init,
.prototype_value = { .object = { .type = NJS_REGEXP } },
};