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