diff --git a/docs/generator.md b/docs/generator.md index 88ffb08..797740d 100644 --- a/docs/generator.md +++ b/docs/generator.md @@ -7,14 +7,14 @@ Here's the body of the `__generator` helper: ```js __generator = function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t; - return { next: verb(0), "throw": verb(1), "return": verb(2) }; + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [0, t.value]; + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; @@ -98,7 +98,7 @@ arguments, and their purpose: | 7 (endfinally) | | Exits a finally block, resuming any previous operation (such as a break, return, throw, etc.) | # State -The `_`, `f`, `y`, and `t` variables make up the persistent state of the `__generator` function. Each variable +The `_`, `f`, `y`, `t`, and `g` variables make up the persistent state of the `__generator` function. Each variable has a specific purpose, as described in the following sections: ## The `_` variable @@ -148,6 +148,12 @@ The `t` variable is a temporary variable that stores one of the following values > NOTE: None of the above cases overlap. +## The `g` variable +The `g` variable is a temporary variable that holds onto the generator object for the purpose of attaching a +`Symbol.iterator` method (if its available), and holds onto that value until the generator is started, allowing + +it to also act as the [`suspendedStart`](https://tc39.es/ecma262/#table-internal-slots-of-generator-instances) state. + # Protected Regions A **Protected Region** is a region within the `body` function that indicates a `try..catch..finally` statement. It consists of a 4-tuple that contains 4 labels: @@ -164,13 +170,18 @@ The final step of the `__generator` helper is the allocation of an object that i `Generator` protocol, to be used by the `__awaiter` helper: ```ts -return { next: verb(0), "throw": verb(1), "return": verb(2) }; +return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, + typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), + g; function verb(n) { return function (v) { return step([n, v]); }; } ``` This object translates calls to `next`, `throw`, and `return` to the appropriate Opcodes and invokes the `step` orchestration function to continue execution. The `throw` and `return` method -names are quoted to better support ES3. +names are quoted to better support ES3. In addition, a `Symbol.iterator` method is added to the +generator if the global `Symbol` constructor is available. Once we return, the object reference in +the `g` variable isn't used by the main [orchestration method](#orchestration), so we can use its +truthiness as a mechanism to determine whether the generator is in the `suspendedStart` state. # Orchestration The `step` function is the main orechestration mechanism for the `__generator` helper. It @@ -181,9 +192,9 @@ Here's a closer look at the `step` function: ```ts function step(op) { if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [0, t.value]; + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; @@ -219,13 +230,25 @@ The main body of the `step` function consists of a `while` loop which continues instructions until the generator exits or is suspended: ```ts -while (_) try ... +while (g && (g = 0, op[0] && (_ = 0)), _) try ... ``` +During the first call to `next()`, `return()`, or `throw()`, the generator will be in the `suspendedStart` state. This +is indicated by the `g` variable being "truthy" since it still holds a reference to the generator object. If `g` is +"truthy", then we reset it and check whether the first instruction sent to the generator (`op[0]`) is either a +`return` (`op[0] === 1`) or `throw` (`op[0] === 2`) Opcode, indicating an abrupt completion. + +If the first instruction is abrupt, we can emulate [GeneratorResumeAbrupt](https://tc39.es/ecma262/#sec-generatorresumeabrupt) +by setting the state variable (`_`) to a falsy value, which will skip the loop body and +[complete the generator](#handling-a-completed-generator). + +If this is _not_ the first instruction sent to the generator, or if the first instruction was `next()`, we will proceed +to evaluate the loop body. + When the generator has run to completion, the `_` state variable will be cleared, forcing the loop to exit. -## Evaluating the generator body. +## Evaluating the generator body ```ts try { ... @@ -272,8 +295,8 @@ reduce the overall footprint of the helper. The first two statements of the `try..finally` statement handle delegation for `yield*`: ```ts -if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; -if (y = 0, t) op = [0, t.value]; +if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; +if (y = 0, t) op = [op[0] & 2, t.value]; ``` If the `y` variable is set, and `y` has a `next`, `throw`, or `return` method (depending on the @@ -282,7 +305,7 @@ current operation), we invoke this method and store the return value (an Iterato If `t` indicates it is a yielded value (e.g. `t.done === false`), we return `t` to the caller. If `t` indicates it is a returned value (e.g. `t.done === true`), we mark the operation with the `next` Opcode, and the returned value. -If `y` did not have the appropriate method, or `t` was a returned value, we reset `y` to a falsey +If `y` did not have the appropriate method, or `t` was a returned value, we reset `y` to a falsy value and continue processing the operation. ## Handling operations @@ -402,7 +425,7 @@ if (!(t = ...) && (op[0] === 6 || op[0] === 2)) { ``` If we encounter an Opcode 6 ("catch") or Opcode 2 ("return"), and we are not in a protected region, -then this operation completes the generator by setting the `_` variable to a falsey value. The +then this operation completes the generator by setting the `_` variable to a falsy value. The `continue` statement resumes execution at the top of the `while` statement, which will exit the loop so that we continue execution at the statement following the loop. @@ -463,7 +486,7 @@ current **protected region** from the stack and spin the `while` statement to ev operation again in the next **protected region** or at the function boundary. ## Handling a completed generator -Once the generator has completed, the `_` state variable will be falsey. As a result, the `while` +Once the generator has completed, the `_` state variable will be falsy. As a result, the `while` loop will terminate and hand control off to the final statement of the orchestration function, which deals with how a completed generator is evaluated: diff --git a/tslib.es6.js b/tslib.es6.js index e6d7777..9e05b06 100644 --- a/tslib.es6.js +++ b/tslib.es6.js @@ -83,7 +83,7 @@ export function __generator(thisArg, body) { function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); - while (_) try { + while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { diff --git a/tslib.js b/tslib.js index 2b7885c..e222adb 100644 --- a/tslib.js +++ b/tslib.js @@ -125,7 +125,7 @@ var __createBinding; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); - while (_) try { + while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) {