Home | History | Annotate | Download | only in harmony
      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