From 242134f3b4458ec9cb82a27cb9b0ac8d4c2bce60 Mon Sep 17 00:00:00 2001 From: Robert Fancsik Date: Tue, 30 Nov 2021 13:30:36 +0100 Subject: [PATCH] Implement optional chaining JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik robert.fancsik@h-lab.eu --- jerry-core/parser/js/byte-code.c | 2 +- jerry-core/parser/js/byte-code.h | 4 +- jerry-core/parser/js/js-lexer.c | 32 + jerry-core/parser/js/js-lexer.h | 1 + jerry-core/parser/js/js-parser-expr.c | 1024 +++++++++++------ jerry-core/parser/js/js-parser-internal.h | 1 + jerry-core/parser/js/js-scanner.c | 142 ++- .../parser/js/parser-error-messages.inc.h | 5 + .../parser/js/parser-error-messages.ini | 1 + jerry-core/vm/vm.c | 28 +- jerry-core/vm/vm.h | 2 + tests/jerry/es.next/optional-chaining.js | 167 +++ tests/test262-esnext-excludelist.xml | 35 - 13 files changed, 1027 insertions(+), 417 deletions(-) create mode 100644 tests/jerry/es.next/optional-chaining.js diff --git a/jerry-core/parser/js/byte-code.c b/jerry-core/parser/js/byte-code.c index 4ba63cd3d1..8b89e61534 100644 --- a/jerry-core/parser/js/byte-code.c +++ b/jerry-core/parser/js/byte-code.c @@ -28,7 +28,7 @@ JERRY_STATIC_ASSERT (offsetof (cbc_uint8_arguments_t, script_value) == offsetof * whenever new bytecodes are introduced or existing ones have been deleted. */ JERRY_STATIC_ASSERT (CBC_END == 238, number_of_cbc_opcodes_changed); -JERRY_STATIC_ASSERT (CBC_EXT_END == 167, number_of_cbc_ext_opcodes_changed); +JERRY_STATIC_ASSERT (CBC_EXT_END == 170, number_of_cbc_ext_opcodes_changed); #if JERRY_PARSER || JERRY_PARSER_DUMP_BYTE_CODE diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index 18d5325e9d..26b2ff8594 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -504,9 +504,9 @@ 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_POP_REFERENCE, CBC_NO_FLAG, -2, VM_OC_POP_REFERENCE) \ + CBC_FORWARD_BRANCH (CBC_EXT_BRANCH_OPTIONAL_CHAIN, 0, VM_OC_BRANCH_OPTIONAL_CHAIN) \ + /* Basic opcodes. */ \ CBC_OPCODE (CBC_EXT_CREATE_ARGUMENTS, CBC_HAS_LITERAL_ARG, 0, VM_OC_CREATE_ARGUMENTS) \ CBC_OPCODE (CBC_EXT_CREATE_VAR_EVAL, CBC_HAS_LITERAL_ARG, 0, VM_OC_EXT_VAR_EVAL) \ CBC_OPCODE (CBC_EXT_CREATE_VAR_FUNC_EVAL, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2, 0, VM_OC_EXT_VAR_EVAL) \ diff --git a/jerry-core/parser/js/js-lexer.c b/jerry-core/parser/js/js-lexer.c index 898303cfbb..a284b12fe7 100644 --- a/jerry-core/parser/js/js-lexer.c +++ b/jerry-core/parser/js/js-lexer.c @@ -390,6 +390,29 @@ lexer_skip_spaces (parser_context_t *context_p) /**< context */ #if JERRY_ESNEXT +/** + * Checks the next token start character. + * + * @return LIT_INVALID_CP - if there is no more characters to read + * next byte - otherwise + */ +lit_code_point_t +lexer_peek_next_character (parser_context_t *context_p) /**< context */ +{ + if (!(context_p->token.flags & LEXER_NO_SKIP_SPACES)) + { + lexer_skip_spaces (context_p); + context_p->token.flags = (uint8_t) (context_p->token.flags | LEXER_NO_SKIP_SPACES); + } + + if (context_p->source_p < context_p->source_end_p) + { + return context_p->source_p[0]; + } + + return LIT_INVALID_CP; +} /* lexer_check_next_character */ + /** * Skip all the continuous empty statements. */ @@ -1845,6 +1868,15 @@ lexer_next_token (parser_context_t *context_p) /**< context */ length = 2; break; } + if (context_p->source_p[1] == (uint8_t) LIT_CHAR_DOT) + { + if (length < 3 || !lit_char_is_decimal_digit (context_p->source_p[2])) + { + context_p->token.type = LEXER_QUESTION_MARK_DOT; + length = 2; + break; + } + } } #endif /* JERRY_ESNEXT */ context_p->token.type = LEXER_QUESTION_MARK; diff --git a/jerry-core/parser/js/js-lexer.h b/jerry-core/parser/js/js-lexer.h index 8d06baab5f..0c1ab0f40c 100644 --- a/jerry-core/parser/js/js-lexer.h +++ b/jerry-core/parser/js/js-lexer.h @@ -171,6 +171,7 @@ typedef enum LEXER_RIGHT_PAREN, /**< ")" */ LEXER_RIGHT_SQUARE, /**< "]" */ LEXER_DOT, /**< "." */ + LEXER_QUESTION_MARK_DOT, /**< "?." */ LEXER_SEMICOLON, /**< ";" */ LEXER_COLON, /**< ":" */ LEXER_COMMA, /**< "," */ diff --git a/jerry-core/parser/js/js-parser-expr.c b/jerry-core/parser/js/js-parser-expr.c index 03309c128d..ab855a5ddf 100644 --- a/jerry-core/parser/js/js-parser-expr.c +++ b/jerry-core/parser/js/js-parser-expr.c @@ -117,6 +117,30 @@ JERRY_STATIC_ASSERT (sizeof (parser_binary_precedence_table) == 36, parser_binary_precedence_table_should_have_36_values_in_es51); #endif /* JERRY_ESNEXT */ +/** + * Call reference status flags + */ +typedef enum +{ + PARSER_CALL_REFERENCE_NONE = 0, /**< no options are present */ + PARSER_CALL_REFERENCE_DIRECT_EVAL = (1 << 0), /**< direct eval call */ + PARSER_CALL_REFERENCE_SPREAD = (1 << 1), /**< spread call */ +} parser_call_reference_flags_t; + +/** + * Call reference + */ +typedef struct +{ + size_t call_arguments; /**< number of call arguments */ + uint16_t status_flags; /**< any combination of parser_call_reference_flags_t */ + uint16_t opcode; /**< call opcode */ +} parser_call_reference_t; + +static void parser_parse_postfix_expresion (parser_context_t *context_p, size_t *grouping_level_p); +static parser_call_reference_t parser_form_call_reference (parser_context_t *context_p); +static bool parser_process_group_expression (parser_context_t *context_p, size_t *grouping_level_p); + /** * Generate byte code for operators with lvalue. */ @@ -2563,383 +2587,672 @@ parser_parse_unary_expression (parser_context_t *context_p, /**< context */ return false; } /* parser_parse_unary_expression */ +#if JERRY_ESNEXT /** - * Parse the postfix part of unary operators, and - * generate byte code for the whole expression. + * Parse expr.#property */ static void -parser_process_unary_expression (parser_context_t *context_p, /**< context */ - size_t grouping_level) /**< grouping level */ +parser_parse_private_property_access (parser_context_t *context_p) /**< context */ { - /* Parse postfix part of a primary expression. */ - while (true) - { - /* Since break would only break the switch, we use - * continue to continue this loop. Without continue, - * the code abandons the loop. */ - switch (context_p->token.type) - { - case LEXER_DOT: - { - parser_push_result (context_p); - -#if JERRY_ESNEXT - if (lexer_check_next_character (context_p, LIT_CHAR_HASHMARK)) - { - lexer_next_token (context_p); + lexer_next_token (context_p); - if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER)) - { - parser_raise_error (context_p, PARSER_ERR_UNEXPECTED_PRIVATE_FIELD); - } + if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER)) + { + parser_raise_error (context_p, PARSER_ERR_UNEXPECTED_PRIVATE_FIELD); + } - if (!lexer_scan_private_identifier (context_p)) - { - parser_raise_error (context_p, PARSER_ERR_IDENTIFIER_EXPECTED); - } + if (!lexer_scan_private_identifier (context_p)) + { + parser_raise_error (context_p, PARSER_ERR_IDENTIFIER_EXPECTED); + } - parser_resolve_private_identifier (context_p); + parser_resolve_private_identifier (context_p); - parser_emit_cbc_ext_literal (context_p, CBC_EXT_PUSH_PRIVATE_PROP_LITERAL, context_p->lit_object.index); - lexer_next_token (context_p); - continue; - } + parser_emit_cbc_ext_literal (context_p, CBC_EXT_PUSH_PRIVATE_PROP_LITERAL, context_p->lit_object.index); + lexer_next_token (context_p); +} /* parser_parse_private_property_access */ #endif /* JERRY_ESNEXT */ - lexer_expect_identifier (context_p, LEXER_STRING_LITERAL); - JERRY_ASSERT (context_p->token.type == LEXER_LITERAL - && context_p->lit_object.literal_p->type == LEXER_STRING_LITERAL); - context_p->token.lit_location.type = LEXER_STRING_LITERAL; - - if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL) - { - JERRY_ASSERT (CBC_ARGS_EQ (CBC_PUSH_PROP_LITERAL_LITERAL, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2)); - context_p->last_cbc_opcode = CBC_PUSH_PROP_LITERAL_LITERAL; - context_p->last_cbc.value = context_p->lit_object.index; - } - else if (context_p->last_cbc_opcode == CBC_PUSH_THIS) - { - context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; - parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_PROP_THIS_LITERAL); - } +/** + * Parse expr.property + */ +static void +parser_parse_property_access (parser_context_t *context_p) /**< context */ +{ #if JERRY_ESNEXT - else if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER)) - { - context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER_PROP_LITERAL); - context_p->last_cbc.literal_index = context_p->lit_object.index; - } + JERRY_ASSERT (context_p->token.type == LEXER_DOT || context_p->token.type == LEXER_QUESTION_MARK_DOT); +#else /* !JERRY_ESNEXT */ + JERRY_ASSERT (context_p->token.type == LEXER_DOT); #endif /* JERRY_ESNEXT */ - else - { - parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_PROP_LITERAL); - } - lexer_next_token (context_p); - continue; - } - case LEXER_LEFT_SQUARE: - { - parser_push_result (context_p); + parser_push_result (context_p); #if JERRY_ESNEXT - uint16_t last_cbc_opcode = context_p->last_cbc_opcode; - - if (last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER)) - { - context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; - } + if (lexer_check_next_character (context_p, LIT_CHAR_HASHMARK)) + { + parser_parse_private_property_access (context_p); + return; + } #endif /* JERRY_ESNEXT */ - lexer_next_token (context_p); - parser_parse_expression (context_p, PARSE_EXPR); - if (context_p->token.type != LEXER_RIGHT_SQUARE) - { - parser_raise_error (context_p, PARSER_ERR_RIGHT_SQUARE_EXPECTED); - } - lexer_next_token (context_p); - -#if JERRY_ESNEXT - if (last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER)) - { - parser_emit_cbc_ext (context_p, CBC_EXT_PUSH_SUPER_PROP); - continue; - } -#endif /* JERRY_ESNEXT */ + lexer_expect_identifier (context_p, LEXER_STRING_LITERAL); - if (PARSER_IS_MUTABLE_PUSH_LITERAL (context_p->last_cbc_opcode)) - { - context_p->last_cbc_opcode = PARSER_PUSH_LITERAL_TO_PUSH_PROP_LITERAL (context_p->last_cbc_opcode); - } - else - { - parser_emit_cbc (context_p, CBC_PUSH_PROP); - } - continue; - } + JERRY_ASSERT (context_p->token.type == LEXER_LITERAL + && context_p->lit_object.literal_p->type == LEXER_STRING_LITERAL); + context_p->token.lit_location.type = LEXER_STRING_LITERAL; + switch (context_p->last_cbc_opcode) + { + case CBC_PUSH_LITERAL: + { + JERRY_ASSERT (CBC_ARGS_EQ (CBC_PUSH_PROP_LITERAL_LITERAL, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2)); + context_p->last_cbc_opcode = CBC_PUSH_PROP_LITERAL_LITERAL; + context_p->last_cbc.value = context_p->lit_object.index; + break; + } + case CBC_PUSH_THIS: + { + context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; + parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_PROP_THIS_LITERAL); + break; + } #if JERRY_ESNEXT - case LEXER_TEMPLATE_LITERAL: + case PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER): + { + context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER_PROP_LITERAL); + context_p->last_cbc.literal_index = context_p->lit_object.index; + break; + } #endif /* JERRY_ESNEXT */ - case LEXER_LEFT_PAREN: - { - size_t call_arguments = 0; - uint16_t opcode = CBC_CALL; - bool is_eval = false; + default: + { + parser_emit_cbc_literal_from_token (context_p, CBC_PUSH_PROP_LITERAL); + break; + } + } - parser_push_result (context_p); + lexer_next_token (context_p); +} /* parser_parse_property_access */ - if (context_p->stack_top_uint8 == LEXER_KEYW_NEW) - { -#if JERRY_ESNEXT - if (context_p->token.type == LEXER_LEFT_PAREN) -#endif /* JERRY_ESNEXT */ - { - parser_stack_pop_uint8 (context_p); - opcode = CBC_NEW; - } - } - else - { - if (context_p->last_cbc_opcode == CBC_PUSH_LITERAL - && context_p->last_cbc.literal_keyword_type == LEXER_KEYW_EVAL - && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) - { - is_eval = true; - } +/** + * Parse an expression enclosed with [] brackets ('[expr]') + */ +static void +parser_parse_square_bracketed_expression (parser_context_t *context_p) /**< context */ +{ + JERRY_ASSERT (context_p->token.type == LEXER_LEFT_SQUARE); - if (PARSER_IS_PUSH_PROP (context_p->last_cbc_opcode)) - { - opcode = CBC_CALL_PROP; - context_p->last_cbc_opcode = PARSER_PUSH_PROP_TO_PUSH_PROP_REFERENCE (context_p->last_cbc_opcode); - } -#if JERRY_ESNEXT - else if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER_CONSTRUCTOR)) - { - opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_SUPER_CALL); - } - else if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER_PROP_LITERAL)) - { - context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_SUPER_PROP_LITERAL_REFERENCE); - opcode = CBC_CALL_PROP; - } - else if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER_PROP)) - { - context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_SUPER_PROP_REFERENCE); - opcode = CBC_CALL_PROP; - } - else if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_PRIVATE_PROP_LITERAL)) - { - context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_PRIVATE_PROP_LITERAL_REFERENCE); - opcode = CBC_CALL_PROP; - } -#endif /* JERRY_ESNEXT */ - else if (JERRY_UNLIKELY (context_p->status_flags & PARSER_INSIDE_WITH) - && PARSER_IS_PUSH_LITERALS_WITH_THIS (context_p->last_cbc_opcode) - && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) - { - opcode = CBC_CALL_PROP; - parser_emit_ident_reference (context_p, CBC_PUSH_IDENT_REFERENCE); - parser_emit_cbc_ext (context_p, CBC_EXT_RESOLVE_BASE); - } - } + lexer_next_token (context_p); + parser_parse_expression (context_p, PARSE_EXPR); -#if JERRY_ESNEXT - bool has_spread_element = false; + if (context_p->token.type != LEXER_RIGHT_SQUARE) + { + parser_raise_error (context_p, PARSER_ERR_RIGHT_SQUARE_EXPECTED); + } + lexer_next_token (context_p); +} /* parser_parser_square_bracketed_expression */ - if (context_p->token.type == LEXER_TEMPLATE_LITERAL) - { - call_arguments = parser_parse_tagged_template_literal (context_p); - } - else - { - lexer_next_token (context_p); +#if JERRY_ESNEXT +/** + * Parse super[expr] + */ +static void +parser_parse_super_element_access_expression (parser_context_t *context_p) /**< context */ +{ + JERRY_ASSERT (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER)); + context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; - while (context_p->token.type != LEXER_RIGHT_PAREN) - { - if (++call_arguments > CBC_MAXIMUM_BYTE_VALUE) - { - parser_raise_error (context_p, PARSER_ERR_ARGUMENT_LIMIT_REACHED); - } + parser_parse_square_bracketed_expression (context_p); - if (context_p->token.type == LEXER_THREE_DOTS) - { - has_spread_element = true; - call_arguments++; - parser_emit_cbc_ext (context_p, CBC_EXT_PUSH_SPREAD_ELEMENT); - lexer_next_token (context_p); - } + parser_emit_cbc_ext (context_p, CBC_EXT_PUSH_SUPER_PROP); +} /* parser_parse_super_element_access_expression */ +#endif /* JERRY_ESNEXT */ - parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA); +/** + * Parse expr[property] + */ +static void +parser_parse_element_access (parser_context_t *context_p) /**< context */ +{ + JERRY_ASSERT (context_p->token.type == LEXER_LEFT_SQUARE); - if (context_p->token.type == LEXER_COMMA) - { - lexer_next_token (context_p); - continue; - } + parser_push_result (context_p); - if (context_p->token.type != LEXER_RIGHT_PAREN) - { - parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED); - } +#if JERRY_ESNEXT + if (context_p->last_cbc_opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER)) + { + parser_parse_super_element_access_expression (context_p); + return; + } +#endif /* JERRY_ESNEXT */ - break; - } - } -#else /* !JERRY_ESNEXT */ - lexer_next_token (context_p); + parser_parse_square_bracketed_expression (context_p); - if (context_p->token.type != LEXER_RIGHT_PAREN) - { - while (true) - { - if (++call_arguments > CBC_MAXIMUM_BYTE_VALUE) - { - parser_raise_error (context_p, PARSER_ERR_ARGUMENT_LIMIT_REACHED); - } + if (PARSER_IS_MUTABLE_PUSH_LITERAL (context_p->last_cbc_opcode)) + { + context_p->last_cbc_opcode = PARSER_PUSH_LITERAL_TO_PUSH_PROP_LITERAL (context_p->last_cbc_opcode); + } + else + { + parser_emit_cbc (context_p, CBC_PUSH_PROP); + } +} /* parser_parse_element_access */ - parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA); +/** + * Form reference base for a call instruction + */ +static parser_call_reference_t +parser_form_call_reference (parser_context_t *context_p) /**< context */ +{ + parser_push_result (context_p); - if (context_p->token.type != LEXER_COMMA) - { - break; - } - lexer_next_token (context_p); - } + parser_call_reference_t desc; + desc.status_flags = PARSER_CALL_REFERENCE_NONE; + desc.opcode = CBC_CALL; - if (context_p->token.type != LEXER_RIGHT_PAREN) - { - parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED); - } - } + if (context_p->stack_top_uint8 == LEXER_KEYW_NEW) + { +#if JERRY_ESNEXT + if (context_p->token.type == LEXER_LEFT_PAREN) #endif /* JERRY_ESNEXT */ + { + parser_stack_pop_uint8 (context_p); + desc.opcode = CBC_NEW; + } - lexer_next_token (context_p); + return desc; + } - if (is_eval) - { - context_p->status_flags |= PARSER_LEXICAL_ENV_NEEDED; + switch (context_p->last_cbc_opcode) + { + case CBC_PUSH_LITERAL: + { + if (context_p->last_cbc.literal_keyword_type == LEXER_KEYW_EVAL + && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) + { + desc.status_flags |= PARSER_CALL_REFERENCE_DIRECT_EVAL; + } -#if JERRY_ESNEXT - uint16_t eval_flags = PARSER_SAVE_STATUS_FLAGS (context_p->status_flags); - const uint32_t required_flags = PARSER_IS_FUNCTION | PARSER_LEXICAL_BLOCK_NEEDED; + /* FALLTHRU */ + } + case CBC_PUSH_TWO_LITERALS: + case CBC_PUSH_THIS_LITERAL: + case CBC_PUSH_THREE_LITERALS: + { + JERRY_ASSERT (PARSER_IS_PUSH_LITERALS_WITH_THIS (context_p->last_cbc_opcode)); - if (context_p->status_flags & PARSER_FUNCTION_IS_PARSING_ARGS) - { - context_p->status_flags |= PARSER_LEXICAL_BLOCK_NEEDED; - } - else if (((context_p->status_flags & (required_flags | PARSER_IS_STRICT)) == required_flags) - || ((context_p->global_status_flags & ECMA_PARSE_FUNCTION_CONTEXT) - && !(context_p->status_flags & PARSER_IS_FUNCTION))) - { - eval_flags |= PARSER_GET_EVAL_FLAG (ECMA_PARSE_FUNCTION_CONTEXT); - } + if (JERRY_UNLIKELY (context_p->status_flags & PARSER_INSIDE_WITH) + && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) + { + parser_emit_ident_reference (context_p, CBC_PUSH_IDENT_REFERENCE); + parser_emit_cbc_ext (context_p, CBC_EXT_RESOLVE_BASE); + break; + } - if (eval_flags != 0) - { - parser_emit_cbc_ext_call (context_p, CBC_EXT_LOCAL_EVAL, eval_flags); - } - else - { -#endif /* JERRY_ESNEXT */ - parser_emit_cbc (context_p, CBC_EVAL); + return desc; + } + case CBC_PUSH_PROP: + case CBC_PUSH_PROP_LITERAL: + case CBC_PUSH_PROP_LITERAL_LITERAL: + case CBC_PUSH_PROP_THIS_LITERAL: + { + JERRY_ASSERT (PARSER_IS_PUSH_PROP (context_p->last_cbc_opcode)); + context_p->last_cbc_opcode = PARSER_PUSH_PROP_TO_PUSH_PROP_REFERENCE (context_p->last_cbc_opcode); + break; + } #if JERRY_ESNEXT - } + case PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER_CONSTRUCTOR): + { + desc.opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_SUPER_CALL); + return desc; + } + case PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER_PROP_LITERAL): + { + context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_SUPER_PROP_LITERAL_REFERENCE); + break; + } + case PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_SUPER_PROP): + { + context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_SUPER_PROP_REFERENCE); + break; + } + case PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_PRIVATE_PROP_LITERAL): + { + context_p->last_cbc_opcode = PARSER_TO_EXT_OPCODE (CBC_EXT_PUSH_PRIVATE_PROP_LITERAL_REFERENCE); + break; + } #endif /* JERRY_ESNEXT */ - } + default: + { + return desc; + } + } -#if JERRY_ESNEXT - if (has_spread_element) - { - uint16_t spread_opcode; + desc.opcode = CBC_CALL_PROP; + return desc; +} /* parser_form_call_reference */ - if (opcode == CBC_CALL) - { - spread_opcode = CBC_EXT_SPREAD_CALL; - } - else if (opcode == CBC_CALL_PROP) - { - spread_opcode = CBC_EXT_SPREAD_CALL_PROP; - } - else if (opcode == CBC_NEW) - { - spread_opcode = CBC_EXT_SPREAD_NEW; - } - else - { - /* opcode is unchanged */ - JERRY_ASSERT (opcode == PARSER_TO_EXT_OPCODE (CBC_EXT_SUPER_CALL)); - spread_opcode = CBC_EXT_SPREAD_SUPER_CALL; - } +/** + * Expect the closing left parenthesis of a call expression + */ +static void +parser_expect_arguments_list_end (parser_context_t *context_p) /**< context */ +{ + if (context_p->token.type != LEXER_RIGHT_PAREN) + { + parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED); + } +} /* parser_expect_arguments_list_end */ - parser_emit_cbc_ext_call (context_p, spread_opcode, call_arguments); - continue; - } +/** + * Parse arguments list of a call expression + */ +static void +parser_process_call_arguments (parser_context_t *context_p, /**< context */ + parser_call_reference_t *call_ref_p) /**< call reference */ +{ +#if JERRY_ESNEXT + if (context_p->token.type == LEXER_TEMPLATE_LITERAL) + { + call_ref_p->call_arguments = (uint16_t) parser_parse_tagged_template_literal (context_p); + return; + } #endif /* JERRY_ESNEXT */ - if (call_arguments <= 1) - { - if (opcode == CBC_CALL) - { - parser_emit_cbc (context_p, (uint16_t) (CBC_CALL0 + (call_arguments * 6))); - continue; - } - if (opcode == CBC_CALL_PROP) - { - parser_emit_cbc (context_p, (uint16_t) (CBC_CALL0_PROP + (call_arguments * 6))); - continue; - } - if (opcode == CBC_NEW) - { - parser_emit_cbc (context_p, (uint16_t) (CBC_NEW0 + call_arguments)); - continue; - } - } - else if (call_arguments == 2) - { - if (opcode == CBC_CALL) - { - parser_emit_cbc (context_p, CBC_CALL2); - continue; - } - if (opcode == CBC_CALL_PROP) - { - parser_flush_cbc (context_p); - /* Manually adjusting stack usage. */ - JERRY_ASSERT (context_p->stack_depth > 0); - context_p->stack_depth--; - parser_emit_cbc (context_p, CBC_CALL2_PROP); - continue; - } - } + uint32_t call_arguments = 0; - parser_emit_cbc_call (context_p, opcode, call_arguments); - continue; + lexer_next_token (context_p); + + while (context_p->token.type != LEXER_RIGHT_PAREN) + { + if (++call_arguments > CBC_MAXIMUM_BYTE_VALUE) + { + parser_raise_error (context_p, PARSER_ERR_ARGUMENT_LIMIT_REACHED); + } + +#if JERRY_ESNEXT + if (context_p->token.type == LEXER_THREE_DOTS) + { + call_ref_p->status_flags |= PARSER_CALL_REFERENCE_SPREAD; + call_arguments++; + parser_emit_cbc_ext (context_p, CBC_EXT_PUSH_SPREAD_ELEMENT); + lexer_next_token (context_p); + } + + parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA); + + if (context_p->token.type == LEXER_COMMA) + { + lexer_next_token (context_p); + continue; + } + + parser_expect_arguments_list_end (context_p); + break; +#else /* !JERRY_ESNEXT */ + parser_parse_expression (context_p, PARSE_EXPR_NO_COMMA); + + if (context_p->token.type != LEXER_COMMA) + { + parser_expect_arguments_list_end (context_p); + break; + } + + lexer_next_token (context_p); +#endif /* JERRY_ESNEXT */ + } + + JERRY_ASSERT (context_p->token.type == LEXER_RIGHT_PAREN); + call_ref_p->call_arguments = call_arguments; +} /* parser_process_call_arguments */ + +/** + * Save local options flags for direct eval + */ +static void +parser_prepare_direct_eval_call (parser_context_t *context_p) /**< context */ +{ + context_p->status_flags |= PARSER_LEXICAL_ENV_NEEDED; + +#if JERRY_ESNEXT + uint16_t eval_flags = PARSER_SAVE_STATUS_FLAGS (context_p->status_flags); + const uint32_t required_flags = PARSER_IS_FUNCTION | PARSER_LEXICAL_BLOCK_NEEDED; + + if (context_p->status_flags & PARSER_FUNCTION_IS_PARSING_ARGS) + { + context_p->status_flags |= PARSER_LEXICAL_BLOCK_NEEDED; + } + else if (((context_p->status_flags & (required_flags | PARSER_IS_STRICT)) == required_flags) + || ((context_p->global_status_flags & ECMA_PARSE_FUNCTION_CONTEXT) + && !(context_p->status_flags & PARSER_IS_FUNCTION))) + { + eval_flags |= PARSER_GET_EVAL_FLAG (ECMA_PARSE_FUNCTION_CONTEXT); + } + + if (eval_flags != 0) + { + parser_emit_cbc_ext_call (context_p, CBC_EXT_LOCAL_EVAL, eval_flags); + } + else + { +#endif /* JERRY_ESNEXT */ + parser_emit_cbc (context_p, CBC_EVAL); +#if JERRY_ESNEXT + } +#endif /* JERRY_ESNEXT */ +} /* parser_prepare_direct_eval_call */ + +#if JERRY_ESNEXT +/** + * Emit bytecode for spread call + */ +static void +parser_emit_spread_call (parser_context_t *context_p, /**< context */ + parser_call_reference_t *call_ref_p) /**< call reference */ +{ + uint16_t call_opcode; + + switch (call_ref_p->opcode) + { + case CBC_CALL: + { + call_opcode = CBC_EXT_SPREAD_CALL; + break; + } + case CBC_CALL_PROP: + { + call_opcode = CBC_EXT_SPREAD_CALL_PROP; + break; + } + case CBC_NEW: + { + call_opcode = CBC_EXT_SPREAD_NEW; + break; + } + case PARSER_TO_EXT_OPCODE (CBC_EXT_SUPER_CALL): + { + call_opcode = CBC_EXT_SPREAD_SUPER_CALL; + break; + } + default: + { + JERRY_UNREACHABLE (); + } + } + + parser_emit_cbc_ext_call (context_p, call_opcode, call_ref_p->call_arguments); +} /* parser_emit_spread_call */ +#endif /* JERRY_ESNEXT */ + +/** + * Emit call opcode with 0 or 1 argument + */ +static void +parser_emit_call_01 (parser_context_t *context_p, /**< context */ + parser_call_reference_t *call_ref_p) /**< call reference */ +{ + JERRY_ASSERT (call_ref_p->call_arguments <= 1); + + switch (call_ref_p->opcode) + { + case CBC_CALL: + { + parser_emit_cbc (context_p, (uint16_t) (CBC_CALL0 + (call_ref_p->call_arguments * 6))); + break; + } + case CBC_CALL_PROP: + { + parser_emit_cbc (context_p, (uint16_t) (CBC_CALL0_PROP + (call_ref_p->call_arguments * 6))); + break; + } + case CBC_NEW: + { + parser_emit_cbc (context_p, (uint16_t) (CBC_NEW0 + call_ref_p->call_arguments)); + break; + } + default: + { + parser_emit_cbc_call (context_p, call_ref_p->opcode, call_ref_p->call_arguments); + break; + } + } +} /* parser_emit_call_01 */ + +/** + * Emit call opcode with 2 arguments + */ +static void +parser_emit_call_2 (parser_context_t *context_p, /**< context */ + parser_call_reference_t *call_ref_p) /**< call reference */ +{ + JERRY_ASSERT (call_ref_p->call_arguments == 2); + + switch (call_ref_p->opcode) + { + case CBC_CALL: + { + parser_emit_cbc (context_p, CBC_CALL2); + break; + } + case CBC_CALL_PROP: + { + parser_flush_cbc (context_p); + /* Manually adjusting stack usage. */ + JERRY_ASSERT (context_p->stack_depth > 0); + context_p->stack_depth--; + parser_emit_cbc (context_p, CBC_CALL2_PROP); + break; + } + default: + { + parser_emit_cbc_call (context_p, call_ref_p->opcode, call_ref_p->call_arguments); + break; + } + } +} /* parser_emit_call_2 */ + +/** + * Parse expr(arguments) or expr`template literal` + */ +static void +parser_parse_call_expression (parser_context_t *context_p, /**< context */ + parser_call_reference_t *call_ref_p) /**< call reference */ +{ +#if JERRY_ESNEXT + JERRY_ASSERT (context_p->token.type == LEXER_LEFT_PAREN || context_p->token.type == LEXER_TEMPLATE_LITERAL); +#else /* !JERRY_ESNEXT */ + JERRY_ASSERT (context_p->token.type == LEXER_LEFT_PAREN); +#endif /* JERRY_ESNEXT */ + + parser_process_call_arguments (context_p, call_ref_p); + + lexer_next_token (context_p); + + if (call_ref_p->status_flags & PARSER_CALL_REFERENCE_DIRECT_EVAL) + { + parser_prepare_direct_eval_call (context_p); + } + +#if JERRY_ESNEXT + if (call_ref_p->status_flags & PARSER_CALL_REFERENCE_SPREAD) + { + parser_emit_spread_call (context_p, call_ref_p); + return; + } +#endif /* JERRY_ESNEXT */ + + if (call_ref_p->call_arguments <= 1) + { + parser_emit_call_01 (context_p, call_ref_p); + } + else if (call_ref_p->call_arguments == 2) + { + parser_emit_call_2 (context_p, call_ref_p); + } + else + { + parser_emit_cbc_call (context_p, call_ref_p->opcode, call_ref_p->call_arguments); + } +} /* parser_parse_call_expression */ + +#if JERRY_ESNEXT +/** + * Process 'expr?.' + */ +static void +parser_parse_optional_chain (parser_context_t *context_p, /**< context */ + size_t *grouping_level_p) /**< grouping level */ +{ + JERRY_ASSERT (context_p->token.type == LEXER_QUESTION_MARK_DOT); + + parser_push_result (context_p); + + parser_branch_t branch; + + switch (lexer_peek_next_character (context_p)) + { + case LIT_CHAR_GRAVE_ACCENT: + { + parser_raise_error (context_p, PARSER_ERR_INVALID_TAGGED_TEMPLATE_OPTIONAL_CHAIN); + break; + } + case LIT_CHAR_LEFT_PAREN: + { + lexer_next_token (context_p); + parser_call_reference_t call_ref = parser_form_call_reference (context_p); + + parser_emit_cbc_ext_forward_branch (context_p, CBC_EXT_BRANCH_OPTIONAL_CHAIN, &branch); + + if (call_ref.opcode == CBC_CALL_PROP) + { + parser_emit_cbc_ext (context_p, CBC_EXT_POP_REFERENCE); + parser_flush_cbc (context_p); + JERRY_ASSERT (context_p->stack_limit - context_p->stack_depth >= 2); + PARSER_PLUS_EQUAL_U16 (context_p->stack_depth, 2); } - default: + + parser_parse_call_expression (context_p, &call_ref); + break; + } + case LIT_CHAR_LEFT_SQUARE: + { + parser_emit_cbc_ext_forward_branch (context_p, CBC_EXT_BRANCH_OPTIONAL_CHAIN, &branch); + + lexer_next_token (context_p); + parser_parse_square_bracketed_expression (context_p); + parser_emit_cbc (context_p, CBC_PUSH_PROP); + break; + } + default: + { + parser_emit_cbc_ext_forward_branch (context_p, CBC_EXT_BRANCH_OPTIONAL_CHAIN, &branch); + parser_parse_property_access (context_p); + break; + } + } + + while (true) + { + switch (context_p->token.type) + { + case LEXER_TEMPLATE_LITERAL: + case LEXER_INCREASE: + case LEXER_DECREASE: { - if (context_p->stack_top_uint8 == LEXER_KEYW_NEW) + parser_raise_error (context_p, PARSER_ERR_INVALID_TAGGED_TEMPLATE_OPTIONAL_CHAIN); + break; + } + case LEXER_DOT: + case LEXER_LEFT_PAREN: + case LEXER_LEFT_SQUARE: + case LEXER_QUESTION_MARK_DOT: + { + size_t grouping_level = *grouping_level_p | PARSE_EXPR_LEFT_HAND_SIDE; + parser_parse_postfix_expresion (context_p, &grouping_level); + break; + } + case LEXER_RIGHT_PAREN: + { + if (parser_process_group_expression (context_p, grouping_level_p)) { - parser_push_result (context_p); - parser_emit_cbc (context_p, CBC_NEW0); - parser_stack_pop_uint8 (context_p); continue; } - - if (!(context_p->token.flags & LEXER_WAS_NEWLINE) - && (context_p->token.type == LEXER_INCREASE || context_p->token.type == LEXER_DECREASE) - && grouping_level != PARSE_EXPR_LEFT_HAND_SIDE) - { - cbc_opcode_t opcode = (context_p->token.type == LEXER_INCREASE) ? CBC_POST_INCR : CBC_POST_DECR; - parser_push_result (context_p); - parser_emit_unary_lvalue_opcode (context_p, opcode); - lexer_next_token (context_p); - } + break; + } + default: + { break; } } + break; } + parser_push_result (context_p); + + parser_set_branch_to_current_position (context_p, &branch); + + if (LEXER_IS_BINARY_LVALUE_OP_TOKEN (context_p->token.type)) + { + parser_raise_error (context_p, PARSER_ERR_INVALID_LHS_ASSIGNMENT); + } +} /* parser_parse_optional_chain */ +#endif /* JERRY_ESNEXT */ + +/** + * Process 'new expr' + */ +static void +parser_process_new_expression (parser_context_t *context_p) /**< context */ +{ + JERRY_ASSERT (context_p->stack_top_uint8 == LEXER_KEYW_NEW); + + parser_push_result (context_p); + parser_emit_cbc (context_p, CBC_NEW0); + parser_stack_pop_uint8 (context_p); +} /* parser_process_new_expression */ + +/** + * Process postfix unary lvalue operators (++, --) + */ +static void +parser_parse_postfix_lvalue (parser_context_t *context_p, /**< context */ + size_t grouping_level) /**< grouping level */ +{ + if ((context_p->token.flags & LEXER_WAS_NEWLINE) || grouping_level == PARSE_EXPR_LEFT_HAND_SIDE) + { + return; + } + + switch (context_p->token.type) + { + case LEXER_INCREASE: + { + parser_push_result (context_p); + parser_emit_unary_lvalue_opcode (context_p, CBC_POST_INCR); + break; + } + case LEXER_DECREASE: + { + parser_push_result (context_p); + parser_emit_unary_lvalue_opcode (context_p, CBC_POST_DECR); + break; + } + default: + { + return; + } + } + + lexer_next_token (context_p); +} /* parser_parse_postfix_lvalue */ + +/** + * Emit bytecode for prefix unary tokes + */ +static void +parser_process_prefix_unary_opcodes (parser_context_t *context_p) /**< context */ +{ #if JERRY_ESNEXT uint8_t last_unary_token = LEXER_INCREASE; #endif /* JERRY_ESNEXT */ @@ -3019,7 +3332,62 @@ parser_process_unary_expression (parser_context_t *context_p, /**< context */ } } } -} /* parser_process_unary_expression */ +} /* parser_process_prefix_unary_opcodes */ + +/** + * Parse the postfix part of the expression + */ +static void +parser_parse_postfix_expresion (parser_context_t *context_p, /**< context */ + size_t *grouping_level_p) /**< grouping level */ +{ + /* Parse postfix part of a primary expression. */ + while (true) + { + /* Since break would only break the switch, we use + * continue to continue this loop. Without continue, + * the code abandons the loop. */ + switch (context_p->token.type) + { + case LEXER_DOT: + { + parser_parse_property_access (context_p); + continue; + } + case LEXER_LEFT_SQUARE: + { + parser_parse_element_access (context_p); + continue; + } +#if JERRY_ESNEXT + case LEXER_QUESTION_MARK_DOT: + { + parser_parse_optional_chain (context_p, grouping_level_p); + break; + } + case LEXER_TEMPLATE_LITERAL: +#endif /* JERRY_ESNEXT */ + case LEXER_LEFT_PAREN: + { + parser_call_reference_t call_ref = parser_form_call_reference (context_p); + parser_parse_call_expression (context_p, &call_ref); + continue; + } + default: + { + if (context_p->stack_top_uint8 == LEXER_KEYW_NEW) + { + parser_process_new_expression (context_p); + continue; + } + + parser_parse_postfix_lvalue (context_p, *grouping_level_p); + break; + } + } + break; + } +} /* parser_parse_postfix_expresion */ /** * Append a binary '=' token. @@ -3225,8 +3593,8 @@ parser_check_invalid_logical_op (parser_context_t *context_p, /**< context */ */ static void parser_append_binary_lvalue_token (parser_context_t *context_p, /**< context */ - bool is_logical_assignment) /**< true - if form logical assignment reference - * false - otherwise */ + bool is_logical_assignment) /**< true - if form logical assignment + * reference false - otherwise */ { if (PARSER_IS_PUSH_LITERALS_WITH_THIS (context_p->last_cbc_opcode) && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) @@ -4337,10 +4705,16 @@ parser_process_expression_sequence (parser_context_t *context_p) /**< context */ /** * Process group expression. */ -static void +static bool parser_process_group_expression (parser_context_t *context_p, /**< context */ size_t *grouping_level_p) /**< 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)) + { + return false; + } + JERRY_ASSERT (*grouping_level_p >= PARSER_GROUPING_LEVEL_INCREASE); (*grouping_level_p) -= PARSER_GROUPING_LEVEL_INCREASE; @@ -4366,6 +4740,8 @@ parser_process_group_expression (parser_context_t *context_p, /**< context */ parser_stack_push_uint8 (context_p, LEXER_ASSIGN_GROUP_EXPR); } #endif /* JERRY_ESNEXT */ + + return true; } /* parser_process_group_expression */ /** @@ -4434,7 +4810,8 @@ parser_parse_expression (parser_context_t *context_p, /**< context */ while (true) { process_unary_expression: - parser_process_unary_expression (context_p, grouping_level); + parser_parse_postfix_expresion (context_p, &grouping_level); + parser_process_prefix_unary_opcodes (context_p); if (JERRY_LIKELY (grouping_level != PARSE_EXPR_LEFT_HAND_SIDE)) { @@ -4473,10 +4850,9 @@ parser_parse_expression (parser_context_t *context_p, /**< context */ parser_process_binary_opcodes (context_p, min_prec_treshold); } - 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)) + + if (parser_process_group_expression (context_p, &grouping_level)) { - parser_process_group_expression (context_p, &grouping_level); continue; } diff --git a/jerry-core/parser/js/js-parser-internal.h b/jerry-core/parser/js/js-parser-internal.h index 476b7996d9..c81a9b0d3d 100644 --- a/jerry-core/parser/js/js-parser-internal.h +++ b/jerry-core/parser/js/js-parser-internal.h @@ -773,6 +773,7 @@ bool lexer_check_next_characters (parser_context_t *context_p, lit_utf8_byte_t c uint8_t lexer_consume_next_character (parser_context_t *context_p); bool lexer_check_post_primary_exp (parser_context_t *context_p); #if JERRY_ESNEXT +lit_code_point_t lexer_peek_next_character (parser_context_t *context_p); void lexer_skip_empty_statements (parser_context_t *context_p); bool lexer_check_arrow (parser_context_t *context_p); bool lexer_check_arrow_param (parser_context_t *context_p); diff --git a/jerry-core/parser/js/js-scanner.c b/jerry-core/parser/js/js-scanner.c index 75b2c2aee7..a21414fd63 100644 --- a/jerry-core/parser/js/js-scanner.c +++ b/jerry-core/parser/js/js-scanner.c @@ -386,6 +386,32 @@ scanner_scan_primary_expression (parser_context_t *context_p, /**< context */ return SCAN_NEXT_TOKEN; } /* scanner_scan_primary_expression */ +#if JERRY_ESNEXT +/** + * Consume the ?. token + * + * @return token type to continue the post primary expression parsing + */ +static lexer_token_type_t +scanner_consume_optional_chain (parser_context_t *context_p) /**< context */ +{ + switch (lexer_peek_next_character (context_p)) + { + case LIT_CHAR_LEFT_PAREN: + case LIT_CHAR_LEFT_SQUARE: + { + lexer_next_token (context_p); + return context_p->token.type; + } + default: + { + return LEXER_DOT; + } + } +} /* scanner_consume_optional_chain */ + +#endif /* JERRY_ESNEXT */ + /** * Scan the tokens after the primary expression. * @@ -397,79 +423,91 @@ scanner_scan_post_primary_expression (parser_context_t *context_p, /**< context lexer_token_type_t type, /**< current token type */ scan_stack_modes_t stack_top) /**< current stack top */ { - switch (type) + while (true) { - case LEXER_DOT: + switch (type) { - lexer_scan_identifier (context_p, LEXER_PARSE_NO_OPTS); - #if JERRY_ESNEXT - if (context_p->token.type == LEXER_HASHMARK) + case LEXER_QUESTION_MARK_DOT: { - context_p->token.flags |= LEXER_NO_SKIP_SPACES; - lexer_next_token (context_p); + type = scanner_consume_optional_chain (context_p); + continue; } #endif /* JERRY_ESNEXT */ - - if (context_p->token.type != LEXER_LITERAL || context_p->token.lit_location.type != LEXER_IDENT_LITERAL) + case LEXER_DOT: { - scanner_raise_error (context_p); - } + lexer_scan_identifier (context_p, LEXER_PARSE_NO_OPTS); - return true; - } - case LEXER_LEFT_PAREN: - { - parser_stack_push_uint8 (context_p, SCAN_STACK_PAREN_EXPRESSION); - scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; - return true; - } #if JERRY_ESNEXT - case LEXER_TEMPLATE_LITERAL: - { - if (JERRY_UNLIKELY (context_p->source_p[-1] != LIT_CHAR_GRAVE_ACCENT)) + if (context_p->token.type == LEXER_HASHMARK) + { + context_p->token.flags |= LEXER_NO_SKIP_SPACES; + lexer_next_token (context_p); + } +#endif /* JERRY_ESNEXT */ + + if (context_p->token.type != LEXER_LITERAL || context_p->token.lit_location.type != LEXER_IDENT_LITERAL) + { + scanner_raise_error (context_p); + } + + return true; + } + case LEXER_LEFT_PAREN: { + parser_stack_push_uint8 (context_p, SCAN_STACK_PAREN_EXPRESSION); scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; - parser_stack_push_uint8 (context_p, SCAN_STACK_TAGGED_TEMPLATE_LITERAL); + return true; + } +#if JERRY_ESNEXT + case LEXER_TEMPLATE_LITERAL: + { + if (JERRY_UNLIKELY (context_p->source_p[-1] != LIT_CHAR_GRAVE_ACCENT)) + { + scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; + parser_stack_push_uint8 (context_p, SCAN_STACK_TAGGED_TEMPLATE_LITERAL); + } + return true; } - return true; - } #endif /* JERRY_ESNEXT */ - case LEXER_LEFT_SQUARE: - { - parser_stack_push_uint8 (context_p, SCAN_STACK_PROPERTY_ACCESSOR); - scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; - return true; - } - case LEXER_INCREASE: - case LEXER_DECREASE: - { - scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION_END; - - if (context_p->token.flags & LEXER_WAS_NEWLINE) + case LEXER_LEFT_SQUARE: { - return false; + parser_stack_push_uint8 (context_p, SCAN_STACK_PROPERTY_ACCESSOR); + scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; + return true; } + case LEXER_INCREASE: + case LEXER_DECREASE: + { + scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION_END; - lexer_next_token (context_p); - type = (lexer_token_type_t) context_p->token.type; + if (context_p->token.flags & LEXER_WAS_NEWLINE) + { + return false; + } + + lexer_next_token (context_p); + type = (lexer_token_type_t) context_p->token.type; - if (type != LEXER_QUESTION_MARK) + if (type != LEXER_QUESTION_MARK) + { + break; + } + /* FALLTHRU */ + } + case LEXER_QUESTION_MARK: + { + parser_stack_push_uint8 (context_p, SCAN_STACK_COLON_EXPRESSION); + scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; + return true; + } + default: { break; } - /* FALLTHRU */ - } - case LEXER_QUESTION_MARK: - { - parser_stack_push_uint8 (context_p, SCAN_STACK_COLON_EXPRESSION); - scanner_context_p->mode = SCAN_MODE_PRIMARY_EXPRESSION; - return true; - } - default: - { - break; } + + break; } if (LEXER_IS_BINARY_OP_TOKEN (type) && (type != LEXER_KEYW_IN || !SCANNER_IS_FOR_START (stack_top))) diff --git a/jerry-core/parser/js/parser-error-messages.inc.h b/jerry-core/parser/js/parser-error-messages.inc.h index 45b158ea87..320bff818e 100644 --- a/jerry-core/parser/js/parser-error-messages.inc.h +++ b/jerry-core/parser/js/parser-error-messages.inc.h @@ -205,6 +205,11 @@ PARSER_ERROR_DEF (PARSER_ERR_IMPORT_META_REQUIRE_MODULE, "Cannot use 'import.met #if JERRY_PARSER PARSER_ERROR_DEF (PARSER_ERR_INVALID_IDENTIFIER_PART, "Character cannot be part of an identifier") PARSER_ERROR_DEF (PARSER_ERR_EVAL_CANNOT_ASSIGNED, "Eval cannot be assigned to in strict mode") +#endif /* JERRY_PARSER */ +#if JERRY_ESNEXT && JERRY_PARSER +PARSER_ERROR_DEF (PARSER_ERR_INVALID_TAGGED_TEMPLATE_OPTIONAL_CHAIN, "Invalid tagged template on optional chain") +#endif /* JERRY_ESNEXT && JERRY_PARSER */ +#if JERRY_PARSER PARSER_ERROR_DEF (PARSER_ERR_WITH_NOT_ALLOWED, "With statement not allowed in strict mode") #endif /* JERRY_PARSER */ #if JERRY_ESNEXT && JERRY_PARSER diff --git a/jerry-core/parser/js/parser-error-messages.ini b/jerry-core/parser/js/parser-error-messages.ini index d709f39a51..572c1d9f19 100644 --- a/jerry-core/parser/js/parser-error-messages.ini +++ b/jerry-core/parser/js/parser-error-messages.ini @@ -142,3 +142,4 @@ PARSER_ERR_UNDECLARED_PRIVATE_FIELD = "Private field must be declared in an encl PARSER_ERR_DELETE_PRIVATE_FIELD = "Private fields can not be deleted" PARSER_ERR_UNEXPECTED_PRIVATE_FIELD = "Unexpected private field" PARSER_ERR_CLASS_PRIVATE_CONSTRUCTOR = "Class constructor may not be a private method" +PARSER_ERR_INVALID_TAGGED_TEMPLATE_OPTIONAL_CHAIN = "Invalid tagged template on optional chain" diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 33f1e0081a..a5d5156164 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -3208,9 +3208,6 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ case VM_OC_EVAL: { JERRY_CONTEXT (status_flags) |= ECMA_STATUS_DIRECT_EVAL; - JERRY_ASSERT ((*byte_code_p >= CBC_CALL && *byte_code_p <= CBC_CALL2_PROP_BLOCK) - || (*byte_code_p == CBC_EXT_OPCODE && byte_code_p[1] >= CBC_EXT_SPREAD_CALL - && byte_code_p[1] <= CBC_EXT_SPREAD_CALL_PROP_BLOCK)); continue; } case VM_OC_CALL: @@ -3342,6 +3339,31 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ continue; } #if JERRY_ESNEXT + case VM_OC_BRANCH_OPTIONAL_CHAIN: + { + left_value = stack_top_p[-1]; + + bool pop_reference = byte_code_p[0] == CBC_EXT_OPCODE && byte_code_p[1] == CBC_EXT_POP_REFERENCE; + + if (!ecma_is_value_null (left_value) && !ecma_is_value_undefined (left_value)) + { + if (pop_reference) + { + byte_code_p += 2; + } + + continue; + } + + stack_top_p[-1] = ECMA_VALUE_UNDEFINED; + byte_code_p = byte_code_start_p + branch_offset; + + if (!pop_reference) + { + continue; + } + /* FALLTHRU */ + } case VM_OC_POP_REFERENCE: { ecma_free_value (stack_top_p[-2]); diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 3cb1bdb931..210ce873dc 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -166,6 +166,7 @@ typedef enum VM_OC_JUMP, /**< jump */ #if JERRY_ESNEXT VM_OC_BRANCH_IF_NULLISH, /** branch if undefined or null */ + VM_OC_BRANCH_OPTIONAL_CHAIN, /** branch if undefined or null and adjust stack */ VM_OC_POP_REFERENCE, /** prop identifier or property reference from the stack */ #endif /* JERRY_ESNEXT */ VM_OC_BRANCH_IF_STRICT_EQUAL, /**< branch if strict equal */ @@ -327,6 +328,7 @@ typedef enum #if !JERRY_ESNEXT VM_OC_EXP = VM_OC_NONE, /**< exponentiation */ VM_OC_BRANCH_IF_NULLISH = VM_OC_NONE, /** branch if undefined or null */ + VM_OC_BRANCH_OPTIONAL_CHAIN = VM_OC_NONE, /** branch if undefined or null and adjust stack */ VM_OC_POP_REFERENCE = VM_OC_NONE, /** prop identifier or property reference from the stack */ #endif /* !JERRY_ESNEXT */ #if !JERRY_DEBUGGER diff --git a/tests/jerry/es.next/optional-chaining.js b/tests/jerry/es.next/optional-chaining.js new file mode 100644 index 0000000000..2452d752b7 --- /dev/null +++ b/tests/jerry/es.next/optional-chaining.js @@ -0,0 +1,167 @@ +// 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. + +function expectSyntaxError(str) { + try { + eval(str); + assert(false); + } catch (e) { + assert(e instanceof SyntaxError); + } +} + +function expectTypeError(cb) { + try { + cb(); + assert(false); + } catch (e) { + assert(e instanceof TypeError); + } +} + +expectSyntaxError("this?.a``"); +expectSyntaxError("this?.['a']``"); +expectSyntaxError("this?.``"); +expectSyntaxError("this.a?.``"); +expectSyntaxError("this['a']?.``"); +expectSyntaxError("this?.a = 9"); +expectSyntaxError("this.a.a.a.a.a?.a = 9"); +expectSyntaxError("this?.a.a.a.a.a.a = 9"); +expectSyntaxError("this?.a++"); +expectSyntaxError("this?.a.a.a.a.a.a++"); +expectSyntaxError("this.a.a.a.a.?a.a++"); +expectSyntaxError("this?.a--"); +expectSyntaxError("this?.a.a.a.a.a.a--"); +expectSyntaxError("this.a.a.a.a.?a.a--"); + +var o = { + a: 4.1, + b() { + return 4.2; + }, + c: { + a: 4.3, + b() { + return 4.4 + } + }, + e(...args) { + return args.reduce((p, c) => p + c); + } +} + + +assert(o?.a === 4.1); +assert(o?.a2 === undefined); +assert(this.o?.a === 4.1); +assert(this.o?.a2 === undefined); +assert(this.o?.['a'] === 4.1); +assert(this.o?.['a2'] === undefined); +assert(typeof o?.a === 'number'); +assert(typeof o?.a2 === 'undefined'); + +assert(o?.c?.a === 4.3); +assert(o?.c?.a2 === undefined); +assert(this.o?.c?.a === 4.3); +assert(this.o?.c?.a2 === undefined); +assert(this.o?.c?.['a'] === 4.3); +assert(this.o?.c?.['a2'] === undefined); +assert(typeof o?.c?.a === 'number'); +assert(typeof o?.c?.a2 === 'undefined'); + +assert(o?.d === undefined); +assert(o?.d?.d === undefined); +assert(o?.d?.d?.['d'] === undefined); + +assert(o?.b?.() === 4.2); +assert(o?.b2?.() === undefined); + +assert(o?.c?.b?.() === 4.4); +assert(o?.c?.b2?.() === undefined); + +assert(o?.e(...[1.25, 2.25, 3.25]) === 6.75); +assert(o?.e(...[1.25, 2.25, 3.25], ...[0.25]) === 7); +assert(o?.['e'](...[1.25, 2.25, 3.25]) === 6.75); +assert(o?.['e'](...[1.25, 2.25, 3.25], ...[0.25]) === 7); + +assert(o?.e?.(...[1.25, 2.25, 3.25]) === 6.75); +assert(o?.e?.(...[1.25, 2.25, 3.25], ...[0.25]) === 7); +assert(o?.['e']?.(...[1.25, 2.25, 3.25]) === 6.75); +assert(o?.['e']?.(...[1.25, 2.25, 3.25], ...[0.25]) === 7); + +// Test short circuit +let count = 0; +assert(undefined?.[count++] === undefined); +assert(undefined?.[count++]() === undefined); +assert(undefined?.[count++]()() === undefined); +assert(null?.[count++] === undefined); +assert(null?.[count++]() === undefined); +assert(null?.[count++]()() === undefined); +assert(count === 0); + +// Test optional call +var g = undefined; + +function f () { + return 4.5; +} + +assert(g?.() === undefined); +assert(this?.g?.() === undefined); +assert(this.g?.() === undefined); + +expectTypeError(_ => { + this.g(); +}); + +assert(f?.() === 4.5); +assert(this?.f?.() === 4.5); +assert(this.f?.() === 4.5); +assert(f() === 4.5); + +// test direct eval +var a = 5.1; +eval('a = 5.2'); +assert(a === 5.2); +eval?.('a = 5.3'); +assert(a === 5.3); +eval?.(...['a = 5.4']); +assert(a === 5.4); + +const saved_eval = eval; +eval = undefined; + +eval?.('a = 5.5') +assert(a === 5.4); +eval?.(...['a = 5.5']) +assert(a === 5.4); + +eval = saved_eval; + +// test optional private property access +class A { + #a = 5.1; + + test(o) { + return o?.#a; + } +} + +let instance = new A; +assert(instance.test(instance) === 5.1); +assert(instance.test(undefined) === undefined); +assert(instance.test(null) === undefined); +expectTypeError(_ => { + instance.test({}); +}); diff --git a/tests/test262-esnext-excludelist.xml b/tests/test262-esnext-excludelist.xml index 801140306c..2ca2403d10 100644 --- a/tests/test262-esnext-excludelist.xml +++ b/tests/test262-esnext-excludelist.xml @@ -1873,16 +1873,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -