From 0507a90bbefeb04ee439ee93f6dd0449a10a0249 Mon Sep 17 00:00:00 2001 From: Valentin Bartenev Date: Thu, 12 Dec 2019 18:50:27 +0300 Subject: [PATCH] Introduced nullish coalescing operator. --- src/njs_disassembler.c | 12 ++++++ src/njs_generator.c | 1 + src/njs_lexer.c | 11 +++++ src/njs_lexer.h | 2 + src/njs_parser_expression.c | 81 +++++++++++++++++++++++++++++++++++-- src/njs_vmcode.c | 9 ++++- src/njs_vmcode.h | 28 +++++++------ src/test/njs_unit_test.c | 17 ++++++++ 8 files changed, 143 insertions(+), 18 deletions(-) diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c index 7f299146..e8f00e7a 100644 --- a/src/njs_disassembler.c +++ b/src/njs_disassembler.c @@ -282,6 +282,18 @@ njs_disassemble(u_char *start, u_char *end) continue; } + if (operation == NJS_VMCODE_COALESCE) { + test_jump = (njs_vmcode_test_jump_t *) p; + + njs_printf("%05uz COALESCE %04Xz %04Xz +%uz\n", + p - start, (size_t) test_jump->retval, + (size_t) test_jump->value, (size_t) test_jump->offset); + + p += sizeof(njs_vmcode_test_jump_t); + + continue; + } + if (operation == NJS_VMCODE_FUNCTION_FRAME) { function = (njs_vmcode_function_frame_t *) p; diff --git a/src/njs_generator.c b/src/njs_generator.c index 1794101f..603d96b1 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -361,6 +361,7 @@ njs_generate(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) case NJS_TOKEN_LOGICAL_AND: case NJS_TOKEN_LOGICAL_OR: + case NJS_TOKEN_COALESCE: return njs_generate_test_jump_expression(vm, generator, node); case NJS_TOKEN_DELETE: diff --git a/src/njs_lexer.c b/src/njs_lexer.c index 61726407..5bd19abe 100644 --- a/src/njs_lexer.c +++ b/src/njs_lexer.c @@ -276,6 +276,11 @@ static const njs_lexer_multi_t njs_greater_token[] = { }; +static const njs_lexer_multi_t njs_conditional_token[] = { + { '?', NJS_TOKEN_COALESCE, 0, NULL }, +}; + + static const njs_lexer_multi_t njs_assignment_token[] = { { '=', NJS_TOKEN_EQUAL, 1, njs_strict_equal_token }, { '>', NJS_TOKEN_ARROW, 0, NULL }, @@ -551,6 +556,12 @@ njs_lexer_next_token(njs_lexer_t *lexer, njs_lexer_token_t *lt) goto multi; + case NJS_TOKEN_CONDITIONAL: + n = njs_nitems(njs_conditional_token), + multi = njs_conditional_token; + + goto multi; + case NJS_TOKEN_LINE_END: lexer->line++; diff --git a/src/njs_lexer.h b/src/njs_lexer.h index 7890e03d..8c3abb82 100644 --- a/src/njs_lexer.h +++ b/src/njs_lexer.h @@ -95,6 +95,8 @@ typedef enum { NJS_TOKEN_BITWISE_NOT, NJS_TOKEN_LOGICAL_NOT, + NJS_TOKEN_COALESCE, + NJS_TOKEN_IN, NJS_TOKEN_INSTANCEOF, NJS_TOKEN_TYPEOF, diff --git a/src/njs_parser_expression.c b/src/njs_parser_expression.c index 7997b210..b83a3573 100644 --- a/src/njs_parser_expression.c +++ b/src/njs_parser_expression.c @@ -41,6 +41,8 @@ static njs_token_t njs_parser_any_expression(njs_vm_t *vm, njs_token_t token); static njs_token_t njs_parser_conditional_expression(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token); +static njs_token_t njs_parser_coalesce_expression(njs_vm_t *vm, + njs_parser_t *parser, njs_token_t token); static njs_token_t njs_parser_binary_expression(njs_vm_t *vm, njs_parser_t *parser, const njs_parser_expression_t *expr, njs_token_t token); @@ -359,9 +361,7 @@ njs_parser_conditional_expression(njs_vm_t *vm, njs_parser_t *parser, { njs_parser_node_t *node, *cond; - token = njs_parser_binary_expression(vm, parser, - &njs_parser_logical_or_expression, - token); + token = njs_parser_coalesce_expression(vm, parser, token); if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { return token; } @@ -420,6 +420,81 @@ njs_parser_conditional_expression(njs_vm_t *vm, njs_parser_t *parser, } +static njs_token_t +njs_parser_coalesce_expression(njs_vm_t *vm, njs_parser_t *parser, + njs_token_t token) +{ + njs_token_t prev_token, next_token; + njs_parser_node_t *node; + + token = njs_parser_binary_expression(vm, parser, + &njs_parser_logical_or_expression, + token); + if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + for ( ;; ) { + if (token != NJS_TOKEN_COALESCE) { + return token; + } + + prev_token = parser->lexer->prev_token; + + node = njs_parser_node_new(vm, parser, NJS_TOKEN_COALESCE); + if (njs_slow_path(node == NULL)) { + return NJS_TOKEN_ERROR; + } + + node->u.operation = NJS_VMCODE_COALESCE; + node->left = parser->node; + node->left->dest = node; + + token = njs_parser_token(vm, parser); + if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + next_token = token; + + token = njs_parser_binary_expression(vm, parser, + &njs_parser_logical_or_expression, + token); + if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { + return token; + } + + node->right = parser->node; + node->right->dest = node; + parser->node = node; + + if (prev_token != NJS_TOKEN_CLOSE_PARENTHESIS + && njs_slow_path(node->left->token == NJS_TOKEN_LOGICAL_OR + || node->left->token == NJS_TOKEN_LOGICAL_AND)) + { + token = node->left->token; + goto require_parentheses; + } + + if (next_token != NJS_TOKEN_OPEN_PARENTHESIS + && njs_slow_path(node->right->token == NJS_TOKEN_LOGICAL_OR + || node->right->token == NJS_TOKEN_LOGICAL_AND)) + { + token = node->right->token; + goto require_parentheses; + } + } + +require_parentheses: + + njs_parser_syntax_error(vm, parser, "Either \"??\" or \"%s\" expression " + "must be parenthesized", + (token == NJS_TOKEN_LOGICAL_OR) ? "||" + : "&&"); + return NJS_TOKEN_ILLEGAL; +} + + static njs_token_t njs_parser_binary_expression(njs_vm_t *vm, njs_parser_t *parser, const njs_parser_expression_t *expr, njs_token_t token) diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 5fdbcf5f..5f6e36e4 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -503,9 +503,14 @@ next: case NJS_VMCODE_TEST_IF_TRUE: case NJS_VMCODE_TEST_IF_FALSE: - ret = njs_is_true(value1); + case NJS_VMCODE_COALESCE: + if (op == NJS_VMCODE_COALESCE) { + ret = !njs_is_null_or_undefined(value1); - ret ^= op - NJS_VMCODE_TEST_IF_TRUE; + } else { + ret = njs_is_true(value1); + ret ^= op - NJS_VMCODE_TEST_IF_TRUE; + } if (ret) { test_jump = (njs_vmcode_test_jump_t *) pc; diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h index 334f30f4..b6e4062b 100644 --- a/src/njs_vmcode.h +++ b/src/njs_vmcode.h @@ -105,19 +105,21 @@ typedef uint8_t njs_vmcode_operation_t; #define NJS_VMCODE_TEST_IF_TRUE VMCODE1(34) #define NJS_VMCODE_TEST_IF_FALSE VMCODE1(35) -#define NJS_VMCODE_UNARY_PLUS VMCODE1(36) -#define NJS_VMCODE_UNARY_NEGATION VMCODE1(37) -#define NJS_VMCODE_BITWISE_NOT VMCODE1(38) -#define NJS_VMCODE_LOGICAL_NOT VMCODE1(39) -#define NJS_VMCODE_OBJECT VMCODE1(40) -#define NJS_VMCODE_ARRAY VMCODE1(41) -#define NJS_VMCODE_FUNCTION VMCODE1(42) -#define NJS_VMCODE_REGEXP VMCODE1(43) - -#define NJS_VMCODE_INSTANCE_OF VMCODE1(44) -#define NJS_VMCODE_TYPEOF VMCODE1(45) -#define NJS_VMCODE_VOID VMCODE1(46) -#define NJS_VMCODE_DELETE VMCODE1(47) +#define NJS_VMCODE_COALESCE VMCODE1(36) + +#define NJS_VMCODE_UNARY_PLUS VMCODE1(37) +#define NJS_VMCODE_UNARY_NEGATION VMCODE1(38) +#define NJS_VMCODE_BITWISE_NOT VMCODE1(39) +#define NJS_VMCODE_LOGICAL_NOT VMCODE1(40) +#define NJS_VMCODE_OBJECT VMCODE1(41) +#define NJS_VMCODE_ARRAY VMCODE1(42) +#define NJS_VMCODE_FUNCTION VMCODE1(43) +#define NJS_VMCODE_REGEXP VMCODE1(44) + +#define NJS_VMCODE_INSTANCE_OF VMCODE1(45) +#define NJS_VMCODE_TYPEOF VMCODE1(46) +#define NJS_VMCODE_VOID VMCODE1(47) +#define NJS_VMCODE_DELETE VMCODE1(48) #define NJS_VMCODE_NOP 255 diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 7482ed6e..9d0ae40b 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -1295,6 +1295,23 @@ static njs_unit_test_t njs_test[] = { njs_str("false && (true || true)"), njs_str("false") }, + { njs_str("true && (null ?? true)"), + njs_str("true") }, + + { njs_str("(null || undefined) ?? (true && true)"), + njs_str("true") }, + + { njs_str("undefined ?? null ?? false ?? true"), + njs_str("false") }, + + { njs_str("1 && 1 ?? true"), + njs_str("SyntaxError: Either \"??\" or \"&&\" expression " + "must be parenthesized in 1") }, + + { njs_str("null ?? 0 || 1"), + njs_str("SyntaxError: Either \"??\" or \"||\" expression " + "must be parenthesized in 1") }, + { njs_str("var a = true; a = -~!a"), njs_str("1") }, -- 2.47.3