|
| 1 | +// Once IteratorClose has been made observable in a realm, that realm never again opens a loop over a String without an |
| 2 | +// iterator object. So each case here gets a realm of its own: the loop (or pattern) is opened while nothing is observable, |
| 3 | +// "return" shows up in the middle, and the close that follows has to call it on a real String Iterator at the right position. |
| 4 | + |
| 5 | +function shouldBe(actual, expected, message) { |
| 6 | + if (actual !== expected) |
| 7 | + throw new Error((message ? message + ": " : "") + "expected " + expected + " but got " + actual); |
| 8 | +} |
| 9 | + |
| 10 | +const source = ` |
| 11 | +var log = []; |
| 12 | +var seenIterators = []; |
| 13 | +const StringIteratorPrototype = Object.getPrototypeOf(""[Symbol.iterator]()); |
| 14 | +const IteratorPrototype = Object.getPrototypeOf(StringIteratorPrototype); |
| 15 | +const originalNext = StringIteratorPrototype.next; |
| 16 | +var holders = { StringIteratorPrototype, IteratorPrototype, ObjectPrototype: Object.prototype }; |
| 17 | +function installReturn(where) { |
| 18 | + where.return = function () { |
| 19 | + let step = originalNext.call(this); |
| 20 | + log.push("return " + Object.prototype.toString.call(this) + " " + (Object.getPrototypeOf(this) === StringIteratorPrototype) + " " + JSON.stringify(step)); |
| 21 | + seenIterators.push(this); |
| 22 | + return {}; |
| 23 | + }; |
| 24 | +} |
| 25 | +function breakOut(string, at, hook) { let seen = []; for (let x of string) { seen.push(x); if (x === at) { hook(); break; } } return seen.join(); } |
| 26 | +function returnOut(string, at, hook) { let seen = []; for (let x of string) { seen.push(x); if (x === at) { hook(); return seen.join(); } } return seen.join(); } |
| 27 | +function throwOut(string, at, hook) { let seen = []; try { for (let x of string) { seen.push(x); if (x === at) { hook(); throw new Error("out"); } } } catch (e) { seen.push(e.message); } return seen.join(); } |
| 28 | +function continueOuter(string, at, hook) { let seen = []; outer: for (let i = 0; i < 2; i++) { for (let x of string) { seen.push(x); if (x === at) { hook(); continue outer; } } } return seen.join(); } |
| 29 | +function finallyOut(string, at, hook) { let seen = []; try { for (let x of string) { seen.push(x); if (x === at) { hook(); return seen.join(); } } } finally { seen.push("finally"); log.push("finally " + seen.join()); } return seen.join(); } |
| 30 | +function runToEnd(string, at, hook) { let seen = []; for (let x of string) { seen.push(x); if (x === at) hook(); } return seen.join(); } |
| 31 | +function nested(string, at, hook) { let seen = []; for (let x of string) { for (let y of string) { seen.push(x + y); if (y === at) { hook(); break; } } if (x === at) break; } return seen.join(); } |
| 32 | +function* each(string, at, hook) { for (let x of string) { if (x === at) hook(); yield x; } } |
| 33 | +function generatorReturn(string, at, hook) { let seen = []; let generator = each(string, at, hook); seen.push(generator.next().value); seen.push(generator.next().value); generator.return(); return seen.join(); } |
| 34 | +`; |
| 35 | + |
| 36 | +const noHook = () => { }; |
| 37 | +const closedAt = (value) => 'return [object String Iterator] true {"value":' + JSON.stringify(value) + ',"done":false}'; |
| 38 | +const closedWhenDone = 'return [object String Iterator] true {"done":true}'; |
| 39 | + |
| 40 | +function run(name, string, at, holderName, expected, expectedLog) { |
| 41 | + let realm = createGlobalObject(); |
| 42 | + realm.eval(source); |
| 43 | + let f = realm[name]; |
| 44 | + for (let i = 0; i < testLoopCount; i++) |
| 45 | + f(string, at, noHook); |
| 46 | + shouldBe(realm.log.filter(s => s.startsWith("return")).length, 0, name + " quiet"); |
| 47 | + realm.log.length = 0; |
| 48 | + |
| 49 | + let message = name + " with return on " + holderName; |
| 50 | + shouldBe(f(string, at, () => realm.installReturn(realm.holders[holderName])), expected, message + " result"); |
| 51 | + shouldBe(realm.log.join("|"), expectedLog, message + " log"); |
| 52 | + // Each close saw its own object. |
| 53 | + shouldBe(new Set(realm.seenIterators).size, realm.seenIterators.length, message + " distinct iterators"); |
| 54 | +} |
| 55 | + |
| 56 | +// Every way out of a loop, with "return" showing up on %StringIteratorPrototype%. |
| 57 | +run("breakOut", "abcd", "b", "StringIteratorPrototype", "a,b", closedAt("c")); |
| 58 | +run("returnOut", "abcd", "c", "StringIteratorPrototype", "a,b,c", closedAt("d")); |
| 59 | +run("throwOut", "abcd", "a", "StringIteratorPrototype", "a,out", closedAt("b")); |
| 60 | +run("continueOuter", "abcd", "d", "StringIteratorPrototype", "a,b,c,d,a,b,c,d", closedWhenDone + "|" + closedWhenDone); |
| 61 | +run("finallyOut", "abcd", "b", "StringIteratorPrototype", "a,b", closedAt("c") + "|finally a,b,finally"); |
| 62 | +run("runToEnd", "abcd", "b", "StringIteratorPrototype", "a,b,c,d", ""); |
| 63 | +run("nested", "abcd", "b", "StringIteratorPrototype", "aa,ab,ba,bb", closedAt("c") + "|" + closedAt("c") + "|" + closedAt("c")); |
| 64 | +run("generatorReturn", "abcd", "b", "StringIteratorPrototype", "a,b", closedAt("c")); |
| 65 | + |
| 66 | +// The position counts code units, and the iterator created for the close has to continue after a whole code point. |
| 67 | +run("breakOut", "a\u{1F600}b\u{1F601}", "\u{1F600}", "StringIteratorPrototype", "a,\u{1F600}", closedAt("b")); |
| 68 | +run("breakOut", "a\u{1F600}\u{1F601}", "\u{1F600}", "StringIteratorPrototype", "a,\u{1F600}", closedAt("\u{1F601}")); |
| 69 | +run("breakOut", "a\ud83db", "\ud83d", "StringIteratorPrototype", "a,\ud83d", closedAt("b")); |
| 70 | +run("breakOut", "ab", "b", "StringIteratorPrototype", "a,b", closedWhenDone); |
| 71 | + |
| 72 | +// "return" showing up on %IteratorPrototype% or Object.prototype instead. |
| 73 | +run("breakOut", "abcd", "b", "IteratorPrototype", "a,b", closedAt("c")); |
| 74 | +run("breakOut", "abcd", "b", "ObjectPrototype", "a,b", closedAt("c")); |
0 commit comments