From b29a78fb02b1df8ad88b2ce159081c37a8dac1e8 Mon Sep 17 00:00:00 2001 From: "wingo@igalia.com" Date: Fri, 7 Jun 2013 11:12:21 +0000 Subject: [PATCH] Baseline for-of implementation Add full-codegen support for the ES6 for-of iteration statement. R=mstarzinger@chromium.org, rossberg@chromium.org TEST=mjsunit/harmony/iteration-semantics BUG=v8:2214 Review URL: https://codereview.chromium.org/15288011 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@15002 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/arm/full-codegen-arm.cc | 64 +++++- src/ast.h | 74 ++++++- src/full-codegen.cc | 9 +- src/heap.h | 3 +- src/ia32/full-codegen-ia32.cc | 63 +++++- src/parser.cc | 78 ++++++- src/parser.h | 6 + src/x64/full-codegen-x64.cc | 63 +++++- test/mjsunit/harmony/iteration-semantics.js | 327 ++++++++++++++++++++++++++++ 9 files changed, 655 insertions(+), 32 deletions(-) create mode 100644 test/mjsunit/harmony/iteration-semantics.js diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index b9322b8..ee5517c 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -1081,9 +1081,8 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { ForIn loop_statement(this, stmt); increment_loop_depth(); - // Get the object to enumerate over. Both SpiderMonkey and JSC - // ignore null and undefined in contrast to the specification; see - // ECMA-262 section 12.6.4. + // Get the object to enumerate over. If the object is null or undefined, skip + // over the loop. See ECMA-262 version 5, section 12.6.4. VisitForAccumulatorValue(stmt->enumerable()); __ LoadRoot(ip, Heap::kUndefinedValueRootIndex); __ cmp(r0, ip); @@ -1259,6 +1258,65 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { } +void FullCodeGenerator::VisitForOfStatement(ForOfStatement* stmt) { + Comment cmnt(masm_, "[ ForOfStatement"); + SetStatementPosition(stmt); + + Iteration loop_statement(this, stmt); + increment_loop_depth(); + + // var iterator = iterable[@@iterator]() + VisitForAccumulatorValue(stmt->assign_iterator()); + + // As with for-in, skip the loop if the iterator is null or undefined. + __ CompareRoot(r0, Heap::kUndefinedValueRootIndex); + __ b(eq, loop_statement.break_label()); + __ CompareRoot(r0, Heap::kNullValueRootIndex); + __ b(eq, loop_statement.break_label()); + + // Convert the iterator to a JS object. + Label convert, done_convert; + __ JumpIfSmi(r0, &convert); + __ CompareObjectType(r0, r1, r1, FIRST_SPEC_OBJECT_TYPE); + __ b(ge, &done_convert); + __ bind(&convert); + __ push(r0); + __ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); + __ bind(&done_convert); + __ push(r0); + + // Loop entry. + __ bind(loop_statement.continue_label()); + + // result = iterator.next() + VisitForEffect(stmt->next_result()); + + // if (result.done) break; + Label result_not_done; + VisitForControl(stmt->result_done(), + loop_statement.break_label(), + &result_not_done, + &result_not_done); + __ bind(&result_not_done); + + // each = result.value + VisitForEffect(stmt->assign_each()); + + // Generate code for the body of the loop. + Visit(stmt->body()); + + // Check stack before looping. + PrepareForBailoutForId(stmt->BackEdgeId(), NO_REGISTERS); + EmitBackEdgeBookkeeping(stmt, loop_statement.continue_label()); + __ jmp(loop_statement.continue_label()); + + // Exit and decrement the loop depth. + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); + __ bind(loop_statement.break_label()); + decrement_loop_depth(); +} + + void FullCodeGenerator::EmitNewClosure(Handle info, bool pretenure) { // Use the fast case closure allocation code that allocates in new diff --git a/src/ast.h b/src/ast.h index e1945b0..f0b140e 100644 --- a/src/ast.h +++ b/src/ast.h @@ -891,25 +891,16 @@ class ForEachStatement: public IterationStatement { Expression* each() const { return each_; } Expression* subject() const { return subject_; } - virtual BailoutId ContinueId() const { return EntryId(); } - virtual BailoutId StackCheckId() const { return body_id_; } - BailoutId BodyId() const { return body_id_; } - BailoutId PrepareId() const { return prepare_id_; } - protected: ForEachStatement(Isolate* isolate, ZoneStringList* labels) : IterationStatement(isolate, labels), each_(NULL), - subject_(NULL), - body_id_(GetNextId(isolate)), - prepare_id_(GetNextId(isolate)) { + subject_(NULL) { } private: Expression* each_; Expression* subject_; - const BailoutId body_id_; - const BailoutId prepare_id_; }; @@ -926,13 +917,22 @@ class ForInStatement: public ForEachStatement { enum ForInType { FAST_FOR_IN, SLOW_FOR_IN }; ForInType for_in_type() const { return for_in_type_; } + BailoutId BodyId() const { return body_id_; } + BailoutId PrepareId() const { return prepare_id_; } + virtual BailoutId ContinueId() const { return EntryId(); } + virtual BailoutId StackCheckId() const { return body_id_; } + protected: ForInStatement(Isolate* isolate, ZoneStringList* labels) : ForEachStatement(isolate, labels), - for_in_type_(SLOW_FOR_IN) { + for_in_type_(SLOW_FOR_IN), + body_id_(GetNextId(isolate)), + prepare_id_(GetNextId(isolate)) { } ForInType for_in_type_; + const BailoutId body_id_; + const BailoutId prepare_id_; }; @@ -940,14 +940,64 @@ class ForOfStatement: public ForEachStatement { public: DECLARE_NODE_TYPE(ForOfStatement) + void Initialize(Expression* each, + Expression* subject, + Statement* body, + Expression* assign_iterator, + Expression* next_result, + Expression* result_done, + Expression* assign_each) { + ForEachStatement::Initialize(each, subject, body); + assign_iterator_ = assign_iterator; + next_result_ = next_result; + result_done_ = result_done; + assign_each_ = assign_each; + } + Expression* iterable() const { return subject(); } + // var iterator = iterable; + Expression* assign_iterator() const { + return assign_iterator_; + } + + // var result = iterator.next(); + Expression* next_result() const { + return next_result_; + } + + // result.done + Expression* result_done() const { + return result_done_; + } + + // each = result.value + Expression* assign_each() const { + return assign_each_; + } + + virtual BailoutId ContinueId() const { return EntryId(); } + virtual BailoutId StackCheckId() const { return BackEdgeId(); } + + BailoutId BackEdgeId() const { return back_edge_id_; } + protected: ForOfStatement(Isolate* isolate, ZoneStringList* labels) - : ForEachStatement(isolate, labels) { + : ForEachStatement(isolate, labels), + assign_iterator_(NULL), + next_result_(NULL), + result_done_(NULL), + assign_each_(NULL), + back_edge_id_(GetNextId(isolate)) { } + + Expression* assign_iterator_; + Expression* next_result_; + Expression* result_done_; + Expression* assign_each_; + const BailoutId back_edge_id_; }; diff --git a/src/full-codegen.cc b/src/full-codegen.cc index c32309a..fe3c43f 100644 --- a/src/full-codegen.cc +++ b/src/full-codegen.cc @@ -164,8 +164,8 @@ void BreakableStatementChecker::VisitForInStatement(ForInStatement* stmt) { void BreakableStatementChecker::VisitForOfStatement(ForOfStatement* stmt) { - // Mark for in statements breakable if the iterable expression is. - Visit(stmt->iterable()); + // For-of is breakable because of the next() call. + is_breakable_ = true; } @@ -1389,11 +1389,6 @@ void FullCodeGenerator::VisitForStatement(ForStatement* stmt) { } -void FullCodeGenerator::VisitForOfStatement(ForOfStatement* stmt) { - // TODO(wingo): Implement. -} - - void FullCodeGenerator::VisitTryCatchStatement(TryCatchStatement* stmt) { Comment cmnt(masm_, "[ TryCatchStatement"); SetStatementPosition(stmt); diff --git a/src/heap.h b/src/heap.h index 92cc660..65deb1a 100644 --- a/src/heap.h +++ b/src/heap.h @@ -296,7 +296,8 @@ namespace internal { V(send_string, "send") \ V(throw_string, "throw") \ V(done_string, "done") \ - V(value_string, "value") + V(value_string, "value") \ + V(next_string, "next") // Forward declarations. class GCTracer; diff --git a/src/ia32/full-codegen-ia32.cc b/src/ia32/full-codegen-ia32.cc index 58ddbad..82ef657 100644 --- a/src/ia32/full-codegen-ia32.cc +++ b/src/ia32/full-codegen-ia32.cc @@ -1033,9 +1033,8 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { ForIn loop_statement(this, stmt); increment_loop_depth(); - // Get the object to enumerate over. Both SpiderMonkey and JSC - // ignore null and undefined in contrast to the specification; see - // ECMA-262 section 12.6.4. + // Get the object to enumerate over. If the object is null or undefined, skip + // over the loop. See ECMA-262 version 5, section 12.6.4. VisitForAccumulatorValue(stmt->enumerable()); __ cmp(eax, isolate()->factory()->undefined_value()); __ j(equal, &exit); @@ -1198,6 +1197,64 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { } +void FullCodeGenerator::VisitForOfStatement(ForOfStatement* stmt) { + Comment cmnt(masm_, "[ ForOfStatement"); + SetStatementPosition(stmt); + + Iteration loop_statement(this, stmt); + increment_loop_depth(); + + // var iterator = iterable[@@iterator]() + VisitForAccumulatorValue(stmt->assign_iterator()); + + // As with for-in, skip the loop if the iterator is null or undefined. + __ CompareRoot(eax, Heap::kUndefinedValueRootIndex); + __ j(equal, loop_statement.break_label()); + __ CompareRoot(eax, Heap::kNullValueRootIndex); + __ j(equal, loop_statement.break_label()); + + // Convert the iterator to a JS object. + Label convert, done_convert; + __ JumpIfSmi(eax, &convert); + __ CmpObjectType(eax, FIRST_SPEC_OBJECT_TYPE, ecx); + __ j(above_equal, &done_convert); + __ bind(&convert); + __ push(eax); + __ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); + __ bind(&done_convert); + + // Loop entry. + __ bind(loop_statement.continue_label()); + + // result = iterator.next() + VisitForEffect(stmt->next_result()); + + // if (result.done) break; + Label result_not_done; + VisitForControl(stmt->result_done(), + loop_statement.break_label(), + &result_not_done, + &result_not_done); + __ bind(&result_not_done); + + // each = result.value + VisitForEffect(stmt->assign_each()); + + // Generate code for the body of the loop. + Visit(stmt->body()); + + // Check stack before looping. + PrepareForBailoutForId(stmt->BackEdgeId(), NO_REGISTERS); + EmitBackEdgeBookkeeping(stmt, loop_statement.continue_label()); + __ jmp(loop_statement.continue_label()); + + // Exit and decrement the loop depth. + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); + __ bind(loop_statement.break_label()); + decrement_loop_depth(); +} + + void FullCodeGenerator::EmitNewClosure(Handle info, bool pretenure) { // Use the fast case closure allocation code that allocates in new diff --git a/src/parser.cc b/src/parser.cc index 27f7a82..fa24bf7 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -2636,6 +2636,78 @@ bool Parser::CheckInOrOf(ForEachStatement::VisitMode* visit_mode) { } +void Parser::InitializeForEachStatement(ForEachStatement* stmt, + Expression* each, + Expression* subject, + Statement* body) { + ForOfStatement* for_of = stmt->AsForOfStatement(); + + if (for_of != NULL) { + Factory* heap_factory = isolate()->factory(); + Handle iterator_str = heap_factory->InternalizeOneByteString( + STATIC_ASCII_VECTOR(".iterator")); + Handle result_str = heap_factory->InternalizeOneByteString( + STATIC_ASCII_VECTOR(".result")); + Variable* iterator = + top_scope_->DeclarationScope()->NewTemporary(iterator_str); + Variable* result = top_scope_->DeclarationScope()->NewTemporary(result_str); + + Expression* assign_iterator; + Expression* next_result; + Expression* result_done; + Expression* assign_each; + + // var iterator = iterable; + { + Expression* iterator_proxy = factory()->NewVariableProxy(iterator); + assign_iterator = factory()->NewAssignment( + Token::ASSIGN, iterator_proxy, subject, RelocInfo::kNoPosition); + } + + // var result = iterator.next(); + { + Expression* iterator_proxy = factory()->NewVariableProxy(iterator); + Expression* next_literal = + factory()->NewLiteral(heap_factory->next_string()); + Expression* next_property = factory()->NewProperty( + iterator_proxy, next_literal, RelocInfo::kNoPosition); + ZoneList* next_arguments = + new(zone()) ZoneList(0, zone()); + Expression* next_call = factory()->NewCall( + next_property, next_arguments, RelocInfo::kNoPosition); + Expression* result_proxy = factory()->NewVariableProxy(result); + next_result = factory()->NewAssignment( + Token::ASSIGN, result_proxy, next_call, RelocInfo::kNoPosition); + } + + // result.done + { + Expression* done_literal = + factory()->NewLiteral(heap_factory->done_string()); + Expression* result_proxy = factory()->NewVariableProxy(result); + result_done = factory()->NewProperty( + result_proxy, done_literal, RelocInfo::kNoPosition); + } + + // each = result.value + { + Expression* value_literal = + factory()->NewLiteral(heap_factory->value_string()); + Expression* result_proxy = factory()->NewVariableProxy(result); + Expression* result_value = factory()->NewProperty( + result_proxy, value_literal, RelocInfo::kNoPosition); + assign_each = factory()->NewAssignment( + Token::ASSIGN, each, result_value, RelocInfo::kNoPosition); + } + + for_of->Initialize(each, subject, body, + assign_iterator, next_result, result_done, assign_each); + } else { + stmt->Initialize(each, subject, body); + } +} + + Statement* Parser::ParseForStatement(ZoneStringList* labels, bool* ok) { // ForStatement :: // 'for' '(' Expression? ';' Expression? ';' Expression? ')' Statement @@ -2670,7 +2742,7 @@ Statement* Parser::ParseForStatement(ZoneStringList* labels, bool* ok) { VariableProxy* each = top_scope_->NewUnresolved(factory(), name, interface); Statement* body = ParseStatement(NULL, CHECK_OK); - loop->Initialize(each, enumerable, body); + InitializeForEachStatement(loop, each, enumerable, body); Block* result = factory()->NewBlock(NULL, 2, false); result->AddStatement(variable_statement, zone()); result->AddStatement(loop, zone()); @@ -2734,7 +2806,7 @@ Statement* Parser::ParseForStatement(ZoneStringList* labels, bool* ok) { body_block->AddStatement(variable_statement, zone()); body_block->AddStatement(assignment_statement, zone()); body_block->AddStatement(body, zone()); - loop->Initialize(temp_proxy, enumerable, body_block); + InitializeForEachStatement(loop, temp_proxy, enumerable, body_block); top_scope_ = saved_scope; for_scope->set_end_position(scanner().location().end_pos); for_scope = for_scope->FinalizeBlockScope(); @@ -2766,7 +2838,7 @@ Statement* Parser::ParseForStatement(ZoneStringList* labels, bool* ok) { Expect(Token::RPAREN, CHECK_OK); Statement* body = ParseStatement(NULL, CHECK_OK); - loop->Initialize(expression, enumerable, body); + InitializeForEachStatement(loop, expression, enumerable, body); top_scope_ = saved_scope; for_scope->set_end_position(scanner().location().end_pos); for_scope = for_scope->FinalizeBlockScope(); diff --git a/src/parser.h b/src/parser.h index ae600d6..b7e0700 100644 --- a/src/parser.h +++ b/src/parser.h @@ -688,6 +688,12 @@ class Parser BASE_EMBEDDED { // in the object literal boilerplate. Handle GetBoilerplateValue(Expression* expression); + // Initialize the components of a for-in / for-of statement. + void InitializeForEachStatement(ForEachStatement* stmt, + Expression* each, + Expression* subject, + Statement* body); + ZoneList* ParseArguments(bool* ok); FunctionLiteral* ParseFunctionLiteral(Handle var_name, bool name_is_reserved, diff --git a/src/x64/full-codegen-x64.cc b/src/x64/full-codegen-x64.cc index ed4896d..62c6073 100644 --- a/src/x64/full-codegen-x64.cc +++ b/src/x64/full-codegen-x64.cc @@ -1046,9 +1046,8 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { ForIn loop_statement(this, stmt); increment_loop_depth(); - // Get the object to enumerate over. Both SpiderMonkey and JSC - // ignore null and undefined in contrast to the specification; see - // ECMA-262 section 12.6.4. + // Get the object to enumerate over. If the object is null or undefined, skip + // over the loop. See ECMA-262 version 5, section 12.6.4. VisitForAccumulatorValue(stmt->enumerable()); __ CompareRoot(rax, Heap::kUndefinedValueRootIndex); __ j(equal, &exit); @@ -1224,6 +1223,64 @@ void FullCodeGenerator::VisitForInStatement(ForInStatement* stmt) { } +void FullCodeGenerator::VisitForOfStatement(ForOfStatement* stmt) { + Comment cmnt(masm_, "[ ForOfStatement"); + SetStatementPosition(stmt); + + Iteration loop_statement(this, stmt); + increment_loop_depth(); + + // var iterator = iterable[@@iterator]() + VisitForAccumulatorValue(stmt->assign_iterator()); + + // As with for-in, skip the loop if the iterator is null or undefined. + __ CompareRoot(rax, Heap::kUndefinedValueRootIndex); + __ j(equal, loop_statement.break_label()); + __ CompareRoot(rax, Heap::kNullValueRootIndex); + __ j(equal, loop_statement.break_label()); + + // Convert the iterator to a JS object. + Label convert, done_convert; + __ JumpIfSmi(rax, &convert); + __ CmpObjectType(rax, FIRST_SPEC_OBJECT_TYPE, rcx); + __ j(above_equal, &done_convert); + __ bind(&convert); + __ push(rax); + __ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION); + __ bind(&done_convert); + + // Loop entry. + __ bind(loop_statement.continue_label()); + + // result = iterator.next() + VisitForEffect(stmt->next_result()); + + // if (result.done) break; + Label result_not_done; + VisitForControl(stmt->result_done(), + loop_statement.break_label(), + &result_not_done, + &result_not_done); + __ bind(&result_not_done); + + // each = result.value + VisitForEffect(stmt->assign_each()); + + // Generate code for the body of the loop. + Visit(stmt->body()); + + // Check stack before looping. + PrepareForBailoutForId(stmt->BackEdgeId(), NO_REGISTERS); + EmitBackEdgeBookkeeping(stmt, loop_statement.continue_label()); + __ jmp(loop_statement.continue_label()); + + // Exit and decrement the loop depth. + PrepareForBailoutForId(stmt->ExitId(), NO_REGISTERS); + __ bind(loop_statement.break_label()); + decrement_loop_depth(); +} + + void FullCodeGenerator::EmitNewClosure(Handle info, bool pretenure) { // Use the fast case closure allocation code that allocates in new diff --git a/test/mjsunit/harmony/iteration-semantics.js b/test/mjsunit/harmony/iteration-semantics.js new file mode 100644 index 0000000..6215522 --- /dev/null +++ b/test/mjsunit/harmony/iteration-semantics.js @@ -0,0 +1,327 @@ +// Copyright 2013 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Flags: --harmony + +// Test for-of semantics. + +"use strict"; + + +// First, some helpers. + +function* values() { + for (var i = 0; i < arguments.length; i++) { + yield arguments[i]; + } +} + +function integers_until(max) { + function next() { + var ret = { value: this.n, done: this.n == max }; + this.n++; + return ret; + } + return { next: next, n: 0 } +} + +function results(results) { + var i = 0; + function next() { + return results[i++]; + } + return { next: next } +} + +function* integers_from(n) { + while (1) yield n++; +} + +// A destructive append. +function append(x, tail) { + tail[tail.length] = x; + return tail; +} + +function sum(x, tail) { + return x + tail; +} + +function fold(cons, seed, iter) { + for (var x of iter) { + seed = cons(x, seed); + } + return seed; +} + +function* take(iter, n) { + if (n == 0) return; + for (let x of iter) { + yield x; + if (--n == 0) break; + } +} + +function nth(iter, n) { + for (let x of iter) { + if (n-- == 0) return x; + } + throw "unreachable"; +} + +function* skip_every(iter, n) { + var i = 0; + for (let x of iter) { + if (++i % n == 0) continue; + yield x; + } +} + +function* iter_map(iter, f) { + for (var x of iter) { + yield f(x); + } +} +function nested_fold(cons, seed, iter) { + var visited = [] + for (let x of iter) { + for (let y of x) { + seed = cons(y, seed); + } + } + return seed; +} + +function* unreachable(iter) { + for (let x of iter) { + throw "not reached"; + } +} + +function one_time_getter(o, prop, val) { + function set_never() { throw "unreachable"; } + var gotten = false; + function get_once() { + if (gotten) throw "got twice"; + gotten = true; + return val; + } + Object.defineProperty(o, prop, {get: get_once, set: set_never}) + return o; +} + +function never_getter(o, prop) { + function never() { throw "unreachable"; } + Object.defineProperty(o, prop, {get: never, set: never}) + return o; +} + +function remove_next_after(iter, n) { + function next() { + if (n-- == 0) delete this.next; + return iter.next(); + } + return { next: next } +} + +function poison_next_after(iter, n) { + function next() { + return iter.next(); + } + function next_getter() { + if (n-- < 0) + throw "poisoned"; + return next; + } + var o = {}; + Object.defineProperty(o, 'next', { get: next_getter }); + return o; +} + +// Now, the tests. + +// Non-generator iterators. +assertEquals(45, fold(sum, 0, integers_until(10))); +// Generator iterators. +assertEquals([1, 2, 3], fold(append, [], values(1, 2, 3))); +// Break. +assertEquals(45, fold(sum, 0, take(integers_from(0), 10))); +// Continue. +assertEquals(90, fold(sum, 0, take(skip_every(integers_from(0), 2), 10))); +// Return. +assertEquals(10, nth(integers_from(0), 10)); +// Nested for-of. +assertEquals([0, 0, 1, 0, 1, 2, 0, 1, 2, 3], + nested_fold(append, + [], + iter_map(integers_until(5), integers_until))); +// Result objects with sparse fields. +assertEquals([undefined, 1, 2, 3], + fold(append, [], + results([{ done: false }, + { value: 1, done: false }, + // A missing "done" is the same as undefined, which + // is false. + { value: 2 }, + // Not done. + { value: 3, done: 0 }, + // Done. + { value: 4, done: 42 }]))); +// Results that are not objects. +assertEquals([undefined, undefined, undefined], + fold(append, [], + results([10, "foo", /qux/, { value: 37, done: true }]))); +// Getters (shudder). +assertEquals([1, 2], + fold(append, [], + results([one_time_getter({ value: 1 }, 'done', false), + one_time_getter({ done: false }, 'value', 2), + { value: 37, done: true }, + never_getter(never_getter({}, 'done'), 'value')]))); + +// Null and undefined do not cause an error. +assertEquals(0, fold(sum, 0, unreachable(null))); +assertEquals(0, fold(sum, 0, unreachable(undefined))); + +// Other non-iterators do cause an error. +assertThrows('fold(sum, 0, unreachable({}))', TypeError); +assertThrows('fold(sum, 0, unreachable("foo"))', TypeError); +assertThrows('fold(sum, 0, unreachable(37))', TypeError); + +// "next" is looked up each time. +assertThrows('fold(sum, 0, remove_next_after(integers_until(10), 5))', + TypeError); +// It is not called at any other time. +assertEquals(45, + fold(sum, 0, remove_next_after(integers_until(10), 10))); +// It is not looked up too many times. +assertEquals(45, + fold(sum, 0, poison_next_after(integers_until(10), 10))); + +function labelled_continue(iter) { + var n = 0; +outer: + while (true) { + n++; + for (var x of iter) continue outer; + break; + } + return n; +} +assertEquals(11, labelled_continue(integers_until(10))); + +function labelled_break(iter) { + var n = 0; +outer: + while (true) { + n++; + for (var x of iter) break outer; + } + return n; +} +assertEquals(1, labelled_break(integers_until(10))); + +// Test continue/break in catch. +function catch_control(iter, k) { + var n = 0; + for (var x of iter) { + try { + return k(x); + } catch (e) { + if (e == "continue") continue; + else if (e == "break") break; + else throw e; + } + } while (false); + return false; +} +assertEquals(false, + catch_control(integers_until(10), + function() { throw "break" })); +assertEquals(false, + catch_control(integers_until(10), + function() { throw "continue" })); +assertEquals(5, + catch_control(integers_until(10), + function(x) { + if (x == 5) return x; + throw "continue"; + })); + +// Test continue/break in try. +function try_control(iter, k) { + var n = 0; + for (var x of iter) { + try { + var e = k(x); + if (e == "continue") continue; + else if (e == "break") break; + return e; + } catch (e) { + throw e; + } + } while (false); + return false; +} +assertEquals(false, + try_control(integers_until(10), + function() { return "break" })); +assertEquals(false, + try_control(integers_until(10), + function() { return "continue" })); +assertEquals(5, + try_control(integers_until(10), + function(x) { return (x == 5) ? x : "continue" })); + +// Proxy results, with getters. +function transparent_proxy(x) { + return Proxy.create({ + get: function(receiver, name) { return x[name]; } + }); +} +assertEquals([1, 2], + fold(append, [], + results([one_time_getter({ value: 1 }, 'done', false), + one_time_getter({ done: false }, 'value', 2), + { value: 37, done: true }, + never_getter(never_getter({}, 'done'), 'value')] + .map(transparent_proxy)))); + +// Proxy iterators. +function poison_proxy_after(x, n) { + return Proxy.create({ + get: function(receiver, name) { + if (name == 'next' && n-- < 0) throw "unreachable"; + return x[name]; + }, + // Needed for integers_until(10)'s this.n++. + set: function(receiver, name, val) { + return x[name] = val; + } + }); +} +assertEquals(45, fold(sum, 0, poison_proxy_after(integers_until(10), 10))); -- 2.7.4