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