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 37 function* values() { 38 for (var i = 0; i < arguments.length; i++) { 39 yield arguments[i]; 40 } 41 } 42 43 function wrap_iterator(iterator) { 44 var iterable = {}; 45 iterable[Symbol.iterator] = function() { return iterator; }; 46 return iterable; 47 } 48 49 function 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 58 function results(results) { 59 var i = 0; 60 function next() { 61 return results[i++]; 62 } 63 return wrap_iterator({ next: next }); 64 } 65 66 function* integers_from(n) { 67 while (1) yield n++; 68 } 69 70 // A destructive append. 71 function append(x, tail) { 72 tail[tail.length] = x; 73 return tail; 74 } 75 76 function sum(x, tail) { 77 return x + tail; 78 } 79 80 function fold(cons, seed, iterable) { 81 for (var x of iterable) { 82 seed = cons(x, seed); 83 } 84 return seed; 85 } 86 87 function* 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 95 function nth(iterable, n) { 96 for (let x of iterable) { 97 if (n-- == 0) return x; 98 } 99 throw "unreachable"; 100 } 101 102 function* 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 110 function* iter_map(iterable, f) { 111 for (var x of iterable) { 112 yield f(x); 113 } 114 } 115 function 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 125 function* unreachable(iterable) { 126 for (let x of iterable) { 127 throw "not reached"; 128 } 129 } 130 131 function 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 143 function never_getter(o, prop) { 144 function never() { throw "unreachable"; } 145 Object.defineProperty(o, prop, {get: never, set: never}) 146 return o; 147 } 148 149 function 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 158 function 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. 176 assertEquals(45, fold(sum, 0, integers_until(10))); 177 // Generator iterators. 178 assertEquals([1, 2, 3], fold(append, [], values(1, 2, 3))); 179 // Break. 180 assertEquals(45, fold(sum, 0, take(integers_from(0), 10))); 181 // Continue. 182 assertEquals(90, fold(sum, 0, take(skip_every(integers_from(0), 2), 10))); 183 // Return. 184 assertEquals(10, nth(integers_from(0), 10)); 185 // Nested for-of. 186 assertEquals([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. 191 assertEquals([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. 203 assertEquals([undefined, undefined, undefined], 204 fold(append, [], 205 results([10, "foo", /qux/, { value: 37, done: true }]))); 206 // Getters (shudder). 207 assertEquals([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. 215 assertThrows('fold(sum, 0, unreachable(null))', TypeError); 216 assertThrows('fold(sum, 0, unreachable(undefined))', TypeError); 217 218 // Other non-iterators do cause an error. 219 assertThrows('fold(sum, 0, unreachable({}))', TypeError); 220 assertThrows('fold(sum, 0, unreachable(false))', TypeError); 221 assertThrows('fold(sum, 0, unreachable(37))', TypeError); 222 223 // "next" is looked up each time. 224 assertThrows('fold(sum, 0, remove_next_after(integers_until(10), 5))', 225 TypeError); 226 // It is not called at any other time. 227 assertEquals(45, 228 fold(sum, 0, remove_next_after(integers_until(10), 10))); 229 // It is not looked up too many times. 230 assertEquals(45, 231 fold(sum, 0, poison_next_after(integers_until(10), 10))); 232 233 function labelled_continue(iterable) { 234 var n = 0; 235 outer: 236 while (true) { 237 n++; 238 for (var x of iterable) continue outer; 239 break; 240 } 241 return n; 242 } 243 assertEquals(11, labelled_continue(integers_until(10))); 244 245 function labelled_break(iterable) { 246 var n = 0; 247 outer: 248 while (true) { 249 n++; 250 for (var x of iterable) break outer; 251 } 252 return n; 253 } 254 assertEquals(1, labelled_break(integers_until(10))); 255 256 // Test continue/break in catch. 257 function 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 } 270 assertEquals(false, 271 catch_control(integers_until(10), 272 function() { throw "break" })); 273 assertEquals(false, 274 catch_control(integers_until(10), 275 function() { throw "continue" })); 276 assertEquals(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. 284 function 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 } 298 assertEquals(false, 299 try_control(integers_until(10), 300 function() { return "break" })); 301 assertEquals(false, 302 try_control(integers_until(10), 303 function() { return "continue" })); 304 assertEquals(5, 305 try_control(integers_until(10), 306 function(x) { return (x == 5) ? x : "continue" })); 307 308 // Proxy results, with getters. 309 function transparent_proxy(x) { 310 return Proxy.create({ 311 get: function(receiver, name) { return x[name]; } 312 }); 313 } 314 assertEquals([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. 323 function 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 } 336 assertEquals(45, fold(sum, 0, poison_proxy_after(integers_until(10), 10))); 337