All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.js.es6.generator_engine.js Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2018 The Closure Compiler Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'require base';
'require es6/symbol';
'require es6/util/setprototypeof';
'require es6/util/makeiterator';

/**
 * @fileoverview Implementation for $jscomp.generator
 *
 * This closure-compiler internal JavaScript library provides an ES3-compatible
 * API for writing generator functions using a minimum of boilerplate.
 *
 * Example:
 * ```javascript
 * // yields numbers starting with the given value, then incrementing by the
 * // value supplied to the next() method until the computed value is <= min or
 * // >= max. Then it returns the total number of times it yielded.
 * // If the client code calls throw(), the error will be logged and then
 * // yielded, but the generator won't terminate.
 * function *es6Definition(start, min, max) {
 *   let currentValue = start;
 *   let yieldCount = 0;
 *   while (currentValue > min && currentValue < max) {
 *     try {
 *       currentValue += yield(currentValue);
 *     } catch (e) {
 *       yield(e);
 *       console.log('client threw error', e);
 *     } finally {
 *       yieldCount++;
 *     }
 *   }
 *   return [yieldCount, currentValue];
 * }
 *
 * function es3Definition(start, min, max) {
 *   var currentValue;
 *   var yieldCount;
 *   var e;
 *
 *   return $jscomp.generator.createGenerator(
 *       es3Definition,
 *       function (context$) {
 *         switch (context$.nextAddress) {
 *           case 1: // execution always starts with 1
 *             currentValue = start;
 *             yieldCount = 0;
 *             // fall-through
 *
 *           case 2:
 *             if (!(currentValue > min && currentValue < max)) {
 *               // exit while loop:
 *               return context$.jumpTo(3);
 *             }
 *             // try {
 *             JSCompiler_temp_const$jscomp$1 = currentValue;
 *             context$.setCatchFinallyBlocks(4, 5);
 *             return context$.yield(currentValue, 7);
 *
 *           case 7:
 *             currentValue =
 *                 JSCompiler_temp_const$jscomp$1 + context$.yieldResult;
 *             // fall-through: execute finally block
 *
 *           case 5: // finally block start
 *             context$.enterFinallyBlock();
 *             yieldCount++;
 *             return context$.leaveFinallyBlock(6);
 *
 *           case 4: // catch block start
 *             e = context$.enterCatchBlock();
 *             return context$.yield(e, 8);
 *
 *           case 8: // finish catch block
 *             console.log('client threw error', e);
 *             return context$.jumpTo(5);
 *
 *           case 6:
 *             context$.jumpTo(2);
 *             break;
 *
 *           case 3:
 *             // come back here when while loop block exits
 *             return context$.return([yieldCount, currentValue]);
 *         }
 *       }
 *   });
 * };
 * ```
 */

/** @const */
$jscomp.generator = {};

/**
 * Ensures that the iterator result is actually an object.
 *
 * @private
 * @final
 * @param {*} result
 * @return {void}
 * @throws {TypeError} if the result is not an instenace of Object.
 */
$jscomp.generator.ensureIteratorResultIsObject_ = function(result) {
  if (result instanceof Object) {
    return;
  }
  throw new TypeError('Iterator result ' + result + ' is not an object');
};


/**
 * Tracks state machine state used by generator.Engine.
 *
 * @template VALUE
 * @constructor
 * @final
 * @struct
 */
$jscomp.generator.Context = function() {
  /**
   * Whether the generator program is being executed at the moment in the
   * current context. Is used to prevent reentrancy.
   *
   * @private
   * @type {boolean}
   */
  this.isRunning_ = false;

  /**
   * An iterator that should yield all its values before the main program can
   * continue.
   *
   * @private
   * @type {?Iterator}
   */
  this.yieldAllIterator_ = null;

  /**
   * The value that will be sent to the program as the result of suspended
   * yield expression.
   *
   * @type {?}
   */
  this.yieldResult = undefined;

  /**
   * The next address where the state machine execution should be resumed.
   *
   * 

Program execution starts at 1 and ends at 0. * * @type {number} */ this.nextAddress = 1; /** * The address that should be executed once an exception is thrown. * *

Value of 0 means no catch block exist that would handles an exception. * * @private * @type {number} */ this.catchAddress_ = 0; /** * The address that should be executed once the result is being returned * or if the exception is thrown and there is no catchAddress specified. * *

Value of 0 means no finally block is set. * * @private * @type {number} */ this.finallyAddress_ = 0; /** * Stores information for the runtime propagation of values and control * flow such as the behaviour of statements (break, continue, return and * throw) that perform nonlocal transfers of control. * * @private * @type {null|{return: VALUE}|{exception, isException: boolean}|{jumpTo: number}}. */ this.abruptCompletion_ = null; /** * The preserved abruptCompletion_ when entering a `finally` block. If * the `finally` block completes normally the preserved abruptCompletion_ is * restored: *

   * try {
   * } finally {  // nesting level 0
   *   // abruptCompletion_ is saved in finallyContexts_[0]
   *   try {
   *   } finally {  // nesting level 1
   *     // abruptCompletion_ is saved in finallyContexts_[1]
   *     ...
   *     // abruptCompletion_ is restored from finallyContexts_[1]
   *   }
   *   // abruptCompletion_ is restored from finallyContexts_[0]
   * }
   * 
   *
   * @private
   * @type {?Array}.
   */
  this.finallyContexts_ = null;
};

/**
 * Marks generator program as being run.
 *
 * @private
 * @final
 * @return {void}
 * @throws {TypeError} if generator is already running.
 */
$jscomp.generator.Context.prototype.start_ = function() {
  if (this.isRunning_) {
    throw new TypeError('Generator is already running');
  }
  this.isRunning_ = true;
};

/**
 *
 *
 * @private
 * @final
 * @return {void}
 */
$jscomp.generator.Context.prototype.stop_ = function() {
  this.isRunning_ = false;
};

/**
 * Transfers program execution to an appropriate catch/finally block that
 * should be executed if exception occurs.
 *
 * @private
 * @final
 * @return {void}
 */
$jscomp.generator.Context.prototype.jumpToErrorHandler_ = function() {
  this.nextAddress = this.catchAddress_ || this.finallyAddress_;
};

/**
 * Sets the result of suspended yield expression.
 *
 * @private
 * @final
 * @param {?=} value The value to send to the generator.
 * @return {void}
 * @suppress {reportUnknownTypes}
 */
$jscomp.generator.Context.prototype.next_ = function(value) {
  this.yieldResult = value;
};

/**
 * Throws exception as the result of suspended yield.
 *
 * @private
 * @final
 * @param {?} e
 * @return {void}
 * @suppress {reportUnknownTypes}
 */
$jscomp.generator.Context.prototype.throw_ = function(e) {
  this.abruptCompletion_ = {exception: e, isException: true};
  this.jumpToErrorHandler_();
};

/**
 * Returns a value as the result of generator function.
 *
 * @final
 * @param {VALUE=} value
 * @return {void}
 * @suppress {reportUnknownTypes}
 */
$jscomp.generator.Context.prototype.return = function(value) {
  this.abruptCompletion_ = {return: /** @type {VALUE} */ (value)};
  this.nextAddress = this.finallyAddress_;
};

/**
 * Changes the context so the program execution will continue from the given
 * state after executing nessesary pending finally blocks first.
 *
 * @final
 * @param {number} nextAddress The state that should be run.
 * @return {void}
 */
$jscomp.generator.Context.prototype.jumpThroughFinallyBlocks = function(
    nextAddress) {
  this.abruptCompletion_ = {jumpTo: nextAddress};
  this.nextAddress = this.finallyAddress_;
};

/**
 * Pauses the state machine program assosiated with generator function to yield
 * a value.
 *
 * @final
 * @param {VALUE} value The value to return from the generator function via
 *     the iterator protocol.
 * @param {number} resumeAddress The address where the program should resume.
 * @return {{value: VALUE}}
 * @suppress {reportUnknownTypes}
 */
$jscomp.generator.Context.prototype.yield = function(value, resumeAddress) {
  this.nextAddress = resumeAddress;
  return {value: value};
};

/**
 * Causes the state machine program to yield all values from an iterator.
 *
 * @final
 * @param {string|!Iterator|!Iterable|!Arguments} iterable
 *     Iterator to yeild all values from.
 * @param {number} resumeAddress The address where the program should resume.
 * @return {void | {value: VALUE}}
 * @suppress {reportUnknownTypes}
 */
$jscomp.generator.Context.prototype.yieldAll = function(
    iterable, resumeAddress) {
  /** @const @type {!Iterator} */ var iterator =
      $jscomp.makeIterator(iterable);
  /** @const */ var result = iterator.next();
  $jscomp.generator.ensureIteratorResultIsObject_(result);
  if (result.done) {
    // If `someGenerator` in `x = yield *someGenerator` completes immediately,
    // x is the return value of that generator.
    this.yieldResult = result.value;
    this.nextAddress = resumeAddress;
    return;
  }
  this.yieldAllIterator_ = iterator;
  return this.yield(result.value, resumeAddress);
};

/**
 * Changes the context so the program execution will continue from the given
 * state.
 *
 * @final
 * @param {number} nextAddress The state the program should continue
 * @return {void}
 */
$jscomp.generator.Context.prototype.jumpTo = function(nextAddress) {
  this.nextAddress = nextAddress;
};

/**
 * Changes the context so the program execution ends.
 *
 * @final
 * @return {void}
 */
$jscomp.generator.Context.prototype.jumpToEnd = function() {
  this.nextAddress = 0;
};

/**
 * Sets catch / finally handlers.
 * Used for try statements with catch blocks.
 *
 * @final
 * @param {number} catchAddress The address of the catch block.
 * @param {number=} finallyAddress The address of the finally block.
 * @return {void}
 */
$jscomp.generator.Context.prototype.setCatchFinallyBlocks = function(
    catchAddress, finallyAddress) {
  this.catchAddress_ = catchAddress;
  if (finallyAddress != undefined) {
    this.finallyAddress_ = finallyAddress;
  }
};

/**
 * Sets finally handler.
 * Used for try statements without catch blocks.
 *
 * @const
 * @param {number=} finallyAddress The address of the finally block or 0.
 * @return {void}
 */
$jscomp.generator.Context.prototype.setFinallyBlock = function(finallyAddress) {
  this.catchAddress_ = 0;
  this.finallyAddress_ = finallyAddress || 0;
};

/**
 * Sets a catch handler and jumps to the next address.
 * Used for try statements without finally blocks.
 *
 * @final
 * @param {number} nextAddress The state that should be run next.
 * @param {number=} catchAddress The address of the catch block or 0.
 * @return {void}
 */
$jscomp.generator.Context.prototype.leaveTryBlock = function(
    nextAddress, catchAddress) {
  this.nextAddress = nextAddress;
  this.catchAddress_ = catchAddress || 0;
};

/**
 * Initializes exception variable in the beginning of catch block.
 *
 * @final
 * @param {number=} nextCatchBlockAddress The address of the next catch block
 *     that is preceded by no finally blocks.
 * @return {?} Returns an exception that was thrown from "try" block.
 * @suppress {reportUnknownTypes}
 */
$jscomp.generator.Context.prototype.enterCatchBlock = function(
    nextCatchBlockAddress) {
  this.catchAddress_ = nextCatchBlockAddress || 0;
  /** @const */ var exception =
      /** @type {{exception, isException: boolean}} */ (this.abruptCompletion_)
          .exception;
  this.abruptCompletion_ = null;
  return exception;
};

/**
 * Saves the current throw context which will be restored at the end of finally
 * block.
 *
 * @final
 * @param {number=} nextCatchAddress
 * @param {number=} nextFinallyAddress
 * @param {number=} finallyDepth The nesting level of current "finally" block.
 * @return {void}
 */
$jscomp.generator.Context.prototype.enterFinallyBlock = function(
    nextCatchAddress, nextFinallyAddress, finallyDepth) {
  if (!finallyDepth) {
    this.finallyContexts_ = [this.abruptCompletion_];
  } else {
    /**
     * @type {!Array}
     */
    (this.finallyContexts_)[finallyDepth] = this.abruptCompletion_;
  }
  this.catchAddress_ = nextCatchAddress || 0;
  this.finallyAddress_ = nextFinallyAddress || 0;
};

/**
 * Figures out whether the program execution should continue normally, or jump
 * to the closest catch/finally block.
 *
 * @final
 * @param {number} nextAddress The state that should be run next.
 * @param {number=} finallyDepth The nesting level of current "finally" block.
 * @return {void}
 * @suppress {strictMissingProperties}
 */
$jscomp.generator.Context.prototype.leaveFinallyBlock = function(
    nextAddress, finallyDepth) {
  // There could be trailing finally contexts if a nested finally throws an
  // exception or return.
  // e.g.
  // try {
  //   ...
  //   return 1;
  // } finally {
  //   // finallyDepth == 0
  //   // finallyContext == [{return: 1}]
  //   try {
  //     ...
  //     try {
  //       throw new Error(2);
  //     } finally {
  //       // finallyDepth == 1
  //       // finallyContext == [{return: 1}, {exception: Error(2)}]
  //       try {
  //         throw new Error(3);
  //       } finally {
  //         // finallyDepth == 2
  //         // finallyContext == [
  //         //     {return: 1},
  //         //     {exception: Error(2)},
  //         //     {exception: Error(3)}
  //         // ]
  //         throw new Error(4); // gets written in abruptCompletion_
  //         // leaveFinallyBlock() never gets called here
  //       }
  //       // leaveFinallyBlock() never gets called here
  //     }
  //   } catch (e) {
  //      // swallow error
  //      // abruptCompletion becomes null
  //   } finally {
  //     // finallyDepth == 1
  //     // finallyContext == [
  //     //     {return: 1},
  //     //     null, // overwritten, because catch swallowed the error
  //     //     {exception: Error(3)}  // left over
  //     // ]
  //     // leaveFinallyBlock() called here
  //     // finallyContext == [{return: 1}]
  //     // abruptCompletion == null
  //   }
  //   // leaveFinallyBlock() called here
  //   // finallyContext = []
  //   // abruptCompletion == {return: 1};
  // }
  /** @const */ var preservedContext =
      /**
       * @type {!Array}
       */
      (this.finallyContexts_).splice(finallyDepth || 0)[0];
  /** @const */ var abruptCompletion = this.abruptCompletion_ =
      this.abruptCompletion_ || preservedContext;
  if (abruptCompletion) {
    if (abruptCompletion.isException) {
      return this.jumpToErrorHandler_();
    }
    // Check if there is a pending break/continue jump that is not preceded by
    // finally blocks that should be executed before.
    // We always generate case numbers for the start and end of loops before
    // numbers for anything they contain, so any finally blocks within will be
    // guaranteed to have higher addresses than the loop break and continue
    // positions.
    // e.g.
    // l1: while (...) {            // generated addresses: 100: break l1;
    //       try {                  // generated addresses: 101: finally,
    //         try {                // generated addresses: 102: finally,
    //           l2: while (...) {  // generated addresses: 103: break l2;
    //
    //                 if (...) {
    //                   break l1;  // becomes
    //                              // $context.jumpThroughFinallyBlocks(101),
    //                              // since 2 finally blocks must be crossed
    //                 }
    //                 break l2;    // becomes $context.jumpTo(103)
    //               }
    //         } finally {
    //           // When leaving this finally block:
    //           // 1. We keep the abrupt completion indicating 'break l1'
    //           // 2. We jump to the enclosing finally block.
    //         }
    //       } finally {
    //         // When leaving this finally block:
    //         // 1. We complete the abruptCompletion indicating 'break l1' by
    //         //   jumping to the loop start address.
    //         // 2. Abrupt completion is now null, so normal execution
    //         //   continues from there.
    //       }
    //     }
    if (abruptCompletion.jumpTo != undefined &&
        this.finallyAddress_ < abruptCompletion.jumpTo) {
      this.nextAddress = abruptCompletion.jumpTo;
      this.abruptCompletion_ = null;
    } else {
      this.nextAddress = this.finallyAddress_;
    }
  } else {
    this.nextAddress = nextAddress;
  }
};

/**
 * Is used in transpilation of `for in` statements.
 *
 * 

for (var i in obj) {...} becomes: *

 * for (var i, $for$in = context$.forIn(obj);
 *      (i = $for$in.getNext()) != null;
 *      ) {
 *   ...
 * }
 * 
* * @final * @param {?} object * @return {!$jscomp.generator.Context.PropertyIterator} * @suppress {reportUnknownTypes} */ $jscomp.generator.Context.prototype.forIn = function(object) { return new $jscomp.generator.Context.PropertyIterator(object); }; /** * @constructor * @final * @struct * @param {?} object * @suppress {reportUnknownTypes} */ $jscomp.generator.Context.PropertyIterator = function(object) { /** * @private * @const * @type {?} */ this.object_ = object; /** * @private * @const * @type {!Array} */ this.properties_ = []; for (var property in /** @type {!Object} */ (object)) { this.properties_.push(property); } this.properties_.reverse(); }; /** * Returns the next object's property that is still valid. * * @final * @return {?string} * @suppress {reportUnknownTypes} */ $jscomp.generator.Context.PropertyIterator.prototype.getNext = function() { // The JS spec does not require that properties added after the loop begins // be included in the loop, but it does require that the current property // must still exist on the object when the loop iteration starts. while (this.properties_.length > 0) { /** @const */ var property = this.properties_.pop(); if (property in /** @type {!Object} */ (this.object_)) { return property; } } return null; }; /** * Engine handling execution of a state machine associated with the generator * program and its context. * * @private * @template VALUE * @constructor * @final * @struct * @param {function(!$jscomp.generator.Context): (void|{value: VALUE})} program */ $jscomp.generator.Engine_ = function(program) { /** * @private * @const * @type {!$jscomp.generator.Context} */ this.context_ = new $jscomp.generator.Context(); /** * @private * @const * @type {function(!$jscomp.generator.Context): (void|{value: VALUE})} */ this.program_ = program; }; /** * Returns an object with two properties done and value. * You can also provide a parameter to the next method to send a value to the * generator. * * @private * @final * @param {?=} value The value to send to the generator. * @return {!IIterableResult} * @suppress {reportUnknownTypes} */ $jscomp.generator.Engine_.prototype.next_ = function(value) { this.context_.start_(); if (this.context_.yieldAllIterator_) { return this.yieldAllStep_( this.context_.yieldAllIterator_.next, value, this.context_.next_); } this.context_.next_(value); return this.nextStep_(); }; /** * Attempts to finish the generator with a given value. * * @private * @final * @param {VALUE} value The value to return. * @return {!IIterableResult} * @suppress {reportUnknownTypes} */ $jscomp.generator.Engine_.prototype.return_ = function(value) { this.context_.start_(); /** @const */ var yieldAllIterator = this.context_.yieldAllIterator_; if (yieldAllIterator) { /** @const @type {function(VALUE): !IIterableResult} */ var returnFunction = 'return' in yieldAllIterator ? yieldAllIterator['return'] : function(v) { return {value: v, done: true}; }; return this.yieldAllStep_(returnFunction, value, this.context_.return); } this.context_.return(value); return this.nextStep_(); }; /** * Resumes the execution of a generator by throwing an error into it and * returns an object with two properties done and value. * * @private * @final * @param {?} exception The exception to throw. * @return {!IIterableResult} * @suppress {reportUnknownTypes} */ $jscomp.generator.Engine_.prototype.throw_ = function(exception) { this.context_.start_(); if (this.context_.yieldAllIterator_) { return this.yieldAllStep_( this.context_.yieldAllIterator_['throw'], exception, this.context_.next_); } this.context_.throw_(exception); return this.nextStep_(); }; /** * Redirects next/throw/return method calls to an iterator passed to "yield *". * * @private * @final * @template T * @param {function(this:Iterator, T): !IIterableResult} action * @param {T} value * @param {function(this:$jscomp.generator.Context, VALUE): void} nextAction * @return {!IIterableResult} * @suppress {reportUnknownTypes} */ $jscomp.generator.Engine_.prototype.yieldAllStep_ = function( action, value, nextAction) { try { /** @const */ var result = action.call( /** @type {!Iterator} */ (this.context_.yieldAllIterator_), value); $jscomp.generator.ensureIteratorResultIsObject_(result); if (!result.done) { this.context_.stop_(); return result; } // After `x = yield *someGenerator()` x is the return value of the // generator, not a value passed to this generator by the next() method. /** @const */ var resultValue = result.value; } catch (e) { this.context_.yieldAllIterator_ = null; this.context_.throw_(e); return this.nextStep_(); } this.context_.yieldAllIterator_ = null; nextAction.call(this.context_, resultValue); return this.nextStep_(); }; /** * Continues/resumes program execution until the next suspension point (yield). * * @private * @final * @return {!IIterableResult} * @suppress {reportUnknownTypes, strictMissingProperties} */ $jscomp.generator.Engine_.prototype.nextStep_ = function() { while (this.context_.nextAddress) { try { /** @const */ var yieldValue = this.program_(this.context_); if (yieldValue) { this.context_.stop_(); return {value: yieldValue.value, done: false}; } } catch (e) { this.context_.yieldResult = undefined; this.context_.throw_(e); } } this.context_.stop_(); if (this.context_.abruptCompletion_) { /** @const */ var abruptCompletion = this.context_.abruptCompletion_; this.context_.abruptCompletion_ = null; if (abruptCompletion.isException) { throw abruptCompletion.exception; } return {value: abruptCompletion.return, done: true}; } return {value: /** @type {?} */ (undefined), done: true}; }; /** * The Generator object that is returned by a generator function and it * conforms to both the iterable protocol and the iterator protocol. * * @private * @template VALUE * @constructor * @final * @implements {Generator} * @param {!$jscomp.generator.Engine_} engine * @suppress {reportUnknownTypes} */ $jscomp.generator.Generator_ = function(engine) { /** @const @override */ this.next = function(opt_value) { return engine.next_(opt_value); }; /** @const @override */ this.throw = function(exception) { return engine.throw_(exception); }; /** @const @override */ this.return = function(value) { return engine.return_(value); }; /** @this {$jscomp.generator.Generator_} */ this[Symbol.iterator] = function() { return this; }; // TODO(skill): uncomment once Symbol.toStringTag is polyfilled: // this[Symbol.toStringTag] = 'Generator'; }; /** * Creates a generator backed up by Engine running a given program. * * @final * @template VALUE * @param {function(this:?, ...): (!Iterator|!Iterable)} generator * @param {function(!$jscomp.generator.Context): (void|{value: VALUE})} program * @return {!Generator} * @suppress {reportUnknownTypes} */ $jscomp.generator.createGenerator = function(generator, program) { /** @const */ var result = new $jscomp.generator.Generator_(new $jscomp.generator.Engine_(program)); // The spec says that `myGenFunc() instanceof myGenFunc` must be true. // We'll make this work by setting the prototype before calling the // constructor every time. All of the methods of the object are defined on the // instance by the constructor, so this does no harm. // We also cast Generator_ to Object to hide dynamic inheritance from // jscompiler, it makes ConformanceRules$BanUnknownThis happy. if ($jscomp.setPrototypeOf) { /** @type {function(!Object, ?Object): !Object} */ ($jscomp.setPrototypeOf)( result, generator.prototype); } return result; };




© 2015 - 2024 Weber Informatics LLC | Privacy Policy