• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Transforms generator functions into a compatible ES5 representation with similar runtime
2// semantics. This is accomplished by first transforming the body of each generator
3// function into an intermediate representation that is the compiled into a JavaScript
4// switch statement.
5//
6// Many functions in this transformer will contain comments indicating the expected
7// intermediate representation. For illustrative purposes, the following intermediate
8// language is used to define this intermediate representation:
9//
10//  .nop                            - Performs no operation.
11//  .local NAME, ...                - Define local variable declarations.
12//  .mark LABEL                     - Mark the location of a label.
13//  .br LABEL                       - Jump to a label. If jumping out of a protected
14//                                    region, all .finally blocks are executed.
15//  .brtrue LABEL, (x)              - Jump to a label IIF the expression `x` is truthy.
16//                                    If jumping out of a protected region, all .finally
17//                                    blocks are executed.
18//  .brfalse LABEL, (x)             - Jump to a label IIF the expression `x` is falsey.
19//                                    If jumping out of a protected region, all .finally
20//                                    blocks are executed.
21//  .yield (x)                      - Yield the value of the optional expression `x`.
22//                                    Resume at the next label.
23//  .yieldstar (x)                  - Delegate yield to the value of the optional
24//                                    expression `x`. Resume at the next label.
25//                                    NOTE: `x` must be an Iterator, not an Iterable.
26//  .loop CONTINUE, BREAK           - Marks the beginning of a loop. Any "continue" or
27//                                    "break" abrupt completions jump to the CONTINUE or
28//                                    BREAK labels, respectively.
29//  .endloop                        - Marks the end of a loop.
30//  .with (x)                       - Marks the beginning of a WithStatement block, using
31//                                    the supplied expression.
32//  .endwith                        - Marks the end of a WithStatement.
33//  .switch                         - Marks the beginning of a SwitchStatement.
34//  .endswitch                      - Marks the end of a SwitchStatement.
35//  .labeled NAME                   - Marks the beginning of a LabeledStatement with the
36//                                    supplied name.
37//  .endlabeled                     - Marks the end of a LabeledStatement.
38//  .try TRY, CATCH, FINALLY, END   - Marks the beginning of a protected region, and the
39//                                    labels for each block.
40//  .catch (x)                      - Marks the beginning of a catch block.
41//  .finally                        - Marks the beginning of a finally block.
42//  .endfinally                     - Marks the end of a finally block.
43//  .endtry                         - Marks the end of a protected region.
44//  .throw (x)                      - Throws the value of the expression `x`.
45//  .return (x)                     - Returns the value of the expression `x`.
46//
47// In addition, the illustrative intermediate representation introduces some special
48// variables:
49//
50//  %sent%                          - Either returns the next value sent to the generator,
51//                                    returns the result of a delegated yield, or throws
52//                                    the exception sent to the generator.
53//  %error%                         - Returns the value of the current exception in a
54//                                    catch block.
55//
56// This intermediate representation is then compiled into JavaScript syntax. The resulting
57// compilation output looks something like the following:
58//
59//  function f() {
60//      var /*locals*/;
61//      /*functions*/
62//      return __generator(function (state) {
63//          switch (state.label) {
64//              /*cases per label*/
65//          }
66//      });
67//  }
68//
69// Each of the above instructions corresponds to JavaScript emit similar to the following:
70//
71//  .local NAME                   | var NAME;
72// -------------------------------|----------------------------------------------
73//  .mark LABEL                   | case LABEL:
74// -------------------------------|----------------------------------------------
75//  .br LABEL                     |     return [3 /*break*/, LABEL];
76// -------------------------------|----------------------------------------------
77//  .brtrue LABEL, (x)            |     if (x) return [3 /*break*/, LABEL];
78// -------------------------------|----------------------------------------------
79//  .brfalse LABEL, (x)           |     if (!(x)) return [3, /*break*/, LABEL];
80// -------------------------------|----------------------------------------------
81//  .yield (x)                    |     return [4 /*yield*/, x];
82//  .mark RESUME                  | case RESUME:
83//      a = %sent%;               |     a = state.sent();
84// -------------------------------|----------------------------------------------
85//  .yieldstar (x)                |     return [5 /*yield**/, x];
86//  .mark RESUME                  | case RESUME:
87//      a = %sent%;               |     a = state.sent();
88// -------------------------------|----------------------------------------------
89//  .with (_a)                    |     with (_a) {
90//      a();                      |         a();
91//                                |     }
92//                                |     state.label = LABEL;
93//  .mark LABEL                   | case LABEL:
94//                                |     with (_a) {
95//      b();                      |         b();
96//                                |     }
97//  .endwith                      |
98// -------------------------------|----------------------------------------------
99//                                | case 0:
100//                                |     state.trys = [];
101//                                | ...
102//  .try TRY, CATCH, FINALLY, END |
103//  .mark TRY                     | case TRY:
104//                                |     state.trys.push([TRY, CATCH, FINALLY, END]);
105//  .nop                          |
106//      a();                      |     a();
107//  .br END                       |     return [3 /*break*/, END];
108//  .catch (e)                    |
109//  .mark CATCH                   | case CATCH:
110//                                |     e = state.sent();
111//      b();                      |     b();
112//  .br END                       |     return [3 /*break*/, END];
113//  .finally                      |
114//  .mark FINALLY                 | case FINALLY:
115//      c();                      |     c();
116//  .endfinally                   |     return [7 /*endfinally*/];
117//  .endtry                       |
118//  .mark END                     | case END:
119
120/*@internal*/
121namespace ts {
122    type Label = number;
123
124    const enum OpCode {
125        Nop,                    // No operation, used to force a new case in the state machine
126        Statement,              // A regular javascript statement
127        Assign,                 // An assignment
128        Break,                  // A break instruction used to jump to a label
129        BreakWhenTrue,          // A break instruction used to jump to a label if a condition evaluates to true
130        BreakWhenFalse,         // A break instruction used to jump to a label if a condition evaluates to false
131        Yield,                  // A completion instruction for the `yield` keyword
132        YieldStar,              // A completion instruction for the `yield*` keyword (not implemented, but reserved for future use)
133        Return,                 // A completion instruction for the `return` keyword
134        Throw,                  // A completion instruction for the `throw` keyword
135        Endfinally              // Marks the end of a `finally` block
136    }
137
138    type OperationArguments = [Label] | [Label, Expression] | [Statement] | [Expression | undefined] | [Expression, Expression];
139
140    // whether a generated code block is opening or closing at the current operation for a FunctionBuilder
141    const enum BlockAction {
142        Open,
143        Close,
144    }
145
146    // the kind for a generated code block in a FunctionBuilder
147    const enum CodeBlockKind {
148        Exception,
149        With,
150        Switch,
151        Loop,
152        Labeled
153    }
154
155    // the state for a generated code exception block
156    const enum ExceptionBlockState {
157        Try,
158        Catch,
159        Finally,
160        Done
161    }
162
163    // A generated code block
164    type CodeBlock = | ExceptionBlock | LabeledBlock | SwitchBlock | LoopBlock | WithBlock;
165
166    // a generated exception block, used for 'try' statements
167    interface ExceptionBlock {
168        kind: CodeBlockKind.Exception;
169        state: ExceptionBlockState;
170        startLabel: Label;
171        catchVariable?: Identifier;
172        catchLabel?: Label;
173        finallyLabel?: Label;
174        endLabel: Label;
175    }
176
177    // A generated code that tracks the target for 'break' statements in a LabeledStatement.
178    interface LabeledBlock {
179        kind: CodeBlockKind.Labeled;
180        labelText: string;
181        isScript: boolean;
182        breakLabel: Label;
183    }
184
185    // a generated block that tracks the target for 'break' statements in a 'switch' statement
186    interface SwitchBlock {
187        kind: CodeBlockKind.Switch;
188        isScript: boolean;
189        breakLabel: Label;
190    }
191
192    // a generated block that tracks the targets for 'break' and 'continue' statements, used for iteration statements
193    interface LoopBlock {
194        kind: CodeBlockKind.Loop;
195        continueLabel: Label;
196        isScript: boolean;
197        breakLabel: Label;
198    }
199
200    // a generated block associated with a 'with' statement
201    interface WithBlock {
202        kind: CodeBlockKind.With;
203        expression: Identifier;
204        startLabel: Label;
205        endLabel: Label;
206    }
207
208    // NOTE: changes to this enum should be reflected in the __generator helper.
209    const enum Instruction {
210        Next = 0,
211        Throw = 1,
212        Return = 2,
213        Break = 3,
214        Yield = 4,
215        YieldStar = 5,
216        Catch = 6,
217        Endfinally = 7,
218    }
219
220    function getInstructionName(instruction: Instruction): string {
221        switch (instruction) {
222            case Instruction.Return: return "return";
223            case Instruction.Break: return "break";
224            case Instruction.Yield: return "yield";
225            case Instruction.YieldStar: return "yield*";
226            case Instruction.Endfinally: return "endfinally";
227            default: return undefined!; // TODO: GH#18217
228        }
229    }
230
231    export function transformGenerators(context: TransformationContext) {
232        const {
233            factory,
234            getEmitHelperFactory: emitHelpers,
235            resumeLexicalEnvironment,
236            endLexicalEnvironment,
237            hoistFunctionDeclaration,
238            hoistVariableDeclaration
239        } = context;
240
241        const compilerOptions = context.getCompilerOptions();
242        const languageVersion = getEmitScriptTarget(compilerOptions);
243        const resolver = context.getEmitResolver();
244        const previousOnSubstituteNode = context.onSubstituteNode;
245        context.onSubstituteNode = onSubstituteNode;
246
247        let renamedCatchVariables: ESMap<string, boolean>;
248        let renamedCatchVariableDeclarations: Identifier[];
249
250        let inGeneratorFunctionBody: boolean;
251        let inStatementContainingYield: boolean;
252
253        // The following three arrays store information about generated code blocks.
254        // All three arrays are correlated by their index. This approach is used over allocating
255        // objects to store the same information to avoid GC overhead.
256        //
257        let blocks: CodeBlock[] | undefined; // Information about the code block
258        let blockOffsets: number[] | undefined; // The operation offset at which a code block begins or ends
259        let blockActions: BlockAction[] | undefined; // Whether the code block is opened or closed
260        let blockStack: CodeBlock[] | undefined; // A stack of currently open code blocks
261
262        // Labels are used to mark locations in the code that can be the target of a Break (jump)
263        // operation. These are translated into case clauses in a switch statement.
264        // The following two arrays are correlated by their index. This approach is used over
265        // allocating objects to store the same information to avoid GC overhead.
266        //
267        let labelOffsets: number[] | undefined; // The operation offset at which the label is defined.
268        let labelExpressions: Mutable<LiteralExpression>[][] | undefined; // The NumericLiteral nodes bound to each label.
269        let nextLabelId = 1; // The next label id to use.
270
271        // Operations store information about generated code for the function body. This
272        // Includes things like statements, assignments, breaks (jumps), and yields.
273        // The following three arrays are correlated by their index. This approach is used over
274        // allocating objects to store the same information to avoid GC overhead.
275        //
276        let operations: OpCode[] | undefined; // The operation to perform.
277        let operationArguments: (OperationArguments | undefined)[] | undefined; // The arguments to the operation.
278        let operationLocations: (TextRange | undefined)[] | undefined; // The source map location for the operation.
279
280        let state: Identifier; // The name of the state object used by the generator at runtime.
281
282        // The following variables store information used by the `build` function:
283        //
284        let blockIndex = 0; // The index of the current block.
285        let labelNumber = 0; // The current label number.
286        let labelNumbers: number[][] | undefined;
287        let lastOperationWasAbrupt: boolean; // Indicates whether the last operation was abrupt (break/continue).
288        let lastOperationWasCompletion: boolean; // Indicates whether the last operation was a completion (return/throw).
289        let clauses: CaseClause[] | undefined; // The case clauses generated for labels.
290        let statements: Statement[] | undefined; // The statements for the current label.
291        let exceptionBlockStack: ExceptionBlock[] | undefined; // A stack of containing exception blocks.
292        let currentExceptionBlock: ExceptionBlock | undefined; // The current exception block.
293        let withBlockStack: WithBlock[] | undefined; // A stack containing `with` blocks.
294
295        return chainBundle(context, transformSourceFile);
296
297        function transformSourceFile(node: SourceFile) {
298            if (node.isDeclarationFile || (node.transformFlags & TransformFlags.ContainsGenerator) === 0) {
299                return node;
300            }
301
302
303            const visited = visitEachChild(node, visitor, context);
304            addEmitHelpers(visited, context.readEmitHelpers());
305            return visited;
306        }
307
308        /**
309         * Visits a node.
310         *
311         * @param node The node to visit.
312         */
313        function visitor(node: Node): VisitResult<Node> {
314            const transformFlags = node.transformFlags;
315            if (inStatementContainingYield) {
316                return visitJavaScriptInStatementContainingYield(node);
317            }
318            else if (inGeneratorFunctionBody) {
319                return visitJavaScriptInGeneratorFunctionBody(node);
320            }
321            else if (isFunctionLikeDeclaration(node) && node.asteriskToken) {
322                return visitGenerator(node);
323            }
324            else if (transformFlags & TransformFlags.ContainsGenerator) {
325                return visitEachChild(node, visitor, context);
326            }
327            else {
328                return node;
329            }
330        }
331
332        /**
333         * Visits a node that is contained within a statement that contains yield.
334         *
335         * @param node The node to visit.
336         */
337        function visitJavaScriptInStatementContainingYield(node: Node): VisitResult<Node> {
338            switch (node.kind) {
339                case SyntaxKind.DoStatement:
340                    return visitDoStatement(<DoStatement>node);
341                case SyntaxKind.WhileStatement:
342                    return visitWhileStatement(<WhileStatement>node);
343                case SyntaxKind.SwitchStatement:
344                    return visitSwitchStatement(<SwitchStatement>node);
345                case SyntaxKind.LabeledStatement:
346                    return visitLabeledStatement(<LabeledStatement>node);
347                default:
348                    return visitJavaScriptInGeneratorFunctionBody(node);
349            }
350        }
351
352        /**
353         * Visits a node that is contained within a generator function.
354         *
355         * @param node The node to visit.
356         */
357        function visitJavaScriptInGeneratorFunctionBody(node: Node): VisitResult<Node> {
358            switch (node.kind) {
359                case SyntaxKind.FunctionDeclaration:
360                    return visitFunctionDeclaration(<FunctionDeclaration>node);
361                case SyntaxKind.FunctionExpression:
362                    return visitFunctionExpression(<FunctionExpression>node);
363                case SyntaxKind.GetAccessor:
364                case SyntaxKind.SetAccessor:
365                    return visitAccessorDeclaration(<AccessorDeclaration>node);
366                case SyntaxKind.VariableStatement:
367                    return visitVariableStatement(<VariableStatement>node);
368                case SyntaxKind.ForStatement:
369                    return visitForStatement(<ForStatement>node);
370                case SyntaxKind.ForInStatement:
371                    return visitForInStatement(<ForInStatement>node);
372                case SyntaxKind.BreakStatement:
373                    return visitBreakStatement(<BreakStatement>node);
374                case SyntaxKind.ContinueStatement:
375                    return visitContinueStatement(<ContinueStatement>node);
376                case SyntaxKind.ReturnStatement:
377                    return visitReturnStatement(<ReturnStatement>node);
378                default:
379                    if (node.transformFlags & TransformFlags.ContainsYield) {
380                        return visitJavaScriptContainingYield(node);
381                    }
382                    else if (node.transformFlags & (TransformFlags.ContainsGenerator | TransformFlags.ContainsHoistedDeclarationOrCompletion)) {
383                        return visitEachChild(node, visitor, context);
384                    }
385                    else {
386                        return node;
387                    }
388            }
389        }
390
391        /**
392         * Visits a node that contains a YieldExpression.
393         *
394         * @param node The node to visit.
395         */
396        function visitJavaScriptContainingYield(node: Node): VisitResult<Node> {
397            switch (node.kind) {
398                case SyntaxKind.BinaryExpression:
399                    return visitBinaryExpression(<BinaryExpression>node);
400                case SyntaxKind.CommaListExpression:
401                    return visitCommaListExpression(<CommaListExpression>node);
402                case SyntaxKind.ConditionalExpression:
403                    return visitConditionalExpression(<ConditionalExpression>node);
404                case SyntaxKind.YieldExpression:
405                    return visitYieldExpression(<YieldExpression>node);
406                case SyntaxKind.ArrayLiteralExpression:
407                    return visitArrayLiteralExpression(<ArrayLiteralExpression>node);
408                case SyntaxKind.ObjectLiteralExpression:
409                    return visitObjectLiteralExpression(<ObjectLiteralExpression>node);
410                case SyntaxKind.ElementAccessExpression:
411                    return visitElementAccessExpression(<ElementAccessExpression>node);
412                case SyntaxKind.CallExpression:
413                    return visitCallExpression(<CallExpression>node);
414                case SyntaxKind.NewExpression:
415                    return visitNewExpression(<NewExpression>node);
416                default:
417                    return visitEachChild(node, visitor, context);
418            }
419        }
420
421        /**
422         * Visits a generator function.
423         *
424         * @param node The node to visit.
425         */
426        function visitGenerator(node: Node): VisitResult<Node> {
427            switch (node.kind) {
428                case SyntaxKind.FunctionDeclaration:
429                    return visitFunctionDeclaration(<FunctionDeclaration>node);
430
431                case SyntaxKind.FunctionExpression:
432                    return visitFunctionExpression(<FunctionExpression>node);
433
434                default:
435                    return Debug.failBadSyntaxKind(node);
436            }
437        }
438
439        /**
440         * Visits a function declaration.
441         *
442         * This will be called when one of the following conditions are met:
443         * - The function declaration is a generator function.
444         * - The function declaration is contained within the body of a generator function.
445         *
446         * @param node The node to visit.
447         */
448        function visitFunctionDeclaration(node: FunctionDeclaration): Statement | undefined {
449            // Currently, we only support generators that were originally async functions.
450            if (node.asteriskToken) {
451                node = setOriginalNode(
452                    setTextRange(
453                        factory.createFunctionDeclaration(
454                            /*decorators*/ undefined,
455                            node.modifiers,
456                            /*asteriskToken*/ undefined,
457                            node.name,
458                            /*typeParameters*/ undefined,
459                            visitParameterList(node.parameters, visitor, context),
460                            /*type*/ undefined,
461                            transformGeneratorFunctionBody(node.body!)
462                        ),
463                        /*location*/ node
464                    ),
465                    node
466                );
467            }
468            else {
469                const savedInGeneratorFunctionBody = inGeneratorFunctionBody;
470                const savedInStatementContainingYield = inStatementContainingYield;
471                inGeneratorFunctionBody = false;
472                inStatementContainingYield = false;
473                node = visitEachChild(node, visitor, context);
474                inGeneratorFunctionBody = savedInGeneratorFunctionBody;
475                inStatementContainingYield = savedInStatementContainingYield;
476            }
477
478            if (inGeneratorFunctionBody) {
479                // Function declarations in a generator function body are hoisted
480                // to the top of the lexical scope and elided from the current statement.
481                hoistFunctionDeclaration(node);
482                return undefined;
483            }
484            else {
485                return node;
486            }
487        }
488
489        /**
490         * Visits a function expression.
491         *
492         * This will be called when one of the following conditions are met:
493         * - The function expression is a generator function.
494         * - The function expression is contained within the body of a generator function.
495         *
496         * @param node The node to visit.
497         */
498        function visitFunctionExpression(node: FunctionExpression): Expression {
499            // Currently, we only support generators that were originally async functions.
500            if (node.asteriskToken) {
501                node = setOriginalNode(
502                    setTextRange(
503                        factory.createFunctionExpression(
504                            /*modifiers*/ undefined,
505                            /*asteriskToken*/ undefined,
506                            node.name,
507                            /*typeParameters*/ undefined,
508                            visitParameterList(node.parameters, visitor, context),
509                            /*type*/ undefined,
510                            transformGeneratorFunctionBody(node.body)
511                        ),
512                        /*location*/ node
513                    ),
514                    node
515                );
516            }
517            else {
518                const savedInGeneratorFunctionBody = inGeneratorFunctionBody;
519                const savedInStatementContainingYield = inStatementContainingYield;
520                inGeneratorFunctionBody = false;
521                inStatementContainingYield = false;
522                node = visitEachChild(node, visitor, context);
523                inGeneratorFunctionBody = savedInGeneratorFunctionBody;
524                inStatementContainingYield = savedInStatementContainingYield;
525            }
526
527            return node;
528        }
529
530        /**
531         * Visits a get or set accessor declaration.
532         *
533         * This will be called when one of the following conditions are met:
534         * - The accessor is contained within the body of a generator function.
535         *
536         * @param node The node to visit.
537         */
538        function visitAccessorDeclaration(node: AccessorDeclaration) {
539            const savedInGeneratorFunctionBody = inGeneratorFunctionBody;
540            const savedInStatementContainingYield = inStatementContainingYield;
541            inGeneratorFunctionBody = false;
542            inStatementContainingYield = false;
543            node = visitEachChild(node, visitor, context);
544            inGeneratorFunctionBody = savedInGeneratorFunctionBody;
545            inStatementContainingYield = savedInStatementContainingYield;
546            return node;
547        }
548
549        /**
550         * Transforms the body of a generator function declaration.
551         *
552         * @param node The function body to transform.
553         */
554        function transformGeneratorFunctionBody(body: Block) {
555            // Save existing generator state
556            const statements: Statement[] = [];
557            const savedInGeneratorFunctionBody = inGeneratorFunctionBody;
558            const savedInStatementContainingYield = inStatementContainingYield;
559            const savedBlocks = blocks;
560            const savedBlockOffsets = blockOffsets;
561            const savedBlockActions = blockActions;
562            const savedBlockStack = blockStack;
563            const savedLabelOffsets = labelOffsets;
564            const savedLabelExpressions = labelExpressions;
565            const savedNextLabelId = nextLabelId;
566            const savedOperations = operations;
567            const savedOperationArguments = operationArguments;
568            const savedOperationLocations = operationLocations;
569            const savedState = state;
570
571            // Initialize generator state
572            inGeneratorFunctionBody = true;
573            inStatementContainingYield = false;
574            blocks = undefined;
575            blockOffsets = undefined;
576            blockActions = undefined;
577            blockStack = undefined;
578            labelOffsets = undefined;
579            labelExpressions = undefined;
580            nextLabelId = 1;
581            operations = undefined;
582            operationArguments = undefined;
583            operationLocations = undefined;
584            state = factory.createTempVariable(/*recordTempVariable*/ undefined);
585
586            // Build the generator
587            resumeLexicalEnvironment();
588
589            const statementOffset = factory.copyPrologue(body.statements, statements, /*ensureUseStrict*/ false, visitor);
590
591            transformAndEmitStatements(body.statements, statementOffset);
592
593            const buildResult = build();
594            insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment());
595            statements.push(factory.createReturnStatement(buildResult));
596
597            // Restore previous generator state
598            inGeneratorFunctionBody = savedInGeneratorFunctionBody;
599            inStatementContainingYield = savedInStatementContainingYield;
600            blocks = savedBlocks;
601            blockOffsets = savedBlockOffsets;
602            blockActions = savedBlockActions;
603            blockStack = savedBlockStack;
604            labelOffsets = savedLabelOffsets;
605            labelExpressions = savedLabelExpressions;
606            nextLabelId = savedNextLabelId;
607            operations = savedOperations;
608            operationArguments = savedOperationArguments;
609            operationLocations = savedOperationLocations;
610            state = savedState;
611
612            return setTextRange(factory.createBlock(statements, body.multiLine), body);
613        }
614
615        /**
616         * Visits a variable statement.
617         *
618         * This will be called when one of the following conditions are met:
619         * - The variable statement is contained within the body of a generator function.
620         *
621         * @param node The node to visit.
622         */
623        function visitVariableStatement(node: VariableStatement): Statement | undefined {
624            if (node.transformFlags & TransformFlags.ContainsYield) {
625                transformAndEmitVariableDeclarationList(node.declarationList);
626                return undefined;
627            }
628            else {
629                // Do not hoist custom prologues.
630                if (getEmitFlags(node) & EmitFlags.CustomPrologue) {
631                    return node;
632                }
633
634                for (const variable of node.declarationList.declarations) {
635                    hoistVariableDeclaration(<Identifier>variable.name);
636                }
637
638                const variables = getInitializedVariables(node.declarationList);
639                if (variables.length === 0) {
640                    return undefined;
641                }
642
643                return setSourceMapRange(
644                    factory.createExpressionStatement(
645                        factory.inlineExpressions(
646                            map(variables, transformInitializedVariable)
647                        )
648                    ),
649                    node
650                );
651            }
652        }
653
654        /**
655         * Visits a binary expression.
656         *
657         * This will be called when one of the following conditions are met:
658         * - The node contains a YieldExpression.
659         *
660         * @param node The node to visit.
661         */
662        function visitBinaryExpression(node: BinaryExpression): Expression {
663            const assoc = getExpressionAssociativity(node);
664            switch (assoc) {
665                case Associativity.Left:
666                    return visitLeftAssociativeBinaryExpression(node);
667                case Associativity.Right:
668                    return visitRightAssociativeBinaryExpression(node);
669                default:
670                    return Debug.assertNever(assoc);
671            }
672        }
673
674        /**
675         * Visits a right-associative binary expression containing `yield`.
676         *
677         * @param node The node to visit.
678         */
679        function visitRightAssociativeBinaryExpression(node: BinaryExpression) {
680            const { left, right } = node;
681            if (containsYield(right)) {
682                let target: Expression;
683                switch (left.kind) {
684                    case SyntaxKind.PropertyAccessExpression:
685                        // [source]
686                        //      a.b = yield;
687                        //
688                        // [intermediate]
689                        //  .local _a
690                        //      _a = a;
691                        //  .yield resumeLabel
692                        //  .mark resumeLabel
693                        //      _a.b = %sent%;
694
695                        target = factory.updatePropertyAccessExpression(
696                            <PropertyAccessExpression>left,
697                            cacheExpression(visitNode((<PropertyAccessExpression>left).expression, visitor, isLeftHandSideExpression)),
698                            (<PropertyAccessExpression>left).name
699                        );
700                        break;
701
702                    case SyntaxKind.ElementAccessExpression:
703                        // [source]
704                        //      a[b] = yield;
705                        //
706                        // [intermediate]
707                        //  .local _a, _b
708                        //      _a = a;
709                        //      _b = b;
710                        //  .yield resumeLabel
711                        //  .mark resumeLabel
712                        //      _a[_b] = %sent%;
713
714                        target = factory.updateElementAccessExpression(<ElementAccessExpression>left,
715                            cacheExpression(visitNode((<ElementAccessExpression>left).expression, visitor, isLeftHandSideExpression)),
716                            cacheExpression(visitNode((<ElementAccessExpression>left).argumentExpression, visitor, isExpression))
717                        );
718                        break;
719
720                    default:
721                        target = visitNode(left, visitor, isExpression);
722                        break;
723                }
724
725                const operator = node.operatorToken.kind;
726                if (isCompoundAssignment(operator)) {
727                    return setTextRange(
728                        factory.createAssignment(
729                            target,
730                            setTextRange(
731                                factory.createBinaryExpression(
732                                    cacheExpression(target),
733                                    getNonAssignmentOperatorForCompoundAssignment(operator),
734                                    visitNode(right, visitor, isExpression)
735                                ),
736                                node
737                            )
738                        ),
739                        node
740                    );
741                }
742                else {
743                    return factory.updateBinaryExpression(node, target, node.operatorToken, visitNode(right, visitor, isExpression));
744                }
745            }
746
747            return visitEachChild(node, visitor, context);
748        }
749
750        function visitLeftAssociativeBinaryExpression(node: BinaryExpression) {
751            if (containsYield(node.right)) {
752                if (isLogicalOperator(node.operatorToken.kind)) {
753                    return visitLogicalBinaryExpression(node);
754                }
755                else if (node.operatorToken.kind === SyntaxKind.CommaToken) {
756                    return visitCommaExpression(node);
757                }
758
759                // [source]
760                //      a() + (yield) + c()
761                //
762                // [intermediate]
763                //  .local _a
764                //      _a = a();
765                //  .yield resumeLabel
766                //      _a + %sent% + c()
767
768                return factory.updateBinaryExpression(node,
769                    cacheExpression(visitNode(node.left, visitor, isExpression)),
770                    node.operatorToken,
771                    visitNode(node.right, visitor, isExpression));
772            }
773
774            return visitEachChild(node, visitor, context);
775        }
776
777        /**
778         * Visits a comma expression containing `yield`.
779         *
780         * @param node The node to visit.
781         */
782        function visitCommaExpression(node: BinaryExpression) {
783            // [source]
784            //      x = a(), yield, b();
785            //
786            // [intermediate]
787            //      a();
788            //  .yield resumeLabel
789            //  .mark resumeLabel
790            //      x = %sent%, b();
791
792            let pendingExpressions: Expression[] = [];
793            visit(node.left);
794            visit(node.right);
795            return factory.inlineExpressions(pendingExpressions);
796
797            function visit(node: Expression) {
798                if (isBinaryExpression(node) && node.operatorToken.kind === SyntaxKind.CommaToken) {
799                    visit(node.left);
800                    visit(node.right);
801                }
802                else {
803                    if (containsYield(node) && pendingExpressions.length > 0) {
804                        emitWorker(OpCode.Statement, [factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))]);
805                        pendingExpressions = [];
806                    }
807
808                    pendingExpressions.push(visitNode(node, visitor, isExpression));
809                }
810            }
811        }
812
813        /**
814         * Visits a comma-list expression.
815         *
816         * @param node The node to visit.
817         */
818        function visitCommaListExpression(node: CommaListExpression) {
819            // flattened version of `visitCommaExpression`
820            let pendingExpressions: Expression[] = [];
821            for (const elem of node.elements) {
822                if (isBinaryExpression(elem) && elem.operatorToken.kind === SyntaxKind.CommaToken) {
823                    pendingExpressions.push(visitCommaExpression(elem));
824                }
825                else {
826                    if (containsYield(elem) && pendingExpressions.length > 0) {
827                        emitWorker(OpCode.Statement, [factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions))]);
828                        pendingExpressions = [];
829                    }
830                    pendingExpressions.push(visitNode(elem, visitor, isExpression));
831                }
832            }
833            return factory.inlineExpressions(pendingExpressions);
834        }
835
836        /**
837         * Visits a logical binary expression containing `yield`.
838         *
839         * @param node A node to visit.
840         */
841        function visitLogicalBinaryExpression(node: BinaryExpression) {
842            // Logical binary expressions (`&&` and `||`) are shortcutting expressions and need
843            // to be transformed as such:
844            //
845            // [source]
846            //      x = a() && yield;
847            //
848            // [intermediate]
849            //  .local _a
850            //      _a = a();
851            //  .brfalse resultLabel, (_a)
852            //  .yield resumeLabel
853            //  .mark resumeLabel
854            //      _a = %sent%;
855            //  .mark resultLabel
856            //      x = _a;
857            //
858            // [source]
859            //      x = a() || yield;
860            //
861            // [intermediate]
862            //  .local _a
863            //      _a = a();
864            //  .brtrue resultLabel, (_a)
865            //  .yield resumeLabel
866            //  .mark resumeLabel
867            //      _a = %sent%;
868            //  .mark resultLabel
869            //      x = _a;
870
871            const resultLabel = defineLabel();
872            const resultLocal = declareLocal();
873
874            emitAssignment(resultLocal, visitNode(node.left, visitor, isExpression), /*location*/ node.left);
875            if (node.operatorToken.kind === SyntaxKind.AmpersandAmpersandToken) {
876                // Logical `&&` shortcuts when the left-hand operand is falsey.
877                emitBreakWhenFalse(resultLabel, resultLocal, /*location*/ node.left);
878            }
879            else {
880                // Logical `||` shortcuts when the left-hand operand is truthy.
881                emitBreakWhenTrue(resultLabel, resultLocal, /*location*/ node.left);
882            }
883
884            emitAssignment(resultLocal, visitNode(node.right, visitor, isExpression), /*location*/ node.right);
885            markLabel(resultLabel);
886            return resultLocal;
887        }
888
889        /**
890         * Visits a conditional expression containing `yield`.
891         *
892         * @param node The node to visit.
893         */
894        function visitConditionalExpression(node: ConditionalExpression): Expression {
895            // [source]
896            //      x = a() ? yield : b();
897            //
898            // [intermediate]
899            //  .local _a
900            //  .brfalse whenFalseLabel, (a())
901            //  .yield resumeLabel
902            //  .mark resumeLabel
903            //      _a = %sent%;
904            //  .br resultLabel
905            //  .mark whenFalseLabel
906            //      _a = b();
907            //  .mark resultLabel
908            //      x = _a;
909
910            // We only need to perform a specific transformation if a `yield` expression exists
911            // in either the `whenTrue` or `whenFalse` branches.
912            // A `yield` in the condition will be handled by the normal visitor.
913            if (containsYield(node.whenTrue) || containsYield(node.whenFalse)) {
914                const whenFalseLabel = defineLabel();
915                const resultLabel = defineLabel();
916                const resultLocal = declareLocal();
917                emitBreakWhenFalse(whenFalseLabel, visitNode(node.condition, visitor, isExpression), /*location*/ node.condition);
918                emitAssignment(resultLocal, visitNode(node.whenTrue, visitor, isExpression), /*location*/ node.whenTrue);
919                emitBreak(resultLabel);
920                markLabel(whenFalseLabel);
921                emitAssignment(resultLocal, visitNode(node.whenFalse, visitor, isExpression), /*location*/ node.whenFalse);
922                markLabel(resultLabel);
923                return resultLocal;
924            }
925
926            return visitEachChild(node, visitor, context);
927        }
928
929        /**
930         * Visits a `yield` expression.
931         *
932         * @param node The node to visit.
933         */
934        function visitYieldExpression(node: YieldExpression): LeftHandSideExpression {
935            // [source]
936            //      x = yield a();
937            //
938            // [intermediate]
939            //  .yield resumeLabel, (a())
940            //  .mark resumeLabel
941            //      x = %sent%;
942
943            const resumeLabel = defineLabel();
944            const expression = visitNode(node.expression, visitor, isExpression);
945            if (node.asteriskToken) {
946                // NOTE: `expression` must be defined for `yield*`.
947                const iterator = (getEmitFlags(node.expression!) & EmitFlags.Iterator) === 0
948                    ? setTextRange(emitHelpers().createValuesHelper(expression!), node)
949                    : expression;
950                emitYieldStar(iterator, /*location*/ node);
951            }
952            else {
953                emitYield(expression, /*location*/ node);
954            }
955
956            markLabel(resumeLabel);
957            return createGeneratorResume(/*location*/ node);
958        }
959
960        /**
961         * Visits an ArrayLiteralExpression that contains a YieldExpression.
962         *
963         * @param node The node to visit.
964         */
965        function visitArrayLiteralExpression(node: ArrayLiteralExpression) {
966            return visitElements(node.elements, /*leadingElement*/ undefined, /*location*/ undefined, node.multiLine);
967        }
968
969        /**
970         * Visits an array of expressions containing one or more YieldExpression nodes
971         * and returns an expression for the resulting value.
972         *
973         * @param elements The elements to visit.
974         * @param multiLine Whether array literals created should be emitted on multiple lines.
975         */
976        function visitElements(elements: NodeArray<Expression>, leadingElement?: Expression, location?: TextRange, multiLine?: boolean) {
977            // [source]
978            //      ar = [1, yield, 2];
979            //
980            // [intermediate]
981            //  .local _a
982            //      _a = [1];
983            //  .yield resumeLabel
984            //  .mark resumeLabel
985            //      ar = _a.concat([%sent%, 2]);
986
987            const numInitialElements = countInitialNodesWithoutYield(elements);
988
989            let temp: Identifier | undefined;
990            if (numInitialElements > 0) {
991                temp = declareLocal();
992                const initialElements = visitNodes(elements, visitor, isExpression, 0, numInitialElements);
993                emitAssignment(temp,
994                    factory.createArrayLiteralExpression(
995                        leadingElement
996                            ? [leadingElement, ...initialElements]
997                            : initialElements
998                    )
999                );
1000                leadingElement = undefined;
1001            }
1002
1003            const expressions = reduceLeft(elements, reduceElement, <Expression[]>[], numInitialElements);
1004            return temp
1005                ? factory.createArrayConcatCall(temp, [factory.createArrayLiteralExpression(expressions, multiLine)])
1006                : setTextRange(
1007                    factory.createArrayLiteralExpression(leadingElement ? [leadingElement, ...expressions] : expressions, multiLine),
1008                    location
1009                );
1010
1011            function reduceElement(expressions: Expression[], element: Expression) {
1012                if (containsYield(element) && expressions.length > 0) {
1013                    const hasAssignedTemp = temp !== undefined;
1014                    if (!temp) {
1015                        temp = declareLocal();
1016                    }
1017
1018                    emitAssignment(
1019                        temp,
1020                        hasAssignedTemp
1021                            ? factory.createArrayConcatCall(
1022                                temp,
1023                                [factory.createArrayLiteralExpression(expressions, multiLine)]
1024                            )
1025                            : factory.createArrayLiteralExpression(
1026                                leadingElement ? [leadingElement, ...expressions] : expressions,
1027                                multiLine
1028                            )
1029                    );
1030                    leadingElement = undefined;
1031                    expressions = [];
1032                }
1033
1034                expressions.push(visitNode(element, visitor, isExpression));
1035                return expressions;
1036            }
1037        }
1038
1039        function visitObjectLiteralExpression(node: ObjectLiteralExpression) {
1040            // [source]
1041            //      o = {
1042            //          a: 1,
1043            //          b: yield,
1044            //          c: 2
1045            //      };
1046            //
1047            // [intermediate]
1048            //  .local _a
1049            //      _a = {
1050            //          a: 1
1051            //      };
1052            //  .yield resumeLabel
1053            //  .mark resumeLabel
1054            //      o = (_a.b = %sent%,
1055            //          _a.c = 2,
1056            //          _a);
1057
1058            const properties = node.properties;
1059            const multiLine = node.multiLine;
1060            const numInitialProperties = countInitialNodesWithoutYield(properties);
1061
1062            const temp = declareLocal();
1063            emitAssignment(temp,
1064                factory.createObjectLiteralExpression(
1065                    visitNodes(properties, visitor, isObjectLiteralElementLike, 0, numInitialProperties),
1066                    multiLine
1067                )
1068            );
1069
1070            const expressions = reduceLeft(properties, reduceProperty, <Expression[]>[], numInitialProperties);
1071            // TODO(rbuckton): Does this need to be parented?
1072            expressions.push(multiLine ? startOnNewLine(setParent(setTextRange(factory.cloneNode(temp), temp), temp.parent)) : temp);
1073            return factory.inlineExpressions(expressions);
1074
1075            function reduceProperty(expressions: Expression[], property: ObjectLiteralElementLike) {
1076                if (containsYield(property) && expressions.length > 0) {
1077                    emitStatement(factory.createExpressionStatement(factory.inlineExpressions(expressions)));
1078                    expressions = [];
1079                }
1080
1081                const expression = createExpressionForObjectLiteralElementLike(factory, node, property, temp);
1082                const visited = visitNode(expression, visitor, isExpression);
1083                if (visited) {
1084                    if (multiLine) {
1085                        startOnNewLine(visited);
1086                    }
1087                    expressions.push(visited);
1088                }
1089                return expressions;
1090            }
1091        }
1092
1093        /**
1094         * Visits an ElementAccessExpression that contains a YieldExpression.
1095         *
1096         * @param node The node to visit.
1097         */
1098        function visitElementAccessExpression(node: ElementAccessExpression) {
1099            if (containsYield(node.argumentExpression)) {
1100                // [source]
1101                //      a = x[yield];
1102                //
1103                // [intermediate]
1104                //  .local _a
1105                //      _a = x;
1106                //  .yield resumeLabel
1107                //  .mark resumeLabel
1108                //      a = _a[%sent%]
1109
1110                return factory.updateElementAccessExpression(node,
1111                    cacheExpression(visitNode(node.expression, visitor, isLeftHandSideExpression)),
1112                    visitNode(node.argumentExpression, visitor, isExpression));
1113            }
1114
1115            return visitEachChild(node, visitor, context);
1116        }
1117
1118        function visitCallExpression(node: CallExpression) {
1119            if (!isImportCall(node) && forEach(node.arguments, containsYield)) {
1120                // [source]
1121                //      a.b(1, yield, 2);
1122                //
1123                // [intermediate]
1124                //  .local _a, _b, _c
1125                //      _b = (_a = a).b;
1126                //      _c = [1];
1127                //  .yield resumeLabel
1128                //  .mark resumeLabel
1129                //      _b.apply(_a, _c.concat([%sent%, 2]));
1130                const { target, thisArg } = factory.createCallBinding(node.expression, hoistVariableDeclaration, languageVersion, /*cacheIdentifiers*/ true);
1131                return setOriginalNode(
1132                    setTextRange(
1133                        factory.createFunctionApplyCall(
1134                            cacheExpression(visitNode(target, visitor, isLeftHandSideExpression)),
1135                            thisArg,
1136                            visitElements(node.arguments)
1137                        ),
1138                        node
1139                    ),
1140                    node
1141                );
1142            }
1143
1144            return visitEachChild(node, visitor, context);
1145        }
1146
1147        function visitNewExpression(node: NewExpression) {
1148            if (forEach(node.arguments, containsYield)) {
1149                // [source]
1150                //      new a.b(1, yield, 2);
1151                //
1152                // [intermediate]
1153                //  .local _a, _b, _c
1154                //      _b = (_a = a.b).bind;
1155                //      _c = [1];
1156                //  .yield resumeLabel
1157                //  .mark resumeLabel
1158                //      new (_b.apply(_a, _c.concat([%sent%, 2])));
1159
1160                const { target, thisArg } = factory.createCallBinding(factory.createPropertyAccessExpression(node.expression, "bind"), hoistVariableDeclaration);
1161                return setOriginalNode(
1162                    setTextRange(
1163                        factory.createNewExpression(
1164                            factory.createFunctionApplyCall(
1165                                cacheExpression(visitNode(target, visitor, isExpression)),
1166                                thisArg,
1167                                visitElements(
1168                                    node.arguments!,
1169                                    /*leadingElement*/ factory.createVoidZero()
1170                                )
1171                            ),
1172                            /*typeArguments*/ undefined,
1173                            []
1174                        ),
1175                        node
1176                    ),
1177                    node
1178                );
1179            }
1180            return visitEachChild(node, visitor, context);
1181        }
1182
1183        function transformAndEmitStatements(statements: readonly Statement[], start = 0) {
1184            const numStatements = statements.length;
1185            for (let i = start; i < numStatements; i++) {
1186                transformAndEmitStatement(statements[i]);
1187            }
1188        }
1189
1190        function transformAndEmitEmbeddedStatement(node: Statement) {
1191            if (isBlock(node)) {
1192                transformAndEmitStatements(node.statements);
1193            }
1194            else {
1195                transformAndEmitStatement(node);
1196            }
1197        }
1198
1199        function transformAndEmitStatement(node: Statement): void {
1200            const savedInStatementContainingYield = inStatementContainingYield;
1201            if (!inStatementContainingYield) {
1202                inStatementContainingYield = containsYield(node);
1203            }
1204
1205            transformAndEmitStatementWorker(node);
1206            inStatementContainingYield = savedInStatementContainingYield;
1207        }
1208
1209        function transformAndEmitStatementWorker(node: Statement): void {
1210            switch (node.kind) {
1211                case SyntaxKind.Block:
1212                    return transformAndEmitBlock(<Block>node);
1213                case SyntaxKind.ExpressionStatement:
1214                    return transformAndEmitExpressionStatement(<ExpressionStatement>node);
1215                case SyntaxKind.IfStatement:
1216                    return transformAndEmitIfStatement(<IfStatement>node);
1217                case SyntaxKind.DoStatement:
1218                    return transformAndEmitDoStatement(<DoStatement>node);
1219                case SyntaxKind.WhileStatement:
1220                    return transformAndEmitWhileStatement(<WhileStatement>node);
1221                case SyntaxKind.ForStatement:
1222                    return transformAndEmitForStatement(<ForStatement>node);
1223                case SyntaxKind.ForInStatement:
1224                    return transformAndEmitForInStatement(<ForInStatement>node);
1225                case SyntaxKind.ContinueStatement:
1226                    return transformAndEmitContinueStatement(<ContinueStatement>node);
1227                case SyntaxKind.BreakStatement:
1228                    return transformAndEmitBreakStatement(<BreakStatement>node);
1229                case SyntaxKind.ReturnStatement:
1230                    return transformAndEmitReturnStatement(<ReturnStatement>node);
1231                case SyntaxKind.WithStatement:
1232                    return transformAndEmitWithStatement(<WithStatement>node);
1233                case SyntaxKind.SwitchStatement:
1234                    return transformAndEmitSwitchStatement(<SwitchStatement>node);
1235                case SyntaxKind.LabeledStatement:
1236                    return transformAndEmitLabeledStatement(<LabeledStatement>node);
1237                case SyntaxKind.ThrowStatement:
1238                    return transformAndEmitThrowStatement(<ThrowStatement>node);
1239                case SyntaxKind.TryStatement:
1240                    return transformAndEmitTryStatement(<TryStatement>node);
1241                default:
1242                    return emitStatement(visitNode(node, visitor, isStatement));
1243            }
1244        }
1245
1246        function transformAndEmitBlock(node: Block): void {
1247            if (containsYield(node)) {
1248                transformAndEmitStatements(node.statements);
1249            }
1250            else {
1251                emitStatement(visitNode(node, visitor, isStatement));
1252            }
1253        }
1254
1255        function transformAndEmitExpressionStatement(node: ExpressionStatement) {
1256            emitStatement(visitNode(node, visitor, isStatement));
1257        }
1258
1259        function transformAndEmitVariableDeclarationList(node: VariableDeclarationList): VariableDeclarationList | undefined {
1260            for (const variable of node.declarations) {
1261                const name = factory.cloneNode(<Identifier>variable.name);
1262                setCommentRange(name, variable.name);
1263                hoistVariableDeclaration(name);
1264            }
1265
1266            const variables = getInitializedVariables(node);
1267            const numVariables = variables.length;
1268            let variablesWritten = 0;
1269            let pendingExpressions: Expression[] = [];
1270            while (variablesWritten < numVariables) {
1271                for (let i = variablesWritten; i < numVariables; i++) {
1272                    const variable = variables[i];
1273                    if (containsYield(variable.initializer) && pendingExpressions.length > 0) {
1274                        break;
1275                    }
1276
1277                    pendingExpressions.push(transformInitializedVariable(variable));
1278                }
1279
1280                if (pendingExpressions.length) {
1281                    emitStatement(factory.createExpressionStatement(factory.inlineExpressions(pendingExpressions)));
1282                    variablesWritten += pendingExpressions.length;
1283                    pendingExpressions = [];
1284                }
1285            }
1286
1287            return undefined;
1288        }
1289
1290        function transformInitializedVariable(node: InitializedVariableDeclaration) {
1291            return setSourceMapRange(
1292                factory.createAssignment(
1293                    setSourceMapRange(<Identifier>factory.cloneNode(node.name), node.name),
1294                    visitNode(node.initializer, visitor, isExpression)
1295                ),
1296                node
1297            );
1298        }
1299
1300        function transformAndEmitIfStatement(node: IfStatement) {
1301            if (containsYield(node)) {
1302                // [source]
1303                //      if (x)
1304                //          /*thenStatement*/
1305                //      else
1306                //          /*elseStatement*/
1307                //
1308                // [intermediate]
1309                //  .brfalse elseLabel, (x)
1310                //      /*thenStatement*/
1311                //  .br endLabel
1312                //  .mark elseLabel
1313                //      /*elseStatement*/
1314                //  .mark endLabel
1315
1316                if (containsYield(node.thenStatement) || containsYield(node.elseStatement)) {
1317                    const endLabel = defineLabel();
1318                    const elseLabel = node.elseStatement ? defineLabel() : undefined;
1319                    emitBreakWhenFalse(node.elseStatement ? elseLabel! : endLabel, visitNode(node.expression, visitor, isExpression), /*location*/ node.expression);
1320                    transformAndEmitEmbeddedStatement(node.thenStatement);
1321                    if (node.elseStatement) {
1322                        emitBreak(endLabel);
1323                        markLabel(elseLabel!);
1324                        transformAndEmitEmbeddedStatement(node.elseStatement);
1325                    }
1326                    markLabel(endLabel);
1327                }
1328                else {
1329                    emitStatement(visitNode(node, visitor, isStatement));
1330                }
1331            }
1332            else {
1333                emitStatement(visitNode(node, visitor, isStatement));
1334            }
1335        }
1336
1337        function transformAndEmitDoStatement(node: DoStatement) {
1338            if (containsYield(node)) {
1339                // [source]
1340                //      do {
1341                //          /*body*/
1342                //      }
1343                //      while (i < 10);
1344                //
1345                // [intermediate]
1346                //  .loop conditionLabel, endLabel
1347                //  .mark loopLabel
1348                //      /*body*/
1349                //  .mark conditionLabel
1350                //  .brtrue loopLabel, (i < 10)
1351                //  .endloop
1352                //  .mark endLabel
1353
1354                const conditionLabel = defineLabel();
1355                const loopLabel = defineLabel();
1356                beginLoopBlock(/*continueLabel*/ conditionLabel);
1357                markLabel(loopLabel);
1358                transformAndEmitEmbeddedStatement(node.statement);
1359                markLabel(conditionLabel);
1360                emitBreakWhenTrue(loopLabel, visitNode(node.expression, visitor, isExpression));
1361                endLoopBlock();
1362            }
1363            else {
1364                emitStatement(visitNode(node, visitor, isStatement));
1365            }
1366        }
1367
1368        function visitDoStatement(node: DoStatement) {
1369            if (inStatementContainingYield) {
1370                beginScriptLoopBlock();
1371                node = visitEachChild(node, visitor, context);
1372                endLoopBlock();
1373                return node;
1374            }
1375            else {
1376                return visitEachChild(node, visitor, context);
1377            }
1378        }
1379
1380        function transformAndEmitWhileStatement(node: WhileStatement) {
1381            if (containsYield(node)) {
1382                // [source]
1383                //      while (i < 10) {
1384                //          /*body*/
1385                //      }
1386                //
1387                // [intermediate]
1388                //  .loop loopLabel, endLabel
1389                //  .mark loopLabel
1390                //  .brfalse endLabel, (i < 10)
1391                //      /*body*/
1392                //  .br loopLabel
1393                //  .endloop
1394                //  .mark endLabel
1395
1396                const loopLabel = defineLabel();
1397                const endLabel = beginLoopBlock(loopLabel);
1398                markLabel(loopLabel);
1399                emitBreakWhenFalse(endLabel, visitNode(node.expression, visitor, isExpression));
1400                transformAndEmitEmbeddedStatement(node.statement);
1401                emitBreak(loopLabel);
1402                endLoopBlock();
1403            }
1404            else {
1405                emitStatement(visitNode(node, visitor, isStatement));
1406            }
1407        }
1408
1409        function visitWhileStatement(node: WhileStatement) {
1410            if (inStatementContainingYield) {
1411                beginScriptLoopBlock();
1412                node = visitEachChild(node, visitor, context);
1413                endLoopBlock();
1414                return node;
1415            }
1416            else {
1417                return visitEachChild(node, visitor, context);
1418            }
1419        }
1420
1421        function transformAndEmitForStatement(node: ForStatement) {
1422            if (containsYield(node)) {
1423                // [source]
1424                //      for (var i = 0; i < 10; i++) {
1425                //          /*body*/
1426                //      }
1427                //
1428                // [intermediate]
1429                //  .local i
1430                //      i = 0;
1431                //  .loop incrementLabel, endLoopLabel
1432                //  .mark conditionLabel
1433                //  .brfalse endLoopLabel, (i < 10)
1434                //      /*body*/
1435                //  .mark incrementLabel
1436                //      i++;
1437                //  .br conditionLabel
1438                //  .endloop
1439                //  .mark endLoopLabel
1440
1441                const conditionLabel = defineLabel();
1442                const incrementLabel = defineLabel();
1443                const endLabel = beginLoopBlock(incrementLabel);
1444                if (node.initializer) {
1445                    const initializer = node.initializer;
1446                    if (isVariableDeclarationList(initializer)) {
1447                        transformAndEmitVariableDeclarationList(initializer);
1448                    }
1449                    else {
1450                        emitStatement(
1451                            setTextRange(
1452                                factory.createExpressionStatement(
1453                                    visitNode(initializer, visitor, isExpression)
1454                                ),
1455                                initializer
1456                            )
1457                        );
1458                    }
1459                }
1460
1461                markLabel(conditionLabel);
1462                if (node.condition) {
1463                    emitBreakWhenFalse(endLabel, visitNode(node.condition, visitor, isExpression));
1464                }
1465
1466                transformAndEmitEmbeddedStatement(node.statement);
1467
1468                markLabel(incrementLabel);
1469                if (node.incrementor) {
1470                    emitStatement(
1471                        setTextRange(
1472                            factory.createExpressionStatement(
1473                                visitNode(node.incrementor, visitor, isExpression)
1474                            ),
1475                            node.incrementor
1476                        )
1477                    );
1478                }
1479                emitBreak(conditionLabel);
1480                endLoopBlock();
1481            }
1482            else {
1483                emitStatement(visitNode(node, visitor, isStatement));
1484            }
1485        }
1486
1487        function visitForStatement(node: ForStatement) {
1488            if (inStatementContainingYield) {
1489                beginScriptLoopBlock();
1490            }
1491
1492            const initializer = node.initializer;
1493            if (initializer && isVariableDeclarationList(initializer)) {
1494                for (const variable of initializer.declarations) {
1495                    hoistVariableDeclaration(<Identifier>variable.name);
1496                }
1497
1498                const variables = getInitializedVariables(initializer);
1499                node = factory.updateForStatement(node,
1500                    variables.length > 0
1501                        ? factory.inlineExpressions(map(variables, transformInitializedVariable))
1502                        : undefined,
1503                    visitNode(node.condition, visitor, isExpression),
1504                    visitNode(node.incrementor, visitor, isExpression),
1505                    visitNode(node.statement, visitor, isStatement, factory.liftToBlock)
1506                );
1507            }
1508            else {
1509                node = visitEachChild(node, visitor, context);
1510            }
1511
1512            if (inStatementContainingYield) {
1513                endLoopBlock();
1514            }
1515
1516            return node;
1517        }
1518
1519        function transformAndEmitForInStatement(node: ForInStatement) {
1520            // TODO(rbuckton): Source map locations
1521            if (containsYield(node)) {
1522                // [source]
1523                //      for (var p in o) {
1524                //          /*body*/
1525                //      }
1526                //
1527                // [intermediate]
1528                //  .local _a, _b, _i
1529                //      _a = [];
1530                //      for (_b in o) _a.push(_b);
1531                //      _i = 0;
1532                //  .loop incrementLabel, endLoopLabel
1533                //  .mark conditionLabel
1534                //  .brfalse endLoopLabel, (_i < _a.length)
1535                //      p = _a[_i];
1536                //      /*body*/
1537                //  .mark incrementLabel
1538                //      _b++;
1539                //  .br conditionLabel
1540                //  .endloop
1541                //  .mark endLoopLabel
1542
1543                const keysArray = declareLocal(); // _a
1544                const key = declareLocal(); // _b
1545                const keysIndex = factory.createLoopVariable(); // _i
1546                const initializer = node.initializer;
1547                hoistVariableDeclaration(keysIndex);
1548                emitAssignment(keysArray, factory.createArrayLiteralExpression());
1549
1550                emitStatement(
1551                    factory.createForInStatement(
1552                        key,
1553                        visitNode(node.expression, visitor, isExpression),
1554                        factory.createExpressionStatement(
1555                            factory.createCallExpression(
1556                                factory.createPropertyAccessExpression(keysArray, "push"),
1557                                /*typeArguments*/ undefined,
1558                                [key]
1559                            )
1560                        )
1561                    )
1562                );
1563
1564                emitAssignment(keysIndex, factory.createNumericLiteral(0));
1565
1566                const conditionLabel = defineLabel();
1567                const incrementLabel = defineLabel();
1568                const endLabel = beginLoopBlock(incrementLabel);
1569
1570                markLabel(conditionLabel);
1571                emitBreakWhenFalse(endLabel, factory.createLessThan(keysIndex, factory.createPropertyAccessExpression(keysArray, "length")));
1572
1573                let variable: Expression;
1574                if (isVariableDeclarationList(initializer)) {
1575                    for (const variable of initializer.declarations) {
1576                        hoistVariableDeclaration(<Identifier>variable.name);
1577                    }
1578
1579                    variable = <Identifier>factory.cloneNode(initializer.declarations[0].name);
1580                }
1581                else {
1582                    variable = visitNode(initializer, visitor, isExpression);
1583                    Debug.assert(isLeftHandSideExpression(variable));
1584                }
1585
1586                emitAssignment(variable, factory.createElementAccessExpression(keysArray, keysIndex));
1587                transformAndEmitEmbeddedStatement(node.statement);
1588
1589                markLabel(incrementLabel);
1590                emitStatement(factory.createExpressionStatement(factory.createPostfixIncrement(keysIndex)));
1591
1592                emitBreak(conditionLabel);
1593                endLoopBlock();
1594            }
1595            else {
1596                emitStatement(visitNode(node, visitor, isStatement));
1597            }
1598        }
1599
1600        function visitForInStatement(node: ForInStatement) {
1601            // [source]
1602            //      for (var x in a) {
1603            //          /*body*/
1604            //      }
1605            //
1606            // [intermediate]
1607            //  .local x
1608            //  .loop
1609            //      for (x in a) {
1610            //          /*body*/
1611            //      }
1612            //  .endloop
1613
1614            if (inStatementContainingYield) {
1615                beginScriptLoopBlock();
1616            }
1617
1618            const initializer = node.initializer;
1619            if (isVariableDeclarationList(initializer)) {
1620                for (const variable of initializer.declarations) {
1621                    hoistVariableDeclaration(<Identifier>variable.name);
1622                }
1623
1624                node = factory.updateForInStatement(node,
1625                    <Identifier>initializer.declarations[0].name,
1626                    visitNode(node.expression, visitor, isExpression),
1627                    visitNode(node.statement, visitor, isStatement, factory.liftToBlock)
1628                );
1629            }
1630            else {
1631                node = visitEachChild(node, visitor, context);
1632            }
1633
1634            if (inStatementContainingYield) {
1635                endLoopBlock();
1636            }
1637
1638            return node;
1639        }
1640
1641        function transformAndEmitContinueStatement(node: ContinueStatement): void {
1642            const label = findContinueTarget(node.label ? idText(node.label) : undefined);
1643            if (label > 0) {
1644                emitBreak(label, /*location*/ node);
1645            }
1646            else {
1647                // invalid continue without a containing loop. Leave the node as is, per #17875.
1648                emitStatement(node);
1649            }
1650        }
1651
1652        function visitContinueStatement(node: ContinueStatement): Statement {
1653            if (inStatementContainingYield) {
1654                const label = findContinueTarget(node.label && idText(node.label));
1655                if (label > 0) {
1656                    return createInlineBreak(label, /*location*/ node);
1657                }
1658            }
1659
1660            return visitEachChild(node, visitor, context);
1661        }
1662
1663        function transformAndEmitBreakStatement(node: BreakStatement): void {
1664            const label = findBreakTarget(node.label ? idText(node.label) : undefined);
1665            if (label > 0) {
1666                emitBreak(label, /*location*/ node);
1667            }
1668            else {
1669                // invalid break without a containing loop, switch, or labeled statement. Leave the node as is, per #17875.
1670                emitStatement(node);
1671            }
1672        }
1673
1674        function visitBreakStatement(node: BreakStatement): Statement {
1675            if (inStatementContainingYield) {
1676                const label = findBreakTarget(node.label && idText(node.label));
1677                if (label > 0) {
1678                    return createInlineBreak(label, /*location*/ node);
1679                }
1680            }
1681
1682            return visitEachChild(node, visitor, context);
1683        }
1684
1685        function transformAndEmitReturnStatement(node: ReturnStatement): void {
1686            emitReturn(
1687                visitNode(node.expression, visitor, isExpression),
1688                /*location*/ node
1689            );
1690        }
1691
1692        function visitReturnStatement(node: ReturnStatement) {
1693            return createInlineReturn(
1694                visitNode(node.expression, visitor, isExpression),
1695                /*location*/ node
1696            );
1697        }
1698
1699        function transformAndEmitWithStatement(node: WithStatement) {
1700            if (containsYield(node)) {
1701                // [source]
1702                //      with (x) {
1703                //          /*body*/
1704                //      }
1705                //
1706                // [intermediate]
1707                //  .with (x)
1708                //      /*body*/
1709                //  .endwith
1710                beginWithBlock(cacheExpression(visitNode(node.expression, visitor, isExpression)));
1711                transformAndEmitEmbeddedStatement(node.statement);
1712                endWithBlock();
1713            }
1714            else {
1715                emitStatement(visitNode(node, visitor, isStatement));
1716            }
1717        }
1718
1719        function transformAndEmitSwitchStatement(node: SwitchStatement) {
1720            if (containsYield(node.caseBlock)) {
1721                // [source]
1722                //      switch (x) {
1723                //          case a:
1724                //              /*caseStatements*/
1725                //          case b:
1726                //              /*caseStatements*/
1727                //          default:
1728                //              /*defaultStatements*/
1729                //      }
1730                //
1731                // [intermediate]
1732                //  .local _a
1733                //  .switch endLabel
1734                //      _a = x;
1735                //      switch (_a) {
1736                //          case a:
1737                //  .br clauseLabels[0]
1738                //      }
1739                //      switch (_a) {
1740                //          case b:
1741                //  .br clauseLabels[1]
1742                //      }
1743                //  .br clauseLabels[2]
1744                //  .mark clauseLabels[0]
1745                //      /*caseStatements*/
1746                //  .mark clauseLabels[1]
1747                //      /*caseStatements*/
1748                //  .mark clauseLabels[2]
1749                //      /*caseStatements*/
1750                //  .endswitch
1751                //  .mark endLabel
1752
1753                const caseBlock = node.caseBlock;
1754                const numClauses = caseBlock.clauses.length;
1755                const endLabel = beginSwitchBlock();
1756
1757                const expression = cacheExpression(visitNode(node.expression, visitor, isExpression));
1758
1759                // Create labels for each clause and find the index of the first default clause.
1760                const clauseLabels: Label[] = [];
1761                let defaultClauseIndex = -1;
1762                for (let i = 0; i < numClauses; i++) {
1763                    const clause = caseBlock.clauses[i];
1764                    clauseLabels.push(defineLabel());
1765                    if (clause.kind === SyntaxKind.DefaultClause && defaultClauseIndex === -1) {
1766                        defaultClauseIndex = i;
1767                    }
1768                }
1769
1770                // Emit switch statements for each run of case clauses either from the first case
1771                // clause or the next case clause with a `yield` in its expression, up to the next
1772                // case clause with a `yield` in its expression.
1773                let clausesWritten = 0;
1774                let pendingClauses: CaseClause[] = [];
1775                while (clausesWritten < numClauses) {
1776                    let defaultClausesSkipped = 0;
1777                    for (let i = clausesWritten; i < numClauses; i++) {
1778                        const clause = caseBlock.clauses[i];
1779                        if (clause.kind === SyntaxKind.CaseClause) {
1780                            if (containsYield(clause.expression) && pendingClauses.length > 0) {
1781                                break;
1782                            }
1783
1784                            pendingClauses.push(
1785                                factory.createCaseClause(
1786                                    visitNode(clause.expression, visitor, isExpression),
1787                                    [
1788                                        createInlineBreak(clauseLabels[i], /*location*/ clause.expression)
1789                                    ]
1790                                )
1791                            );
1792                        }
1793                        else {
1794                            defaultClausesSkipped++;
1795                        }
1796                    }
1797
1798                    if (pendingClauses.length) {
1799                        emitStatement(factory.createSwitchStatement(expression, factory.createCaseBlock(pendingClauses)));
1800                        clausesWritten += pendingClauses.length;
1801                        pendingClauses = [];
1802                    }
1803                    if (defaultClausesSkipped > 0) {
1804                        clausesWritten += defaultClausesSkipped;
1805                        defaultClausesSkipped = 0;
1806                    }
1807                }
1808
1809                if (defaultClauseIndex >= 0) {
1810                    emitBreak(clauseLabels[defaultClauseIndex]);
1811                }
1812                else {
1813                    emitBreak(endLabel);
1814                }
1815
1816                for (let i = 0; i < numClauses; i++) {
1817                    markLabel(clauseLabels[i]);
1818                    transformAndEmitStatements(caseBlock.clauses[i].statements);
1819                }
1820
1821                endSwitchBlock();
1822            }
1823            else {
1824                emitStatement(visitNode(node, visitor, isStatement));
1825            }
1826        }
1827
1828        function visitSwitchStatement(node: SwitchStatement) {
1829            if (inStatementContainingYield) {
1830                beginScriptSwitchBlock();
1831            }
1832
1833            node = visitEachChild(node, visitor, context);
1834
1835            if (inStatementContainingYield) {
1836                endSwitchBlock();
1837            }
1838
1839            return node;
1840        }
1841
1842        function transformAndEmitLabeledStatement(node: LabeledStatement) {
1843            if (containsYield(node)) {
1844                // [source]
1845                //      x: {
1846                //          /*body*/
1847                //      }
1848                //
1849                // [intermediate]
1850                //  .labeled "x", endLabel
1851                //      /*body*/
1852                //  .endlabeled
1853                //  .mark endLabel
1854                beginLabeledBlock(idText(node.label));
1855                transformAndEmitEmbeddedStatement(node.statement);
1856                endLabeledBlock();
1857            }
1858            else {
1859                emitStatement(visitNode(node, visitor, isStatement));
1860            }
1861        }
1862
1863        function visitLabeledStatement(node: LabeledStatement) {
1864            if (inStatementContainingYield) {
1865                beginScriptLabeledBlock(idText(node.label));
1866            }
1867
1868            node = visitEachChild(node, visitor, context);
1869
1870            if (inStatementContainingYield) {
1871                endLabeledBlock();
1872            }
1873
1874            return node;
1875        }
1876
1877        function transformAndEmitThrowStatement(node: ThrowStatement): void {
1878            // TODO(rbuckton): `expression` should be required on `throw`.
1879            emitThrow(
1880                visitNode(node.expression ?? factory.createVoidZero(), visitor, isExpression),
1881                /*location*/ node
1882            );
1883        }
1884
1885        function transformAndEmitTryStatement(node: TryStatement) {
1886            if (containsYield(node)) {
1887                // [source]
1888                //      try {
1889                //          /*tryBlock*/
1890                //      }
1891                //      catch (e) {
1892                //          /*catchBlock*/
1893                //      }
1894                //      finally {
1895                //          /*finallyBlock*/
1896                //      }
1897                //
1898                // [intermediate]
1899                //  .local _a
1900                //  .try tryLabel, catchLabel, finallyLabel, endLabel
1901                //  .mark tryLabel
1902                //  .nop
1903                //      /*tryBlock*/
1904                //  .br endLabel
1905                //  .catch
1906                //  .mark catchLabel
1907                //      _a = %error%;
1908                //      /*catchBlock*/
1909                //  .br endLabel
1910                //  .finally
1911                //  .mark finallyLabel
1912                //      /*finallyBlock*/
1913                //  .endfinally
1914                //  .endtry
1915                //  .mark endLabel
1916
1917                beginExceptionBlock();
1918                transformAndEmitEmbeddedStatement(node.tryBlock);
1919                if (node.catchClause) {
1920                    beginCatchBlock(node.catchClause.variableDeclaration!); // TODO: GH#18217
1921                    transformAndEmitEmbeddedStatement(node.catchClause.block);
1922                }
1923
1924                if (node.finallyBlock) {
1925                    beginFinallyBlock();
1926                    transformAndEmitEmbeddedStatement(node.finallyBlock);
1927                }
1928
1929                endExceptionBlock();
1930            }
1931            else {
1932                emitStatement(visitEachChild(node, visitor, context));
1933            }
1934        }
1935
1936        function containsYield(node: Node | undefined): boolean {
1937            return !!node && (node.transformFlags & TransformFlags.ContainsYield) !== 0;
1938        }
1939
1940        function countInitialNodesWithoutYield(nodes: NodeArray<Node>) {
1941            const numNodes = nodes.length;
1942            for (let i = 0; i < numNodes; i++) {
1943                if (containsYield(nodes[i])) {
1944                    return i;
1945                }
1946            }
1947
1948            return -1;
1949        }
1950
1951        function onSubstituteNode(hint: EmitHint, node: Node): Node {
1952            node = previousOnSubstituteNode(hint, node);
1953            if (hint === EmitHint.Expression) {
1954                return substituteExpression(<Expression>node);
1955            }
1956            return node;
1957        }
1958
1959        function substituteExpression(node: Expression): Expression {
1960            if (isIdentifier(node)) {
1961                return substituteExpressionIdentifier(node);
1962            }
1963            return node;
1964        }
1965
1966        function substituteExpressionIdentifier(node: Identifier) {
1967            if (!isGeneratedIdentifier(node) && renamedCatchVariables && renamedCatchVariables.has(idText(node))) {
1968                const original = getOriginalNode(node);
1969                if (isIdentifier(original) && original.parent) {
1970                    const declaration = resolver.getReferencedValueDeclaration(original);
1971                    if (declaration) {
1972                        const name = renamedCatchVariableDeclarations[getOriginalNodeId(declaration)];
1973                        if (name) {
1974                            // TODO(rbuckton): Does this need to be parented?
1975                            const clone = setParent(setTextRange(factory.cloneNode(name), name), name.parent);
1976                            setSourceMapRange(clone, node);
1977                            setCommentRange(clone, node);
1978                            return clone;
1979                        }
1980                    }
1981                }
1982            }
1983
1984            return node;
1985        }
1986
1987        function cacheExpression(node: Expression): Identifier {
1988            if (isGeneratedIdentifier(node) || getEmitFlags(node) & EmitFlags.HelperName) {
1989                return <Identifier>node;
1990            }
1991
1992            const temp = factory.createTempVariable(hoistVariableDeclaration);
1993            emitAssignment(temp, node, /*location*/ node);
1994            return temp;
1995        }
1996
1997        function declareLocal(name?: string): Identifier {
1998            const temp = name
1999                ? factory.createUniqueName(name)
2000                : factory.createTempVariable(/*recordTempVariable*/ undefined);
2001            hoistVariableDeclaration(temp);
2002            return temp;
2003        }
2004
2005        /**
2006         * Defines a label, uses as the target of a Break operation.
2007         */
2008        function defineLabel(): Label {
2009            if (!labelOffsets) {
2010                labelOffsets = [];
2011            }
2012
2013            const label = nextLabelId;
2014            nextLabelId++;
2015            labelOffsets[label] = -1;
2016            return label;
2017        }
2018
2019        /**
2020         * Marks the current operation with the specified label.
2021         */
2022        function markLabel(label: Label): void {
2023            Debug.assert(labelOffsets !== undefined, "No labels were defined.");
2024            labelOffsets[label] = operations ? operations.length : 0;
2025        }
2026
2027        /**
2028         * Begins a block operation (With, Break/Continue, Try/Catch/Finally)
2029         *
2030         * @param block Information about the block.
2031         */
2032        function beginBlock(block: CodeBlock): number {
2033            if (!blocks) {
2034                blocks = [];
2035                blockActions = [];
2036                blockOffsets = [];
2037                blockStack = [];
2038            }
2039
2040            const index = blockActions!.length;
2041            blockActions![index] = BlockAction.Open;
2042            blockOffsets![index] = operations ? operations.length : 0;
2043            blocks[index] = block;
2044            blockStack!.push(block);
2045            return index;
2046        }
2047
2048        /**
2049         * Ends the current block operation.
2050         */
2051        function endBlock(): CodeBlock {
2052            const block = peekBlock();
2053            if (block === undefined) return Debug.fail("beginBlock was never called.");
2054
2055            const index = blockActions!.length;
2056            blockActions![index] = BlockAction.Close;
2057            blockOffsets![index] = operations ? operations.length : 0;
2058            blocks![index] = block;
2059            blockStack!.pop();
2060            return block;
2061        }
2062
2063        /**
2064         * Gets the current open block.
2065         */
2066        function peekBlock() {
2067            return lastOrUndefined(blockStack!);
2068        }
2069
2070        /**
2071         * Gets the kind of the current open block.
2072         */
2073        function peekBlockKind(): CodeBlockKind | undefined {
2074            const block = peekBlock();
2075            return block && block.kind;
2076        }
2077
2078        /**
2079         * Begins a code block for a generated `with` statement.
2080         *
2081         * @param expression An identifier representing expression for the `with` block.
2082         */
2083        function beginWithBlock(expression: Identifier): void {
2084            const startLabel = defineLabel();
2085            const endLabel = defineLabel();
2086            markLabel(startLabel);
2087            beginBlock({
2088                kind: CodeBlockKind.With,
2089                expression,
2090                startLabel,
2091                endLabel
2092            });
2093        }
2094
2095        /**
2096         * Ends a code block for a generated `with` statement.
2097         */
2098        function endWithBlock(): void {
2099            Debug.assert(peekBlockKind() === CodeBlockKind.With);
2100            const block = <WithBlock>endBlock();
2101            markLabel(block.endLabel);
2102        }
2103
2104        /**
2105         * Begins a code block for a generated `try` statement.
2106         */
2107        function beginExceptionBlock(): Label {
2108            const startLabel = defineLabel();
2109            const endLabel = defineLabel();
2110            markLabel(startLabel);
2111            beginBlock({
2112                kind: CodeBlockKind.Exception,
2113                state: ExceptionBlockState.Try,
2114                startLabel,
2115                endLabel
2116            });
2117            emitNop();
2118            return endLabel;
2119        }
2120
2121        /**
2122         * Enters the `catch` clause of a generated `try` statement.
2123         *
2124         * @param variable The catch variable.
2125         */
2126        function beginCatchBlock(variable: VariableDeclaration): void {
2127            Debug.assert(peekBlockKind() === CodeBlockKind.Exception);
2128
2129            // generated identifiers should already be unique within a file
2130            let name: Identifier;
2131            if (isGeneratedIdentifier(variable.name)) {
2132                name = variable.name;
2133                hoistVariableDeclaration(variable.name);
2134            }
2135            else {
2136                const text = idText(<Identifier>variable.name);
2137                name = declareLocal(text);
2138                if (!renamedCatchVariables) {
2139                    renamedCatchVariables = new Map<string, boolean>();
2140                    renamedCatchVariableDeclarations = [];
2141                    context.enableSubstitution(SyntaxKind.Identifier);
2142                }
2143
2144                renamedCatchVariables.set(text, true);
2145                renamedCatchVariableDeclarations[getOriginalNodeId(variable)] = name;
2146            }
2147
2148            const exception = <ExceptionBlock>peekBlock();
2149            Debug.assert(exception.state < ExceptionBlockState.Catch);
2150
2151            const endLabel = exception.endLabel;
2152            emitBreak(endLabel);
2153
2154            const catchLabel = defineLabel();
2155            markLabel(catchLabel);
2156            exception.state = ExceptionBlockState.Catch;
2157            exception.catchVariable = name;
2158            exception.catchLabel = catchLabel;
2159
2160            emitAssignment(name, factory.createCallExpression(factory.createPropertyAccessExpression(state, "sent"), /*typeArguments*/ undefined, []));
2161            emitNop();
2162        }
2163
2164        /**
2165         * Enters the `finally` block of a generated `try` statement.
2166         */
2167        function beginFinallyBlock(): void {
2168            Debug.assert(peekBlockKind() === CodeBlockKind.Exception);
2169
2170            const exception = <ExceptionBlock>peekBlock();
2171            Debug.assert(exception.state < ExceptionBlockState.Finally);
2172
2173            const endLabel = exception.endLabel;
2174            emitBreak(endLabel);
2175
2176            const finallyLabel = defineLabel();
2177            markLabel(finallyLabel);
2178            exception.state = ExceptionBlockState.Finally;
2179            exception.finallyLabel = finallyLabel;
2180        }
2181
2182        /**
2183         * Ends the code block for a generated `try` statement.
2184         */
2185        function endExceptionBlock(): void {
2186            Debug.assert(peekBlockKind() === CodeBlockKind.Exception);
2187            const exception = <ExceptionBlock>endBlock();
2188            const state = exception.state;
2189            if (state < ExceptionBlockState.Finally) {
2190                emitBreak(exception.endLabel);
2191            }
2192            else {
2193                emitEndfinally();
2194            }
2195
2196            markLabel(exception.endLabel);
2197            emitNop();
2198            exception.state = ExceptionBlockState.Done;
2199        }
2200
2201        /**
2202         * Begins a code block that supports `break` or `continue` statements that are defined in
2203         * the source tree and not from generated code.
2204         *
2205         * @param labelText Names from containing labeled statements.
2206         */
2207        function beginScriptLoopBlock(): void {
2208            beginBlock({
2209                kind: CodeBlockKind.Loop,
2210                isScript: true,
2211                breakLabel: -1,
2212                continueLabel: -1
2213            });
2214        }
2215
2216        /**
2217         * Begins a code block that supports `break` or `continue` statements that are defined in
2218         * generated code. Returns a label used to mark the operation to which to jump when a
2219         * `break` statement targets this block.
2220         *
2221         * @param continueLabel A Label used to mark the operation to which to jump when a
2222         *                      `continue` statement targets this block.
2223         */
2224        function beginLoopBlock(continueLabel: Label): Label {
2225            const breakLabel = defineLabel();
2226            beginBlock({
2227                kind: CodeBlockKind.Loop,
2228                isScript: false,
2229                breakLabel,
2230                continueLabel,
2231            });
2232            return breakLabel;
2233        }
2234
2235        /**
2236         * Ends a code block that supports `break` or `continue` statements that are defined in
2237         * generated code or in the source tree.
2238         */
2239        function endLoopBlock(): void {
2240            Debug.assert(peekBlockKind() === CodeBlockKind.Loop);
2241            const block = <SwitchBlock>endBlock();
2242            const breakLabel = block.breakLabel;
2243            if (!block.isScript) {
2244                markLabel(breakLabel);
2245            }
2246        }
2247
2248        /**
2249         * Begins a code block that supports `break` statements that are defined in the source
2250         * tree and not from generated code.
2251         *
2252         */
2253        function beginScriptSwitchBlock(): void {
2254            beginBlock({
2255                kind: CodeBlockKind.Switch,
2256                isScript: true,
2257                breakLabel: -1
2258            });
2259        }
2260
2261        /**
2262         * Begins a code block that supports `break` statements that are defined in generated code.
2263         * Returns a label used to mark the operation to which to jump when a `break` statement
2264         * targets this block.
2265         */
2266        function beginSwitchBlock(): Label {
2267            const breakLabel = defineLabel();
2268            beginBlock({
2269                kind: CodeBlockKind.Switch,
2270                isScript: false,
2271                breakLabel,
2272            });
2273            return breakLabel;
2274        }
2275
2276        /**
2277         * Ends a code block that supports `break` statements that are defined in generated code.
2278         */
2279        function endSwitchBlock(): void {
2280            Debug.assert(peekBlockKind() === CodeBlockKind.Switch);
2281            const block = <SwitchBlock>endBlock();
2282            const breakLabel = block.breakLabel;
2283            if (!block.isScript) {
2284                markLabel(breakLabel);
2285            }
2286        }
2287
2288        function beginScriptLabeledBlock(labelText: string) {
2289            beginBlock({
2290                kind: CodeBlockKind.Labeled,
2291                isScript: true,
2292                labelText,
2293                breakLabel: -1
2294            });
2295        }
2296
2297        function beginLabeledBlock(labelText: string) {
2298            const breakLabel = defineLabel();
2299            beginBlock({
2300                kind: CodeBlockKind.Labeled,
2301                isScript: false,
2302                labelText,
2303                breakLabel
2304            });
2305        }
2306
2307        function endLabeledBlock() {
2308            Debug.assert(peekBlockKind() === CodeBlockKind.Labeled);
2309            const block = <LabeledBlock>endBlock();
2310            if (!block.isScript) {
2311                markLabel(block.breakLabel);
2312            }
2313        }
2314
2315        /**
2316         * Indicates whether the provided block supports `break` statements.
2317         *
2318         * @param block A code block.
2319         */
2320        function supportsUnlabeledBreak(block: CodeBlock): block is SwitchBlock | LoopBlock {
2321            return block.kind === CodeBlockKind.Switch
2322                || block.kind === CodeBlockKind.Loop;
2323        }
2324
2325        /**
2326         * Indicates whether the provided block supports `break` statements with labels.
2327         *
2328         * @param block A code block.
2329         */
2330        function supportsLabeledBreakOrContinue(block: CodeBlock): block is LabeledBlock {
2331            return block.kind === CodeBlockKind.Labeled;
2332        }
2333
2334        /**
2335         * Indicates whether the provided block supports `continue` statements.
2336         *
2337         * @param block A code block.
2338         */
2339        function supportsUnlabeledContinue(block: CodeBlock): block is LoopBlock {
2340            return block.kind === CodeBlockKind.Loop;
2341        }
2342
2343        function hasImmediateContainingLabeledBlock(labelText: string, start: number) {
2344            for (let j = start; j >= 0; j--) {
2345                const containingBlock = blockStack![j];
2346                if (supportsLabeledBreakOrContinue(containingBlock)) {
2347                    if (containingBlock.labelText === labelText) {
2348                        return true;
2349                    }
2350                }
2351                else {
2352                    break;
2353                }
2354            }
2355
2356            return false;
2357        }
2358
2359        /**
2360         * Finds the label that is the target for a `break` statement.
2361         *
2362         * @param labelText An optional name of a containing labeled statement.
2363         */
2364        function findBreakTarget(labelText?: string): Label {
2365            if (blockStack) {
2366                if (labelText) {
2367                    for (let i = blockStack.length - 1; i >= 0; i--) {
2368                        const block = blockStack[i];
2369                        if (supportsLabeledBreakOrContinue(block) && block.labelText === labelText) {
2370                            return block.breakLabel;
2371                        }
2372                        else if (supportsUnlabeledBreak(block) && hasImmediateContainingLabeledBlock(labelText, i - 1)) {
2373                            return block.breakLabel;
2374                        }
2375                    }
2376                }
2377                else {
2378                    for (let i = blockStack.length - 1; i >= 0; i--) {
2379                        const block = blockStack[i];
2380                        if (supportsUnlabeledBreak(block)) {
2381                            return block.breakLabel;
2382                        }
2383                    }
2384                }
2385            }
2386            return 0;
2387        }
2388
2389        /**
2390         * Finds the label that is the target for a `continue` statement.
2391         *
2392         * @param labelText An optional name of a containing labeled statement.
2393         */
2394        function findContinueTarget(labelText?: string): Label {
2395            if (blockStack) {
2396                if (labelText) {
2397                    for (let i = blockStack.length - 1; i >= 0; i--) {
2398                        const block = blockStack[i];
2399                        if (supportsUnlabeledContinue(block) && hasImmediateContainingLabeledBlock(labelText, i - 1)) {
2400                            return block.continueLabel;
2401                        }
2402                    }
2403                }
2404                else {
2405                    for (let i = blockStack.length - 1; i >= 0; i--) {
2406                        const block = blockStack[i];
2407                        if (supportsUnlabeledContinue(block)) {
2408                            return block.continueLabel;
2409                        }
2410                    }
2411                }
2412            }
2413            return 0;
2414        }
2415
2416        /**
2417         * Creates an expression that can be used to indicate the value for a label.
2418         *
2419         * @param label A label.
2420         */
2421        function createLabel(label: Label | undefined): Expression {
2422            if (label !== undefined && label > 0) {
2423                if (labelExpressions === undefined) {
2424                    labelExpressions = [];
2425                }
2426
2427                const expression = factory.createNumericLiteral(-1);
2428                if (labelExpressions[label] === undefined) {
2429                    labelExpressions[label] = [expression];
2430                }
2431                else {
2432                    labelExpressions[label].push(expression);
2433                }
2434
2435                return expression;
2436            }
2437
2438            return factory.createOmittedExpression();
2439        }
2440
2441        /**
2442         * Creates a numeric literal for the provided instruction.
2443         */
2444        function createInstruction(instruction: Instruction): NumericLiteral {
2445            const literal = factory.createNumericLiteral(instruction);
2446            addSyntheticTrailingComment(literal, SyntaxKind.MultiLineCommentTrivia, getInstructionName(instruction));
2447            return literal;
2448        }
2449
2450        /**
2451         * Creates a statement that can be used indicate a Break operation to the provided label.
2452         *
2453         * @param label A label.
2454         * @param location An optional source map location for the statement.
2455         */
2456        function createInlineBreak(label: Label, location?: TextRange): ReturnStatement {
2457            Debug.assertLessThan(0, label, "Invalid label");
2458            return setTextRange(
2459                factory.createReturnStatement(
2460                    factory.createArrayLiteralExpression([
2461                        createInstruction(Instruction.Break),
2462                        createLabel(label)
2463                    ])
2464                ),
2465                location
2466            );
2467        }
2468
2469        /**
2470         * Creates a statement that can be used indicate a Return operation.
2471         *
2472         * @param expression The expression for the return statement.
2473         * @param location An optional source map location for the statement.
2474         */
2475        function createInlineReturn(expression?: Expression, location?: TextRange): ReturnStatement {
2476            return setTextRange(
2477                factory.createReturnStatement(
2478                    factory.createArrayLiteralExpression(expression
2479                        ? [createInstruction(Instruction.Return), expression]
2480                        : [createInstruction(Instruction.Return)]
2481                    )
2482                ),
2483                location
2484            );
2485        }
2486
2487        /**
2488         * Creates an expression that can be used to resume from a Yield operation.
2489         */
2490        function createGeneratorResume(location?: TextRange): LeftHandSideExpression {
2491            return setTextRange(
2492                factory.createCallExpression(
2493                    factory.createPropertyAccessExpression(state, "sent"),
2494                    /*typeArguments*/ undefined,
2495                    []
2496                ),
2497                location
2498            );
2499        }
2500
2501        /**
2502         * Emits an empty instruction.
2503         */
2504        function emitNop() {
2505            emitWorker(OpCode.Nop);
2506        }
2507
2508        /**
2509         * Emits a Statement.
2510         *
2511         * @param node A statement.
2512         */
2513        function emitStatement(node: Statement): void {
2514            if (node) {
2515                emitWorker(OpCode.Statement, [node]);
2516            }
2517            else {
2518                emitNop();
2519            }
2520        }
2521
2522        /**
2523         * Emits an Assignment operation.
2524         *
2525         * @param left The left-hand side of the assignment.
2526         * @param right The right-hand side of the assignment.
2527         * @param location An optional source map location for the assignment.
2528         */
2529        function emitAssignment(left: Expression, right: Expression, location?: TextRange): void {
2530            emitWorker(OpCode.Assign, [left, right], location);
2531        }
2532
2533        /**
2534         * Emits a Break operation to the specified label.
2535         *
2536         * @param label A label.
2537         * @param location An optional source map location for the assignment.
2538         */
2539        function emitBreak(label: Label, location?: TextRange): void {
2540            emitWorker(OpCode.Break, [label], location);
2541        }
2542
2543        /**
2544         * Emits a Break operation to the specified label when a condition evaluates to a truthy
2545         * value at runtime.
2546         *
2547         * @param label A label.
2548         * @param condition The condition.
2549         * @param location An optional source map location for the assignment.
2550         */
2551        function emitBreakWhenTrue(label: Label, condition: Expression, location?: TextRange): void {
2552            emitWorker(OpCode.BreakWhenTrue, [label, condition], location);
2553        }
2554
2555        /**
2556         * Emits a Break to the specified label when a condition evaluates to a falsey value at
2557         * runtime.
2558         *
2559         * @param label A label.
2560         * @param condition The condition.
2561         * @param location An optional source map location for the assignment.
2562         */
2563        function emitBreakWhenFalse(label: Label, condition: Expression, location?: TextRange): void {
2564            emitWorker(OpCode.BreakWhenFalse, [label, condition], location);
2565        }
2566
2567        /**
2568         * Emits a YieldStar operation for the provided expression.
2569         *
2570         * @param expression An optional value for the yield operation.
2571         * @param location An optional source map location for the assignment.
2572         */
2573        function emitYieldStar(expression?: Expression, location?: TextRange): void {
2574            emitWorker(OpCode.YieldStar, [expression], location);
2575        }
2576
2577        /**
2578         * Emits a Yield operation for the provided expression.
2579         *
2580         * @param expression An optional value for the yield operation.
2581         * @param location An optional source map location for the assignment.
2582         */
2583        function emitYield(expression?: Expression, location?: TextRange): void {
2584            emitWorker(OpCode.Yield, [expression], location);
2585        }
2586
2587        /**
2588         * Emits a Return operation for the provided expression.
2589         *
2590         * @param expression An optional value for the operation.
2591         * @param location An optional source map location for the assignment.
2592         */
2593        function emitReturn(expression?: Expression, location?: TextRange): void {
2594            emitWorker(OpCode.Return, [expression], location);
2595        }
2596
2597        /**
2598         * Emits a Throw operation for the provided expression.
2599         *
2600         * @param expression A value for the operation.
2601         * @param location An optional source map location for the assignment.
2602         */
2603        function emitThrow(expression: Expression, location?: TextRange): void {
2604            emitWorker(OpCode.Throw, [expression], location);
2605        }
2606
2607        /**
2608         * Emits an Endfinally operation. This is used to handle `finally` block semantics.
2609         */
2610        function emitEndfinally(): void {
2611            emitWorker(OpCode.Endfinally);
2612        }
2613
2614        /**
2615         * Emits an operation.
2616         *
2617         * @param code The OpCode for the operation.
2618         * @param args The optional arguments for the operation.
2619         */
2620        function emitWorker(code: OpCode, args?: OperationArguments, location?: TextRange): void {
2621            if (operations === undefined) {
2622                operations = [];
2623                operationArguments = [];
2624                operationLocations = [];
2625            }
2626
2627            if (labelOffsets === undefined) {
2628                // mark entry point
2629                markLabel(defineLabel());
2630            }
2631
2632            const operationIndex = operations.length;
2633            operations[operationIndex] = code;
2634            operationArguments![operationIndex] = args;
2635            operationLocations![operationIndex] = location;
2636        }
2637
2638        /**
2639         * Builds the generator function body.
2640         */
2641        function build() {
2642            blockIndex = 0;
2643            labelNumber = 0;
2644            labelNumbers = undefined;
2645            lastOperationWasAbrupt = false;
2646            lastOperationWasCompletion = false;
2647            clauses = undefined;
2648            statements = undefined;
2649            exceptionBlockStack = undefined;
2650            currentExceptionBlock = undefined;
2651            withBlockStack = undefined;
2652
2653            const buildResult = buildStatements();
2654            return emitHelpers().createGeneratorHelper(
2655                setEmitFlags(
2656                    factory.createFunctionExpression(
2657                        /*modifiers*/ undefined,
2658                        /*asteriskToken*/ undefined,
2659                        /*name*/ undefined,
2660                        /*typeParameters*/ undefined,
2661                        [factory.createParameterDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, /*dotDotDotToken*/ undefined, state)],
2662                        /*type*/ undefined,
2663                        factory.createBlock(
2664                            buildResult,
2665                            /*multiLine*/ buildResult.length > 0
2666                        )
2667                    ),
2668                    EmitFlags.ReuseTempVariableScope
2669                )
2670            );
2671        }
2672
2673        /**
2674         * Builds the statements for the generator function body.
2675         */
2676        function buildStatements(): Statement[] {
2677            if (operations) {
2678                for (let operationIndex = 0; operationIndex < operations.length; operationIndex++) {
2679                    writeOperation(operationIndex);
2680                }
2681
2682                flushFinalLabel(operations.length);
2683            }
2684            else {
2685                flushFinalLabel(0);
2686            }
2687
2688            if (clauses) {
2689                const labelExpression = factory.createPropertyAccessExpression(state, "label");
2690                const switchStatement = factory.createSwitchStatement(labelExpression, factory.createCaseBlock(clauses));
2691                return [startOnNewLine(switchStatement)];
2692            }
2693
2694            if (statements) {
2695                return statements;
2696            }
2697
2698            return [];
2699        }
2700
2701        /**
2702         * Flush the current label and advance to a new label.
2703         */
2704        function flushLabel(): void {
2705            if (!statements) {
2706                return;
2707            }
2708
2709            appendLabel(/*markLabelEnd*/ !lastOperationWasAbrupt);
2710
2711            lastOperationWasAbrupt = false;
2712            lastOperationWasCompletion = false;
2713            labelNumber++;
2714        }
2715
2716        /**
2717         * Flush the final label of the generator function body.
2718         */
2719        function flushFinalLabel(operationIndex: number): void {
2720            if (isFinalLabelReachable(operationIndex)) {
2721                tryEnterLabel(operationIndex);
2722                withBlockStack = undefined;
2723                writeReturn(/*expression*/ undefined, /*operationLocation*/ undefined);
2724            }
2725
2726            if (statements && clauses) {
2727                appendLabel(/*markLabelEnd*/ false);
2728            }
2729
2730            updateLabelExpressions();
2731        }
2732
2733        /**
2734         * Tests whether the final label of the generator function body
2735         * is reachable by user code.
2736         */
2737        function isFinalLabelReachable(operationIndex: number) {
2738            // if the last operation was *not* a completion (return/throw) then
2739            // the final label is reachable.
2740            if (!lastOperationWasCompletion) {
2741                return true;
2742            }
2743
2744            // if there are no labels defined or referenced, then the final label is
2745            // not reachable.
2746            if (!labelOffsets || !labelExpressions) {
2747                return false;
2748            }
2749
2750            // if the label for this offset is referenced, then the final label
2751            // is reachable.
2752            for (let label = 0; label < labelOffsets.length; label++) {
2753                if (labelOffsets[label] === operationIndex && labelExpressions[label]) {
2754                    return true;
2755                }
2756            }
2757
2758            return false;
2759        }
2760
2761        /**
2762         * Appends a case clause for the last label and sets the new label.
2763         *
2764         * @param markLabelEnd Indicates that the transition between labels was a fall-through
2765         *                     from a previous case clause and the change in labels should be
2766         *                     reflected on the `state` object.
2767         */
2768        function appendLabel(markLabelEnd: boolean): void {
2769            if (!clauses) {
2770                clauses = [];
2771            }
2772
2773            if (statements) {
2774                if (withBlockStack) {
2775                    // The previous label was nested inside one or more `with` blocks, so we
2776                    // surround the statements in generated `with` blocks to create the same environment.
2777                    for (let i = withBlockStack.length - 1; i >= 0; i--) {
2778                        const withBlock = withBlockStack[i];
2779                        statements = [factory.createWithStatement(withBlock.expression, factory.createBlock(statements))];
2780                    }
2781                }
2782
2783                if (currentExceptionBlock) {
2784                    // The previous label was nested inside of an exception block, so we must
2785                    // indicate entry into a protected region by pushing the label numbers
2786                    // for each block in the protected region.
2787                    const { startLabel, catchLabel, finallyLabel, endLabel } = currentExceptionBlock;
2788                    statements.unshift(
2789                        factory.createExpressionStatement(
2790                            factory.createCallExpression(
2791                                factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(state, "trys"), "push"),
2792                                /*typeArguments*/ undefined,
2793                                [
2794                                    factory.createArrayLiteralExpression([
2795                                        createLabel(startLabel),
2796                                        createLabel(catchLabel),
2797                                        createLabel(finallyLabel),
2798                                        createLabel(endLabel)
2799                                    ])
2800                                ]
2801                            )
2802                        )
2803                    );
2804
2805                    currentExceptionBlock = undefined;
2806                }
2807
2808                if (markLabelEnd) {
2809                    // The case clause for the last label falls through to this label, so we
2810                    // add an assignment statement to reflect the change in labels.
2811                    statements.push(
2812                        factory.createExpressionStatement(
2813                            factory.createAssignment(
2814                                factory.createPropertyAccessExpression(state, "label"),
2815                                factory.createNumericLiteral(labelNumber + 1)
2816                            )
2817                        )
2818                    );
2819                }
2820            }
2821
2822            clauses.push(
2823                factory.createCaseClause(
2824                    factory.createNumericLiteral(labelNumber),
2825                    statements || []
2826                )
2827            );
2828
2829            statements = undefined;
2830        }
2831
2832        /**
2833         * Tries to enter into a new label at the current operation index.
2834         */
2835        function tryEnterLabel(operationIndex: number): void {
2836            if (!labelOffsets) {
2837                return;
2838            }
2839
2840            for (let label = 0; label < labelOffsets.length; label++) {
2841                if (labelOffsets[label] === operationIndex) {
2842                    flushLabel();
2843                    if (labelNumbers === undefined) {
2844                        labelNumbers = [];
2845                    }
2846                    if (labelNumbers[labelNumber] === undefined) {
2847                        labelNumbers[labelNumber] = [label];
2848                    }
2849                    else {
2850                        labelNumbers[labelNumber].push(label);
2851                    }
2852                }
2853            }
2854        }
2855
2856        /**
2857         * Updates literal expressions for labels with actual label numbers.
2858         */
2859        function updateLabelExpressions() {
2860            if (labelExpressions !== undefined && labelNumbers !== undefined) {
2861                for (let labelNumber = 0; labelNumber < labelNumbers.length; labelNumber++) {
2862                    const labels = labelNumbers[labelNumber];
2863                    if (labels !== undefined) {
2864                        for (const label of labels) {
2865                            const expressions = labelExpressions[label];
2866                            if (expressions !== undefined) {
2867                                for (const expression of expressions) {
2868                                    expression.text = String(labelNumber);
2869                                }
2870                            }
2871                        }
2872                    }
2873                }
2874            }
2875        }
2876
2877        /**
2878         * Tries to enter or leave a code block.
2879         */
2880        function tryEnterOrLeaveBlock(operationIndex: number): void {
2881            if (blocks) {
2882                for (; blockIndex < blockActions!.length && blockOffsets![blockIndex] <= operationIndex; blockIndex++) {
2883                    const block: CodeBlock = blocks[blockIndex];
2884                    const blockAction = blockActions![blockIndex];
2885                    switch (block.kind) {
2886                        case CodeBlockKind.Exception:
2887                            if (blockAction === BlockAction.Open) {
2888                                if (!exceptionBlockStack) {
2889                                    exceptionBlockStack = [];
2890                                }
2891
2892                                if (!statements) {
2893                                    statements = [];
2894                                }
2895
2896                                exceptionBlockStack.push(currentExceptionBlock!);
2897                                currentExceptionBlock = block;
2898                            }
2899                            else if (blockAction === BlockAction.Close) {
2900                                currentExceptionBlock = exceptionBlockStack!.pop();
2901                            }
2902                            break;
2903                        case CodeBlockKind.With:
2904                            if (blockAction === BlockAction.Open) {
2905                                if (!withBlockStack) {
2906                                    withBlockStack = [];
2907                                }
2908
2909                                withBlockStack.push(block);
2910                            }
2911                            else if (blockAction === BlockAction.Close) {
2912                                withBlockStack!.pop();
2913                            }
2914                            break;
2915                        // default: do nothing
2916                    }
2917                }
2918            }
2919        }
2920
2921        /**
2922         * Writes an operation as a statement to the current label's statement list.
2923         *
2924         * @param operation The OpCode of the operation
2925         */
2926        function writeOperation(operationIndex: number): void {
2927            tryEnterLabel(operationIndex);
2928            tryEnterOrLeaveBlock(operationIndex);
2929
2930            // early termination, nothing else to process in this label
2931            if (lastOperationWasAbrupt) {
2932                return;
2933            }
2934
2935            lastOperationWasAbrupt = false;
2936            lastOperationWasCompletion = false;
2937
2938            const opcode = operations![operationIndex];
2939            if (opcode === OpCode.Nop) {
2940                return;
2941            }
2942            else if (opcode === OpCode.Endfinally) {
2943                return writeEndfinally();
2944            }
2945
2946            const args = operationArguments![operationIndex]!;
2947            if (opcode === OpCode.Statement) {
2948                return writeStatement(<Statement>args[0]);
2949            }
2950
2951            const location = operationLocations![operationIndex];
2952            switch (opcode) {
2953                case OpCode.Assign:
2954                    return writeAssign(<Expression>args[0], <Expression>args[1], location);
2955                case OpCode.Break:
2956                    return writeBreak(<Label>args[0], location);
2957                case OpCode.BreakWhenTrue:
2958                    return writeBreakWhenTrue(<Label>args[0], <Expression>args[1], location);
2959                case OpCode.BreakWhenFalse:
2960                    return writeBreakWhenFalse(<Label>args[0], <Expression>args[1], location);
2961                case OpCode.Yield:
2962                    return writeYield(<Expression>args[0], location);
2963                case OpCode.YieldStar:
2964                    return writeYieldStar(<Expression>args[0], location);
2965                case OpCode.Return:
2966                    return writeReturn(<Expression>args[0], location);
2967                case OpCode.Throw:
2968                    return writeThrow(<Expression>args[0], location);
2969            }
2970        }
2971
2972        /**
2973         * Writes a statement to the current label's statement list.
2974         *
2975         * @param statement A statement to write.
2976         */
2977        function writeStatement(statement: Statement): void {
2978            if (statement) {
2979                if (!statements) {
2980                    statements = [statement];
2981                }
2982                else {
2983                    statements.push(statement);
2984                }
2985            }
2986        }
2987
2988        /**
2989         * Writes an Assign operation to the current label's statement list.
2990         *
2991         * @param left The left-hand side of the assignment.
2992         * @param right The right-hand side of the assignment.
2993         * @param operationLocation The source map location for the operation.
2994         */
2995        function writeAssign(left: Expression, right: Expression, operationLocation: TextRange | undefined): void {
2996            writeStatement(setTextRange(factory.createExpressionStatement(factory.createAssignment(left, right)), operationLocation));
2997        }
2998
2999        /**
3000         * Writes a Throw operation to the current label's statement list.
3001         *
3002         * @param expression The value to throw.
3003         * @param operationLocation The source map location for the operation.
3004         */
3005        function writeThrow(expression: Expression, operationLocation: TextRange | undefined): void {
3006            lastOperationWasAbrupt = true;
3007            lastOperationWasCompletion = true;
3008            writeStatement(setTextRange(factory.createThrowStatement(expression), operationLocation));
3009        }
3010
3011        /**
3012         * Writes a Return operation to the current label's statement list.
3013         *
3014         * @param expression The value to return.
3015         * @param operationLocation The source map location for the operation.
3016         */
3017        function writeReturn(expression: Expression | undefined, operationLocation: TextRange | undefined): void {
3018            lastOperationWasAbrupt = true;
3019            lastOperationWasCompletion = true;
3020            writeStatement(
3021                setEmitFlags(
3022                    setTextRange(
3023                        factory.createReturnStatement(
3024                            factory.createArrayLiteralExpression(expression
3025                                ? [createInstruction(Instruction.Return), expression]
3026                                : [createInstruction(Instruction.Return)]
3027                            )
3028                        ),
3029                        operationLocation
3030                    ),
3031                    EmitFlags.NoTokenSourceMaps
3032                )
3033            );
3034        }
3035
3036        /**
3037         * Writes a Break operation to the current label's statement list.
3038         *
3039         * @param label The label for the Break.
3040         * @param operationLocation The source map location for the operation.
3041         */
3042        function writeBreak(label: Label, operationLocation: TextRange | undefined): void {
3043            lastOperationWasAbrupt = true;
3044            writeStatement(
3045                setEmitFlags(
3046                    setTextRange(
3047                        factory.createReturnStatement(
3048                            factory.createArrayLiteralExpression([
3049                                createInstruction(Instruction.Break),
3050                                createLabel(label)
3051                            ])
3052                        ),
3053                        operationLocation
3054                    ),
3055                    EmitFlags.NoTokenSourceMaps
3056                )
3057            );
3058        }
3059
3060        /**
3061         * Writes a BreakWhenTrue operation to the current label's statement list.
3062         *
3063         * @param label The label for the Break.
3064         * @param condition The condition for the Break.
3065         * @param operationLocation The source map location for the operation.
3066         */
3067        function writeBreakWhenTrue(label: Label, condition: Expression, operationLocation: TextRange | undefined): void {
3068            writeStatement(
3069                setEmitFlags(
3070                    factory.createIfStatement(
3071                        condition,
3072                        setEmitFlags(
3073                            setTextRange(
3074                                factory.createReturnStatement(
3075                                    factory.createArrayLiteralExpression([
3076                                        createInstruction(Instruction.Break),
3077                                        createLabel(label)
3078                                    ])
3079                                ),
3080                                operationLocation
3081                            ),
3082                            EmitFlags.NoTokenSourceMaps
3083                        )
3084                    ),
3085                    EmitFlags.SingleLine
3086                )
3087            );
3088        }
3089
3090        /**
3091         * Writes a BreakWhenFalse operation to the current label's statement list.
3092         *
3093         * @param label The label for the Break.
3094         * @param condition The condition for the Break.
3095         * @param operationLocation The source map location for the operation.
3096         */
3097        function writeBreakWhenFalse(label: Label, condition: Expression, operationLocation: TextRange | undefined): void {
3098            writeStatement(
3099                setEmitFlags(
3100                    factory.createIfStatement(
3101                        factory.createLogicalNot(condition),
3102                        setEmitFlags(
3103                            setTextRange(
3104                                factory.createReturnStatement(
3105                                    factory.createArrayLiteralExpression([
3106                                        createInstruction(Instruction.Break),
3107                                        createLabel(label)
3108                                    ])
3109                                ),
3110                                operationLocation
3111                            ),
3112                            EmitFlags.NoTokenSourceMaps
3113                        )
3114                    ),
3115                    EmitFlags.SingleLine
3116                )
3117            );
3118        }
3119
3120        /**
3121         * Writes a Yield operation to the current label's statement list.
3122         *
3123         * @param expression The expression to yield.
3124         * @param operationLocation The source map location for the operation.
3125         */
3126        function writeYield(expression: Expression, operationLocation: TextRange | undefined): void {
3127            lastOperationWasAbrupt = true;
3128            writeStatement(
3129                setEmitFlags(
3130                    setTextRange(
3131                        factory.createReturnStatement(
3132                            factory.createArrayLiteralExpression(
3133                                expression
3134                                    ? [createInstruction(Instruction.Yield), expression]
3135                                    : [createInstruction(Instruction.Yield)]
3136                            )
3137                        ),
3138                        operationLocation
3139                    ),
3140                    EmitFlags.NoTokenSourceMaps
3141                )
3142            );
3143        }
3144
3145        /**
3146         * Writes a YieldStar instruction to the current label's statement list.
3147         *
3148         * @param expression The expression to yield.
3149         * @param operationLocation The source map location for the operation.
3150         */
3151        function writeYieldStar(expression: Expression, operationLocation: TextRange | undefined): void {
3152            lastOperationWasAbrupt = true;
3153            writeStatement(
3154                setEmitFlags(
3155                    setTextRange(
3156                        factory.createReturnStatement(
3157                            factory.createArrayLiteralExpression([
3158                                createInstruction(Instruction.YieldStar),
3159                                expression
3160                            ])
3161                        ),
3162                        operationLocation
3163                    ),
3164                    EmitFlags.NoTokenSourceMaps
3165                )
3166            );
3167        }
3168
3169        /**
3170         * Writes an Endfinally instruction to the current label's statement list.
3171         */
3172        function writeEndfinally(): void {
3173            lastOperationWasAbrupt = true;
3174            writeStatement(
3175                factory.createReturnStatement(
3176                    factory.createArrayLiteralExpression([
3177                        createInstruction(Instruction.Endfinally)
3178                    ])
3179                )
3180            );
3181        }
3182    }
3183}
3184