From 343e80053bedf87c9c523d105ff1f9be8c8c1695 Mon Sep 17 00:00:00 2001 From: Robert Fancsik Date: Fri, 7 Jun 2019 06:35:42 -0400 Subject: [PATCH] Implement the for of statement (#2871) JerryScript-DCO-1.0-Signed-off-by: Robert Fancsik frobert@inf.u-szeged.hu --- jerry-core/config.h | 8 + jerry-core/include/jerryscript-snapshot.h | 2 +- jerry-core/parser/js/byte-code.h | 12 +- jerry-core/parser/js/js-lexer.h | 4 + jerry-core/parser/js/js-parser-scanner.c | 36 ++- jerry-core/parser/js/js-parser-statm.c | 273 ++++++++++++++++++---- jerry-core/parser/js/js-parser-util.c | 6 + jerry-core/parser/js/js-parser.h | 3 + jerry-core/profiles/README.md | 4 + jerry-core/vm/vm-stack.c | 10 + jerry-core/vm/vm-stack.h | 3 + jerry-core/vm/vm.c | 88 +++++++ jerry-core/vm/vm.h | 10 + tests/jerry/es2015/for-of.js | 117 ++++++++++ tests/unit-core/test-snapshot.c | 2 +- 15 files changed, 530 insertions(+), 48 deletions(-) create mode 100644 tests/jerry/es2015/for-of.js diff --git a/jerry-core/config.h b/jerry-core/config.h index 23fa383c3..e179898a1 100644 --- a/jerry-core/config.h +++ b/jerry-core/config.h @@ -110,6 +110,10 @@ # define JERRY_ES2015_CLASS JERRY_ES2015 #endif /* !defined (JERRY_ES2015_CLASS) */ +#ifndef JERRY_ES2015_FOR_OF +# define JERRY_ES2015_FOR_OF JERRY_ES2015 +#endif /* !defined (JERRY_ES2015_FOR_OF) */ + #ifndef JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER # define JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER JERRY_ES2015 #endif /* !defined (JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER) */ @@ -298,6 +302,10 @@ || ((JERRY_ES2015_CLASS != 0) && (JERRY_ES2015_CLASS != 1)) # error "Invalid value for JERRY_ES2015_CLASS macro." #endif +#if !defined (JERRY_ES2015_FOR_OF) \ +|| ((JERRY_ES2015_FOR_OF != 0) && (JERRY_ES2015_FOR_OF != 1)) +# error "Invalid value for JERRY_ES2015_FOR_OF macro." +#endif #if !defined (JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER) \ || ((JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER != 0) && (JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER != 1)) # error "Invalid value for JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER macro." diff --git a/jerry-core/include/jerryscript-snapshot.h b/jerry-core/include/jerryscript-snapshot.h index 0def79fbf..b52e7dc66 100644 --- a/jerry-core/include/jerryscript-snapshot.h +++ b/jerry-core/include/jerryscript-snapshot.h @@ -30,7 +30,7 @@ extern "C" /** * Jerry snapshot format version. */ -#define JERRY_SNAPSHOT_VERSION (22u) +#define JERRY_SNAPSHOT_VERSION (23u) /** * Flags for jerry_generate_snapshot and jerry_generate_function_snapshot. diff --git a/jerry-core/parser/js/byte-code.h b/jerry-core/parser/js/byte-code.h index f51a45082..37a73e005 100644 --- a/jerry-core/parser/js/byte-code.h +++ b/jerry-core/parser/js/byte-code.h @@ -188,6 +188,8 @@ /* Stack consumption of opcodes with context. */ +/* PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION must be <= 3 */ +#define PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION 3 /* PARSER_FOR_IN_CONTEXT_STACK_ALLOCATION must be <= 4 */ #define PARSER_FOR_IN_CONTEXT_STACK_ALLOCATION 4 /* PARSER_WITH_CONTEXT_STACK_ALLOCATION must be <= 4 */ @@ -492,6 +494,14 @@ VM_OC_SET_GETTER | VM_OC_NON_STATIC_FLAG | VM_OC_GET_LITERAL_LITERAL) \ CBC_BACKWARD_BRANCH (CBC_EXT_BRANCH_IF_FOR_IN_HAS_NEXT, 0, \ VM_OC_FOR_IN_HAS_NEXT) \ + CBC_OPCODE (CBC_EXT_FOR_OF_GET_NEXT, CBC_NO_FLAG, 1, \ + VM_OC_FOR_OF_GET_NEXT | VM_OC_PUT_STACK) \ + CBC_FORWARD_BRANCH (CBC_EXT_FOR_OF_CREATE_CONTEXT, \ + -1 + PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION, VM_OC_FOR_OF_CREATE_CONTEXT) \ + CBC_OPCODE (CBC_EXT_PUSH_NAMED_FUNC_EXPRESSION, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2, 1, \ + VM_OC_PUSH_NAMED_FUNC_EXPR | VM_OC_GET_LITERAL_LITERAL) \ + CBC_BACKWARD_BRANCH (CBC_EXT_BRANCH_IF_FOR_OF_HAS_NEXT, 0, \ + VM_OC_FOR_OF_HAS_NEXT) \ CBC_OPCODE (CBC_EXT_SET_SETTER, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2, 0, \ VM_OC_SET_SETTER | VM_OC_NON_STATIC_FLAG | VM_OC_GET_LITERAL_LITERAL) \ CBC_FORWARD_BRANCH (CBC_EXT_TRY_CREATE_CONTEXT, PARSER_TRY_CONTEXT_STACK_ALLOCATION, \ @@ -510,8 +520,6 @@ -1 + PARSER_SUPER_CLASS_CONTEXT_STACK_ALLOCATION, VM_OC_CLASS_HERITAGE) \ \ /* Basic opcodes. */ \ - CBC_OPCODE (CBC_EXT_PUSH_NAMED_FUNC_EXPRESSION, CBC_HAS_LITERAL_ARG | CBC_HAS_LITERAL_ARG2, 1, \ - VM_OC_PUSH_NAMED_FUNC_EXPR | VM_OC_GET_LITERAL_LITERAL) \ CBC_OPCODE (CBC_EXT_PUSH_LITERAL_PUSH_NUMBER_0, CBC_HAS_LITERAL_ARG, 2, \ VM_OC_PUSH_LIT_0 | VM_OC_GET_LITERAL) \ CBC_OPCODE (CBC_EXT_PUSH_LITERAL_PUSH_NUMBER_POS_BYTE, CBC_HAS_LITERAL_ARG | CBC_HAS_BYTE_ARG, 2, \ diff --git a/jerry-core/parser/js/js-lexer.h b/jerry-core/parser/js/js-lexer.h index 4014729eb..3316b4547 100644 --- a/jerry-core/parser/js/js-lexer.h +++ b/jerry-core/parser/js/js-lexer.h @@ -153,6 +153,10 @@ typedef enum LEXER_COMMA_SEP_LIST, /**< comma separated bracketed expression list */ LEXER_SCAN_SWITCH, /**< special value for switch pre-scan */ LEXER_CLASS_CONSTRUCTOR, /**< special value for class constructor method */ +#if ENABLED (JERRY_ES2015_FOR_OF) + LEXER_FOR_IN_OF, /**< special value during for in/of statmenet scanning */ + LEXER_LITERAL_OF, /**< 'of' literal */ +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ #if !ENABLED (JERRY_ES2015) /* Future reserved words: these keywords diff --git a/jerry-core/parser/js/js-parser-scanner.c b/jerry-core/parser/js/js-parser-scanner.c index 6bf508a78..60bff8fef 100644 --- a/jerry-core/parser/js/js-parser-scanner.c +++ b/jerry-core/parser/js/js-parser-scanner.c @@ -675,7 +675,13 @@ parser_scan_until (parser_context_t *context_p, /**< context */ { lexer_next_token (context_p); - if (end_type == LEXER_KEYW_IN) +#if ENABLED (JERRY_ES2015_FOR_OF) + lexer_token_type_t for_in_of_token = LEXER_FOR_IN_OF; +#else /* !ENABLED (JERRY_ES2015_FOR_OF) */ + lexer_token_type_t for_in_of_token = LEXER_KEYW_IN; +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ + + if (end_type == for_in_of_token) { end_type_b = LEXER_SEMICOLON; if (context_p->token.type == LEXER_KEYW_VAR) @@ -697,11 +703,31 @@ parser_scan_until (parser_context_t *context_p, /**< context */ parser_raise_error (context_p, PARSER_ERR_EXPRESSION_EXPECTED); } - if (stack_top == SCAN_STACK_HEAD - && (type == end_type || type == end_type_b)) + if (stack_top == SCAN_STACK_HEAD) { - parser_stack_pop_uint8 (context_p); - return; + if (type == end_type || type == end_type_b) + { + parser_stack_pop_uint8 (context_p); + return; + } + +#if ENABLED (JERRY_ES2015_FOR_OF) + if (end_type == LEXER_FOR_IN_OF) + { + if (type == LEXER_KEYW_IN) + { + parser_stack_pop_uint8 (context_p); + context_p->token.type = LEXER_KEYW_IN; + return; + } + else if (type == LEXER_LITERAL && lexer_compare_raw_identifier_to_current (context_p, "of", 2)) + { + parser_stack_pop_uint8 (context_p); + context_p->token.type = LEXER_LITERAL_OF; + return; + } + } +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ } switch (mode) diff --git a/jerry-core/parser/js/js-parser-statm.c b/jerry-core/parser/js/js-parser-statm.c index 99d1576e4..4d411627a 100644 --- a/jerry-core/parser/js/js-parser-statm.c +++ b/jerry-core/parser/js/js-parser-statm.c @@ -21,6 +21,13 @@ #include "ecma-helpers.h" #include "lit-char-helpers.h" +#if ENABLED (JERRY_ES2015_FOR_OF) +#if !ENABLED (JERRY_ES2015_BUILTIN_ITERATOR) +#error "For of support requires ES2015 iterator support" +#endif /* !ENABLED (JERRY_ES2015_BUILTIN_ITERATOR) */ +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ + + /** \addtogroup parser Parser * @{ * @@ -71,6 +78,9 @@ typedef enum * Break and continue uses another instruction form * when crosses their borders. */ PARSER_STATEMENT_FOR_IN, +#if ENABLED (JERRY_ES2015_FOR_OF) + PARSER_STATEMENT_FOR_OF, +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ PARSER_STATEMENT_WITH, PARSER_STATEMENT_TRY, } parser_statement_type_t; @@ -147,6 +157,17 @@ typedef struct uint32_t start_offset; /**< start byte code offset */ } parser_for_in_statement_t; +#if ENABLED (JERRY_ES2015_FOR_OF) +/** + * For-of statement. + */ +typedef struct +{ + parser_branch_t branch; /**< branch to the end */ + uint32_t start_offset; /**< start byte code offset */ +} parser_for_of_statement_t; +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ + /** * With statement. */ @@ -183,7 +204,7 @@ typedef struct static inline size_t parser_statement_length (uint8_t type) /**< type of statement */ { - static const uint8_t statement_lengths[12] = + static const uint8_t statement_lengths[] = { /* PARSER_STATEMENT_BLOCK */ 1, @@ -205,6 +226,10 @@ parser_statement_length (uint8_t type) /**< type of statement */ (uint8_t) (sizeof (parser_for_statement_t) + sizeof (parser_loop_statement_t) + 1), /* PARSER_STATEMENT_FOR_IN */ (uint8_t) (sizeof (parser_for_in_statement_t) + sizeof (parser_loop_statement_t) + 1), +#if ENABLED (JERRY_ES2015_FOR_OF) + /* PARSER_STATEMENT_FOR_OF */ + (uint8_t) (sizeof (parser_for_of_statement_t) + sizeof (parser_loop_statement_t) + 1), +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ /* PARSER_STATEMENT_WITH */ (uint8_t) (sizeof (parser_with_statement_t) + 1), /* PARSER_STATEMENT_TRY */ @@ -212,7 +237,6 @@ parser_statement_length (uint8_t type) /**< type of statement */ }; JERRY_ASSERT (type >= PARSER_STATEMENT_BLOCK && type <= PARSER_STATEMENT_TRY); - JERRY_ASSERT (PARSER_STATEMENT_TRY - PARSER_STATEMENT_BLOCK == 11); return statement_lengths[type - PARSER_STATEMENT_BLOCK]; } /* parser_statement_length */ @@ -837,6 +861,52 @@ parser_parse_while_statement_end (parser_context_t *context_p) /**< context */ context_p->token = current_token; } /* parser_parse_while_statement_end */ +/** + * Check whether the opcode is a valid LeftHandSide expression + * and convert it back to an assignment. + * + * @return the compatible assignment opcode + */ +static uint16_t +parser_check_left_hand_side_expression (parser_context_t *context_p, /**< context */ + uint16_t opcode) /**< opcode to check */ +{ + if (opcode == CBC_PUSH_LITERAL + && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) + { + context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; + return CBC_ASSIGN_SET_IDENT; + } + else if (opcode == CBC_PUSH_PROP) + { + context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; + return CBC_ASSIGN; + } + else if (opcode == CBC_PUSH_PROP_LITERAL) + { + context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; + return CBC_ASSIGN_PROP_LITERAL; + } + else if (opcode == CBC_PUSH_PROP_LITERAL_LITERAL) + { + context_p->last_cbc_opcode = CBC_PUSH_TWO_LITERALS; + return CBC_ASSIGN; + } + else if (opcode == CBC_PUSH_PROP_THIS_LITERAL) + { + context_p->last_cbc_opcode = CBC_PUSH_THIS_LITERAL; + return CBC_ASSIGN; + } + else + { + /* Invalid LeftHandSide expression. */ + parser_emit_cbc_ext (context_p, CBC_EXT_THROW_REFERENCE_ERROR); + return CBC_ASSIGN; + } + + return opcode; +} /* parser_check_left_hand_side_expression */ + /** * Parse for statement (starting part). */ @@ -854,7 +924,13 @@ parser_parse_for_statement_start (parser_context_t *context_p) /**< context */ parser_raise_error (context_p, PARSER_ERR_LEFT_PAREN_EXPECTED); } - parser_scan_until (context_p, &start_range, LEXER_KEYW_IN); +#if ENABLED (JERRY_ES2015_FOR_OF) + lexer_token_type_t scan_token = LEXER_FOR_IN_OF; +#else /* !ENABLED (JERRY_ES2015_FOR_OF) */ + lexer_token_type_t scan_token = LEXER_KEYW_IN; +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ + + parser_scan_until (context_p, &start_range, scan_token); if (context_p->token.type == LEXER_KEYW_IN) { @@ -925,38 +1001,7 @@ parser_parse_for_statement_start (parser_context_t *context_p) /**< context */ JERRY_ASSERT (opcode != CBC_PUSH_TWO_LITERALS && opcode != CBC_PUSH_THREE_LITERALS); - if (opcode == CBC_PUSH_LITERAL - && context_p->last_cbc.literal_type == LEXER_IDENT_LITERAL) - { - opcode = CBC_ASSIGN_SET_IDENT; - context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; - } - else if (opcode == CBC_PUSH_PROP) - { - opcode = CBC_ASSIGN; - context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; - } - else if (opcode == CBC_PUSH_PROP_LITERAL) - { - opcode = CBC_ASSIGN_PROP_LITERAL; - context_p->last_cbc_opcode = PARSER_CBC_UNAVAILABLE; - } - else if (opcode == CBC_PUSH_PROP_LITERAL_LITERAL) - { - opcode = CBC_ASSIGN; - context_p->last_cbc_opcode = CBC_PUSH_TWO_LITERALS; - } - else if (opcode == CBC_PUSH_PROP_THIS_LITERAL) - { - opcode = CBC_ASSIGN; - context_p->last_cbc_opcode = CBC_PUSH_THIS_LITERAL; - } - else - { - /* Invalid LeftHandSide expression. */ - parser_emit_cbc_ext (context_p, CBC_EXT_THROW_REFERENCE_ERROR); - opcode = CBC_ASSIGN; - } + opcode = parser_check_left_hand_side_expression (context_p, opcode); parser_emit_cbc_ext (context_p, CBC_EXT_FOR_IN_GET_NEXT); parser_flush_cbc (context_p); @@ -980,6 +1025,101 @@ parser_parse_for_statement_start (parser_context_t *context_p) /**< context */ parser_stack_push_uint8 (context_p, PARSER_STATEMENT_FOR_IN); parser_stack_iterator_init (context_p, &context_p->last_statement); } +#if ENABLED (JERRY_ES2015_FOR_OF) + else if (context_p->token.type == LEXER_LITERAL_OF) + { + parser_for_of_statement_t for_of_statement; + lexer_range_t range; + + lexer_next_token (context_p); + parser_parse_expression (context_p, PARSE_EXPR); + + if (context_p->token.type != LEXER_RIGHT_PAREN) + { + parser_raise_error (context_p, PARSER_ERR_RIGHT_PAREN_EXPECTED); + } + +#ifndef JERRY_NDEBUG + PARSER_PLUS_EQUAL_U16 (context_p->context_stack_depth, PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION); +#endif /* !JERRY_NDEBUG */ + + parser_emit_cbc_ext_forward_branch (context_p, + CBC_EXT_FOR_OF_CREATE_CONTEXT, + &for_of_statement.branch); + + JERRY_ASSERT (context_p->last_cbc_opcode == PARSER_CBC_UNAVAILABLE); + for_of_statement.start_offset = context_p->byte_code_size; + + parser_save_range (context_p, &range, context_p->source_end_p); + parser_set_range (context_p, &start_range); + lexer_next_token (context_p); + + if (context_p->token.type == LEXER_KEYW_VAR) + { + uint16_t literal_index; + + lexer_expect_identifier (context_p, LEXER_IDENT_LITERAL); + JERRY_ASSERT (context_p->token.type == LEXER_LITERAL + && context_p->token.lit_location.type == LEXER_IDENT_LITERAL); + + context_p->lit_object.literal_p->status_flags |= LEXER_FLAG_VAR; + + literal_index = context_p->lit_object.index; + + lexer_next_token (context_p); + + if (context_p->token.type == LEXER_ASSIGN) + { + parser_branch_t branch; + + /* Initialiser is never executed. */ + parser_emit_cbc_forward_branch (context_p, CBC_JUMP_FORWARD, &branch); + lexer_next_token (context_p); + parser_parse_expression (context_p, + PARSE_EXPR_STATEMENT | PARSE_EXPR_NO_COMMA); + parser_set_branch_to_current_position (context_p, &branch); + } + + parser_emit_cbc_ext (context_p, CBC_EXT_FOR_OF_GET_NEXT); + parser_emit_cbc_literal (context_p, CBC_ASSIGN_SET_IDENT, literal_index); + } + else + { + uint16_t opcode; + + parser_parse_expression (context_p, PARSE_EXPR); + + opcode = context_p->last_cbc_opcode; + + /* The CBC_EXT_FOR_OF_CREATE_CONTEXT flushed the opcode combiner. */ + JERRY_ASSERT (opcode != CBC_PUSH_TWO_LITERALS + && opcode != CBC_PUSH_THREE_LITERALS); + + opcode = parser_check_left_hand_side_expression (context_p, opcode); + + parser_emit_cbc_ext (context_p, CBC_EXT_FOR_OF_GET_NEXT); + parser_flush_cbc (context_p); + + context_p->last_cbc_opcode = opcode; + } + + if (context_p->token.type != LEXER_EOS) + { + parser_raise_error (context_p, PARSER_ERR_OF_EXPECTED); + } + + parser_flush_cbc (context_p); + parser_set_range (context_p, &range); + lexer_next_token (context_p); + + loop.branch_list_p = NULL; + + parser_stack_push (context_p, &for_of_statement, sizeof (parser_for_of_statement_t)); + parser_stack_push (context_p, &loop, sizeof (parser_loop_statement_t)); + parser_stack_push_uint8 (context_p, PARSER_STATEMENT_FOR_OF); + parser_stack_iterator_init (context_p, &context_p->last_statement); + } +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ else { parser_for_statement_t for_statement; @@ -1482,6 +1622,9 @@ parser_parse_break_statement (parser_context_t *context_p) /**< context */ } if (type == PARSER_STATEMENT_FOR_IN +#if ENABLED (JERRY_ES2015_FOR_OF) + || type == PARSER_STATEMENT_FOR_OF +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ || type == PARSER_STATEMENT_WITH || type == PARSER_STATEMENT_TRY) { @@ -1523,6 +1666,9 @@ parser_parse_break_statement (parser_context_t *context_p) /**< context */ } if (type == PARSER_STATEMENT_FOR_IN +#if ENABLED (JERRY_ES2015_FOR_OF) + || type == PARSER_STATEMENT_FOR_OF +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ || type == PARSER_STATEMENT_WITH || type == PARSER_STATEMENT_TRY) { @@ -1534,6 +1680,9 @@ parser_parse_break_statement (parser_context_t *context_p) /**< context */ || type == PARSER_STATEMENT_DO_WHILE || type == PARSER_STATEMENT_WHILE || type == PARSER_STATEMENT_FOR +#if ENABLED (JERRY_ES2015_FOR_OF) + || type == PARSER_STATEMENT_FOR_OF +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ || type == PARSER_STATEMENT_FOR_IN) { parser_loop_statement_t loop; @@ -1568,7 +1717,7 @@ parser_parse_continue_statement (parser_context_t *context_p) /**< context */ && context_p->token.lit_location.type == LEXER_IDENT_LITERAL) { parser_stack_iterator_t loop_iterator; - bool for_in_was_seen = false; + bool for_in_of_was_seen = false; loop_iterator.current_p = NULL; @@ -1608,20 +1757,29 @@ parser_parse_continue_statement (parser_context_t *context_p) /**< context */ continue; } +#if ENABLED (JERRY_ES2015_FOR_OF) + bool is_for_in_of_statement = (type == PARSER_STATEMENT_FOR_IN) || (type == PARSER_STATEMENT_FOR_OF); +#else /* !ENABLED (JERRY_ES2015_FOR_OF) */ + bool is_for_in_of_statement = (type == PARSER_STATEMENT_FOR_IN); +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ + if (type == PARSER_STATEMENT_WITH || type == PARSER_STATEMENT_TRY - || for_in_was_seen) + || for_in_of_was_seen) { opcode = CBC_JUMP_FORWARD_EXIT_CONTEXT; } - else if (type == PARSER_STATEMENT_FOR_IN) + else if (is_for_in_of_statement) { - for_in_was_seen = true; + for_in_of_was_seen = true; } if (type == PARSER_STATEMENT_DO_WHILE || type == PARSER_STATEMENT_WHILE || type == PARSER_STATEMENT_FOR +#if ENABLED (JERRY_ES2015_FOR_OF) + || type == PARSER_STATEMENT_FOR_OF +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ || type == PARSER_STATEMENT_FOR_IN) { loop_iterator = iterator; @@ -1647,6 +1805,9 @@ parser_parse_continue_statement (parser_context_t *context_p) /**< context */ if (type == PARSER_STATEMENT_DO_WHILE || type == PARSER_STATEMENT_WHILE || type == PARSER_STATEMENT_FOR +#if ENABLED (JERRY_ES2015_FOR_OF) + || type == PARSER_STATEMENT_FOR_OF +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ || type == PARSER_STATEMENT_FOR_IN) { parser_loop_statement_t loop; @@ -2182,6 +2343,9 @@ parser_parse_statements (parser_context_t *context_p) /**< context */ || context_p->stack_top_uint8 == PARSER_STATEMENT_WHILE || context_p->stack_top_uint8 == PARSER_STATEMENT_FOR || context_p->stack_top_uint8 == PARSER_STATEMENT_FOR_IN +#if ENABLED (JERRY_ES2015_FOR_OF) + || context_p->stack_top_uint8 == PARSER_STATEMENT_FOR_OF +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ || context_p->stack_top_uint8 == PARSER_STATEMENT_WITH) { parser_raise_error (context_p, PARSER_ERR_STATEMENT_EXPECTED); @@ -2619,6 +2783,34 @@ parser_parse_statements (parser_context_t *context_p) /**< context */ parser_set_branch_to_current_position (context_p, &for_in_statement.branch); continue; } +#if ENABLED (JERRY_ES2015_FOR_OF) + case PARSER_STATEMENT_FOR_OF: + { + parser_for_of_statement_t for_of_statement; + parser_loop_statement_t loop; + + parser_stack_pop_uint8 (context_p); + parser_stack_pop (context_p, &loop, sizeof (parser_loop_statement_t)); + parser_stack_pop (context_p, &for_of_statement, sizeof (parser_for_of_statement_t)); + parser_stack_iterator_init (context_p, &context_p->last_statement); + + parser_set_continues_to_current_position (context_p, loop.branch_list_p); + + parser_flush_cbc (context_p); + PARSER_MINUS_EQUAL_U16 (context_p->stack_depth, PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION); +#ifndef JERRY_NDEBUG + PARSER_MINUS_EQUAL_U16 (context_p->context_stack_depth, PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION); +#endif /* !JERRY_NDEBUG */ + + parser_emit_cbc_ext_backward_branch (context_p, + CBC_EXT_BRANCH_IF_FOR_OF_HAS_NEXT, + for_of_statement.start_offset); + + parser_set_breaks_to_current_position (context_p, loop.branch_list_p); + parser_set_branch_to_current_position (context_p, &for_of_statement.branch); + continue; + } +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ case PARSER_STATEMENT_WITH: { @@ -2705,6 +2897,9 @@ parser_free_jumps (parser_stack_iterator_t iterator) /**< iterator position */ case PARSER_STATEMENT_WHILE: case PARSER_STATEMENT_FOR: case PARSER_STATEMENT_FOR_IN: +#if ENABLED (JERRY_ES2015_FOR_OF) + case PARSER_STATEMENT_FOR_OF: +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ { parser_loop_statement_t loop; diff --git a/jerry-core/parser/js/js-parser-util.c b/jerry-core/parser/js/js-parser-util.c index 6ca59d47f..92a4ccefc 100644 --- a/jerry-core/parser/js/js-parser-util.c +++ b/jerry-core/parser/js/js-parser-util.c @@ -954,6 +954,12 @@ parser_error_to_string (parser_error_t error) /**< error code */ { return "Expected 'in' token."; } +#if ENABLED (JERRY_ES2015_FOR_OF) + case PARSER_ERR_OF_EXPECTED: + { + return "Expected 'of' token."; + } +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ case PARSER_ERR_WHILE_EXPECTED: { return "While expected for do-while loop."; diff --git a/jerry-core/parser/js/js-parser.h b/jerry-core/parser/js/js-parser.h index af3f21455..1dc114e49 100644 --- a/jerry-core/parser/js/js-parser.h +++ b/jerry-core/parser/js/js-parser.h @@ -98,6 +98,9 @@ typedef enum PARSER_ERR_COLON_FOR_CONDITIONAL_EXPECTED, /**< colon expected for conditional expression */ PARSER_ERR_SEMICOLON_EXPECTED, /**< semicolon expected */ PARSER_ERR_IN_EXPECTED, /**< in keyword expected */ +#if ENABLED (JERRY_ES2015_FOR_OF) + PARSER_ERR_OF_EXPECTED, /**< of keyword expected */ +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ PARSER_ERR_WHILE_EXPECTED, /**< while expected for do-while loop */ PARSER_ERR_CATCH_FINALLY_EXPECTED, /**< catch or finally expected */ PARSER_ERR_ARRAY_ITEM_SEPARATOR_EXPECTED, /**< array item separator expected */ diff --git a/jerry-core/profiles/README.md b/jerry-core/profiles/README.md index 03e3d1b54..1272fa647 100644 --- a/jerry-core/profiles/README.md +++ b/jerry-core/profiles/README.md @@ -39,6 +39,7 @@ JERRY_ES2015_BUILTIN_SET=0 JERRY_ES2015_BUILTIN_SYMBOL=0 JERRY_ES2015_BUILTIN_TYPEDARRAY=0 JERRY_ES2015_CLASS=0 +JERRY_ES2015_FOR_OF=0 JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER=0 JERRY_ES2015_FUNCTION_REST_PARAMETER=0 JERRY_ES2015_MODULE_SYSTEM=0 @@ -127,6 +128,8 @@ defined to `1`. Enables or disables the [ArrayBuffer](http://www.ecma-international.org/ecma-262/6.0/#sec-arraybuffer-objects) and [TypedArray](http://www.ecma-international.org/ecma-262/6.0/#sec-typedarray-objects) built-ins. * `JERRY_ES2015_CLASS`: Enables or disables the [class](https://www.ecma-international.org/ecma-262/6.0/#sec-class-definitions) language element. +* `JERRY_ES2015_FOR_OF`: + Enables or disables the [for of](https://www.ecma-international.org/ecma-262/6.0/#sec-for-in-and-for-of-statements) language element. * `JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER`: Enables or disables the [default value](http://www.ecma-international.org/ecma-262/6.0/#sec-function-definitions) for formal parameters. * `JERRY_ES2015_FUNCTION_REST_PARAMETER`: @@ -150,6 +153,7 @@ defined to `1`. * `JERRY_ES2015_BUILTIN_SYMBOL` * `JERRY_ES2015_BUILTIN_TYPEDARRAY` * `JERRY_ES2015_CLASS` + * `JERRY_ES2015_FOR_OF` * `JERRY_ES2015_FUNCTION_PARAMETER_INITIALIZER` * `JERRY_ES2015_FUNCTION_REST_PARAMETER` * `JERRY_ES2015_MODULE_SYSTEM` diff --git a/jerry-core/vm/vm-stack.c b/jerry-core/vm/vm-stack.c index 06b7160e2..7add98589 100644 --- a/jerry-core/vm/vm-stack.c +++ b/jerry-core/vm/vm-stack.c @@ -79,6 +79,16 @@ vm_stack_context_abort (vm_frame_ctx_t *frame_ctx_p, /**< frame context */ vm_stack_top_p -= PARSER_WITH_CONTEXT_STACK_ALLOCATION; break; } +#if ENABLED (JERRY_ES2015_FOR_OF) + case VM_CONTEXT_FOR_OF: + { + ecma_free_value (vm_stack_top_p[-2]); + ecma_free_value (vm_stack_top_p[-3]); + VM_MINUS_EQUAL_U16 (frame_ctx_p->context_depth, PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION); + vm_stack_top_p -= PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION; + break; + } +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ default: { JERRY_ASSERT (VM_GET_CONTEXT_TYPE (vm_stack_top_p[-1]) == VM_CONTEXT_FOR_IN); diff --git a/jerry-core/vm/vm-stack.h b/jerry-core/vm/vm-stack.h index b80bc442d..331c264b0 100644 --- a/jerry-core/vm/vm-stack.h +++ b/jerry-core/vm/vm-stack.h @@ -44,6 +44,9 @@ typedef enum VM_CONTEXT_SUPER_CLASS, /**< super class context */ #endif /* ENABLED (JERRY_ES2015_CLASS) */ VM_CONTEXT_FOR_IN, /**< for-in context */ +#if ENABLED (JERRY_ES2015_FOR_OF) + VM_CONTEXT_FOR_OF, /**< for-of context */ +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ } vm_stack_context_type_t; ecma_value_t *vm_stack_context_abort (vm_frame_ctx_t *frame_ctx_p, ecma_value_t *vm_stack_top_p); diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index 3e54f7a60..6e3fd0905 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -24,6 +24,7 @@ #include "ecma-function-object.h" #include "ecma-gc.h" #include "ecma-helpers.h" +#include "ecma-iterator-object.h" #include "ecma-lcache.h" #include "ecma-lex-env.h" #include "ecma-objects.h" @@ -2888,6 +2889,93 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ } continue; } +#if ENABLED (JERRY_ES2015_FOR_OF) + case VM_OC_FOR_OF_CREATE_CONTEXT: + { + ecma_value_t value = *(--stack_top_p); + + JERRY_ASSERT (frame_ctx_p->registers_p + register_end + frame_ctx_p->context_depth == stack_top_p); + + ecma_value_t iterator = ecma_op_get_iterator (value, ECMA_VALUE_EMPTY); + + ecma_free_value (value); + + if (ECMA_IS_VALUE_ERROR (iterator)) + { + result = iterator; + goto error; + } + + ecma_value_t iterator_step = ecma_op_iterator_step (iterator); + + if (ECMA_IS_VALUE_ERROR (iterator_step)) + { + ecma_free_value (iterator); + result = iterator_step; + goto error; + } + + if (ecma_is_value_false (iterator_step)) + { + ecma_free_value (iterator); + byte_code_p = byte_code_start_p + branch_offset; + continue; + } + + branch_offset += (int32_t) (byte_code_start_p - frame_ctx_p->byte_code_start_p); + + VM_PLUS_EQUAL_U16 (frame_ctx_p->context_depth, PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION); + stack_top_p += PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION; + stack_top_p[-1] = (ecma_value_t) VM_CREATE_CONTEXT (VM_CONTEXT_FOR_OF, branch_offset); + stack_top_p[-2] = iterator_step; + stack_top_p[-3] = iterator; + + continue; + } + case VM_OC_FOR_OF_GET_NEXT: + { + ecma_value_t *context_top_p = frame_ctx_p->registers_p + register_end + frame_ctx_p->context_depth; + JERRY_ASSERT (VM_GET_CONTEXT_TYPE (context_top_p[-1]) == VM_CONTEXT_FOR_OF); + + ecma_value_t next_value = ecma_op_iterator_value (context_top_p[-2]); + + if (ECMA_IS_VALUE_ERROR (next_value)) + { + result = next_value; + goto error; + } + + *stack_top_p++ = next_value; + continue; + } + case VM_OC_FOR_OF_HAS_NEXT: + { + JERRY_ASSERT (frame_ctx_p->registers_p + register_end + frame_ctx_p->context_depth == stack_top_p); + + ecma_value_t iterator_step = ecma_op_iterator_step (stack_top_p[-3]); + + if (ECMA_IS_VALUE_ERROR (iterator_step)) + { + result = iterator_step; + goto error; + } + + if (!ecma_is_value_false (iterator_step)) + { + ecma_free_value (stack_top_p[-2]); + stack_top_p[-2] = iterator_step; + byte_code_p = byte_code_start_p + branch_offset; + continue; + } + + ecma_free_value (stack_top_p[-2]); + ecma_free_value (stack_top_p[-3]); + VM_MINUS_EQUAL_U16 (frame_ctx_p->context_depth, PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION); + stack_top_p -= PARSER_FOR_OF_CONTEXT_STACK_ALLOCATION; + + continue; + } +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ case VM_OC_TRY: { /* Try opcode simply creates the try context. */ diff --git a/jerry-core/vm/vm.h b/jerry-core/vm/vm.h index 8df670773..0327831c1 100644 --- a/jerry-core/vm/vm.h +++ b/jerry-core/vm/vm.h @@ -207,6 +207,11 @@ typedef enum VM_OC_FOR_IN_CREATE_CONTEXT, /**< for in create context */ VM_OC_FOR_IN_GET_NEXT, /**< get next */ VM_OC_FOR_IN_HAS_NEXT, /**< has next */ +#if ENABLED (JERRY_ES2015_FOR_OF) + VM_OC_FOR_OF_CREATE_CONTEXT, /**< for of create context */ + VM_OC_FOR_OF_GET_NEXT, /**< get next */ + VM_OC_FOR_OF_HAS_NEXT, /**< has next */ +#endif /* ENABLED (JERRY_ES2015_FOR_OF) */ VM_OC_TRY, /**< try */ VM_OC_CATCH, /**< catch */ VM_OC_FINALLY, /**< finally */ @@ -269,6 +274,11 @@ typedef enum VM_OC_PUSH_CONSTRUCTOR_THIS = VM_OC_NONE, /**< push 'this' inside a class constructor */ VM_OC_CONSTRUCTOR_RET = VM_OC_NONE, /**< explicit return from a class constructor */ #endif /* !ENABLED (JERRY_ES2015_CLASS) */ +#if !ENABLED (JERRY_ES2015_FOR_OF) + VM_OC_FOR_OF_CREATE_CONTEXT = VM_OC_NONE, /**< for of create context */ + VM_OC_FOR_OF_GET_NEXT = VM_OC_NONE, /**< get next */ + VM_OC_FOR_OF_HAS_NEXT = VM_OC_NONE, /**< has next */ +#endif /* !ENABLED (JERRY_ES2015_FOR_OF) */ VM_OC_UNUSED = VM_OC_NONE /**< placeholder if the list is empty */ } vm_oc_unused_types; diff --git a/tests/jerry/es2015/for-of.js b/tests/jerry/es2015/for-of.js new file mode 100644 index 000000000..f76ff1b40 --- /dev/null +++ b/tests/jerry/es2015/for-of.js @@ -0,0 +1,117 @@ +// 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 parse (txt) { + try { + eval (txt) + assert (false) + } catch (e) { + assert (e instanceof SyntaxError) + } +} + +function checkError (obj) { + try { + for (var a of obj); + assert (false) + } catch (e) { + assert (e instanceof TypeError) + } +} + +var arr = [1,2,3,4] + +var forOf = + "for var prop of obj" + + " obj [prop] += 4" +parse (forOf) + +var forOf = + "for [var prop of obj]" + + " obj[prop] += 4;" +parse (forOf) + +var forOf = + "for (var prop obj)" + + " obj[prop] += 4;" +parse (forOf) + +var forOf = + "foreach (var prop of obj)" + + " obj[prop] += 4;" +parse (forOf) + +checkError(5) + +var obj = {} +Object.defineProperty(obj, Symbol.iterator, { get : function () { throw TypeError ('foo');}}); +checkError (obj); + +var obj = { + [Symbol.iterator] : 5 +} + +checkError (obj); + +var obj = { + [Symbol.iterator] () { + return 5 + } +} + +checkError(obj); + +var obj = { + [Symbol.iterator] () { + return {} + } +} +checkError(obj); + +var obj = { + [Symbol.iterator] () { + return { + next() { + return 5; + } + } + } +} +checkError(obj); + +var array = [0, 1, 2, 3, 4, 5]; + +var i = 0; +for (var a of array) { + assert (a === i++); +} + +var obj = { + [Symbol.iterator]() { + return { + counter : 0, + next () { + if (this.counter == 10) { + return { done : true, value : undefined }; + } + return { done: false, value: this.counter++ }; + } + } + } +} + +var i = 0; +for (var a of obj) { + assert (a === i++); +} diff --git a/tests/unit-core/test-snapshot.c b/tests/unit-core/test-snapshot.c index ecbc031fb..2d9fe027d 100644 --- a/tests/unit-core/test-snapshot.c +++ b/tests/unit-core/test-snapshot.c @@ -223,7 +223,7 @@ main (void) /* Check the snapshot data. Unused bytes should be filled with zeroes */ const uint8_t expected_data[] = { - 0x4A, 0x52, 0x52, 0x59, 0x16, 0x00, 0x00, 0x00, + 0x4A, 0x52, 0x52, 0x59, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00,