text:
package com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.javascript.jscomp.AstFactory.type;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.jscomp.colors.Color;
import com.google.javascript.jscomp.colors.StandardColors;
import com.google.javascript.jscomp.parsing.parser.FeatureSet;
import com.google.javascript.jscomp.parsing.parser.FeatureSet.Feature;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.QualifiedName;
import com.google.javascript.rhino.StaticScope;
import com.google.javascript.rhino.Token;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import javax.annotation.Nullable;

/**
 * Converts ES6 generator functions to valid ES3 code. This pass runs after all ES6 features except
 * for yield and generators have been transpiled.
 *
 *
Genertor transpilation pass uses two sets of node properties:
 *
 *

 *
generatorMarker property - to indicate that subtee contains YIELD nodes;
 *
generatorSafe property - the node is known to require no further modifications to work in
 *       the transpiled form of the generator body.
 *

 *
 *
The conversion is done in the following steps:
 *
 *

 *
Find a generator function: function *() {}
 *
Replace its original body with a template
 *
Mark all nodes in original body that contain any YIELD nodes
 *
Transpile every statement of the original body into replaced template
 *

 *
unmarked nodes may be copied into the template with a trivial transpilation of
 *             "this", "break", "continue", "return" and "arguments" keywords.
 *
marked nodes must be broken up into multiple states to support the yields they
 *             contain.
 *

 *

 *
 *
{@code Es6RewriteGenerators} depends on {@link InjectTranspilationRuntimeLibraries} to inject
 * generator_engine.js template.
 */
final class Es6RewriteGenerators implements CompilerPass {

  private static final String GENERATOR_FUNCTION = "$jscomp$generator$function";
  private static final String GENERATOR_CONTEXT = "$jscomp$generator$context";
  private static final String GENERATOR_ARGUMENTS = "$jscomp$generator$arguments";
  private static final String GENERATOR_THIS = "$jscomp$generator$this";
  private static final String GENERATOR_FORIN_PREFIX = "$jscomp$generator$forin$";

  private static final QualifiedName JSCOMP_ASYNC_EXECUTE =
      QualifiedName.of("$jscomp.asyncExecutePromiseGeneratorFunction");

  private static final FeatureSet transpiledFeatures =
      FeatureSet.BARE_MINIMUM.with(Feature.GENERATORS);

  private final AbstractCompiler compiler;
  private final StaticScope namespace;
  private final AstFactory astFactory;

  private final Color nullableStringType;
  private final Supplier<AstFactory.Type> generatorContextType;
  private final Supplier<AstFactory.Type> propertyIteratorType;

  Es6RewriteGenerators(AbstractCompiler compiler) {
    checkNotNull(compiler);
    this.compiler = compiler;
    this.astFactory = compiler.createAstFactory();
    this.namespace = compiler.getTranspilationNamespace();

    if (compiler.hasOptimizationColors()) {
      // typechecking has run, so we must preserve and propagate type information
      nullableStringType =
          Color.createUnion(ImmutableSet.of(StandardColors.NULL_OR_VOID, StandardColors.STRING));
    } else {
      nullableStringType = null;
    }
    generatorContextType =
        Suppliers.memoize(
            () ->
                type(
                    astFactory.createNewNode(
                        astFactory.createQName(this.namespace, "$jscomp.generator.Context"))));
    propertyIteratorType =
        Suppliers.memoize(
            () ->
                type(
                    astFactory.createNewNode(
                        astFactory.createQName(
                            this.namespace, "$jscomp.generator.Context.PropertyIterator"))));
  }

  @Override
  public void process(Node externs, Node root) {
    TranspilationPasses.processTranspile(
        compiler, root, transpiledFeatures, new GeneratorFunctionsTranspiler());
    TranspilationPasses.maybeMarkFeaturesAsTranspiledAway(compiler, transpiledFeatures);
  }

  /**
   * Exposes expression with yield inside to an equivalent expression in which yield is of the form:
   *
   *

   * var name = yield expr;
   *

   *
   *
For example, changes the following code:
   *
   *

   * { return x || yield y; }
   *

   *
   * into:
   *
   *

   * {
   *   var temp$$0;
   *   if (temp$$0 = x); else temp$$0 = yield y;
   *   return temp$$0;
   * }
   *

   *
   *
Expression should always be inside a block, so that other statements could be added at need.
   *
   *
Uses the {@link ExpressionDecomposer} class.
   */
  private class YieldExposer extends NodeTraversal.AbstractPreOrderCallback {

    final ExpressionDecomposer decomposer;

    YieldExposer() {
      decomposer = compiler.createDefaultExpressionDecomposer();
    }

    @Override
    public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
      n.setGeneratorMarker(false);
      if (n.isFunction()) {
        return false;
      }
      if (n.isYield()) {
        visitYield(n);
        return false;
      }
      return true;
    }

    void visitYield(Node n) {
      if (n.getParent().isExprResult()) {
        return;
      }
      if (decomposer.canExposeExpression(n)
          != ExpressionDecomposer.DecompositionType.UNDECOMPOSABLE) {
        decomposer.maybeExposeExpression(n);
      } else {
        String link =
            "https://github.com/google/closure-compiler/wiki/FAQ"
                + "#i-get-an-undecomposable-expression-error-for-my-yield-or-await-expression"
                + "-what-do-i-do";
        String suggestion = "Please rewrite the yield or await as a separate statement.";
        String message = "Undecomposable expression: " + suggestion + "\nSee " + link;
        compiler.report(JSError.make(n, Es6ToEs3Util.CANNOT_CONVERT, message));
      }
    }
  }

  /** Finds generator functions and performs ES6 -> ES3 trnspilation */
  private class GeneratorFunctionsTranspiler implements NodeTraversal.Callback {
    int generatorNestingLevel = 0;

    @Override
    public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
      if (n.isGeneratorFunction()) {
        ++generatorNestingLevel;
      }
      return true;
    }

    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (n.isGeneratorFunction()) {
        new SingleGeneratorFunctionTranspiler(n, --generatorNestingLevel).transpile();
      }
    }
  }

  /** Transpiles a single generator function into a state machine program. */
  private class SingleGeneratorFunctionTranspiler {

    final int generatorNestingLevel;

    /** The transpilation context for the state machine program. */
    final TranspilationContext context;

    /** The body of original generator function that should be transpiled */
    final Node originalGeneratorBody;

    /**
     * The body of a replacement function.
     *
     *
It's a block node that hoists local variables of a generator program and returns an actual
     * generator object created from that program:
     *
     *

     * {
     *   var a;
     *   var b;
     *   ...
     *   return createGenerator(function ($jscomp$generator$context) { ... });
     * }
     *

     *
     * The assumtion is that the hoist block always ends with a return statement, and all local
     * variables are added before this "return" statement.
     */
    Node newGeneratorHoistBlock;

    SingleGeneratorFunctionTranspiler(Node genFunc, int genaratorNestingLevel) {
      this.generatorNestingLevel = genaratorNestingLevel;
      this.originalGeneratorBody = genFunc.getLastChild();
      this.context = new TranspilationContext();
    }

    /**
     * Hoists a node inside {@link #newGeneratorHoistBlock}.
     *
     * @see #newGeneratorHoistBlock
     */
    private void hoistNode(Node node) {
      node.insertBefore(newGeneratorHoistBlock.getLastChild());
    }

    /**
     * Detects whether the generator function was generated by async function transpilation:
     *
     *

     *   function() {
     *     ...
     *     return $jscomp.asyncExecutePromiseGeneratorFunction(function* genFunc() {...});
     *   }
     *

     */
    private boolean isTranspiledAsyncFunction(Node generatorFunction) {
      if (generatorFunction.getParent().isCall() && generatorFunction.getPrevious() != null) {
        Node callTarget = generatorFunction.getParent().getFirstChild();
        if (generatorFunction.getPrevious() == callTarget
            && generatorFunction.getNext() == null
            && JSCOMP_ASYNC_EXECUTE.matches(callTarget)) {
          checkState(generatorFunction.getGrandparent().isReturn());
          checkState(generatorFunction.getGrandparent().getNext() == null);
          return true;
        }
      }
      return false;
    }

    public void transpile() {
      Node generatorFunction = originalGeneratorBody.getParent();
      checkState(generatorFunction.isGeneratorFunction());
      generatorFunction.putBooleanProp(Node.GENERATOR_FN, false);

      // A "program" function:
      //   function ($jscomp$generator$context) {
      //   }
      final Node program;
      // function(!Context<YIELD_TYPE>): (void|{value: YIELD_TYPE})
      Color programType = StandardColors.TOP_OBJECT;
      Node generatorBody = astFactory.createBlock();

      final Node changeScopeNode;
      if (isTranspiledAsyncFunction(generatorFunction)) {
        // Our generatorFunction is a transpiled async function

        // $jscomp.asyncExecutePromiseGeneratorFunction
        Node callTarget = generatorFunction.getPrevious();
        checkState(callTarget.isGetProp());

        // Use original async function as a hoist block for local generator variables:
        // generator function -> call -> return -> async function body
        newGeneratorHoistBlock = generatorFunction.getGrandparent().getParent();
        checkState(newGeneratorHoistBlock.isBlock(), newGeneratorHoistBlock);
        changeScopeNode = NodeUtil.getEnclosingFunction(newGeneratorHoistBlock);
        checkState(changeScopeNode.isFunction(), changeScopeNode);

        // asyncExecutePromiseGeneratorFunction   =>   asyncExecutePromiseGeneratorProgram
        callTarget.setString("asyncExecutePromiseGeneratorProgram");

        program = originalGeneratorBody.getParent();
        // function *() {...}   =>   function *(context) {}
        originalGeneratorBody.getPrevious().addChildToBack(
            context.getJsContextNameNode(originalGeneratorBody));
        originalGeneratorBody.replaceWith(generatorBody);
      } else {
        changeScopeNode = generatorFunction;
        Node genFuncName = generatorFunction.getFirstChild();
        checkState(genFuncName.isName());
        // The transpiled function needs to be able to refer to itself, so make sure it has a name.
        if (genFuncName.getString().isEmpty()) {
          genFuncName.setString(context.getScopedName(GENERATOR_FUNCTION));
          if (astFactory.isAddingColors()) {
            genFuncName.setColor(StandardColors.TOP_OBJECT);
          }
        }

        // Prepare a "program" function:
        //   function ($jscomp$generator$context) {
        //   }
        program =
            astFactory.createFunction(
                "",
                IR.paramList(context.getJsContextNameNode(originalGeneratorBody)),
                generatorBody,
                type(programType));

        // $jscomp.generator.createGenerator
        Node createGenerator =
            astFactory.createQName(namespace, "$jscomp.generator.createGenerator");
        // Replace original generator function body with:
        // return $jscomp.generator.createGenerator(<origGenerator>, <program function>);
        newGeneratorHoistBlock =
            astFactory.createBlock(
                astFactory
                    .createReturn(
                        // TODO(b/142881197): we can't give a more accurate type right now.
                        astFactory.createCallWithUnknownType(
                            createGenerator,
                            // function name passed as parameter must have the type of the
                            // generator function itself
                            astFactory
                                .createName(genFuncName.getString(), type(generatorFunction))
                                .srcref(genFuncName),
                            program))
                    .srcrefTree(originalGeneratorBody));
        originalGeneratorBody.replaceWith(newGeneratorHoistBlock);
      }

      // New scopes and any changes to scopes should be reported individually.
      compiler.reportChangeToChangeScope(program);

      NodeTraversal.traverse(compiler, originalGeneratorBody, new YieldNodeMarker());

      // Test if end of generator function is reachable
      boolean shouldAddFinalJump = !isEndOfBlockUnreachable(originalGeneratorBody);

      // Transpile statements from original generator function
      while (originalGeneratorBody.hasChildren()) {
        transpileStatement(originalGeneratorBody.removeFirstChild());
      }

      // Ensure that the state machine program ends
      Node finalBlock = astFactory.createBlock();
      if (shouldAddFinalJump) {
        finalBlock.addChildToBack(
            context.callContextMethodResult(
                originalGeneratorBody, "jumpToEnd", type(StandardColors.NULL_OR_VOID)));
      }
      context.currentCase.jumpTo(context.programEndCase, finalBlock);
      context.currentCase.mayFallThrough = true;

      context.finalizeTransformation(generatorBody);
      context.checkStateIsEmpty();

      compiler.reportChangeToChangeScope(changeScopeNode);
    }

    /** @see #transpileStatement(Node, TranspilationContext.Case, TranspilationContext.Case) */
    void transpileStatement(Node statement) {
      transpileStatement(statement, null, null);
    }

    /**
     * Transpiles a detached node and adds transpiled version of it to the {@link
     * TranspilationContext.Case#currentCase currentCase} of the {@link #context}.
     *
     * @param statement Node to transpile
     * @param breakCase
     * @param continueCase
     */
    void transpileStatement(
        Node statement,
        @Nullable TranspilationContext.Case breakCase,
        @Nullable TranspilationContext.Case continueCase) {
      checkState(IR.mayBeStatement(statement));
      checkState(statement.getParent() == null);

      if (!statement.isGeneratorMarker()) {
        transpileUnmarkedNode(statement);
        return;
      }
      switch (statement.getToken()) {
        case LABEL:
          transpileLabel(statement);
          break;

        case BLOCK:
          transpileBlock(statement);
          break;

        case EXPR_RESULT:
          transpileExpressionResult(statement);
          break;

        case VAR:
          transpileVar(statement);
          break;

        case RETURN:
          transpileReturn(statement);
          break;

        case THROW:
          transpileThrow(statement);
          break;

        case IF:
          transpileIf(statement, breakCase);
          break;

        case FOR:
          transpileFor(statement, breakCase, continueCase);
          break;

        case FOR_IN:
          transpileForIn(statement, breakCase, continueCase);
          break;

        case WHILE:
          transpileWhile(statement, breakCase, continueCase);
          break;

        case DO:
          transpileDo(statement, breakCase, continueCase);
          break;

        case TRY:
          transpileTry(statement, breakCase);
          break;

        case SWITCH:
          transpileSwitch(statement, breakCase);
          break;

        default:
          throw new IllegalStateException("Unsupported token: " + statement.getToken());
      }
    }

    /** Transpiles code that doesn't contain yields. */
    void transpileUnmarkedNode(Node n) {
      checkState(!n.isGeneratorMarker());
      if (n.isFunction()) {
        // All function statemnts will be normalized:
        // "function a() {}"   =>   "var a = function() {};"
        // so we have to move them to the outer scope.
        String functionName = n.getFirstChild().getString();
        // Make sure there are no "function (...) {...}" statements (note that
        // "function *(...) {...}" becomes "function $jscomp$generator$function(...) {...}" as
        // inner generator functions are transpiled first).
        checkState(!functionName.isEmpty() && !functionName.startsWith(GENERATOR_FUNCTION));
        hoistNode(n);
        return;
      }
      context.transpileUnmarkedBlock(n.isBlock() || n.isAddedBlock() ? n : IR.block(n));
    }

    /** Transpiles a label with marked statement. */
    void transpileLabel(Node n) {
      // Collect all labels names in "a: b: c: {}" statement
      ArrayList<Node> labelNames = new ArrayList<>();
      while (n.isLabel()) {
        labelNames.add(n.removeFirstChild());
        n = n.removeFirstChild();
      }

      // Push label names and continue transpilation
      TranspilationContext.Case continueCase =
          NodeUtil.isLoopStructure(n) ? context.createCase() : null;
      TranspilationContext.Case breakCase = context.createCase();
      context.pushLabels(labelNames, breakCase, continueCase);
      transpileStatement(n, breakCase, continueCase);
      context.popLabels(labelNames);

      // Switch to endCase if it's not yet active.
      if (breakCase != context.currentCase) {
        context.switchCaseTo(breakCase);
      }
    }

    /** Transpiles a block. */
    void transpileBlock(Node n) {
      while (n.hasChildren()) {
        transpileStatement(n.removeFirstChild());
      }
    }

    /** Transpiles marked expression result statement. */
    void transpileExpressionResult(Node n) {
      Node exposedExpression = exposeYieldAndTranspileRest(n.removeFirstChild());
      Node decomposed = transpileYields(exposedExpression);

      // Tanspile "a = yield;" into "a = $context.yieldResult;"
      // But don't transpile "yield;" into "$context.yieldResult;"
      // As it influences the collapsing of empty case sections.
      if (!exposedExpression.isYield()) {
        n.addChildToFront(prepareNodeForWrite(decomposed));
        n.setGeneratorMarker(false);
        context.writeGeneratedNode(n);
      }
    }

    /** Transpiles marked "var" statement. */
    void transpileVar(Node n) {
      n.setGeneratorMarker(false);
      Node newVars = n.cloneNode();
      while (n.hasChildren()) {
        Node var;
        // Just collect all unmarked vars and transpile them together.
        while ((var = n.removeFirstChild()) != null && !var.isGeneratorMarker()) {
          newVars.addChildToBack(var);
        }
        if (newVars.hasChildren()) {
          transpileUnmarkedNode(newVars);
          newVars = n.cloneNode();
        }

        // Transpile marked var
        if (var != null) {
          checkState(var.isGeneratorMarker());
          var.addChildToFront(maybeDecomposeExpression(var.removeFirstChild()));
          var.setGeneratorMarker(false);
          newVars.addChildToBack(var);
        }
      }

      // Flush the vars if not empty
      if (newVars.hasChildren()) {
        transpileUnmarkedNode(newVars);
      }
    }

    /** Transpiles marked "return" statement. */
    void transpileReturn(Node n) {
      n.addChildToFront(
          context.returnExpression(
              n, prepareNodeForWrite(maybeDecomposeExpression(n.removeFirstChild()))));
      context.writeGeneratedNode(n);
      context.currentCase.mayFallThrough = false;
    }

    /** Transpiles marked "throw" statement. */
    void transpileThrow(Node n) {
      n.addChildToFront(prepareNodeForWrite(maybeDecomposeExpression(n.removeFirstChild())));
      context.writeGeneratedNode(n);
      context.currentCase.mayFallThrough = false;
    }

    /** Exposes YIELD operator so it's free of side effects transpiling some code on the way. */
    Node exposeYieldAndTranspileRest(Node n) {
      checkState(n.isGeneratorMarker());
      if (n.isYield()) {
        return n;
      }

      // Assuming the initial node is "a + (a = b) + (b = yield) + a".

      // YieldExposer may break n up into multiple statements.
      // Place n into a temporary block to hold those statements:
      // {
      //   var JSCompiler_temp_const$jscomp$0 = a + (a = b);
      //   return JSCompiler_temp_const$jscomp$0 + (b = yield) + a;
      // }
      // Need to put expression nodes into return node so that they always stay expression nodes
      // If expression put into expression result YieldExposer may turn it into an "if" statement.
      boolean isExpression = IR.mayBeExpression(n);
      Node block = astFactory.createBlock(isExpression ? astFactory.createReturn(n) : n);
      NodeTraversal.traverse(compiler, n, new YieldExposer());
      // Make sure newly created statements are correctly marked for recursive transpileStatement()
      // calls.
      NodeTraversal.traverse(compiler, block, new YieldNodeMarker());

      // The last child of decomposed block free of side effects.
      Node decomposed = block.getLastChild().detach();
      transpileStatement(block);
      return isExpression ? decomposed.removeFirstChild() : decomposed;
    }

    /** Converts an expression node containing YIELD into an unmarked analogue. */
    Node maybeDecomposeExpression(@Nullable Node n) {
      if (n == null || !n.isGeneratorMarker()) {
        return n;
      }
      return transpileYields(exposeYieldAndTranspileRest(n));
    }

    /**
     * Makes unmarked node containing arbitary code suitable to write using {@link
     * TranspilationContext#writeGeneratedNode} method.
     */
    Node prepareNodeForWrite(@Nullable Node n) {
      if (n == null) {
        return null;
      }

      // Need to wrap a node so it can be replaced in the tree with some other node if nessesary.
      Node wrapper = IR.mayBeStatement(n) ? astFactory.createBlock(n) : astFactory.exprResult(n);
      NodeTraversal.traverse(compiler, wrapper, context.new UnmarkedNodeTranspiler());
      checkState(wrapper.hasOneChild());
      return wrapper.removeFirstChild();
    }

    /** Converts node with YIELD into $jscomp$generator$context.yieldResult. */
    Node transpileYields(Node n) {
      if (!n.isGeneratorMarker()) {
        // In some cases exposing yield causes it to disapear from the resulting statement.
        // I.e. the following node: "0 || yield;" becomes:
        // {
        //   var JSCompiler_temp$jscomp$0;
        //   if (JSCompiler_temp$jscomp$0 = 0); else JSCompiler_temp$jscomp$0 = yield;
        // }
        // JSCompiler_temp$jscomp$0; // This is our resulting statement.
        return n;
      }
      TranspilationContext.Case jumpToSection = context.createCase();
      Node yieldNode = findYield(n);
      Node yieldExpression =
          prepareNodeForWrite(maybeDecomposeExpression(yieldNode.removeFirstChild()));
      if (yieldNode.isYieldAll()) {
        context.yieldAll(yieldExpression, jumpToSection, yieldNode);
      } else {
        context.yield(yieldExpression, jumpToSection, yieldNode);
      }
      context.switchCaseTo(jumpToSection);
      Node yieldResult = context.yieldResult(yieldNode);
      if (yieldNode == n) {
        return yieldResult;
      }
      // Replace YIELD with $context.yeildResult
      yieldNode.replaceWith(yieldResult);
      // Remove generator markings from subtree
      while (yieldResult != n) {
        yieldResult = yieldResult.getParent();
        yieldResult.setGeneratorMarker(false);
      }
      return n;
    }

    /** Transpiles marked "if" stetement. */
    void transpileIf(Node n, @Nullable TranspilationContext.Case breakCase) {
      // Decompose condition first
      Node condition = maybeDecomposeExpression(n.removeFirstChild());
      Node ifBlock = n.getFirstChild();
      Node elseBlock = ifBlock.getNext();

      // No transpilation is needed
      if (!ifBlock.isGeneratorMarker() && (elseBlock == null || !elseBlock.isGeneratorMarker())) {
        n.addChildToFront(condition);
        n.setGeneratorMarker(false);
        transpileUnmarkedNode(n);
        return;
      }

      ifBlock.detach();
      if (elseBlock == null) {
        // No "else" block, just create an empty one as will need it anyway.
        elseBlock = astFactory.createBlock().srcref(n);
      } else {
        elseBlock.detach();
      }

      // Only "else" block is unmarked, swap "if" and "else" blocks and negate the condition.
      if (ifBlock.isGeneratorMarker() && !elseBlock.isGeneratorMarker()) {
        condition = astFactory.createNot(condition).srcref(condition);
        Node tmpNode = ifBlock;
        ifBlock = elseBlock;
        elseBlock = tmpNode;
      }

      // Unmarked "if" block (marked "else")
      if (!ifBlock.isGeneratorMarker()) {
        TranspilationContext.Case endCase = context.maybeCreateCase(breakCase);
        Node jumoToBlock = context.createJumpToBlock(endCase, /** allowEmbedding=*/ false, ifBlock);
        while (jumoToBlock.hasChildren()) {
          Node jumpToNode = jumoToBlock.removeFirstChild();
          jumpToNode.setGeneratorSafe(true);
          ifBlock.addChildToBack(jumpToNode);
        }
        transpileUnmarkedNode(astFactory.createIf(condition, ifBlock).srcref(n));
        transpileStatement(elseBlock);
        context.switchCaseTo(endCase);
        return;
      }

      TranspilationContext.Case ifCase = context.createCase();
      TranspilationContext.Case endCase = context.maybeCreateCase(breakCase);

      // "if" and "else" blocks marked
      condition = prepareNodeForWrite(condition);
      Node newIfBlock = context.createJumpToBlock(ifCase, /** allowEmbedding=*/ true, n);
      context.writeGeneratedNode(
          astFactory.createIf(prepareNodeForWrite(condition), newIfBlock).srcref(n));
      transpileStatement(elseBlock);
      context.writeJumpTo(endCase, elseBlock);
      context.switchCaseTo(ifCase);
      transpileStatement(ifBlock);
      context.switchCaseTo(endCase);
    }

    /** Transpiles marked "for" statement. */
    void transpileFor(
        Node n,
        @Nullable TranspilationContext.Case breakCase,
        @Nullable TranspilationContext.Case continueCase) {
      // Decompose init first
      Node init = maybeDecomposeExpression(n.removeFirstChild());
      Node condition = n.getFirstChild();
      Node increment = condition.getNext();
      Node body = increment.getNext();

      // No transpilation is needed
      if (!condition.isGeneratorMarker()
          && !increment.isGeneratorMarker()
          && !body.isGeneratorMarker()) {
        n.addChildToFront(init);
        n.setGeneratorMarker(false);
        transpileUnmarkedNode(n);
        return;
      }

      // Move init expression out of for loop.
      if (!init.isEmpty()) {
        if (IR.mayBeExpression(init)) {
          // Convert expression into expression result.
          init = astFactory.exprResult(init).srcref(init);
        }
        transpileUnmarkedNode(init);
      }

      TranspilationContext.Case startCase = context.createCase();
      TranspilationContext.Case incrementCase = context.maybeCreateCase(continueCase);
      TranspilationContext.Case endCase = context.maybeCreateCase(breakCase);

      context.switchCaseTo(startCase);

      // Transpile condition expression
      if (!condition.isEmpty()) {
        condition = prepareNodeForWrite(maybeDecomposeExpression(condition.detach()));
        context.writeGeneratedNode(
            astFactory
                .createIf(
                    astFactory.createNot(condition).srcref(condition),
                    context.createJumpToBlock(
                        endCase,
                        /** allowEmbedding= */
                        true,
                        n))
                .srcref(n));
      }

      // Transpile "for" body
      context.pushBreakContinueContext(endCase, incrementCase);
      transpileStatement(body.detach());
      context.popBreakContinueContext();

      // Transpile increment expression
      context.switchCaseTo(incrementCase);
      if (!increment.isEmpty()) {
        increment = maybeDecomposeExpression(increment.detach());
        transpileUnmarkedNode(astFactory.exprResult(increment).srcref(increment));
      }
      context.writeJumpTo(startCase, n);

      context.switchCaseTo(endCase);
    }

    /**
     * Transpile "for in" statement by converting it into "for".
     *
     *
for (var i in expr) {} will be converted into
     * for (var i, $for$in = $context.forIn(expr); i = $for$in.getNext(); ) {}
     */
    void transpileForIn(
        Node n,
        @Nullable TranspilationContext.Case breakCase,
        @Nullable TranspilationContext.Case continueCase) {
      // Decompose condition first
      Node detachedExpr = maybeDecomposeExpression(n.getSecondChild().detach());
      Node target = n.getFirstChild();
      Node body = n.getSecondChild();

      // No transpilation is needed
      if (!target.isGeneratorMarker() && !body.isGeneratorMarker()) {
        detachedExpr.insertAfter(target);
        n.setGeneratorMarker(false);
        transpileUnmarkedNode(n);
        return;
      }

      // Prepare a new init statement
      final Node init;
      if (target.detach().isVar()) {
        // "var i in x"    =>   "var i"
        checkState(!target.isGeneratorMarker());
        init = target;
        checkState(!init.getFirstChild().hasChildren());
        target = init.getFirstChild().cloneNode();
      } else {
        // "i in x"   =>    "var"
        init = new Node(Token.VAR).srcref(target);
      }

      // "$for$in"
      Node child =
          context.callContextMethod(target, "forIn", propertyIteratorType.get(), detachedExpr);
      Node forIn =
          astFactory
              .createName(
                  context.getScopedName(
                      GENERATOR_FORIN_PREFIX + compiler.getUniqueNameIdSupplier().get()),
                  type(child))
              .srcref(target);
      // "$context.forIn(x)"
      forIn.addChildToFront(child);
      // "var ..., $for$in = $context.forIn(expr)"
      init.addChildToBack(forIn);

      // "$for$in.getNext()"
      Node forInGetNext =
          astFactory
              .createGetProp(forIn.cloneNode(), "getNext", type(StandardColors.TOP_OBJECT))
              .srcref(detachedExpr);

      // "(i = $for$in.getNext()) != null"
      Node forCond =
          astFactory
              .createNe(
                  astFactory
                      .createAssign(
                          target.setColor(nullableStringType),
                          astFactory
                              .createCall(forInGetNext, type(nullableStringType))
                              .srcref(detachedExpr))
                      .srcref(detachedExpr),
                  astFactory.createNull().srcref(forIn))
              .srcref(detachedExpr);
      forCond.setGeneratorMarker(target.isGeneratorMarker());

      // Prepare "for" statement.
      // "for (var i, $for$in = $context.forIn(expr); (i = $for$in.getNext()) != null; ) {}"
      Node forNode = IR.forNode(init, forCond, IR.empty().srcref(n), body.detach()).srcref(n);

      // Transpile "for" instead of "for in".
      transpileFor(forNode, breakCase, continueCase);
    }

    /** Transpiles "while" statement. */
    void transpileWhile(
        Node n,
        @Nullable TranspilationContext.Case breakCase,
        @Nullable TranspilationContext.Case continueCase) {
      TranspilationContext.Case startCase = context.maybeCreateCase(continueCase);
      TranspilationContext.Case endCase = context.maybeCreateCase(breakCase);

      context.switchCaseTo(startCase);

      // Transpile condition
      Node condition = prepareNodeForWrite(maybeDecomposeExpression(n.removeFirstChild()));
      Node body = n.removeFirstChild();
      context.writeGeneratedNode(
          astFactory
              .createIf(
                  astFactory.createNot(condition).srcref(condition),
                  context.createJumpToBlock(
                      endCase,
                      /** allowEmbedding= */
                      true,
                      n))
              .srcref(n));

      // Transpile "while" body
      context.pushBreakContinueContext(endCase, startCase);
      transpileStatement(body);
      context.popBreakContinueContext();
      context.writeJumpTo(startCase, n);

      context.switchCaseTo(endCase);
    }

    /** Transpiles "do while" statement. */
    void transpileDo(
        Node n,
        @Nullable TranspilationContext.Case breakCase,
        @Nullable TranspilationContext.Case continueCase) {
      TranspilationContext.Case startCase = context.createCase();
      breakCase = context.maybeCreateCase(breakCase);
      continueCase = context.maybeCreateCase(continueCase);

      context.switchCaseTo(startCase);

      // Transpile body
      Node body = n.removeFirstChild();
      context.pushBreakContinueContext(breakCase, continueCase);
      transpileStatement(body);
      context.popBreakContinueContext();

      // Transpile condition
      context.switchCaseTo(continueCase);
      Node condition = prepareNodeForWrite(maybeDecomposeExpression(n.removeFirstChild()));
      context.writeGeneratedNode(
          astFactory
              .createIf(
                  condition,
                  context.createJumpToBlock(
                      startCase,
                      /** allowEmbedding= */
                      false,
                      n))
              .srcref(n));
      context.switchCaseTo(breakCase);
    }

    /** Transpiles "try" statement */
    void transpileTry(Node n, @Nullable TranspilationContext.Case breakCase) {
      Node tryBlock = n.removeFirstChild();
      Node catchBlock = n.removeFirstChild();
      Node finallyBlock = n.removeFirstChild();

      TranspilationContext.Case catchCase = catchBlock.hasChildren() ? context.createCase() : null;
      TranspilationContext.Case finallyCase = finallyBlock == null ? null : context.createCase();
      TranspilationContext.Case endCase = context.maybeCreateCase(breakCase);

      // Transpile "try" block
      context.enterTryBlock(catchCase, finallyCase, tryBlock);
      transpileStatement(tryBlock);

      if (finallyBlock == null) {
        context.leaveTryBlock(catchCase, endCase, tryBlock);
      } else {
        // Transpile "finally" block
        context.switchCaseTo(finallyCase);
        context.enterFinallyBlock(catchCase, finallyCase, finallyBlock);
        transpileStatement(finallyBlock);
        context.leaveFinallyBlock(endCase, finallyBlock);
      }

      // Transpile "catch" block
      if (catchBlock.hasChildren()) {
        checkState(catchBlock.getFirstChild().isCatch());

        context.switchCaseTo(catchCase);
        Node exceptionName = catchBlock.getFirstFirstChild().detach();
        context.enterCatchBlock(finallyCase, exceptionName);

        Node catchBody = catchBlock.getFirstFirstChild().detach();
        checkState(catchBody.isBlock());
        transpileStatement(catchBody);
        context.leaveCatchBlock(finallyCase, catchBody);
      }

      context.switchCaseTo(endCase);
    }

    // Transpiles "switch" statement.
    void transpileSwitch(Node n, @Nullable TranspilationContext.Case breakCase) {
      // Transpile condition first
      n.addChildToFront(maybeDecomposeExpression(n.removeFirstChild()));

      // Are all "switch" cases unmarked?
      boolean hasGeneratorMarker = false;
      for (Node caseSection = n.getSecondChild();
          caseSection != null;
          caseSection = caseSection.getNext()) {
        if (caseSection.isGeneratorMarker()) {
          hasGeneratorMarker = true;
          break;
        }
      }
      // No transpilation is needed
      if (!hasGeneratorMarker) {
        n.setGeneratorMarker(false);
        transpileUnmarkedNode(n);
        return;
      }

      /** Stores a detached body of a case statement and a case section assosiated with it. */
      class SwitchCase {
        private final TranspilationContext.Case generatedCase;
        private final Node body;

        SwitchCase(TranspilationContext.Case generatedCase, Node caseNode) {
          this.generatedCase = generatedCase;
          this.body = caseNode;
        }
      }

      // TODO(skill): Don't move all case sections.
      ArrayList<SwitchCase> detachedCases = new ArrayList<>();

      // We don't have to transpile unmarked cases at the beginning of "switch".
      boolean canSkipUnmarkedCases = true;
      for (Node caseSection = n.getSecondChild();
          caseSection != null;
          caseSection = caseSection.getNext()) {
        if (!caseSection.isDefaultCase() && caseSection.getFirstChild().isGeneratorMarker()) {
          // Following example is possible to transpile, but it's not trivial.
          // switch (cond) {
          //   case yield "test": break;
          //   case 5 + yield:  break;
          // }
          compiler.report(
              JSError.make(
                  n, Es6ToEs3Util.CANNOT_CONVERT_YET, "Case statements that contain yields"));
          return;
        }
        Node body = caseSection.getLastChild();

        if (!body.hasChildren() || (canSkipUnmarkedCases && !body.isGeneratorMarker())) {
          // Can skip empty or unmarked case.
          continue;
        }

        // Check whether we can start skipping unmarked cases again
        canSkipUnmarkedCases = isEndOfBlockUnreachable(body);

        // Move case's body under a global switch statement...

        // Allocate a new case
        TranspilationContext.Case generatedCase = context.createCase();
        generatedCase.caseBlock.srcref(body);

        // Replace old body with a jump instruction.
        Node newBody = IR.block(context.createJumpToNode(generatedCase, body));
        newBody.setIsAddedBlock(true);
        newBody.setGeneratorSafe(true); // make sure we don't transpile generated "jump" instruction
        body.replaceWith(newBody);

        // Remember the body and the case under which the body will be moved.
        detachedCases.add(new SwitchCase(generatedCase, body));

        caseSection.setGeneratorMarker(false);
      }

      TranspilationContext.Case endCase = context.maybeCreateCase(breakCase);

      // Transpile the barebone of original "switch" statement
      n.setGeneratorMarker(false);
      transpileUnmarkedNode(n);
      context.writeJumpTo(endCase, n); // TODO(skill): do not always add this.

      // Transpile all detached case bodies
      context.pushBreakContext(endCase);
      for (SwitchCase detachedCase : detachedCases) {
        TranspilationContext.Case generatedCase = detachedCase.generatedCase;
        context.switchCaseTo(generatedCase);
        transpileStatement(detachedCase.body);
      }
      context.popBreakContext();

      context.switchCaseTo(endCase);
    }

    /** Finds the only YIELD node in a tree. */
    Node findYield(Node n) {
      YieldFinder yieldFinder = new YieldFinder();
      NodeTraversal.traverse(compiler, n, yieldFinder);
      return yieldFinder.getYieldNode();
    }

    /** Finds the only YIELD node in a tree. */
    private class YieldFinder extends NodeTraversal.AbstractPreOrderCallback {

      private Node yieldNode;

      @Override
      public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
        if (n.isFunction()) {
          return false;
        }
        if (n.isYield()) {
          checkState(yieldNode == null);
          yieldNode = n;
          return false;
        }
        return true;
      }

      Node getYieldNode() {
        checkNotNull(yieldNode);
        return yieldNode;
      }
    }

    /**
     * Returns whether any statements added to the end of the block would be unreachable.
     *
     *
It's OK for this method to return false-negatives.
     */
    private boolean isEndOfBlockUnreachable(Node block) {
      checkState(block.isBlock());
      if (!block.hasChildren()) {
        return false;
      }
      switch (block.getLastChild().getToken()) {
        case BLOCK:
          return isEndOfBlockUnreachable(block.getLastChild());

        case RETURN:
        case THROW:
        case CONTINUE:
        case BREAK:
          return true;

        default:
          return false;
      }
    }

    /** State machine context that is used during generator function transpilation. */
    private class TranspilationContext {
      final HashMap<String, LabelCases> namedLabels = new HashMap<>();
      final ArrayDeque<Case> breakCases = new ArrayDeque<>();
      final ArrayDeque<Case> continueCases = new ArrayDeque<>();

      final ArrayDeque<CatchCase> catchCases = new ArrayDeque<>();
      final ArrayDeque<Case> finallyCases = new ArrayDeque<>();
      final HashSet<String> catchNames = new HashSet<>();

      /** All "case" sections that will be added to generator program. */
      final ArrayList<Case> allCases = new ArrayList<>();

      /** All "break" nodes that exit from the generator primary switch statement */
      final ArrayList<Node> switchBreaks = new ArrayList<>();

      /** A virtual case that indicates end of program */
      final Case programEndCase;

      /** Most recently assigned id. */
      int caseIdCounter;

      /**
       * Points to the switch case that is being populated with transpiled instructions from the
       * original generator function that is being transpiled.
       */
      Case currentCase;

      int nestedFinallyBlockCount = 0;

      boolean thisReferenceFound;
      boolean argumentsReferenceFound;

      TranspilationContext() {
        programEndCase = new Case();
        checkState(programEndCase.id == 0);
        currentCase = new Case();
        checkState(currentCase.id == 1);
        allCases.add(currentCase);
      }

      /**
       * Removes unnesesary cases.
       *
       *
This optimization is needed to reduce number of switch cases, which is used then to
       * generate even shorter state machine programs.
       */
      void optimizeCaseIds() {
        checkState(!allCases.isEmpty(), allCases);

        // Shortcut jump chains:
        //   case 100:
        //     $context.yield("something", 101);
        //     break;
        //   case 101:
        //     $context.jumpTo(102);
        //     break;
        //   case 102:
        //     $context.jumpTo(200);
        //     break;
        // becomes:
        //   case 100:
        //     $context.yield("something", 200);
        //     break;
        //   case 101:
        //     $context.jumpTo(102);
        //     break;
        //   case 102:
        //     $context.jumpTo(200);
        //     break;
        for (Case currentCase : allCases) {
          if (currentCase.jumpTo != null) {
            // Flatten jumps chains:
            // 1 -> 2
            // 2 -> 8
            // 8 -> 300
            // to:
            // 1 -> 300
            // 2 -> 300
            // 8 -> 300
            while (currentCase.jumpTo.jumpTo != null) {
              currentCase.jumpTo = currentCase.jumpTo.jumpTo;
            }

            if (currentCase.embedInto != null && currentCase.references.size() == 1) {
              currentCase.jumpTo.embedInto = currentCase.embedInto;
            }
            currentCase.embedInto = null;

            // Update references to jump to the final case in the chain
            for (Node reference : currentCase.references) {
              reference.setDouble(currentCase.jumpTo.id);
            }
            currentCase.jumpTo.references.addAll(currentCase.references);
            currentCase.references.clear();
          }
        }

        // Merge cases without any references with the previous case:
        //   case 100:
        //     doSomething();
        //   case 101:
        //     doSomethingElse();
        //     break;
        //   case 102:
        //     doEvenMore();
        // becomes:
        //   case 100:
        //     doSomething();
        //     doSomethingElse();
        //     break;
        Iterator<Case> it = allCases.iterator();
        Case prevCase = it.next();
        checkState(prevCase.id == 1);
        while (it.hasNext()) {
          Case currentCase = it.next();
          if (currentCase.references.isEmpty()) {
            // No jump references, just append the body to a previous case if needed.
            checkState(currentCase.embedInto == null);
            if (prevCase.mayFallThrough) {
              prevCase.caseBlock.addChildrenToBack(currentCase.caseBlock.removeChildren());
              prevCase.mayFallThrough = currentCase.mayFallThrough;
            }
            it.remove();
            continue;
          }
          if (currentCase.embedInto != null) {
            checkState(currentCase.jumpTo == null);
            // Cases can be embedded only if they are referenced once and don't fall through.
            if (currentCase.references.size() == 1 && !currentCase.mayFallThrough) {
              currentCase.embedInto.replaceWith(currentCase.caseBlock);
              it.remove();
              continue;
            }
          }
          if (prevCase.jumpTo == currentCase) {
            // Merging "case 1:" with the following case. The standard merging cannot be used
            // as "case 1:" is an entry point and it cannot be renamed.
            //   case 1:
            //   case 2:
            //     doSomethingElse();
            //     break;
            //   case 102:
            //     $context.jumpTo(2);
            //     break;
            // becomes:
            //   case 1:
            //     doSomethingElse();
            //     break;
            //   case 102:
            //     $context.jumpTo(1);
            //     break;
            checkState(prevCase.mayFallThrough);
            checkState(!prevCase.caseBlock.hasChildren());
            checkState(currentCase.jumpTo == null);

            prevCase.caseBlock.addChildrenToBack(currentCase.caseBlock.removeChildren());
            prevCase.mayFallThrough = currentCase.mayFallThrough;
            for (Node reference : currentCase.references) {
              reference.setDouble(prevCase.id);
            }
            prevCase.jumpTo = currentCase.jumpTo;
            prevCase.references.addAll(currentCase.references);
            it.remove();
            continue;
          }
          prevCase = currentCase;
        }
      }

      /**
       * Replaces "...; break;" with "return ...;".
       */
      void eliminateSwitchBreaks() {
        for (Node breakNode : switchBreaks) {
          Node prevStatement = breakNode.getPrevious();
          checkState(prevStatement != null);
          checkState(prevStatement.isExprResult());
          prevStatement.replaceWith(IR.returnNode(prevStatement.removeFirstChild()));
          breakNode.detach();
        }
        switchBreaks.clear();
      }

      /** Finalizes transpilation by dumping all generated "case" nodes. */
      public void finalizeTransformation(Node generatorBody) {
        optimizeCaseIds();

        // If number of cases is small we render them without using "switch"
        //   switch ($context.nextAddress) {
        //     case 1: a();
        //     case 2: b();
        //     case 3: c();
        //   }
        // are rendered as:
        //   if ($context.nextAddress == 1) a();
        //   if ($context.nextAddress != 3) b();
        //   c();
        if (allCases.size() == 2 || allCases.size() == 3) {
          generatorBody.addChildToBack(
              astFactory
                  .createIf(
                      astFactory.createEq(
                          getContextField(generatorBody, "nextAddress"),
                          astFactory.createNumber(1)),
                      allCases.remove(0).caseBlock)
                  .srcrefTreeIfMissing(generatorBody));
        }

        // If number of cases is small we render them without using "switch"
        //   switch ($context.nextAddress) {
        //     case 1: a();
        //     case 2: b();
        //   }
        // are rendered as:
        //   if ($context.nextAddress == 1) a();
        //   b();
        if (allCases.size() == 2) {
          generatorBody.addChildToBack(
              astFactory
                  .createIf(
                      astFactory.createNe(
                          getContextField(generatorBody, "nextAddress"),
                          astFactory.createNumber(allCases.get(1).id)),
                      allCases.remove(0).caseBlock)
                  .srcrefTreeIfMissing(generatorBody));
        }

        // If number of cases is small we render them without using "switch"
        //   switch ($context.nextAddress) {
        //     case 1: a();
        //   }
        // are rendered as:
        //   a();
        if (allCases.size() == 1) {
          generatorBody.addChildrenToBack(allCases.remove(0).caseBlock.removeChildren());
          eliminateSwitchBreaks();
          return;
        }

        //  switch ($jscomp$generator$context.nextAddress) {}
        Node switchNode =
            IR.switchNode(getContextField(generatorBody, "nextAddress")).srcref(generatorBody);
        generatorBody.addChildToBack(switchNode);

        // Populate "switch" statement with "case"s.
        for (Case currentCase : allCases) {
          switchNode.addChildToBack(currentCase.createCaseNode());
        }
        allCases.clear();
      }

      /** Ensures that the context has an empty state. */
      public void checkStateIsEmpty() {
        checkState(namedLabels.isEmpty());
        checkState(breakCases.isEmpty());
        checkState(continueCases.isEmpty());
        checkState(catchCases.isEmpty());
        checkState(finallyCases.isEmpty());
        checkState(nestedFinallyBlockCount == 0);
        checkState(allCases.isEmpty());
      }

      /** Adds a block of original code to the end of the current case. */
      void transpileUnmarkedBlock(Node block) {
        if (block.hasChildren()) {
          NodeTraversal.traverse(compiler, block, new UnmarkedNodeTranspiler());
          while (block.hasChildren()) {
            writeGeneratedNode(block.removeFirstChild());
          }
        }
      }

      /** Adds a new generated node to the end of the current case. */
      void writeGeneratedNode(Node n) {
        currentCase.addNode(n);
      }

      /** Adds a new generated node to the end of the current case and finializes it. */
      void writeGeneratedNodeAndBreak(Node n) {
        writeGeneratedNode(n);
        writeGeneratedNode(createBreakNodeFor(n));
        currentCase.mayFallThrough = false;
      }

      /** Creates a new detached case statement. */
      Case createCase() {
        return new Case();
      }

      /** Returns a passed case object or creates a new one if it's null. */
      Case maybeCreateCase(@Nullable Case other) {
        if (other != null) {
          return other;
        }
        return createCase();
      }

      /** Returns the name node of context parameter passed to the program. */
      Node getJsContextNameNode(Node sourceNode) {
        return astFactory
            .createName(getScopedName(GENERATOR_CONTEXT), generatorContextType.get())
            .srcref(sourceNode);
      }

      /** Returns unique name in the current context. */
      String getScopedName(String name) {
        return name + (generatorNestingLevel == 0 ? "" : "$" + generatorNestingLevel);
      }

      /** Creates node that access a specified field of the current context. */
      Node getContextField(Node sourceNode, String fieldName) {
        return astFactory
            .createGetPropWithUnknownType(getJsContextNameNode(sourceNode), fieldName)
            .srcref(sourceNode);
      }

      /** Creates node that make a call to a context function. */
      Node callContextMethod(
          Node sourceNode, String methodName, AstFactory.Type type, Node... args) {
        Node contextField = getContextField(sourceNode, methodName);
        return astFactory.createCall(contextField, type, args).srcref(sourceNode);
      }

      /** Creates node that make a call to a context function. */
      Node callContextMethodResult(
          Node sourceNode, String methodName, AstFactory.Type type, Node... args) {
        return astFactory
            .exprResult(callContextMethod(sourceNode, methodName, type, args))
            .srcref(sourceNode);
      }

      /** Creates node that returns the result of a call to a context function. */
      Node returnContextMethod(
          Node sourceNode, String methodName, AstFactory.Type type, Node... args) {
        return astFactory
            .createReturn(callContextMethod(sourceNode, methodName, type, args))
            .srcref(sourceNode);
      }

      /**
       * Creates a "break;" statement that will follow {@code preBreak} node.
       *
       *
This is used to be able to generatate a state machine program outside of "swtich"
       * statement so:
       *
       *

       *   $context.jumpTo(5);
       *   break;
       *

       *
       * could be converted into:
       *
       *

       *   return $context.jumpTo(5);
       *

       */
      Node createBreakNodeFor(Node preBreak) {
        Node breakNode = IR.breakNode().srcref(preBreak);
        switchBreaks.add(breakNode);
        return breakNode;
      }

      /**
       * Returns a node that instructs a state machine program to jump to a selected case section.
       */
      Node createJumpToNode(Case section, Node sourceNode) {
        return returnContextMethod(
            sourceNode, "jumpTo", type(StandardColors.NULL_OR_VOID), section.getNumber(sourceNode));
      }

      /** Instructs a state machine program to jump to a selected case section. */
      void writeJumpTo(Case section, Node sourceNode) {
        currentCase.jumpTo(
            section, createJumpToBlock(section, /** allowEmbedding=*/ false, sourceNode));
      }

      /**
       * Creates a block node that contains a jump instruction.
       *
       * @param allowEmbedding Whether the code from the target section can be embedded into jump
       *     block.
       */
      Node createJumpToBlock(Case section, boolean allowEmbedding, Node sourceNode) {
        checkState(section.embedInto == null);
        Node jumpBlock =
            astFactory
                .createBlock(
                    callContextMethodResult(
                        sourceNode,
                        "jumpTo",
                        type(StandardColors.NULL_OR_VOID),
                        section.getNumber(sourceNode)),
                    createBreakNodeFor(sourceNode))
                .srcref(sourceNode);
        if (allowEmbedding) {
          section.embedInto = jumpBlock;
        }
        return jumpBlock;
      }

      /** Converts "break" and "continue" statements into state machine jumps. */
      void replaceBreakContinueWithJump(Node sourceNode, Case section, int breakSuppressors) {
        final String jumpMethod;
        if (finallyCases.isEmpty() || finallyCases.getFirst().id
  $context.jumpTo(x); break;
          callContextMethodResult(
                  sourceNode,
                  jumpMethod,
                  type(StandardColors.NULL_OR_VOID),
                  section.getNumber(sourceNode))
              .insertBefore(sourceNode);
          sourceNode.replaceWith(createBreakNodeFor(sourceNode));
        } else {
          // "break;" inside a loop or swtich statement:
          // for (...) {
          //   break l1;
          // }
          // becomes:
          // for (...) {                  // loop doesn't allow to use "break" to advance to the
          //   return $context.jumpTo(x); // next address, so "return" is used instead.
          // }
          sourceNode.replaceWith(
              returnContextMethod(
                  sourceNode,
                  jumpMethod,
                  type(StandardColors.NULL_OR_VOID),
                  section.getNumber(sourceNode)));
        }
      }

      /**
       * Instructs a state machine program to yield a value and then jump to a selected case
       * section.
       */
      void yield(
          @Nullable Node expression, TranspilationContext.Case jumpToSection, Node sourceNode) {
        ArrayList<Node> args = new ArrayList<>();
        args.add(
            expression == null
                ? astFactory.createUndefinedValue().srcrefTree(sourceNode)
                : expression);
        args.add(jumpToSection.getNumber(sourceNode));
        context.writeGeneratedNode(
            returnContextMethod(
                sourceNode, "yield", type(StandardColors.UNKNOWN), args.toArray(new Node[0])));
        context.currentCase.mayFallThrough = false;
      }

      /**
       * Instructs a state machine program to yield all values and then jump to a selected case
       * section.
       */
      void yieldAll(Node expression, TranspilationContext.Case jumpToSection, Node sourceNode) {
        writeGeneratedNode(
            returnContextMethod(
                sourceNode,
                "yieldAll",
                type(StandardColors.UNKNOWN),
                expression,
                jumpToSection.getNumber(sourceNode)));
        context.currentCase.mayFallThrough = false;
      }

      /** Instructs a state machine program to return a given expression. */
      Node returnExpression(Node sourceNode, @Nullable Node expression) {
        if (expression == null) {
          return callContextMethod(sourceNode, "return", type(StandardColors.NULL_OR_VOID));
        }
        return callContextMethod(
            sourceNode, "return", type(StandardColors.NULL_OR_VOID), expression);
      }

      /** Instructs a state machine program to consume a yield result after yielding. */
      Node yieldResult(Node sourceNode) {
        return getContextField(sourceNode, "yieldResult");
      }

      /** Adds references to catch and finally blocks to the transpilation context. */
      private void addCatchFinallyCases(@Nullable Case catchCase, @Nullable Case finallyCase) {
        if (finallyCase != null) {
          if (!catchCases.isEmpty()) {
            ++catchCases.getFirst().finallyBlocks;
          }
          finallyCases.addFirst(finallyCase);
        }
        if (catchCase != null) {
          catchCases.addFirst(new CatchCase(catchCase));
        }
      }

      /** Returns the case section of the next catch block that is not hidden by finally blocks. */
      @Nullable
      private Case getNextCatchCase() {
        for (CatchCase catchCase : catchCases) {
          if (catchCase.finallyBlocks == 0) {
            return catchCase.catchCase;
          }
          break;
        }
        return null;
      }

      /** Returns the case section of the next finally block. */
      @Nullable
      private Case getNextFinallyCase() {
        return finallyCases.isEmpty() ? null : finallyCases.getFirst();
      }

      /** Removes references to catch and finally blocks from the transpilation context. */
      private void removeCatchFinallyCases(@Nullable Case catchCase, @Nullable Case finallyCase) {
        if (catchCase != null) {
          CatchCase lastCatch = catchCases.removeFirst();
          checkState(lastCatch.finallyBlocks == 0);
          checkState(lastCatch.catchCase == catchCase);
        }
        if (finallyCase != null) {
          if (!catchCases.isEmpty()) {
            int finallyBlocks = --catchCases.getFirst().finallyBlocks;
            checkState(finallyBlocks >= 0);
          }
          Case lastFinally = finallyCases.removeFirst();
          checkState(lastFinally == finallyCase);
        }
      }

      /** Writes a statement Node that should be placed at the beginning of try block. */
      void enterTryBlock(@Nullable Case catchCase, @Nullable Case finallyCase, Node sourceNode) {
        addCatchFinallyCases(catchCase, finallyCase);

        final String methodName;
        ArrayList<Node> args = new ArrayList<>();
        if (catchCase == null) {
          methodName = "setFinallyBlock";
          args.add(finallyCase.getNumber(sourceNode));
        } else {
          methodName = "setCatchFinallyBlocks";
          args.add(catchCase.getNumber(sourceNode));
          if (finallyCase != null) {
            args.add(finallyCase.getNumber(sourceNode));
          }
        }
        writeGeneratedNode(
            callContextMethodResult(
                sourceNode,
                methodName,
                type(StandardColors.NULL_OR_VOID),
                args.toArray(new Node[0])));
      }

      /**
       * Writes a statements that should be placed at the end of try block if finally block is not
       * present.
       */
      void leaveTryBlock(@Nullable Case catchCase, Case endCase, Node sourceNode) {
        removeCatchFinallyCases(catchCase, null);
        ArrayList<Node> args = new ArrayList<>();
        args.add(endCase.getNumber(sourceNode));
        // Find the next catch block that is not hidden by any finally blocks.
        Case nextCatchCase = getNextCatchCase();
        if (nextCatchCase != null) {
          args.add(nextCatchCase.getNumber(sourceNode));
        }
        writeGeneratedNodeAndBreak(
            callContextMethodResult(
                sourceNode,
                "leaveTryBlock",
                type(StandardColors.NULL_OR_VOID),
                args.toArray(new Node[0])));
      }

      /** Writes a statement Node that should be placed at the beginning of catch block. */
      void enterCatchBlock(@Nullable Case finallyCase, Node exceptionName) {
        checkState(exceptionName.isName());
        addCatchFinallyCases(null, finallyCase);

        // Find the next catch block that is not hidden by any finally blocks.
        Case nextCatchCase = getNextCatchCase();

        if (catchNames.add(exceptionName.getString())) {
          hoistNode(IR.var(exceptionName.cloneNode()).srcref(exceptionName));
        }

        ArrayList<Node> args = new ArrayList<>();
        if (nextCatchCase != null) {
          args.add(nextCatchCase.getNumber(exceptionName));
        }

        Node enterCatchBlockCall =
            callContextMethod(
                exceptionName,
                "enterCatchBlock",
                type(StandardColors.UNKNOWN),
                args.toArray(new Node[0]));
        exceptionName.setColor(enterCatchBlockCall.getColor());
        writeGeneratedNode(
            astFactory
                .exprResult(
                    astFactory
                        .createAssign(exceptionName, enterCatchBlockCall)
                        .srcref(exceptionName))
                .srcref(exceptionName));
      }

      /** Writes a statement to jump to the finally block if it's present. */
      void leaveCatchBlock(@Nullable Case finallyCase, Node sourceNode) {
        if (finallyCase != null) {
          removeCatchFinallyCases(null, finallyCase);
          writeJumpTo(finallyCase, sourceNode);
        }
      }

      /** Writes a Node that should be placed at the beginning of finally block. */
      void enterFinallyBlock(
          @Nullable Case catchCase, @Nullable Case finallyCase, Node sourceNode) {
        removeCatchFinallyCases(catchCase, finallyCase);

        Case nextCatchCase = getNextCatchCase();
        Case nextFinallyCase = getNextFinallyCase();

        ArrayList<Node> args = new ArrayList<>();
        if (nestedFinallyBlockCount == 0) {
          if (nextCatchCase != null || nextFinallyCase != null) {
            args.add(
                nextCatchCase == null
                    ? astFactory.createNumber(0).srcref(sourceNode)
                    : nextCatchCase.getNumber(sourceNode));
            if (nextFinallyCase != null) {
              args.add(nextFinallyCase.getNumber(sourceNode));
            }
          }
        } else {
          args.add(
              nextCatchCase == null
                  ? astFactory.createNumber(0).srcref(sourceNode)
                  : nextCatchCase.getNumber(sourceNode));
          args.add(
              nextFinallyCase == null
                  ? astFactory.createNumber(0).srcref(sourceNode)
                  : nextFinallyCase.getNumber(sourceNode));
          args.add(astFactory.createNumber(nestedFinallyBlockCount).srcref(sourceNode));
        }

        writeGeneratedNode(
            callContextMethodResult(
                sourceNode,
                "enterFinallyBlock",
                type(StandardColors.NULL_OR_VOID),
                args.toArray(new Node[0])));

        ++nestedFinallyBlockCount;
      }

      /** Writes a Node that should be placed at the end of finally block. */
      void leaveFinallyBlock(Case endCase, Node sourceNode) {
        ArrayList<Node> args = new ArrayList<>();
        args.add(endCase.getNumber(sourceNode));
        if (--nestedFinallyBlockCount != 0) {
          args.add(astFactory.createNumber(nestedFinallyBlockCount).srcref(sourceNode));
        }

        writeGeneratedNodeAndBreak(
            callContextMethodResult(
                sourceNode,
                "leaveFinallyBlock",
                type(StandardColors.NULL_OR_VOID),
                args.toArray(new Node[0])));
      }

      /** Changes the {@link #currentCase} to a new one. */
      void switchCaseTo(Case caseSection) {
        currentCase.willFollowBy(caseSection);
        allCases.add(caseSection);
        currentCase = caseSection;
      }

      /** Adds a named labels to the context. */
      public void pushLabels(
          ArrayList<Node> labelNames, Case breakCase, @Nullable Case continueCase) {
        for (Node labelName : labelNames) {
          checkState(labelName.isLabelName());
          namedLabels.put(labelName.getString(), new LabelCases(breakCase, continueCase));
        }
      }

      /** Removes the named labels from the context. */
      public void popLabels(ArrayList<Node> labelNames) {
        for (Node labelName : labelNames) {
          checkState(labelName.isLabelName());
          namedLabels.remove(labelName.getString());
        }
      }

      /** Adds "break" jump point to the context */
      public void pushBreakContext(Case breakCase) {
        breakCases.push(breakCase);
      }

      /** Adds "break" and "continue" jump points to the context */
      public void pushBreakContinueContext(Case breakCase, Case continueCase) {
        pushBreakContext(breakCase);
        continueCases.push(continueCase);
      }

      /** Removes "break" jump point from the context, restoring the previous one */
      public void popBreakContext() {
        breakCases.pop();
      }

      /**
       * Removes "break" and "continue" jump points from the context, restoring the previous ones.
       */
      public void popBreakContinueContext() {
        popBreakContext();
        continueCases.pop();
      }

      /** A case section in a switch block of generator program. */
      private class Case {
        final int id;
        final Node caseBlock;

        /**
         * Records number of times the section was referenced.
         *
         *
It's used to drop unreferenced sections.
         */
        final ArrayList<Node> references = new ArrayList<>();

        /**
         * Indicates that this case is a simple jump or a fall-though case. Points to the target
         * case.
         */
        @Nullable Case jumpTo;

        /**
         * Indicates that the body of this case could potentially be embedded into another block
         * node.
         *
         *
Usually "if (a) {b();} else { c(); }" is transpiled into:
         *

         *   if (a) { goto labelIf; }
         *   c();
         *   goto labelEnd;
         * labelIf:
         *   b();
         * labelEnd:
         *

         *
         * but "labelIf: b();" can be inlined to get shorter output:
         *
         *

         *   if (a) { b(); goto labelEnd; }
         *   c();
         * labelEnd:
         *

         *
         * In this example "labelIf" case can be embedded into "{ goto labelIf; }"
         * block.
         */
        @Nullable Node embedInto;

        /** Tells whether this case might fall-through. */
        boolean mayFallThrough = true;

        /** Creates a new empty case section and assings a new id. */
        Case() {
          this.id = caseIdCounter++;
          this.caseBlock = astFactory.createBlock().srcref(originalGeneratorBody);
        }

        Node createCaseNode() {
          return IR.caseNode(astFactory.createNumber(id).srcref(caseBlock), caseBlock)
              .srcref(caseBlock);
        }

        /** Returns the number node of the case section and increments a reference counter. */
        Node getNumber(Node sourceNode) {
          if (jumpTo != null) {
            return jumpTo.getNumber(sourceNode);
          }
          Node node = astFactory.createNumber(id).srcref(sourceNode);
          references.add(node);
          return node;
        }

        /**
         * Finalizes the case section with a jump instruction.
         *
         *
{@link #addNode} cannot be invoked after this method is called.
         */
        void jumpTo(Case other, Node jumpBlock) {
          checkState(jumpBlock.isBlock());
          checkState(jumpTo == null);
          willFollowBy(other);
          caseBlock.addChildrenToBack(jumpBlock.removeChildren());
          mayFallThrough = false;
        }

        /**
         * Informs which other case will be executed after this one.
         *
         *
It's used to detect and then eliminate case statements that are used as simple jump
         * hops:
         *
         *

         *  case 100:
         *    $context.jumpTo(200);
         *    break;
         *

         *
         * or
         *
         *

         *  case 300:
         *

         */
        void willFollowBy(Case other) {
          if (jumpTo == null && !caseBlock.hasChildren()) {
            checkState(other.jumpTo == null);
            jumpTo = other;
          }
        }

        /** Adds a new node to the end of the case block. */
        void addNode(Node n) {
          checkState(jumpTo == null);
          checkState(IR.mayBeStatement(n));
          caseBlock.addChildToBack(n);
        }
      }

      /**
       * Adjust YIELD-free nodes to run correctly inside a state machine program.
       *
       *
The following transformations are performed:
       *
       *

       *
moving var into hois scope;
       *
transpiling return statements;
       *
transpiling break and continue statements;
       *
transpiling references to this and arguments.
       *

       */
      private class UnmarkedNodeTranspiler implements NodeTraversal.Callback {

        // Count the number of enclosing statements that a bare break could address.
        // A value > 0 means that a bare break statement we encounter can be left unmodified,
        // since it addresses a statement within the node we are transpiling.
        int breakSuppressors;
        // Same as breakSuppressors, but for bare continue statements.
        int continueSuppressors;

        @Override
        public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
          if (n.isGeneratorSafe()) {
            // Skip nodes that were generated by the compiler.
            n.setGeneratorSafe(false);
            return false;
          }

          checkState(!n.isGeneratorMarker());
          checkState(!n.isSuper(), "Reference to SUPER is not supported");

          if (NodeUtil.isLoopStructure(n)) {
            ++continueSuppressors;
            ++breakSuppressors;
          } else if (n.isSwitch()) {
            ++breakSuppressors;
          }

          if (n.isBreak() || n.isContinue()) {
            if (n.hasChildren()) {
              visitNamedBreakContinue(n);
            } else {
              visitBreakContinue(n);
            }
            return false;
          }

          return !n.isFunction();
        }

        @Override
        public void visit(NodeTraversal t, Node n, Node parent) {
          if (NodeUtil.isLoopStructure(n)) {
            --continueSuppressors;
            --breakSuppressors;
          } else if (n.isSwitch()) {
            --breakSuppressors;
          } else if (n.isThis()) {
            visitThis(n);
          } else if (n.isReturn()) {
            visitReturn(n);
          } else if (n.isName() && n.getString().equals("arguments")) {
            visitArguments(n);
          } else if (n.isVar()) {
            if (parent.isVanillaFor()) {
              visitVanillaForLoopVar(n);
            } else if (parent.isForIn()) {
              visitForInLoopVar(n);
            } else {
              // NOTE: for-of loops are transpiled away before this pass
              visitVarStatement(n);
            }
          } // else no changes need to be made
        }

        /** Adjust return statements. */
        void visitReturn(Node n) {
          // return ...;   =>   return $context.return(...);
          n.addChildToFront(returnExpression(n, n.removeFirstChild()));
        }

        /** Converts labeled break or continue statement into a jump. */
        void visitNamedBreakContinue(Node n) {
          checkState(n.getFirstChild().isLabelName());
          LabelCases cases = namedLabels.get(n.getFirstChild().getString());
          if (cases != null) {
            Case caseSection = n.isBreak() ? cases.breakCase : cases.continueCase;
            context.replaceBreakContinueWithJump(n, caseSection, breakSuppressors);
          }
        }

        /** Converts break or continue statement into a jump. */
        void visitBreakContinue(Node n) {
          Case caseSection = null;
          if (n.isBreak() && breakSuppressors == 0) {
            caseSection = breakCases.getFirst();
          }
          if (n.isContinue() && continueSuppressors == 0) {
            caseSection = continueCases.getFirst();
          }
          if (caseSection != null) {
            context.replaceBreakContinueWithJump(n, caseSection, breakSuppressors);
          }
        }

        /** Replaces reference to this with $jscomp$generator$this. */
        void visitThis(Node n) {
          Node newThis = astFactory.createName(context.getScopedName(GENERATOR_THIS), type(n));
          n.replaceWith(newThis);
          if (!thisReferenceFound) {
            Node var = IR.var(newThis.cloneNode().srcref(n), n).srcref(newGeneratorHoistBlock);
            hoistNode(var);
            thisReferenceFound = true;
          }
        }

        /**
         * Replaces reference to arguments with $jscomp$generator$arguments
         * .
         */
        void visitArguments(Node n) {
          Node newArguments =
              astFactory.createName(context.getScopedName(GENERATOR_ARGUMENTS), type(n)).srcref(n);
          n.replaceWith(newArguments);
          if (!argumentsReferenceFound) {
            Node var = IR.var(newArguments.cloneNode(), n).srcref(newGeneratorHoistBlock);
            hoistNode(var);
            argumentsReferenceFound = true;
          }
        }

        /**
         * Hoists {@code var} statements into the closure containing the generator to preserve their
         * state across multiple invocation of state machine program.
         *
         *

         *
         *

         * var a = "test", b = i + 5;
         *

         *
         * is transpiled to:
         *
         *

         * var a, b;
         * a = "test", b = i + 5;
         *

         */
        void visitVarStatement(Node varStatement) {
          Node commaExpression = extractAssignmentsToCommaExpression(varStatement);
          if (commaExpression == null) {
            varStatement.detach();
          } else {
            varStatement.replaceWith(astFactory.exprResult(commaExpression));
          }
          // Move declaration without initial values to just before the program method definition.
          hoistNode(varStatement);
        }

        /**
         * Hoists {@code var} declarations in vanilla for loops into the closure containing the
         * generator to preserve their state across multiple invocation of state machine program.
         *
         *

         *
         *

         * for (var a = "test", b = i + 5; ... ; )
         *

         *
         * is transpiled to:
         *
         *

         * var a, b;
         * for (a = "test", b = i + 5; ...; )
         *

         */
        private void visitVanillaForLoopVar(Node varDeclaration) {
          Node commaExpression = extractAssignmentsToCommaExpression(varDeclaration);
          if (commaExpression == null) {
            // `for (var x; ` becomes `for (; `
            varDeclaration.replaceWith(IR.empty());
          } else {
            // `for (var i = 0, j = 0; `... becomes `for (i = 0, j = 0; `...
            varDeclaration.replaceWith(commaExpression);
          }
          // Move declaration without initial values to just before the program method definition.
          hoistNode(varDeclaration);
        }

        /**
         * Hoists {@code var} declarations in for-in loops into the closure containing the
         * generator to preserve their state across multiple invocation of state machine program.
         *
         *

         *
         *

         * for (var a in obj)
         *

         *
         * is transpiled to:
         *
         *

         * var a;
         * for (a in obj))
         *

         */
        private void visitForInLoopVar(Node varDeclaration) {
          // `for (var varName in ` ...
          Node varName = varDeclaration.getOnlyChild();
          checkState(!varName.hasChildren(), varName);
          Node clonedVarName = varName.cloneNode().setJSDocInfo(null);
          // becomes `for (varName in ` ...
          varDeclaration.replaceWith(clonedVarName);
          // Move declaration without initial values to just before the program method definition.
          hoistNode(varDeclaration);
        }

        /**
         * Removes all initializers from a var declaration and returns them as a single expression
         * of comma-separated assignments or null if there aren't any initializers.
         *
         * @param varDeclaration VAR node
         * @return null or expression node (e.g. `varName1 = 1, varName2 = y`)
         */
        @Nullable
        private Node extractAssignmentsToCommaExpression(Node varDeclaration) {
          ArrayList<Node> assignments = new ArrayList<>();
          for (Node varName = varDeclaration.getFirstChild();
              varName != null;
              varName = varName.getNext()) {
            if (varName.hasChildren()) {
              Node copiedVarName = varName.cloneNode().setJSDocInfo(null);
              Node assign =
                  astFactory
                      .createAssign(copiedVarName, varName.removeFirstChild())
                      .srcref(varName);
              assignments.add(assign);
            }
          }
          Node commaExpression = null;
          for (Node assignment : assignments) {
            commaExpression =
                commaExpression == null
                    ? assignment
                    : astFactory.createComma(commaExpression, assignment).srcref(assignment);
          }
          return commaExpression;
        }
      }

      /** Reprasents a catch case that is used by try/catch transpilation */
      class CatchCase {
        final Case catchCase;

        /**
         * Number of finally blocks that should be executed before exception can be handled by this
         * catch case.
         */
        int finallyBlocks;

        CatchCase(Case catchCase) {
          this.catchCase = catchCase;
        }
      }

      /** Stores "break" and "continue" case sections assosiated with a label. */
      class LabelCases {

        final Case breakCase;

        @Nullable final Case continueCase;

        LabelCases(Case breakCase, @Nullable Case continueCase) {
          this.breakCase = breakCase;
          this.continueCase = continueCase;
        }
      }
    }
  }

  /** Marks "yield" nodes and propagates this information up through the tree */
  private static class YieldNodeMarker implements NodeTraversal.Callback {

    @Override
    public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent) {
      return !n.isFunction();
    }

    @Override
    public void visit(NodeTraversal t, Node n, Node parent) {
      if (n.isYield()) {
        n.setGeneratorMarker(true);
      }

      // This class is used on a tree that is detached from the main AST, so this will not end up
      // marking the parent of the node used to start the traversal.
      if (parent != null && n.isGeneratorMarker()) {
        parent.setGeneratorMarker(true);
      }
    }
  }
}


text:
(function() {
  var audioContext = wavesAudio.audioContext;
  var loader = new wavesLoaders.SuperLoader();
  var containerId = '#transport-container';

  // load audio and marker files
  loader.load(["http://wavesjs.github.io/assets/drum-loop.wav", "http://wavesjs.github.io/assets/drum-loop.json"])
    .then(function(loaded) {
      var audioBuffer = loaded[0];
      var markerBuffer = loaded[1];
      var beatDuration = audioBuffer.duration / 4;

      // create and connect metronome engine
      var metronome = new wavesAudio.Metronome();
      metronome.period = beatDuration;
      metronome.connect(audioContext.destination);

      var playerEngine = new wavesAudio.PlayerEngine({
        buffer: audioBuffer,
        cyclic: true
      });

      playerEngine.connect(audioContext.destination);

      // create and connect granular engine
      var granularEngine = new wavesAudio.GranularEngine({
        buffer: audioBuffer,
        centered: false, // to be synchronous with other engines
        cyclic: true
      });

      granularEngine.connect(audioContext.destination);

      // create and connect segment engine
      var segmentEngine = new wavesAudio.SegmentEngine({
        buffer: audioBuffer,
        cyclic: true,
        positionArray: markerBuffer.time,
        durationArray: markerBuffer.duration
      });

      segmentEngine.connect(audioContext.destination);

      // create position display (as transported TimeEngine)
      var positionDisplay = new wavesAudio.TimeEngine();
      positionDisplay.period = 0.01 * beatDuration;

      positionDisplay.syncPosition = function(time, position, speed) {
        var nextPosition = Math.floor(position / this.period) * this.period;

        if (speed > 0 && nextPosition < position)
          nextPosition += this.period;
        else if (speed < 0 && nextPosition > position)
          nextPosition -= this.period;

        return nextPosition;
      };

      positionDisplay.advancePosition = function(time, position, speed) {
        seekSlider.value = (playControl.currentPosition / beatDuration).toFixed();

        if (speed < 0)
          return position - this.period;

        return position + this.period;
      };

      // create transport and add engines
      var transport = new wavesAudio.Transport();
      transport.add(metronome);
      transport.add(playerEngine);
      transport.add(granularEngine);
      transport.add(segmentEngine);
      transport.add(positionDisplay);

      // create play control
      var playControl = new wavesAudio.PlayControl(transport);
      playControl.setLoopBoundaries(0, 2 * audioBuffer.duration);
      playControl.loop = true;

      // create GUI elements
      new wavesBasicControllers.Title("Transport Play Control", containerId);

      new wavesBasicControllers.Toggle("Play", false, containerId, function(value) {
        if (value)
          playControl.start();
        else
          playControl.stop();
      });

      var speedSlider = new wavesBasicControllers.Slider("Speed", -2, 2, 0.01, 1, "", '', containerId, function(value) {
        playControl.speed = value;
        speedSlider.value = playControl.speed;
      });

      var seekSlider = new wavesBasicControllers.Slider("Seek", 0, 8, 0.125, 0, "beats", 'large', containerId, function(value) {
        playControl.seek(value * beatDuration);
      });

      new wavesBasicControllers.Slider("Loop Start", 0, 8, 0.25, 0, "beats", 'large', containerId, function(value) {
        playControl.loopStart = value * beatDuration;
      });

      new wavesBasicControllers.Slider("Loop End", 0, 8, 0.25, 8, "beats", 'large', containerId, function(value) {
        playControl.loopEnd = value * beatDuration;
      });

      new wavesBasicControllers.Title("Enable Engines", containerId);

      new wavesBasicControllers.Toggle("Metronome", true, containerId, function(value) {
        if (value)
          transport.add(metronome);
        else
          transport.remove(metronome);
      });

      new wavesBasicControllers.Toggle("Player Engine", true, containerId, function(value) {
        if (value)
          transport.add(playerEngine);
        else
          transport.remove(playerEngine);
      });

      new wavesBasicControllers.Toggle("Granular Engine", true, containerId, function(value) {
        if (value)
          transport.add(granularEngine);
        else
          transport.remove(granularEngine);
      });

      new wavesBasicControllers.Toggle("Segment Engine", true, containerId, function(value) {
        if (value)
          transport.add(segmentEngine);
        else
          transport.remove(segmentEngine);
      });
    });
})();

redpajama_set_name: RedPajamaGithub


text:
cask 'eclipse-ide' do
  version '4.7.1,oxygen:1a'
  sha256 '7beeec0531181da673086568cfe14151ac8b96e76b0bfeaf3880d7ca495b9068'

  url "https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/#{version.after_comma.before_colon}/#{version.after_colon}/eclipse-committers-#{version.after_comma.before_colon}-#{version.after_colon}-macosx-cocoa-x86_64.dmg&r=1"
  name 'Eclipse IDE for Eclipse Committers'
  homepage 'https://eclipse.org/'

  depends_on macos: '>= :leopard'

  app 'Eclipse.app'
end


text:
import {Component} from "@angular/core";
import {WeatherService} from "./weather.service";
import {Weather} from "./Weather";
import {Observable} from 'rxjs/Observable';
import {WeatherComponent} from "./weather.component";

@Component({
    selector: 'weather-list',
    template: `
        <div *ngFor="let weather of weatherInfo | async" class="col-sm-6 col-md-2">
            <weather [weather]="weather"></weather>
        </div>
    `,
    providers: [WeatherService],
    directives: [WeatherComponent],
})
export class WeatherListComponent {

    private weatherInfo : Observable<Array<Weather>>

    constructor(private _weatherService:WeatherService) {
        this.weatherInfo = _weatherService.getWeather();
    }
}