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-iteration 29 // Flags: --harmony-generators --harmony-scoping --harmony-proxies 30 // Flags: --harmony-symbols 31 32 // Test for-of semantics. 33 34 "use strict"; 35 36 37 // First, some helpers. 38 39 function* values() { 40 for (var i = 0; i < arguments.length; i++) { 41 yield arguments[i]; 42 } 43 } 44 45 function wrap_iterator(iterator) { 46 var iterable = {}; 47 iterable[Symbol.iterator] = function() { return iterator; }; 48 return iterable; 49 } 50 51 function integers_until(max) { 52 function next() { 53 var ret = { value: this.n, done: this.n == max }; 54 this.n++; 55 return ret; 56 } 57 return wrap_iterator({ next: next, n: 0 }); 58 } 59 60 function results(results) { 61 var i = 0; 62 function next() { 63 return results[i++]; 64 } 65 return wrap_iterator({ next: next }); 66 } 67 68 function* integers_from(n) { 69 while (1) yield n++; 70 } 71 72 // A destructive append. 73 function append(x, tail) { 74 tail[tail.length] = x; 75 return tail; 76 } 77 78 function sum(x, tail) { 79 return x + tail; 80 } 81 82 function fold(cons, seed, iterable) { 83 for (var x of iterable) { 84 seed = cons(x, seed); 85 } 86 return seed; 87 } 88 89 function* take(iterable, n) { 90 if (n == 0) return; 91 for (let x of iterable) { 92 yield x; 93 if (--n == 0) break; 94 } 95 } 96 97 function nth(iterable, n) { 98 for (let x of iterable) { 99 if (n-- == 0) return x; 100 } 101 throw "unreachable"; 102 } 103 104 function* skip_every(iterable, n) { 105 var i = 0; 106 for (let x of iterable) { 107 if (++i % n == 0) continue; 108 yield x; 109 } 110 } 111 112 function* iter_map(iterable, f) { 113 for (var x of iterable) { 114 yield f(x); 115 } 116 } 117 function nested_fold(cons, seed, iterable) { 118 var visited = [] 119 for (let x of iterable) { 120 for (let y of x) { 121 seed = cons(y, seed); 122 } 123 } 124 return seed; 125 } 126 127 function* unreachable(iterable) { 128 for (let x of iterable) { 129 throw "not reached"; 130 } 131 } 132 133 function one_time_getter(o, prop, val) { 134 function set_never() { throw "unreachable"; } 135 var gotten = false; 136 function get_once() { 137 if (gotten) throw "got twice"; 138 gotten = true; 139 return val; 140 } 141 Object.defineProperty(o, prop, {get: get_once, set: set_never}) 142 return o; 143 } 144 145 function never_getter(o, prop) { 146 function never() { throw "unreachable"; } 147 Object.defineProperty(o, prop, {get: never, set: never}) 148 return o; 149 } 150 151 function remove_next_after(iterable, n) { 152 var iterator = iterable[Symbol.iterator](); 153 function next() { 154 if (n-- == 0) delete this.next; 155 return iterator.next(); 156 } 157 return wrap_iterator({ next: next }); 158 } 159 160 function poison_next_after(iterable, n) { 161 var iterator = iterable[Symbol.iterator](); 162 function next() { 163 return iterator.next(); 164 } 165 function next_getter() { 166 if (n-- < 0) 167 throw "poisoned"; 168 return next; 169 } 170 var o = {}; 171 Object.defineProperty(o, 'next', { get: next_getter }); 172 return wrap_iterator(o); 173 } 174 175 // Now, the tests. 176 177 // Non-generator iterators. 178 assertEquals(45, fold(sum, 0, integers_until(10))); 179 // Generator iterators. 180 assertEquals([1, 2, 3], fold(append, [], values(1, 2, 3))); 181 // Break. 182 assertEquals(45, fold(sum, 0, take(integers_from(0), 10))); 183 // Continue. 184 assertEquals(90, fold(sum, 0, take(skip_every(integers_from(0), 2), 10))); 185 // Return. 186 assertEquals(10, nth(integers_from(0), 10)); 187 // Nested for-of. 188 assertEquals([0, 0, 1, 0, 1, 2, 0, 1, 2, 3], 189 nested_fold(append, 190 [], 191 iter_map(integers_until(5), integers_until))); 192 // Result objects with sparse fields. 193 assertEquals([undefined, 1, 2, 3], 194 fold(append, [], 195 results([{ done: false }, 196 { value: 1, done: false }, 197 // A missing "done" is the same as undefined, which 198 // is false. 199 { value: 2 }, 200 // Not done. 201 { value: 3, done: 0 }, 202 // Done. 203 { value: 4, done: 42 }]))); 204 // Results that are not objects. 205 assertEquals([undefined, undefined, undefined], 206 fold(append, [], 207 results([10, "foo", /qux/, { value: 37, done: true }]))); 208 // Getters (shudder). 209 assertEquals([1, 2], 210 fold(append, [], 211 results([one_time_getter({ value: 1 }, 'done', false), 212 one_time_getter({ done: false }, 'value', 2), 213 { value: 37, done: true }, 214 never_getter(never_getter({}, 'done'), 'value')]))); 215 216 // Null and undefined do not cause an error. 217 assertEquals(0, fold(sum, 0, unreachable(null))); 218 assertEquals(0, fold(sum, 0, unreachable(undefined))); 219 220 // Other non-iterators do cause an error. 221 assertThrows('fold(sum, 0, unreachable({}))', TypeError); 222 assertThrows('fold(sum, 0, unreachable("foo"))', TypeError); 223 assertThrows('fold(sum, 0, unreachable(37))', TypeError); 224 225 // "next" is looked up each time. 226 assertThrows('fold(sum, 0, remove_next_after(integers_until(10), 5))', 227 TypeError); 228 // It is not called at any other time. 229 assertEquals(45, 230 fold(sum, 0, remove_next_after(integers_until(10), 10))); 231 // It is not looked up too many times. 232 assertEquals(45, 233 fold(sum, 0, poison_next_after(integers_until(10), 10))); 234 235 function labelled_continue(iterable) { 236 var n = 0; 237 outer: 238 while (true) { 239 n++; 240 for (var x of iterable) continue outer; 241 break; 242 } 243 return n; 244 } 245 assertEquals(11, labelled_continue(integers_until(10))); 246 247 function labelled_break(iterable) { 248 var n = 0; 249 outer: 250 while (true) { 251 n++; 252 for (var x of iterable) break outer; 253 } 254 return n; 255 } 256 assertEquals(1, labelled_break(integers_until(10))); 257 258 // Test continue/break in catch. 259 function catch_control(iterable, k) { 260 var n = 0; 261 for (var x of iterable) { 262 try { 263 return k(x); 264 } catch (e) { 265 if (e == "continue") continue; 266 else if (e == "break") break; 267 else throw e; 268 } 269 } while (false); 270 return false; 271 } 272 assertEquals(false, 273 catch_control(integers_until(10), 274 function() { throw "break" })); 275 assertEquals(false, 276 catch_control(integers_until(10), 277 function() { throw "continue" })); 278 assertEquals(5, 279 catch_control(integers_until(10), 280 function(x) { 281 if (x == 5) return x; 282 throw "continue"; 283 })); 284 285 // Test continue/break in try. 286 function try_control(iterable, k) { 287 var n = 0; 288 for (var x of iterable) { 289 try { 290 var e = k(x); 291 if (e == "continue") continue; 292 else if (e == "break") break; 293 return e; 294 } catch (e) { 295 throw e; 296 } 297 } while (false); 298 return false; 299 } 300 assertEquals(false, 301 try_control(integers_until(10), 302 function() { return "break" })); 303 assertEquals(false, 304 try_control(integers_until(10), 305 function() { return "continue" })); 306 assertEquals(5, 307 try_control(integers_until(10), 308 function(x) { return (x == 5) ? x : "continue" })); 309 310 // Proxy results, with getters. 311 function transparent_proxy(x) { 312 return Proxy.create({ 313 get: function(receiver, name) { return x[name]; } 314 }); 315 } 316 assertEquals([1, 2], 317 fold(append, [], 318 results([one_time_getter({ value: 1 }, 'done', false), 319 one_time_getter({ done: false }, 'value', 2), 320 { value: 37, done: true }, 321 never_getter(never_getter({}, 'done'), 'value')] 322 .map(transparent_proxy)))); 323 324 // Proxy iterators. 325 function poison_proxy_after(iterable, n) { 326 var iterator = iterable[Symbol.iterator](); 327 return wrap_iterator(Proxy.create({ 328 get: function(receiver, name) { 329 if (name == 'next' && n-- < 0) throw "unreachable"; 330 return iterator[name]; 331 }, 332 // Needed for integers_until(10)'s this.n++. 333 set: function(receiver, name, val) { 334 return iterator[name] = val; 335 } 336 })); 337 } 338 assertEquals(45, fold(sum, 0, poison_proxy_after(integers_until(10), 10))); 339