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