1// Copyright 2013 the V8 project authors. All rights reserved. 2// Redistribution and use in source and binary forms, with or without 3// modification, are permitted provided that the following conditions are 4// met: 5// 6// * Redistributions of source code must retain the above copyright 7// notice, this list of conditions and the following disclaimer. 8// * Redistributions in binary form must reproduce the above 9// copyright notice, this list of conditions and the following 10// disclaimer in the documentation and/or other materials provided 11// with the distribution. 12// * Neither the name of Google Inc. nor the names of its 13// contributors may be used to endorse or promote products derived 14// from this software without specific prior written permission. 15// 16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28// Flags: --harmony-scoping --harmony-proxies 29 30// Test for-of semantics. 31 32"use strict"; 33 34 35// First, some helpers. 36 37function* values() { 38 for (var i = 0; i < arguments.length; i++) { 39 yield arguments[i]; 40 } 41} 42 43function wrap_iterator(iterator) { 44 var iterable = {}; 45 iterable[Symbol.iterator] = function() { return iterator; }; 46 return iterable; 47} 48 49function integers_until(max) { 50 function next() { 51 var ret = { value: this.n, done: this.n == max }; 52 this.n++; 53 return ret; 54 } 55 return wrap_iterator({ next: next, n: 0 }); 56} 57 58function results(results) { 59 var i = 0; 60 function next() { 61 return results[i++]; 62 } 63 return wrap_iterator({ next: next }); 64} 65 66function* integers_from(n) { 67 while (1) yield n++; 68} 69 70// A destructive append. 71function append(x, tail) { 72 tail[tail.length] = x; 73 return tail; 74} 75 76function sum(x, tail) { 77 return x + tail; 78} 79 80function fold(cons, seed, iterable) { 81 for (var x of iterable) { 82 seed = cons(x, seed); 83 } 84 return seed; 85} 86 87function* take(iterable, n) { 88 if (n == 0) return; 89 for (let x of iterable) { 90 yield x; 91 if (--n == 0) break; 92 } 93} 94 95function nth(iterable, n) { 96 for (let x of iterable) { 97 if (n-- == 0) return x; 98 } 99 throw "unreachable"; 100} 101 102function* skip_every(iterable, n) { 103 var i = 0; 104 for (let x of iterable) { 105 if (++i % n == 0) continue; 106 yield x; 107 } 108} 109 110function* iter_map(iterable, f) { 111 for (var x of iterable) { 112 yield f(x); 113 } 114} 115function nested_fold(cons, seed, iterable) { 116 var visited = [] 117 for (let x of iterable) { 118 for (let y of x) { 119 seed = cons(y, seed); 120 } 121 } 122 return seed; 123} 124 125function* unreachable(iterable) { 126 for (let x of iterable) { 127 throw "not reached"; 128 } 129} 130 131function one_time_getter(o, prop, val) { 132 function set_never() { throw "unreachable"; } 133 var gotten = false; 134 function get_once() { 135 if (gotten) throw "got twice"; 136 gotten = true; 137 return val; 138 } 139 Object.defineProperty(o, prop, {get: get_once, set: set_never}) 140 return o; 141} 142 143function never_getter(o, prop) { 144 function never() { throw "unreachable"; } 145 Object.defineProperty(o, prop, {get: never, set: never}) 146 return o; 147} 148 149function remove_next_after(iterable, n) { 150 var iterator = iterable[Symbol.iterator](); 151 function next() { 152 if (n-- == 0) delete this.next; 153 return iterator.next(); 154 } 155 return wrap_iterator({ next: next }); 156} 157 158function poison_next_after(iterable, n) { 159 var iterator = iterable[Symbol.iterator](); 160 function next() { 161 return iterator.next(); 162 } 163 function next_getter() { 164 if (n-- < 0) 165 throw "poisoned"; 166 return next; 167 } 168 var o = {}; 169 Object.defineProperty(o, 'next', { get: next_getter }); 170 return wrap_iterator(o); 171} 172 173// Now, the tests. 174 175// Non-generator iterators. 176assertEquals(45, fold(sum, 0, integers_until(10))); 177// Generator iterators. 178assertEquals([1, 2, 3], fold(append, [], values(1, 2, 3))); 179// Break. 180assertEquals(45, fold(sum, 0, take(integers_from(0), 10))); 181// Continue. 182assertEquals(90, fold(sum, 0, take(skip_every(integers_from(0), 2), 10))); 183// Return. 184assertEquals(10, nth(integers_from(0), 10)); 185// Nested for-of. 186assertEquals([0, 0, 1, 0, 1, 2, 0, 1, 2, 3], 187 nested_fold(append, 188 [], 189 iter_map(integers_until(5), integers_until))); 190// Result objects with sparse fields. 191assertEquals([undefined, 1, 2, 3], 192 fold(append, [], 193 results([{ done: false }, 194 { value: 1, done: false }, 195 // A missing "done" is the same as undefined, which 196 // is false. 197 { value: 2 }, 198 // Not done. 199 { value: 3, done: 0 }, 200 // Done. 201 { value: 4, done: 42 }]))); 202// Results that are not objects. 203assertEquals([undefined, undefined, undefined], 204 fold(append, [], 205 results([10, "foo", /qux/, { value: 37, done: true }]))); 206// Getters (shudder). 207assertEquals([1, 2], 208 fold(append, [], 209 results([one_time_getter({ value: 1 }, 'done', false), 210 one_time_getter({ done: false }, 'value', 2), 211 { value: 37, done: true }, 212 never_getter(never_getter({}, 'done'), 'value')]))); 213 214// Unlike the case with for-in, null and undefined cause an error. 215assertThrows('fold(sum, 0, unreachable(null))', TypeError); 216assertThrows('fold(sum, 0, unreachable(undefined))', TypeError); 217 218// Other non-iterators do cause an error. 219assertThrows('fold(sum, 0, unreachable({}))', TypeError); 220assertThrows('fold(sum, 0, unreachable(false))', TypeError); 221assertThrows('fold(sum, 0, unreachable(37))', TypeError); 222 223// "next" is looked up each time. 224assertThrows('fold(sum, 0, remove_next_after(integers_until(10), 5))', 225 TypeError); 226// It is not called at any other time. 227assertEquals(45, 228 fold(sum, 0, remove_next_after(integers_until(10), 10))); 229// It is not looked up too many times. 230assertEquals(45, 231 fold(sum, 0, poison_next_after(integers_until(10), 10))); 232 233function labelled_continue(iterable) { 234 var n = 0; 235outer: 236 while (true) { 237 n++; 238 for (var x of iterable) continue outer; 239 break; 240 } 241 return n; 242} 243assertEquals(11, labelled_continue(integers_until(10))); 244 245function labelled_break(iterable) { 246 var n = 0; 247outer: 248 while (true) { 249 n++; 250 for (var x of iterable) break outer; 251 } 252 return n; 253} 254assertEquals(1, labelled_break(integers_until(10))); 255 256// Test continue/break in catch. 257function catch_control(iterable, k) { 258 var n = 0; 259 for (var x of iterable) { 260 try { 261 return k(x); 262 } catch (e) { 263 if (e == "continue") continue; 264 else if (e == "break") break; 265 else throw e; 266 } 267 } while (false); 268 return false; 269} 270assertEquals(false, 271 catch_control(integers_until(10), 272 function() { throw "break" })); 273assertEquals(false, 274 catch_control(integers_until(10), 275 function() { throw "continue" })); 276assertEquals(5, 277 catch_control(integers_until(10), 278 function(x) { 279 if (x == 5) return x; 280 throw "continue"; 281 })); 282 283// Test continue/break in try. 284function try_control(iterable, k) { 285 var n = 0; 286 for (var x of iterable) { 287 try { 288 var e = k(x); 289 if (e == "continue") continue; 290 else if (e == "break") break; 291 return e; 292 } catch (e) { 293 throw e; 294 } 295 } while (false); 296 return false; 297} 298assertEquals(false, 299 try_control(integers_until(10), 300 function() { return "break" })); 301assertEquals(false, 302 try_control(integers_until(10), 303 function() { return "continue" })); 304assertEquals(5, 305 try_control(integers_until(10), 306 function(x) { return (x == 5) ? x : "continue" })); 307 308// Proxy results, with getters. 309function transparent_proxy(x) { 310 return Proxy.create({ 311 get: function(receiver, name) { return x[name]; } 312 }); 313} 314assertEquals([1, 2], 315 fold(append, [], 316 results([one_time_getter({ value: 1 }, 'done', false), 317 one_time_getter({ done: false }, 'value', 2), 318 { value: 37, done: true }, 319 never_getter(never_getter({}, 'done'), 'value')] 320 .map(transparent_proxy)))); 321 322// Proxy iterators. 323function poison_proxy_after(iterable, n) { 324 var iterator = iterable[Symbol.iterator](); 325 return wrap_iterator(Proxy.create({ 326 get: function(receiver, name) { 327 if (name == 'next' && n-- < 0) throw "unreachable"; 328 return iterator[name]; 329 }, 330 // Needed for integers_until(10)'s this.n++. 331 set: function(receiver, name, val) { 332 return iterator[name] = val; 333 } 334 })); 335} 336assertEquals(45, fold(sum, 0, poison_proxy_after(integers_until(10), 10))); 337