diff --git a/jerry-core/parser/js/opcodes-dumper.cpp b/jerry-core/parser/js/opcodes-dumper.cpp index 93df79ad8..cd48c8d07 100644 --- a/jerry-core/parser/js/opcodes-dumper.cpp +++ b/jerry-core/parser/js/opcodes-dumper.cpp @@ -708,6 +708,23 @@ eval_ret_operand (void) return ret; } /* eval_ret_operand */ +/** + * Creates operand for taking iterator value (next property name) + * from for-in opcode handler. + * + * @return constructed operand + */ +operand +jsp_create_operand_for_in_special_reg (void) +{ + operand ret; + + ret.type = OPERAND_TMP; + ret.data.uid = OPCODE_REG_SPECIAL_FOR_IN_PROPERTY_NAME; + + return ret; +} /* jsp_create_operand_for_in_special_reg */ + bool operand_is_empty (operand op) { @@ -2362,6 +2379,62 @@ dump_with_end (void) serializer_dump_op_meta (create_op_meta_000 (opcode)); } /* dump_with_end */ +/** + * Dump template of 'for_in' instruction. + * + * Note: + * the instruction's flags field is written later (see also: rewrite_for_in). + * + * @return position of dumped instruction + */ +opcode_counter_t +dump_for_in_for_rewrite (operand op) /**< operand - result of evaluating Expression + * in for-in statement */ +{ + opcode_counter_t oc = serializer_get_current_opcode_counter (); + + if (op.type == OPERAND_LITERAL) + { + const opcode_t opcode = getop_for_in (LITERAL_TO_REWRITE, INVALID_VALUE, INVALID_VALUE); + serializer_dump_op_meta (create_op_meta_100 (opcode, op.data.lit_id)); + } + else + { + JERRY_ASSERT (op.type == OPERAND_TMP); + + const opcode_t opcode = getop_for_in (op.data.uid, INVALID_VALUE, INVALID_VALUE); + serializer_dump_op_meta (create_op_meta_000 (opcode)); + } + + return oc; +} /* dump_for_in_for_rewrite */ + +/** + * Write position of 'for_in' block's end to specified 'for_in' instruction template, + * dumped earlier (see also: dump_for_in_for_rewrite). + */ +void +rewrite_for_in (opcode_counter_t oc) /**< opcode counter of the instruction template */ +{ + op_meta for_in_op_meta = serializer_get_op_meta (oc); + + idx_t id1, id2; + split_opcode_counter (get_diff_from (oc), &id1, &id2); + for_in_op_meta.op.data.for_in.oc_idx_1 = id1; + for_in_op_meta.op.data.for_in.oc_idx_2 = id2; + serializer_rewrite_op_meta (oc, for_in_op_meta); +} /* rewrite_for_in */ + +/** + * Dump 'meta' instruction of 'end for_in' type + */ +void +dump_for_in_end (void) +{ + const opcode_t opcode = getop_meta (OPCODE_META_TYPE_END_FOR_IN, INVALID_VALUE, INVALID_VALUE); + serializer_dump_op_meta (create_op_meta_000 (opcode)); +} /* dump_for_in_end */ + void dump_try_for_rewrite (void) { diff --git a/jerry-core/parser/js/opcodes-dumper.h b/jerry-core/parser/js/opcodes-dumper.h index 72ed8573c..2870d7222 100644 --- a/jerry-core/parser/js/opcodes-dumper.h +++ b/jerry-core/parser/js/opcodes-dumper.h @@ -49,6 +49,7 @@ typedef enum __attr_packed___ operand empty_operand (void); operand literal_operand (lit_cpointer_t); operand eval_ret_operand (void); +operand jsp_create_operand_for_in_special_reg (void); bool operand_is_empty (operand); void dumper_init (void); @@ -211,6 +212,10 @@ opcode_counter_t dump_with_for_rewrite (operand); void rewrite_with (opcode_counter_t); void dump_with_end (void); +opcode_counter_t dump_for_in_for_rewrite (operand); +void rewrite_for_in (opcode_counter_t); +void dump_for_in_end (void); + void dump_try_for_rewrite (void); void rewrite_try (void); void dump_catch_for_rewrite (operand); diff --git a/jerry-core/parser/js/parser.cpp b/jerry-core/parser/js/parser.cpp index a2696256c..3413ef762 100644 --- a/jerry-core/parser/js/parser.cpp +++ b/jerry-core/parser/js/parser.cpp @@ -1768,7 +1768,7 @@ parse_expression (bool in_allowed, /**< flag indicating if 'in' is allowed insid initialiser : '=' LT!* assignment_expression ; */ -static void +static operand parse_variable_declaration (void) { current_token_must_be (TOK_NAME); @@ -1785,6 +1785,8 @@ parse_variable_declaration (void) { lexer_save_token (tok); } + + return name; } /* variable_declaration_list @@ -1933,15 +1935,176 @@ jsp_parse_for_statement (jsp_label_t *outermost_stmt_label_p, /**< outermost (fi } } /* jsp_parse_for_statement */ -static void -parse_for_in (jsp_label_t *outermost_stmt_label_p) /**< outermost (first) label, corresponding to - * the statement (or NULL, if there are no named - * labels associated with the statement) */ +/** + * Parse VariableDeclarationNoIn / LeftHandSideExpression (iterator part) of for-in statement + * + * See also: + * jsp_parse_for_in_statement + * + * @return true - if iterator consists of base and property name, + * false - otherwise, iterator consists of an identifier name (without base). + */ +static bool +jsp_parse_for_in_statement_iterator (operand *base_p, /**< out: base value of member expression, if any, + * empty operand - otherwise */ + operand *identifier_p) /**< out: property name (if base value is not empty), + * identifier - otherwise */ { - (void) outermost_stmt_label_p; + JERRY_ASSERT (base_p != NULL); + JERRY_ASSERT (identifier_p != NULL); - EMIT_SORRY ("'for in' loops are not supported yet"); -} + if (is_keyword (KW_VAR)) + { + skip_newlines (); + + *base_p = empty_operand (); + *identifier_p = parse_variable_declaration (); + + return false; + } + else + { + operand base, identifier; + + /* + * FIXME: + * Remove evaluation of last part of identifier chain + */ + operand i = parse_left_hand_side_expression (&base, &identifier); + + if (operand_is_empty (base)) + { + *base_p = empty_operand (); + *identifier_p = i; + + return false; + } + else + { + *base_p = base; + *identifier_p = identifier; + + return true; + } + } +} /* jsp_parse_for_in_statement_iterator */ + +/** + * Parse for-in statement + * + * See also: + * ECMA-262 v5, 12.6.4 + * + * Note: + * Syntax: + * Iterator Collection Body LoopEnd + * - for ( LeftHandSideExpression in Expression) Statement + * - for (var VariableDeclarationNoIn in Expression) Statement + * + * Note: + * Layout of generate byte-code is the following: + * tmp <- Collection (Expression) + * for_in instruction (tmp, opcode counter of for-in end mark) + * { + * Assignment of OPCODE_REG_SPECIAL_FOR_IN_PROPERTY_NAME to + * Iterator (VariableDeclarationNoIn / LeftHandSideExpression) + * } + * Body (Statement) + * ContinueTarget: + * meta (OPCODE_META_TYPE_END_FOR_IN) + */ +static void +jsp_parse_for_in_statement (jsp_label_t *outermost_stmt_label_p, /**< outermost (first) label, + * corresponding to the statement + * (or NULL, if there are no name + * labels associated with the statement) */ + locus for_body_statement_loc) /**< locus of loop body statement */ +{ + jsp_label_raise_nested_jumpable_border (); + + current_token_must_be (TOK_OPEN_PAREN); + skip_newlines (); + + // Save Iterator location + locus iterator_loc = tok.loc; + + while (tok.loc < for_body_statement_loc) + { + if (jsp_find_next_token_before_the_locus (TOK_KEYWORD, + for_body_statement_loc, + true)) + { + if (is_keyword (KW_IN)) + { + break; + } + else + { + skip_token (); + } + } + else + { + EMIT_ERROR ("Invalid for statement"); + } + } + + JERRY_ASSERT (is_keyword (KW_IN)); + skip_newlines (); + + // Collection + operand collection = parse_expression (true, JSP_EVAL_RET_STORE_NOT_DUMP); + current_token_must_be (TOK_CLOSE_PAREN); + skip_token (); + + // Dump for-in instruction + opcode_counter_t for_in_oc = dump_for_in_for_rewrite (collection); + + // Dump assignment VariableDeclarationNoIn / LeftHandSideExpression <- OPCODE_REG_SPECIAL_FOR_IN_PROPERTY_NAME + lexer_seek (iterator_loc); + tok = lexer_next_token (); + + operand iterator_base, iterator_identifier, for_in_special_reg; + for_in_special_reg = jsp_create_operand_for_in_special_reg (); + + if (jsp_parse_for_in_statement_iterator (&iterator_base, &iterator_identifier)) + { + dump_prop_setter (iterator_base, iterator_identifier, for_in_special_reg); + } + else + { + JERRY_ASSERT (operand_is_empty (iterator_base)); + dump_variable_assignment (iterator_identifier, for_in_special_reg); + } + + // Body + lexer_seek (for_body_statement_loc); + tok = lexer_next_token (); + + parse_statement (NULL); + + // Save LoopEnd locus + const locus loop_end_loc = tok.loc; + + // Setup ContinueTarget + jsp_label_setup_continue_target (outermost_stmt_label_p, + serializer_get_current_opcode_counter ()); + + // Write position of for-in end to for_in instruction + rewrite_for_in (for_in_oc); + + // Dump meta (OPCODE_META_TYPE_END_FOR_IN) + dump_for_in_end (); + + lexer_seek (loop_end_loc); + tok = lexer_next_token (); + if (tok.type != TOK_CLOSE_BRACE) + { + lexer_save_token (tok); + } + + jsp_label_remove_nested_jumpable_border (); +} /* jsp_parse_for_in_statement */ /** * Parse for/for-in statements @@ -1982,7 +2145,7 @@ jsp_parse_for_or_for_in_statement (jsp_label_t *outermost_stmt_label_p) /**< out } else { - parse_for_in (outermost_stmt_label_p); + jsp_parse_for_in_statement (outermost_stmt_label_p, for_body_statement_loc); } } /* jsp_parse_for_or_for_in_statement */ diff --git a/jerry-core/parser/js/scopes-tree.cpp b/jerry-core/parser/js/scopes-tree.cpp index 7a00d9399..013ef856d 100644 --- a/jerry-core/parser/js/scopes-tree.cpp +++ b/jerry-core/parser/js/scopes-tree.cpp @@ -306,6 +306,7 @@ generate_opcode (scopes_tree tree, opcode_counter_t opc_index, lit_id_hash_table case OPCODE (obj_decl): case OPCODE (this_binding): case OPCODE (with): + case OPCODE (for_in): case OPCODE (throw_value): case OPCODE (is_true_jmp_up): case OPCODE (is_true_jmp_down): diff --git a/jerry-core/vm/pretty-printer.cpp b/jerry-core/vm/pretty-printer.cpp index f510bfc49..6dba56b34 100644 --- a/jerry-core/vm/pretty-printer.cpp +++ b/jerry-core/vm/pretty-printer.cpp @@ -232,6 +232,7 @@ pp_op_meta (const opcode_t *opcodes_p, PP_OP (delete_prop, "%s = delete %s.%s;"); PP_OP (typeof, "%s = typeof %s;"); PP_OP (with, "with (%s);"); + PP_OP (for_in, "for_in (%s);"); case NAME_TO_ID (is_true_jmp_up): printf ("if (%s) goto %d;", VAR (1), oc - OC (2, 3)); break; case NAME_TO_ID (is_false_jmp_up): printf ("if (%s == false) goto %d;", VAR (1), oc - OC (2, 3)); break; case NAME_TO_ID (is_true_jmp_down): printf ("if (%s) goto %d;", VAR (1), oc + OC (2, 3)); break; @@ -554,6 +555,11 @@ pp_op_meta (const opcode_t *opcodes_p, printf ("end with;"); break; } + case OPCODE_META_TYPE_END_FOR_IN: + { + printf ("end for-in;"); + break; + } case OPCODE_META_TYPE_FUNCTION_END: { printf ("function end: %d;", oc + OC (2, 3)); diff --git a/tests/jerry/for-in.js b/tests/jerry/for-in.js new file mode 100644 index 000000000..7ff73cc3f --- /dev/null +++ b/tests/jerry/for-in.js @@ -0,0 +1,283 @@ +// Copyright 2015 Samsung Electronics Co., Ltd. +// +// 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. + +// 1. +var simple_obj = {a: 1, b: 2, c: 3, d: 4}; +for (var prop_of_simple_obj in simple_obj) { + simple_obj[prop_of_simple_obj] += 4; +} + +assert(simple_obj.a === 5 + && simple_obj.b === 6 + && simple_obj.c === 7 + && simple_obj.d === 8); + +// 2. +for + ( + var + prop_of_simple_obj in simple_obj + ) { + simple_obj[prop_of_simple_obj] -= 4; +} + +assert(simple_obj.a === 1 + && simple_obj.b === 2 + && simple_obj.c === 3 + && simple_obj.d === 4); + +// 3. +function test() { + var cnt = 0; + + for (var prop_of_simple_obj in simple_obj) { + if (prop_of_simple_obj === 'b') + continue; + + cnt++; + + simple_obj[prop_of_simple_obj] += 4; + } + + return cnt; +} + +var ret_val = test(); + +assert((simple_obj.a === 5 + && simple_obj.b === 2 + && simple_obj.c === 7 + && simple_obj.d == 8) + && ret_val === 3); + +// 4. +var array_obj = new Array(1, 2, 3, 4, 5, 6, 7); +var prop_of_array_obj; + +array_obj.eight = 8; + +for (prop_of_array_obj in array_obj) { + array_obj[prop_of_array_obj] += 1; +} + +assert(array_obj[0] === 2 + && array_obj[1] === 3 + && array_obj[2] === 4 + && array_obj[3] === 5 + && array_obj[4] === 6 + && array_obj[5] === 7 + && array_obj[6] === 8 + && array_obj['eight'] === 9); + +// 5. +var null_obj = null; +for (var prop_of_null_obj in null_obj) { + assert(false); +} + +// 6. +var empty_object = {}; +for (var prop_of_empty_object in empty_object) { + assert(false); +} + +// 7. +for (var i in undefined) { + assert(false); +} + +// 8. +var base_obj = {base_prop: "base"}; + +function constr() { + this.derived_prop = "derived"; +} + +constr.prototype = base_obj; + +var derived_obj = new constr(); + +for (var prop_of_derived_obj in derived_obj) { + derived_obj[prop_of_derived_obj] += "A"; +} + +assert(derived_obj.base_prop === "baseA" && derived_obj.derived_prop === "derivedA"); + +// 9. +log = {}; +count = 0; + +for (i in {q : 1}) +{ + log [i] = true; + count++; +} + +assert (count == 1 && 'q' in log); + +// 10. +log = {}; +count = 0; + +for (i in {q : 1, p : 2, get f() { ; }, set f (v) { ; }, get t () { }, set c (v) {}}) +{ + log [i] = true; + count++; +} + +assert (count == 5 + && 'q' in log + && 'p' in log + && 'f' in log + && 't' in log + && 'c' in log); + +// 11. +log = {}; +count = 0; + +var a = []; +a[5] = 5; +for (var x in a) +{ + log[x] = true; + count++; +} + +assert (count == 1 + && '5' in log); + +// 12. +log = {}; +count = 0; + +q = { c : 3, d : 4 }; + +function p_constructor () +{ + this.a = 1; + this.b = 2; + + return this; +} + +p_constructor.prototype = q; +p = new p_constructor (); + +Object.defineProperty (p, 'h', { value : 5, enumerable : false, configurable : true }); +Object.defineProperty (q, 'h', { value : 6, enumerable : true, configurable : true }); + +for (var i in p) +{ + log[i] = true; + count++; +} + +assert (count == 4 + && 'a' in log + && 'b' in log + && 'c' in log + && 'd' in log); + +// 13. +log = {}; +count = 0; + +function f() +{ + var tmp = { a: 1, b: 2, c: 3, d: 4 }; + + return tmp; +} + +for (var i in f()) +{ + log[i] = true; + count++; +} + +assert (count == 4 + && 'a' in log + && 'b' in log + && 'c' in log + && 'd' in log); + +// 14. +log = {}; +count = 0; + +b = 'prop'; +c = { prop : 1 }; +Boolean.prototype.boolean_prototype_prop = 1; + +for (a in b in c) +{ + log[a] = true; + count++; +} + +assert (count == 1 + && 'boolean_prototype_prop' in log); + +// 15. +log = {}; +count = 0; + +for (a in 'prop' in { prop : 1 }) +{ + log[a] = true; + count++; +} + +assert (count == 1 + && 'boolean_prototype_prop' in log); + +// 16. +a = 'str'; +b = {}; +for ((a in b) ; ; ) +{ + break; +} + +// 17. +log = {}; +count = 0; + +var base_obj = { base_prop1: "base1", base_prop2: "base2" }; + +function l () { + this.derived_prop1 = "derived1"; + this.derived_prop2 = "derived2"; +} + +l.prototype = base_obj; + +var derived_obj = new l(); + +for (var prop_of_derived_obj in derived_obj) { + delete derived_obj.derived_prop1; + delete derived_obj.derived_prop2; + delete base_obj.base_prop1; + delete base_obj.base_prop2; + + log[prop_of_derived_obj] = true; + count++; +} + +assert(count == 1 + && ('base_prop1' in log + || 'base_prop2' in log + || 'derived_prop1' in log + || 'derived_prop2' in log));