v8/test/mjsunit/harmony/iteration-semantics.js

329 lines
9.0 KiB
JavaScript
Raw Normal View History

// 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-iteration
// Flags: --harmony-generators --harmony-scoping --harmony-proxies
// 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)));