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