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