From dfd9d4497ae5955cedb7f6cea7dfb71fe747c82a Mon Sep 17 00:00:00 2001 From: kisbg Date: Fri, 17 Jul 2020 15:42:51 +0200 Subject: [PATCH] Implement nullish coalescing operator (#3997) JerryScript-DCO-1.0-Signed-off-by: bence gabor kis kisbg@inf.u-szeged.hu --- jerry-core/parser/js/byte-code.c | 2 +- jerry-core/parser/js/byte-code.h | 6 +- jerry-core/parser/js/js-lexer.c | 19 ++- jerry-core/parser/js/js-lexer.h | 51 +++--- jerry-core/parser/js/js-parser-expr.c | 194 +++++++++++++++++++--- jerry-core/parser/js/js-parser-internal.h | 2 + jerry-core/parser/js/js-parser-mem.c | 23 +++ jerry-core/parser/js/js-parser-statm.c | 24 --- jerry-core/parser/js/js-parser-util.c | 4 + jerry-core/parser/js/js-parser.h | 1 + jerry-core/vm/vm.c | 14 ++ jerry-core/vm/vm.h | 4 + tests/jerry/es.next/nullish-coalescing.js | 51 ++++++ 13 files changed, 323 insertions(+), 72 deletions(-) create mode 100644 tests/jerry/es.next/nullish-coalescing.js diff --git a/jerry-core/parser/js/byte-code.c b/jerry-core/parser/js/byte-code.c index 5445198eb..38b5bc205 100644 --- a/jerry-core/parser/js/byte-code.c +++ b/jerry-core/parser/js/byte-code.c @@ -27,7 +27,7 @@ JERRY_STATIC_ASSERT ((sizeof (cbc_uint16_arguments_t) % sizeof (jmem_cpointer_t) */ JERRY_STATIC_ASSERT (CBC_END == 238, number_of_cbc_opcodes_changed); -JERRY_STATIC_ASSERT (CBC_EXT_END == 132, +JERRY_STATIC_ASSERT (CBC_EXT_END == 135, number_of_cbc_ext_opcodes_changed); #if ENABLED (JERRY_PARSER) diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index 645dcf48a..1e8a3cf97 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -576,6 +576,10 @@ VM_OC_INITIALIZER_PUSH_PROP | VM_OC_GET_STACK) \ CBC_FORWARD_BRANCH (CBC_EXT_DEFAULT_INITIALIZER, -1, \ VM_OC_DEFAULT_INITIALIZER) \ + CBC_OPCODE (CBC_EXT_ERROR, CBC_NO_FLAG, 0, \ + VM_OC_ERROR) \ + CBC_FORWARD_BRANCH (CBC_EXT_BRANCH_IF_NULLISH, -1, \ + VM_OC_BRANCH_IF_NULLISH) \ \ /* Basic opcodes. */ \ CBC_OPCODE (CBC_EXT_PUSH_LITERAL_PUSH_NUMBER_0, CBC_HAS_LITERAL_ARG, 2, \ @@ -600,8 +604,6 @@ VM_OC_GET_TEMPLATE_OBJECT | VM_OC_PUT_STACK) \ CBC_OPCODE (CBC_EXT_LINE, CBC_NO_FLAG, 0, \ VM_OC_LINE) \ - CBC_OPCODE (CBC_EXT_ERROR, CBC_NO_FLAG, 0, \ - VM_OC_ERROR) \ CBC_OPCODE (CBC_EXT_THROW_REFERENCE_ERROR, CBC_NO_FLAG, 1, \ VM_OC_THROW_REFERENCE_ERROR) \ CBC_OPCODE (CBC_EXT_THROW_ASSIGN_CONST_ERROR, CBC_NO_FLAG, 0, \ diff --git a/jerry-core/parser/js/js-lexer.c b/jerry-core/parser/js/js-lexer.c index 20f2551e5..3dd07885a 100644 --- a/jerry-core/parser/js/js-lexer.c +++ b/jerry-core/parser/js/js-lexer.c @@ -1753,7 +1753,24 @@ lexer_next_token (parser_context_t *context_p) /**< context */ LEXER_ASSIGN_BIT_XOR) LEXER_TYPE_A_TOKEN (LIT_CHAR_TILDE, LEXER_BIT_NOT); - LEXER_TYPE_A_TOKEN (LIT_CHAR_QUESTION, LEXER_QUESTION_MARK); + case (uint8_t) (LIT_CHAR_QUESTION): + { +#if ENABLED (JERRY_ESNEXT) + if (length >= 2) + { + if (context_p->source_p[1] == (uint8_t) LIT_CHAR_QUESTION) + { + context_p->token.type = LEXER_NULLISH_COALESCING; + length = 2; + break; + } + } +#endif /* ENABLED (JERRY_ESNEXT) */ + context_p->token.type = LEXER_QUESTION_MARK; + length = 1; + break; + } + LEXER_TYPE_A_TOKEN (LIT_CHAR_COLON, LEXER_COLON); case LIT_CHAR_SINGLE_QUOTE: diff --git a/jerry-core/parser/js/js-lexer.h b/jerry-core/parser/js/js-lexer.h index 0e5db50ed..f646f84db 100644 --- a/jerry-core/parser/js/js-lexer.h +++ b/jerry-core/parser/js/js-lexer.h @@ -98,31 +98,34 @@ typedef enum LEXER_ASSIGN_BIT_OR, /**< "|=" (prec: 3) */ LEXER_ASSIGN_BIT_XOR, /**< "^=" (prec: 3) */ LEXER_QUESTION_MARK, /**< "?" (prec: 4) */ - LEXER_LOGICAL_OR, /**< "||" (prec: 5) */ - LEXER_LOGICAL_AND, /**< "&&" (prec: 6) */ - LEXER_BIT_OR, /**< "|" (prec: 7) */ - LEXER_BIT_XOR, /**< "^" (prec: 8) */ - LEXER_BIT_AND, /**< "&" (prec: 9) */ - LEXER_EQUAL, /**< "==" (prec: 10) */ - LEXER_NOT_EQUAL, /**< "!=" (prec: 10) */ - LEXER_STRICT_EQUAL, /**< "===" (prec: 10) */ - LEXER_STRICT_NOT_EQUAL, /**< "!==" (prec: 10) */ - LEXER_LESS, /**< "<" (prec: 11) */ - LEXER_GREATER, /**< ">" (prec: 11) */ - LEXER_LESS_EQUAL, /**< "<=" (prec: 11) */ - LEXER_GREATER_EQUAL, /**< ">=" (prec: 11) */ - LEXER_KEYW_IN, /**< in (prec: 11) */ - LEXER_KEYW_INSTANCEOF, /**< instanceof (prec: 11) */ - LEXER_LEFT_SHIFT, /**< "<<" (prec: 12) */ - LEXER_RIGHT_SHIFT, /**< ">>" (prec: 12) */ - LEXER_UNS_RIGHT_SHIFT, /**< ">>>" (prec: 12) */ - LEXER_ADD, /**< "+" (prec: 13) */ - LEXER_SUBTRACT, /**< "-" (prec: 13) */ - LEXER_MULTIPLY, /**< "*" (prec: 14) */ - LEXER_DIVIDE, /**< "/" (prec: 14) */ - LEXER_MODULO, /**< "%" (prec: 14) */ #if ENABLED (JERRY_ESNEXT) - LEXER_EXPONENTIATION, /**< "**" (prec: 15) */ + LEXER_NULLISH_COALESCING, /**< "??" (prec: 5) */ +#endif /* ENABLED (JERRY_ESNEXT) */ + LEXER_LOGICAL_OR, /**< "||" (prec: 6) */ + LEXER_LOGICAL_AND, /**< "&&" (prec: 7) */ + LEXER_BIT_OR, /**< "|" (prec: 8) */ + LEXER_BIT_XOR, /**< "^" (prec: 9) */ + LEXER_BIT_AND, /**< "&" (prec: 10) */ + LEXER_EQUAL, /**< "==" (prec: 11) */ + LEXER_NOT_EQUAL, /**< "!=" (prec: 11) */ + LEXER_STRICT_EQUAL, /**< "===" (prec: 11) */ + LEXER_STRICT_NOT_EQUAL, /**< "!==" (prec: 11) */ + LEXER_LESS, /**< "<" (prec: 12) */ + LEXER_GREATER, /**< ">" (prec: 12) */ + LEXER_LESS_EQUAL, /**< "<=" (prec: 12) */ + LEXER_GREATER_EQUAL, /**< ">=" (prec: 12) */ + LEXER_KEYW_IN, /**< in (prec: 12) */ + LEXER_KEYW_INSTANCEOF, /**< instanceof (prec: 12) */ + LEXER_LEFT_SHIFT, /**< "<<" (prec: 13) */ + LEXER_RIGHT_SHIFT, /**< ">>" (prec: 13) */ + LEXER_UNS_RIGHT_SHIFT, /**< ">>>" (prec: 13) */ + LEXER_ADD, /**< "+" (prec: 14) */ + LEXER_SUBTRACT, /**< "-" (prec: 14) */ + LEXER_MULTIPLY, /**< "*" (prec: 15) */ + LEXER_DIVIDE, /**< "/" (prec: 15) */ + LEXER_MODULO, /**< "%" (prec: 15) */ +#if ENABLED (JERRY_ESNEXT) + LEXER_EXPONENTIATION, /**< "**" (prec: 16) */ #endif /* ENABLED (JERRY_ESNEXT) */ LEXER_LEFT_BRACE, /**< "{" */ diff --git a/jerry-core/parser/js/js-parser-expr.c b/jerry-core/parser/js/js-parser-expr.c index 1a46cfdea..cc82778d7 100644 --- a/jerry-core/parser/js/js-parser-expr.c +++ b/jerry-core/parser/js/js-parser-expr.c @@ -35,7 +35,7 @@ /** * Maximum precedence for right-to-left binary operation evaluation. */ -#define PARSER_RIGHT_TO_LEFT_ORDER_MAX_PRECEDENCE 6 +#define PARSER_RIGHT_TO_LEFT_ORDER_MAX_PRECEDENCE 7 /** * Precedence for ternary operation. @@ -45,12 +45,17 @@ /** * Precedence for exponentiation operation. */ -#define PARSER_RIGHT_TO_LEFT_ORDER_EXPONENTIATION 15 +#define PARSER_RIGHT_TO_LEFT_ORDER_EXPONENTIATION 16 /** * Value of grouping level increase and decrease. */ -#define PARSER_GROUPING_LEVEL_INCREASE 2 +#define PARSER_GROUPING_LEVEL_INCREASE 4 + +/** + * Represents whether logical expression was emitted in the current group expression. + */ +#define PARSER_GROUPING_LOGICAL_FOUND (1 << 1) /** * Precedence of the binary tokens. @@ -60,21 +65,56 @@ */ static const uint8_t parser_binary_precedence_table[] = { - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, /**< "=" */ + 3, /**< "+=" */ + 3, /**< "-=" */ + 3, /**< "*=" */ + 3, /**< "/=" */ + 3, /**< "=" */ + 3, /**< "<<=" */ + 3, /**< ">>=" */ + 3, /**< ">>>=" */ + 3, /**< "&=" */ + 3, /**< "|=" */ + 3, /**< "^=" */ #if ENABLED (JERRY_ESNEXT) - 3, + 3, /**< "**=" */ #endif /* ENABLED (JERRY_ESNEXT) */ - 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, - 11, 11, 11, 11, 11, 11, 12, 12, 12, - 13, 13, 14, 14, 14, + 4, /**< "?"*/ #if ENABLED (JERRY_ESNEXT) - 15, + 5, /**< "??" */ +#endif /* ENABLED (JERRY_ESNEXT) */ + 6, /**< "||" */ + 7, /**< "&&" */ + 8, /**< "|" */ + 9, /**< "^" */ + 10, /**< "&" */ + 11, /**< "==" */ + 11, /**< "!=" */ + 11, /**< "===" */ + 11, /**< "!==" */ + 12, /**< "<" */ + 12, /**< ">" */ + 12, /**< "<=" */ + 12, /**< ">=" */ + 12, /**< in */ + 12, /**< instanceof */ + 13, /**< "<<" */ + 13, /**< ">>" */ + 13, /**< ">>>" */ + 14, /**< "+" */ + 14, /**< "-" */ + 15, /**< "*" */ + 15, /**< "/" */ + 15, /**< "%" */ +#if ENABLED (JERRY_ESNEXT) + 16, /**< "**" */ #endif /* ENABLED (JERRY_ESNEXT) */ }; #if ENABLED (JERRY_ESNEXT) -JERRY_STATIC_ASSERT (sizeof (parser_binary_precedence_table) == 38, - parser_binary_precedence_table_should_have_38_values_in_es2015); +JERRY_STATIC_ASSERT (sizeof (parser_binary_precedence_table) == 39, + parser_binary_precedence_table_should_have_39_values_in_es2015); #else /* !ENABLED (JERRY_ESNEXT) */ JERRY_STATIC_ASSERT (sizeof (parser_binary_precedence_table) == 36, parser_binary_precedence_table_should_have_36_values_in_es51); @@ -2653,14 +2693,87 @@ parser_append_binary_single_assignment_token (parser_context_t *context_p, /**< return assign_opcode; } /* parser_append_binary_single_assignment_token */ +/** + * Check for invalid logical operator and nullish chaining + */ +#if ENABLED (JERRY_ESNEXT) +static void +parser_check_invalid_logical_op (parser_context_t *context_p, /**< context */ + size_t grouping_level) /**< grouping_level */ +{ + parser_stack_iterator_t iterator; + parser_stack_iterator_init (context_p, &iterator); + bool logical_found = (grouping_level & PARSER_GROUPING_LOGICAL_FOUND) != 0; + bool nullish_found = false; + + while (true) + { + uint8_t token = parser_stack_iterator_read_uint8 (&iterator); + + if (token == LEXER_EXPRESSION_START + || token == LEXER_LEFT_PAREN + || !LEXER_IS_BINARY_OP_TOKEN (token)) + { + return; + } + + parser_stack_iterator_skip (&iterator, sizeof (uint8_t)); + + if (token == LEXER_ASSIGN) + { + cbc_opcode_t opcode = (cbc_opcode_t) parser_stack_iterator_read_uint8 (&iterator); + + if (cbc_flags[opcode] & CBC_HAS_LITERAL_ARG) + { + parser_stack_iterator_skip (&iterator, sizeof (uint16_t)); + } + token = parser_stack_iterator_read_uint8 (&iterator); + + if (token == LEXER_ASSIGN_GROUP_EXPR) + { + parser_stack_iterator_skip (&iterator, sizeof (uint8_t)); + } + if (token == LEXER_ASSIGN_CONST) + { + parser_stack_iterator_skip (&iterator, sizeof (uint8_t)); + } + } + else if (token == LEXER_LOGICAL_OR || token == LEXER_LOGICAL_AND) + { + if (nullish_found) + { + parser_raise_error (context_p, PARSER_ERR_INVALID_NULLISH_COALESCING); + } + + parser_stack_iterator_skip (&iterator, sizeof (parser_branch_t)); + logical_found = true; + } + else if (token == LEXER_NULLISH_COALESCING) + { + if (logical_found) + { + parser_raise_error (context_p, PARSER_ERR_INVALID_NULLISH_COALESCING); + } + + parser_stack_iterator_skip (&iterator, sizeof (parser_branch_t)); + nullish_found = true; + } + } +} /* parser_check_invalid_logical_op */ + +#endif /* ENABLED (JERRY_ESNEXT) */ + /** * Append a binary token. */ static void -parser_append_binary_token (parser_context_t *context_p) /**< context */ +parser_append_binary_token (parser_context_t *context_p, /**< context */ + size_t grouping_level) /**< grouping_level */ { JERRY_ASSERT (LEXER_IS_BINARY_OP_TOKEN (context_p->token.type)); - +#if !ENABLED (JERRY_ESNEXT) + JERRY_UNUSED (grouping_level); +#endif /* !ENABLED (JERRY_ESNEXT) */ parser_push_result (context_p); if (context_p->token.type == LEXER_ASSIGN) @@ -2715,7 +2828,25 @@ parser_append_binary_token (parser_context_t *context_p) /**< context */ parser_emit_cbc_forward_branch (context_p, opcode, &branch); parser_stack_push (context_p, &branch, sizeof (parser_branch_t)); + parser_stack_push_uint8 (context_p, context_p->token.type); +#if ENABLED (JERRY_ESNEXT) + parser_check_invalid_logical_op (context_p, grouping_level); +#endif /* ENABLED (JERRY_ESNEXT) */ + return; } +#if ENABLED (JERRY_ESNEXT) + else if (context_p->token.type == LEXER_NULLISH_COALESCING) + { + parser_branch_t branch; + + uint16_t opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_BRANCH_IF_NULLISH); + parser_emit_cbc_forward_branch (context_p, opcode, &branch); + parser_stack_push (context_p, &branch, sizeof (parser_branch_t)); + parser_stack_push_uint8 (context_p, context_p->token.type); + parser_check_invalid_logical_op (context_p, grouping_level); + return; + } +#endif /* ENABLED (JERRY_ESNEXT) */ parser_stack_push_uint8 (context_p, context_p->token.type); } /* parser_append_binary_token */ @@ -2725,8 +2856,12 @@ parser_append_binary_token (parser_context_t *context_p) /**< context */ */ static void parser_process_binary_opcodes (parser_context_t *context_p, /**< context */ - uint8_t min_prec_treshold) /**< minimal precedence of tokens */ + uint8_t min_prec_treshold, /**< minimal precedence of tokens */ + size_t *grouping_level_p) /**< grouping level */ { +#if !ENABLED (JERRY_ESNEXT) + JERRY_UNUSED (grouping_level_p); +#endif /* !ENABLED (JERRY_ESNEXT) */ while (true) { uint8_t token = context_p->stack_top_uint8; @@ -2843,8 +2978,22 @@ parser_process_binary_opcodes (parser_context_t *context_p, /**< context */ parser_branch_t branch; parser_stack_pop (context_p, &branch, sizeof (parser_branch_t)); parser_set_branch_to_current_position (context_p, &branch); +#if ENABLED (JERRY_ESNEXT) + JERRY_ASSERT (grouping_level_p != NULL); + *grouping_level_p |= PARSER_GROUPING_LOGICAL_FOUND; +#endif /* ENABLED (JERRY_ESNEXT) */ continue; } +#if ENABLED (JERRY_ESNEXT) + else if (token == LEXER_NULLISH_COALESCING) + { + parser_branch_t branch; + parser_stack_pop (context_p, &branch, sizeof (parser_branch_t)); + parser_set_branch_to_current_position (context_p, &branch); + continue; + } +#endif /* ENABLED (JERRY_ESNEXT) */ + else { opcode = LEXER_BINARY_OP_TOKEN_TO_OPCODE (token); @@ -3054,7 +3203,7 @@ parser_pattern_form_assignment (parser_context_t *context_p, /**< context */ parser_set_branch_to_current_position (context_p, &skip_init); } - parser_process_binary_opcodes (context_p, 0); + parser_process_binary_opcodes (context_p, 0, NULL); JERRY_ASSERT (context_p->stack_top_uint8 == LEXER_EXPRESSION_START); parser_stack_pop_uint8 (context_p); @@ -3433,7 +3582,7 @@ parser_process_ternary_expression (parser_context_t *context_p) /**< context */ * the result may come from the first branch. */ parser_flush_cbc (context_p); - parser_process_binary_opcodes (context_p, 0); + parser_process_binary_opcodes (context_p, 0, NULL); } /* parser_process_ternary_expression */ /** @@ -3477,6 +3626,12 @@ parser_process_group_expression (parser_context_t *context_p, /**< context */ parser_push_result (context_p); parser_flush_cbc (context_p); } +#if ENABLED (JERRY_ESNEXT) + else + { + *grouping_level_p &= (size_t) ~(PARSER_GROUPING_LOGICAL_FOUND); + } +#endif /* ENABLED (JERRY_ESNEXT) */ parser_stack_pop_uint8 (context_p); lexer_next_token (context_p); @@ -3555,7 +3710,7 @@ parser_parse_expression (parser_context_t *context_p, /**< context */ { if (parser_parse_unary_expression (context_p, &grouping_level)) { - parser_process_binary_opcodes (context_p, 0); + parser_process_binary_opcodes (context_p, 0, &grouping_level); break; } @@ -3592,9 +3747,8 @@ process_unary_expression: #endif /* ENABLED (JERRY_ESNEXT) */ } - parser_process_binary_opcodes (context_p, min_prec_treshold); + parser_process_binary_opcodes (context_p, min_prec_treshold, &grouping_level); } - if (context_p->token.type == LEXER_RIGHT_PAREN && (context_p->stack_top_uint8 == LEXER_LEFT_PAREN || context_p->stack_top_uint8 == LEXER_COMMA_SEP_LIST)) @@ -3622,7 +3776,7 @@ process_unary_expression: } else if (LEXER_IS_BINARY_OP_TOKEN (context_p->token.type)) { - parser_append_binary_token (context_p); + parser_append_binary_token (context_p, grouping_level); lexer_next_token (context_p); continue; } diff --git a/jerry-core/parser/js/js-parser-internal.h b/jerry-core/parser/js/js-parser-internal.h index db459dfa0..902647aa7 100644 --- a/jerry-core/parser/js/js-parser-internal.h +++ b/jerry-core/parser/js/js-parser-internal.h @@ -646,6 +646,8 @@ void parser_stack_push_uint16 (parser_context_t *context_p, uint16_t uint16_valu uint16_t parser_stack_pop_uint16 (parser_context_t *context_p); void parser_stack_push (parser_context_t *context_p, const void *data_p, uint32_t length); void parser_stack_pop (parser_context_t *context_p, void *data_p, uint32_t length); +void parser_stack_iterator_init (parser_context_t *context_p, parser_stack_iterator_t *iterator); +uint8_t parser_stack_iterator_read_uint8 (parser_stack_iterator_t *iterator); void parser_stack_iterator_skip (parser_stack_iterator_t *iterator, size_t length); void parser_stack_iterator_read (parser_stack_iterator_t *iterator, void *data_p, size_t length); void parser_stack_iterator_write (parser_stack_iterator_t *iterator, const void *data_p, size_t length); diff --git a/jerry-core/parser/js/js-parser-mem.c b/jerry-core/parser/js/js-parser-mem.c index e6963eeeb..f999b53ff 100644 --- a/jerry-core/parser/js/js-parser-mem.c +++ b/jerry-core/parser/js/js-parser-mem.c @@ -621,6 +621,29 @@ parser_stack_pop (parser_context_t *context_p, /**< context */ } } /* parser_stack_pop */ +/** + * Initialize stack iterator. + */ +inline void +parser_stack_iterator_init (parser_context_t *context_p, /**< context */ + parser_stack_iterator_t *iterator) /**< iterator */ +{ + iterator->current_p = context_p->stack.first_p; + iterator->current_position = context_p->stack.last_position; +} /* parser_stack_iterator_init */ + +/** + * Read the next byte from the stack. + * + * @return byte + */ +inline uint8_t +parser_stack_iterator_read_uint8 (parser_stack_iterator_t *iterator) /**< iterator */ +{ + JERRY_ASSERT (iterator->current_position > 0 && iterator->current_position <= PARSER_STACK_PAGE_SIZE); + return iterator->current_p->bytes[iterator->current_position - 1]; +} /* parser_stack_iterator_read_uint8 */ + /** * Skip the next n bytes of the stack. */ diff --git a/jerry-core/parser/js/js-parser-statm.c b/jerry-core/parser/js/js-parser-statm.c index ce381ccc9..af47e49d3 100644 --- a/jerry-core/parser/js/js-parser-statm.c +++ b/jerry-core/parser/js/js-parser-statm.c @@ -307,30 +307,6 @@ parser_statement_length (uint8_t type) /**< type of statement */ return statement_lengths[type - PARSER_STATEMENT_BLOCK]; } /* parser_statement_length */ - -/** - * Initialize stack iterator. - */ -static inline void -parser_stack_iterator_init (parser_context_t *context_p, /**< context */ - parser_stack_iterator_t *iterator) /**< iterator */ -{ - iterator->current_p = context_p->stack.first_p; - iterator->current_position = context_p->stack.last_position; -} /* parser_stack_iterator_init */ - -/** - * Read the next byte from the stack. - * - * @return byte - */ -static inline uint8_t -parser_stack_iterator_read_uint8 (parser_stack_iterator_t *iterator) /**< iterator */ -{ - JERRY_ASSERT (iterator->current_position > 0 && iterator->current_position <= PARSER_STACK_PAGE_SIZE); - return iterator->current_p->bytes[iterator->current_position - 1]; -} /* parser_stack_iterator_read_uint8 */ - /** * Parse expression enclosed in parens. */ diff --git a/jerry-core/parser/js/js-parser-util.c b/jerry-core/parser/js/js-parser-util.c index f8360d33a..06ad46ba1 100644 --- a/jerry-core/parser/js/js-parser-util.c +++ b/jerry-core/parser/js/js-parser-util.c @@ -1206,6 +1206,10 @@ parser_error_to_string (parser_error_t error) /**< error code */ { return "Left operand of ** operator cannot be unary expression."; } + case PARSER_ERR_INVALID_NULLISH_COALESCING: + { + return "Cannot chain nullish with logical AND or OR."; + } case PARSER_ERR_FORMAL_PARAM_AFTER_REST_PARAMETER: { return "Rest parameter must be the last formal parameter."; diff --git a/jerry-core/parser/js/js-parser.h b/jerry-core/parser/js/js-parser.h index 49b21a26f..46bc5fcde 100644 --- a/jerry-core/parser/js/js-parser.h +++ b/jerry-core/parser/js/js-parser.h @@ -154,6 +154,7 @@ typedef enum PARSER_ERR_INVALID_DESTRUCTURING_PATTERN, /**< invalid destructuring pattern */ PARSER_ERR_ILLEGAL_PROPERTY_IN_DECLARATION, /**< illegal property in declaration context */ PARSER_ERR_INVALID_EXPONENTIATION, /**< left operand of ** operator cannot be unary expression */ + PARSER_ERR_INVALID_NULLISH_COALESCING, /**< Cannot chain nullish with logical AND or OR. */ PARSER_ERR_NEW_TARGET_EXPECTED, /**< expected new.target expression */ PARSER_ERR_NEW_TARGET_NOT_ALLOWED, /**< new.target is not allowed in the given context */ #endif /* ENABLED (JERRY_ESNEXT) */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index dff18ca39..53c5de051 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -2922,6 +2922,20 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ ecma_fast_free_value (value); continue; } +#if ENABLED (JERRY_ESNEXT) + case VM_OC_BRANCH_IF_NULLISH: + { + left_value = stack_top_p[-1]; + + if (!ecma_is_value_null (left_value) && !ecma_is_value_undefined (left_value)) + { + byte_code_p = byte_code_start_p + branch_offset; + continue; + } + --stack_top_p; + continue; + } +#endif /* ENABLED (JERRY_ESNEXT) */ case VM_OC_PLUS: case VM_OC_MINUS: { diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 7b462f7a7..7da5f446c 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -161,6 +161,9 @@ typedef enum VM_OC_ERROR, /**< error while the vm_loop is suspended */ VM_OC_JUMP, /**< jump */ +#if ENABLED (JERRY_ESNEXT) + VM_OC_BRANCH_IF_NULLISH, /** branch if undefined or null */ +#endif /* ENABLED (JERRY_ESNEXT) */ VM_OC_BRANCH_IF_STRICT_EQUAL, /**< branch if strict equal */ /* These four opcodes must be in this order. */ @@ -296,6 +299,7 @@ typedef enum { #if !ENABLED (JERRY_ESNEXT) VM_OC_EXP = VM_OC_NONE, /**< exponentiation */ + VM_OC_BRANCH_IF_NULLISH = VM_OC_NONE, /** branch if undefined or null */ #endif /* !ENABLED (JERRY_ESNEXT) */ #if !ENABLED (JERRY_DEBUGGER) VM_OC_BREAKPOINT_ENABLED = VM_OC_NONE, /**< enabled breakpoint for debugger is unused */ diff --git a/tests/jerry/es.next/nullish-coalescing.js b/tests/jerry/es.next/nullish-coalescing.js new file mode 100644 index 000000000..96dce3212 --- /dev/null +++ b/tests/jerry/es.next/nullish-coalescing.js @@ -0,0 +1,51 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//basic valid cases with logical operator +assert((1 ?? 2) == 1) +assert((0 ?? 2) == 0) +assert(null ?? 2 == 2) +assert(null ?? undefined == undefined) +assert(null ?? undefined ?? 2 == 2) +assert(null ?? undefined ?? null ?? 10 == 10) +assert(null ?? (undefined || null) ?? 10 == 10) +assert(null ?? (undefined && null) ?? 10 == 10) +assert((null ?? true) && (true ?? null) == true) + +//cannot evulate the right expression if left is not null or undefined +function poison () { + throw 23; +} +assert(true ?? poison ()) +assert(null ?? null ?? true ?? poison ()) + +function checkSyntax (str) { + try { + eval (str); + assert (false); + } catch (e) { + assert (e instanceof SyntaxError); + } +} + +// invalid use cases +var headNullish1 = "(null ?? null || null )"; +var headNullish2 = "(null ?? null && null )"; +var tailNullish1 = "(null || null ?? null )"; +var tailNullish2 = "(null || null ?? null )"; + +checkSyntax (headNullish1); +checkSyntax (headNullish2); +checkSyntax (tailNullish1); +checkSyntax (tailNullish2);