Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library rewrite_async; | 5 library rewrite_async; |
| 6 | 6 |
| 7 import "dart:math" show max; | 7 import "dart:math" show max; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'package:_internal/compiler/js_lib/shared/async_await_error_codes.dart' | 10 import 'package:_internal/compiler/js_lib/shared/async_await_error_codes.dart' |
| 11 as error_codes; | 11 as error_codes; |
| 12 | 12 |
| 13 import "js.dart" as js; | 13 import "js.dart" as js; |
| 14 | 14 |
| 15 import '../util/util.dart'; | 15 import '../util/util.dart'; |
| 16 import '../dart2jslib.dart' show DiagnosticListener; | 16 import '../dart2jslib.dart' show DiagnosticListener; |
| 17 | 17 |
| 18 import "../helpers/helpers.dart"; | 18 import "../helpers/helpers.dart"; |
| 19 | 19 |
| 20 /// Rewrites a [js.Fun] with async/sync*/async* functions and await and yield | 20 /// Rewrites a [js.Fun] with async/sync*/async* functions and await and yield |
| 21 /// (with dart-like semantics) to an equivalent function without these. | 21 /// (with dart-like semantics) to an equivalent function without these. |
| 22 /// await-for is not handled and must be rewritten before. (Currently handled | 22 /// await-for is not handled and must be rewritten before. (Currently handled |
| 23 /// in ssa/builder.dart). | 23 /// in ssa/builder.dart). |
| 24 /// | 24 /// |
| 25 /// When generating the input to this, special care must be taken that | 25 /// When generating the input to this, special care must be taken that |
| 26 /// parameters to sync* functions that are mutated in the body must be boxed. | 26 /// parameters to sync* functions that are mutated in the body must be boxed. |
| 27 /// (Currently handled in closure.dart). | 27 /// (Currently handled in closure.dart). |
| 28 /// | 28 /// |
| 29 /// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation. | 29 /// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation. |
| 30 class AsyncRewriter extends js.NodeVisitor { | 30 abstract class AsyncRewriterBase extends js.NodeVisitor { |
| 31 | 31 |
| 32 // Local variables are hoisted to the top of the function, so they are | 32 // Local variables are hoisted to the top of the function, so they are |
| 33 // collected here. | 33 // collected here. |
| 34 List<js.VariableDeclaration> localVariables = | 34 List<js.VariableDeclaration> localVariables = |
| 35 new List<js.VariableDeclaration>(); | 35 new List<js.VariableDeclaration>(); |
| 36 | 36 |
| 37 Map<js.Node, int> continueLabels = new Map<js.Node, int>(); | 37 Map<js.Node, int> continueLabels = new Map<js.Node, int>(); |
| 38 Map<js.Node, int> breakLabels = new Map<js.Node, int>(); | 38 Map<js.Node, int> breakLabels = new Map<js.Node, int>(); |
| 39 | 39 |
| 40 /// The label of a finally part. | 40 /// The label of a finally part. |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 101 /// result = [[a]]; | 101 /// result = [[a]]; |
| 102 /// goto = joinLabel; | 102 /// goto = joinLabel; |
| 103 /// case elseLabel: | 103 /// case elseLabel: |
| 104 /// result = [[b]]; | 104 /// result = [[b]]; |
| 105 /// case joinLabel: | 105 /// case joinLabel: |
| 106 /// // Now the result of computing the condition is in result. | 106 /// // Now the result of computing the condition is in result. |
| 107 /// .... | 107 /// .... |
| 108 /// } | 108 /// } |
| 109 /// } | 109 /// } |
| 110 /// | 110 /// |
| 111 /// It is a parameter to the [bodyName] function, so that [asyncHelper] and | 111 /// It is a parameter to the [body] function, so that [awaitStatement] can |
| 112 /// [streamHelper] can call [bodyName] with the result of an awaited Future. | 112 /// call [body] with the result of an awaited Future. |
| 113 js.VariableUse get result => new js.VariableUse(resultName); | |
| 113 String resultName; | 114 String resultName; |
| 114 | 115 |
| 115 /// A parameter to the [bodyName] function. Indicating if we are in success | 116 /// A parameter to the [bodyName] function. Indicating if we are in success |
| 116 /// or error case. | 117 /// or error case. |
| 117 String errorCodeName; | 118 String errorCodeName; |
| 118 | 119 |
| 119 /// The name of the inner function that is scheduled to do each await/yield, | 120 /// The inner function that is scheduled to do each await/yield, |
| 120 /// and called to do a new iteration for sync*. | 121 /// and called to do a new iteration for sync*. |
| 122 js.VariableUse get body => new js.VariableUse(bodyName); | |
| 121 String bodyName; | 123 String bodyName; |
| 122 | 124 |
| 123 /// The Completer that will finish an async function. | |
| 124 /// | |
| 125 /// Not used for sync* or async* functions. | |
| 126 String completerName; | |
| 127 | |
| 128 /// The StreamController that controls an async* function. | |
| 129 /// | |
| 130 /// Not used for async and sync* functions | |
| 131 String controllerName; | |
| 132 | |
| 133 /// Used to simulate a goto. | 125 /// Used to simulate a goto. |
| 134 /// | 126 /// |
| 135 /// To "goto" a label, the label is assigned to this | 127 /// To "goto" a label, the label is assigned to this variable, and break out |
| 136 /// variable, and break out of the switch to take another iteration in the | 128 /// of the switch to take another iteration in the while loop. See [addGoto] |
| 137 /// while loop. See [addGoto] | 129 js.VariableUse get goto => new js.VariableUse(gotoName); |
| 138 String gotoName; | 130 String gotoName; |
| 139 | 131 |
| 140 /// The label of the current error handler. | 132 /// Variable containing the label of the current error handler. |
| 133 js.VariableUse get handler => new js.VariableUse(handlerName); | |
| 141 String handlerName; | 134 String handlerName; |
| 142 | 135 |
| 143 /// Current caught error. | |
| 144 String errorName; | |
| 145 | |
| 146 /// A stack of labels of finally blocks to visit, and the label to go to after | 136 /// A stack of labels of finally blocks to visit, and the label to go to after |
| 147 /// the last. | 137 /// the last. |
| 138 js.VariableUse get next => new js.VariableUse(nextName); | |
| 148 String nextName; | 139 String nextName; |
| 149 | 140 |
| 150 /// The stack of labels of finally blocks to assign to [nextName] if the | |
| 151 /// async* [StreamSubscription] was canceled during a yield. | |
| 152 String nextWhenCanceledName; | |
| 153 | |
| 154 /// The current returned value (a finally block may overwrite it). | 141 /// The current returned value (a finally block may overwrite it). |
| 142 js.VariableUse get returnValue => new js.VariableUse(returnValueName); | |
| 155 String returnValueName; | 143 String returnValueName; |
| 156 | 144 |
| 157 /// If we are in the process of handling an error, stores the current error. | 145 /// Stores the current error when we are in the process of handling an error. |
| 146 js.VariableUse get currentError => new js.VariableUse(currentErrorName); | |
| 158 String currentErrorName; | 147 String currentErrorName; |
| 159 | 148 |
| 160 /// The label of the outer loop. | 149 /// The label of the outer loop. |
| 161 /// | 150 /// |
| 162 /// Used if there are untransformed loops containing break or continues to | 151 /// Used if there are untransformed loops containing break or continues to |
| 163 /// targets outside the loop. | 152 /// targets outside the loop. |
| 164 String outerLabelName; | 153 String outerLabelName; |
| 165 | 154 |
| 166 /// If javascript `this` is used, it is accessed via this variable, in the | 155 /// If javascript `this` is used, it is accessed via this variable, in the |
| 167 /// [bodyName] function. | 156 /// [bodyName] function. |
| 157 js.VariableUse get self => new js.VariableUse(selfName); | |
| 168 String selfName; | 158 String selfName; |
| 169 | 159 |
| 170 // These expressions are hooks for communicating with the runtime. | |
| 171 | |
| 172 /// The function called by an async function to simulate an await or return. | |
| 173 /// | |
| 174 /// For an await it is called with: | |
| 175 /// | |
| 176 /// - The value to await | |
| 177 /// - The body function [bodyName] | |
| 178 /// - The completer object [completerName] | |
| 179 /// | |
| 180 /// For a return it is called with: | |
| 181 /// | |
| 182 /// - The value to complete the completer with. | |
| 183 /// - [error_codes.SUCCESS] | |
| 184 /// - The completer object [completerName] | |
| 185 /// | |
| 186 /// For a throw it is called with: | |
| 187 /// | |
| 188 /// - The error to complete the completer with. | |
| 189 /// - [error_codes.ERROR] | |
| 190 /// - The completer object [completerName] | |
| 191 final js.Expression asyncHelper; | |
| 192 | |
| 193 /// The function called by an async* function to simulate an await, yield or | |
| 194 /// yield*. | |
| 195 /// | |
| 196 /// For an await/yield/yield* it is called with: | |
| 197 /// | |
| 198 /// - The value to await/yieldExpression(value to yield)/ | |
| 199 /// yieldStarExpression(stream to yield) | |
| 200 /// - The body function [bodyName] | |
| 201 /// - The controller object [controllerName] | |
| 202 /// | |
| 203 /// For a return it is called with: | |
| 204 /// | |
| 205 /// - null | |
| 206 /// - null | |
| 207 /// - The [controllerName] | |
| 208 /// - null. | |
| 209 final js.Expression streamHelper; | |
| 210 | |
| 211 /// Contructor used to initialize the [completerName] variable. | |
| 212 /// | |
| 213 /// Specific to async methods. | |
| 214 final js.Expression newCompleter; | |
| 215 | |
| 216 /// Contructor used to initialize the [controllerName] variable. | |
| 217 /// | |
| 218 /// Specific to async* methods. | |
| 219 final js.Expression newController; | |
| 220 | |
| 221 /// Used to get the `Stream` out of the [controllerName] variable. | |
| 222 /// | |
| 223 /// Specific to async* methods. | |
| 224 final js.Expression streamOfController; | |
| 225 | |
| 226 /// Contructor creating the Iterable for a sync* method. Called with | |
| 227 /// [bodyName]. | |
| 228 final js.Expression newIterable; | |
| 229 | |
| 230 /// A JS Expression that creates a marker showing that iteration is over. | |
| 231 /// | |
| 232 /// Called without arguments. | |
| 233 final js.Expression endOfIteration; | |
| 234 | |
| 235 /// A JS Expression that creates a marker indicating a 'yield' statement. | |
| 236 /// | |
| 237 /// Called with the value to yield. | |
| 238 final js.Expression yieldExpression; | |
| 239 | |
| 240 /// A JS Expression that creates a marker indication a 'yield*' statement. | |
| 241 /// | |
| 242 /// Called with the stream to yield from. | |
| 243 final js.Expression yieldStarExpression; | |
| 244 | |
| 245 /// Used by sync* functions to throw exeptions. | |
| 246 final js.Expression uncaughtErrorExpression; | |
| 247 | |
| 248 final DiagnosticListener diagnosticListener; | 160 final DiagnosticListener diagnosticListener; |
| 249 // For error reporting only. | 161 // For error reporting only. |
| 250 Spannable get spannable { | 162 Spannable get spannable { |
| 251 return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable; | 163 return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable; |
| 252 } | 164 } |
| 253 | 165 |
| 254 Spannable _spannable; | 166 Spannable _spannable; |
| 255 | 167 |
| 256 int _currentLabel = 0; | 168 int _currentLabel = 0; |
| 257 | 169 |
| 258 // The highest temporary variable index currently in use. | 170 // The highest temporary variable index currently in use. |
| 259 int currentTempVarIndex = 0; | 171 int currentTempVarIndex = 0; |
| 260 // The highest temporary variable index ever in use in this function. | 172 // The highest temporary variable index ever in use in this function. |
| 261 int tempVarHighWaterMark = 0; | 173 int tempVarHighWaterMark = 0; |
| 262 Map<int, js.Expression> tempVarNames = new Map<int, js.Expression>(); | 174 Map<int, js.Expression> tempVarNames = new Map<int, js.Expression>(); |
| 263 | 175 |
| 264 js.AsyncModifier async; | 176 bool get isAsync => false; |
| 177 bool get isSyncStar => false; | |
| 178 bool get isAsyncStar => false; | |
| 265 | 179 |
| 266 bool get isSync => async == const js.AsyncModifier.sync(); | 180 AsyncRewriterBase(this.diagnosticListener, |
| 267 bool get isAsync => async == const js.AsyncModifier.async(); | 181 spannable, |
| 268 bool get isSyncStar => async == const js.AsyncModifier.syncStar(); | 182 this.safeVariableName) |
| 269 bool get isAsyncStar => async == const js.AsyncModifier.asyncStar(); | 183 : _spannable = spannable; |
| 270 | 184 |
| 271 AsyncRewriter(this.diagnosticListener, | 185 /// Initialize names used by the subClass. |
| 272 spannable, | 186 void initializeNames(); |
| 273 {this.asyncHelper, | |
| 274 this.streamHelper, | |
| 275 this.streamOfController, | |
| 276 this.newCompleter, | |
| 277 this.newController, | |
| 278 this.endOfIteration, | |
| 279 this.newIterable, | |
| 280 this.yieldExpression, | |
| 281 this.yieldStarExpression, | |
| 282 this.uncaughtErrorExpression, | |
| 283 this.safeVariableName}) | |
| 284 : _spannable = spannable; | |
| 285 | 187 |
| 286 /// Main entry point. | 188 /// Main entry point. |
| 287 /// Rewrites a sync*/async/async* function to an equivalent normal function. | 189 /// Rewrites a sync*/async/async* function to an equivalent normal function. |
| 288 /// | 190 /// |
| 289 /// [spannable] can be passed to have a location for error messages. | 191 /// [spannable] can be passed to have a location for error messages. |
| 290 js.Fun rewrite(js.Fun node, [Spannable spannable]) { | 192 js.Fun rewrite(js.Fun node, [Spannable spannable]) { |
| 291 _spannable = spannable; | 193 _spannable = spannable; |
| 292 | 194 |
| 293 async = node.asyncModifier; | |
| 294 assert(!isSync); | |
| 295 | |
| 296 analysis = new PreTranslationAnalysis(unsupported); | 195 analysis = new PreTranslationAnalysis(unsupported); |
| 297 analysis.analyze(node); | 196 analysis.analyze(node); |
| 298 | 197 |
| 299 // To avoid name collisions with existing names, the fresh names are | 198 // To avoid name collisions with existing names, the fresh names are |
| 300 // generated after the analysis. | 199 // generated after the analysis. |
| 301 resultName = freshName("result"); | 200 resultName = freshName("result"); |
| 302 errorCodeName = freshName("errorCode"); | 201 errorCodeName = freshName("errorCode"); |
| 303 completerName = freshName("completer"); | |
| 304 controllerName = freshName("controller"); | |
| 305 bodyName = freshName("body"); | 202 bodyName = freshName("body"); |
| 306 gotoName = freshName("goto"); | 203 gotoName = freshName("goto"); |
| 307 handlerName = freshName("handler"); | 204 handlerName = freshName("handler"); |
| 308 errorName = freshName("error"); | |
| 309 nextName = freshName("next"); | 205 nextName = freshName("next"); |
| 310 nextWhenCanceledName = freshName("nextWhenCanceled"); | |
| 311 returnValueName = freshName("returnValue"); | 206 returnValueName = freshName("returnValue"); |
| 312 currentErrorName = freshName("currentError"); | 207 currentErrorName = freshName("currentError"); |
| 313 outerLabelName = freshName("outer"); | 208 outerLabelName = freshName("outer"); |
| 314 selfName = freshName("self"); | 209 selfName = freshName("self"); |
| 210 // Initialize names specific to the subclass. | |
| 211 initializeNames(); | |
| 315 | 212 |
| 316 return node.accept(this); | 213 return node.accept(this); |
| 317 } | 214 } |
| 318 | 215 |
| 319 js.Expression get currentErrorHandler { | 216 js.Expression get currentErrorHandler { |
| 320 return js.number(handlerLabels[jumpTargets.lastWhere( | 217 return js.number(handlerLabels[jumpTargets.lastWhere( |
| 321 (node) => handlerLabels[node] != null)]); | 218 (node) => handlerLabels[node] != null)]); |
| 322 } | 219 } |
| 323 | 220 |
| 324 int allocateTempVar() { | 221 int allocateTempVar() { |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 395 assert(!labelledParts.containsKey(label)); | 292 assert(!labelledParts.containsKey(label)); |
| 396 currentStatementBuffer = new List<js.Statement>(); | 293 currentStatementBuffer = new List<js.Statement>(); |
| 397 labelledParts[label] = currentStatementBuffer; | 294 labelledParts[label] = currentStatementBuffer; |
| 398 addStatement(new js.Comment(labelComments[label])); | 295 addStatement(new js.Comment(labelComments[label])); |
| 399 } | 296 } |
| 400 | 297 |
| 401 /// Returns a statement assigning to the variable named [gotoName]. | 298 /// Returns a statement assigning to the variable named [gotoName]. |
| 402 /// This should be followed by a break for the goto to be executed. Use | 299 /// This should be followed by a break for the goto to be executed. Use |
| 403 /// [gotoWithBreak] or [addGoto] for this. | 300 /// [gotoWithBreak] or [addGoto] for this. |
| 404 js.Statement setGotoVariable(int label) { | 301 js.Statement setGotoVariable(int label) { |
| 405 return js.js.statement('# = #;', [gotoName, js.number(label)]); | 302 return js.js.statement('# = #;', [goto, js.number(label)]); |
| 406 } | 303 } |
| 407 | 304 |
| 408 /// Returns a block that has a goto to [label] including the break. | 305 /// Returns a block that has a goto to [label] including the break. |
| 409 /// | 306 /// |
| 410 /// Also inserts a comment describing the label if available. | 307 /// Also inserts a comment describing the label if available. |
| 411 js.Block gotoAndBreak(int label) { | 308 js.Block gotoAndBreak(int label) { |
| 412 List<js.Statement> statements = new List<js.Statement>(); | 309 List<js.Statement> statements = new List<js.Statement>(); |
| 413 if (labelComments.containsKey(label)) { | 310 if (labelComments.containsKey(label)) { |
| 414 statements.add(new js.Comment("goto ${labelComments[label]}")); | 311 statements.add(new js.Comment("goto ${labelComments[label]}")); |
| 415 } | 312 } |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 555 // All expressions before that must be stored in temp-vars. | 452 // All expressions before that must be stored in temp-vars. |
| 556 int lastTransformIndex = 0; | 453 int lastTransformIndex = 0; |
| 557 for (int i = nodes.length - 1; i >= 0; --i) { | 454 for (int i = nodes.length - 1; i >= 0; --i) { |
| 558 if (nodes[i] == null) continue; | 455 if (nodes[i] == null) continue; |
| 559 if (shouldTransform(nodes[i])) { | 456 if (shouldTransform(nodes[i])) { |
| 560 lastTransformIndex = i; | 457 lastTransformIndex = i; |
| 561 break; | 458 break; |
| 562 } | 459 } |
| 563 } | 460 } |
| 564 List<js.Node> visited = nodes.take(lastTransformIndex).map((js.Node node) { | 461 List<js.Node> visited = nodes.take(lastTransformIndex).map((js.Node node) { |
| 565 return node == null ? null : _storeIfNecessary(visitExpression(node)); | 462 return (node == null) ? null : _storeIfNecessary(visitExpression(node)); |
| 566 }).toList(); | 463 }).toList(); |
| 567 visited.addAll(nodes.skip(lastTransformIndex).map((js.Node node) { | 464 visited.addAll(nodes.skip(lastTransformIndex).map((js.Node node) { |
| 568 return node == null ? null : visitExpression(node); | 465 return (node == null) ? null : visitExpression(node); |
| 569 })); | 466 })); |
| 570 var result = fn(visited); | 467 var result = fn(visited); |
| 571 currentTempVarIndex = oldTempVarIndex; | 468 currentTempVarIndex = oldTempVarIndex; |
| 572 return result; | 469 return result; |
| 573 } | 470 } |
| 574 | 471 |
| 575 /// Emits the return block that all returns should jump to (after going | 472 /// Emits the return block that all returns jumps to (after going |
|
floitsch
2015/02/25 16:27:59
jump
sigurdm
2015/02/27 09:46:33
Done.
sigurdm
2015/02/27 09:46:33
Done.
| |
| 576 /// through all the enclosing finally blocks). The jump to here is made in | 473 /// through all the enclosing finally blocks). The jump to here is made in |
| 577 /// [visitReturn]. | 474 /// [visitReturn]. |
| 578 /// | 475 void addSuccesExit(); |
| 579 /// Returning from an async method calls the [asyncHelper] with the result. | 476 |
| 580 /// (the result might have been stored in [returnValueName] by some finally | 477 /// Emits the block that control flows to if an error has been thrown |
| 581 /// block). | 478 /// but not caught. (after going through all the enclosing finally blocks). |
| 582 /// | 479 void addErrorExit(); |
| 583 /// Returning from a sync* function returns an [endOfIteration] marker. | 480 |
| 584 /// | 481 void addFunctionExits() { |
| 585 /// Returning from an async* function calls the [streamHelper] with an | 482 addSuccesExit(); |
| 586 /// [endOfIteration] marker. | 483 addErrorExit(); |
| 587 void addExit() { | |
| 588 if (analysis.hasExplicitReturns || isAsyncStar) { | |
| 589 beginLabel(exitLabel); | |
| 590 } else { | |
| 591 addStatement(new js.Comment("implicit return")); | |
| 592 } | |
| 593 switch (async) { | |
| 594 case const js.AsyncModifier.async(): | |
| 595 addStatement(js.js.statement( | |
| 596 "return #runtimeHelper(#returnValue, #successCode, " | |
| 597 "#completer, null);", { | |
| 598 "runtimeHelper": asyncHelper, | |
| 599 "successCode": js.number(error_codes.SUCCESS), | |
| 600 "returnValue": analysis.hasExplicitReturns | |
| 601 ? returnValueName | |
| 602 : new js.LiteralNull(), | |
| 603 "completer": completerName})); | |
| 604 break; | |
| 605 case const js.AsyncModifier.syncStar(): | |
| 606 addStatement(js.js.statement('return #();', [endOfIteration])); | |
| 607 break; | |
| 608 case const js.AsyncModifier.asyncStar(): | |
| 609 addStatement(js.js.statement( | |
| 610 "return #streamHelper(null, #successCode, #controller);", { | |
| 611 "streamHelper": streamHelper, | |
| 612 "successCode": js.number(error_codes.SUCCESS), | |
| 613 "controller": controllerName})); | |
| 614 break; | |
| 615 default: | |
| 616 diagnosticListener.internalError( | |
| 617 spannable, "Internal error, unexpected asyncmodifier $async"); | |
| 618 } | |
| 619 if (isAsync || isAsyncStar) { | |
| 620 beginLabel(rethrowLabel); | |
| 621 addStatement(js.js.statement( | |
| 622 "return #thenHelper(#currentError, #errorCode, #controller);", { | |
| 623 "thenHelper": isAsync ? asyncHelper : streamHelper, | |
| 624 "errorCode": js.number(error_codes.ERROR), | |
| 625 "currentError": currentErrorName, | |
| 626 "controller": isAsync ? completerName : controllerName})); | |
| 627 } else { | |
| 628 assert(isSyncStar); | |
| 629 beginLabel(rethrowLabel); | |
| 630 addStatement(js.js.statement('return #(#);', | |
| 631 [uncaughtErrorExpression, currentErrorName])); | |
| 632 } | |
| 633 } | 484 } |
| 634 | 485 |
| 635 /// The initial call to [asyncHelper]/[streamHelper]. | 486 /// Returns the rewritten function. |
| 636 /// | 487 js.Fun generateFunction(List<js.Parameter> parameters, |
|
sigurdm
2015/02/25 11:43:18
Needs a better name...
floitsch
2015/02/25 16:27:59
finishFunction ?
sigurdm
2015/02/27 09:46:33
Done.
| |
| 637 /// There is no value to await/yield, so the first argument is `null` and | 488 js.Statement rewrittenBody, |
| 638 /// also the errorCallback is `null`. | 489 js.VariableDeclarationList variableDeclarations); |
| 639 /// | 490 |
| 640 /// Returns the [Future]/[Stream] coming from [completerName]/ | 491 Iterable<js.VariableInitialization> variableInitializations(); |
| 641 /// [controllerName]. | |
| 642 js.Statement generateInitializer() { | |
| 643 if (isAsync) { | |
| 644 return js.js.statement( | |
| 645 "return #asyncHelper(null, #body, #completer, null);", { | |
| 646 "asyncHelper": asyncHelper, | |
| 647 "body": bodyName, | |
| 648 "completer": completerName, | |
| 649 }); | |
| 650 } else if (isAsyncStar) { | |
| 651 return js.js.statement( | |
| 652 "return #streamOfController(#controller);", { | |
| 653 "streamOfController": streamOfController, | |
| 654 "controller": controllerName, | |
| 655 }); | |
| 656 } else { | |
| 657 throw diagnosticListener.internalError( | |
| 658 spannable, "Unexpected asyncModifier: $async"); | |
| 659 } | |
| 660 } | |
| 661 | 492 |
| 662 /// Rewrites an async/sync*/async* function to a normal Javascript function. | 493 /// Rewrites an async/sync*/async* function to a normal Javascript function. |
| 663 /// | 494 /// |
| 664 /// The control flow is flattened by simulating 'goto' using a switch in a | 495 /// The control flow is flattened by simulating 'goto' using a switch in a |
| 665 /// loop and a state variable [gotoName] inside a nested function [bodyName] | 496 /// loop and a state variable [goto] inside a nested function [body] |
| 666 /// that can be called back by [asyncHelper]/[asyncStarHelper]/the [Iterator]. | 497 /// that can be called back by [asyncStarHelper]/[asyncStarHelper]/the |
| 498 /// [Iterator]. | |
| 667 /// | 499 /// |
| 668 /// Local variables are hoisted outside the helper. | 500 /// Local variables are hoisted outside the helper. |
| 669 /// | 501 /// |
| 670 /// Awaits in async/async* are translated to code that remembers the current | 502 /// Awaits in async/async* are translated to code that remembers the current |
| 671 /// location (so the function can resume from where it was) followed by a call | 503 /// location (so the function can resume from where it was) followed by a |
| 672 /// to the [asyncHelper]. The helper sets up the waiting for the awaited value | 504 /// [awaitStatement]. The helper sets up the waiting for the awaited |
| 673 /// and returns a future which is immediately returned by the translated | 505 /// value and returns a future which is immediately returned by the |
| 674 /// await. | 506 /// [awaitStatement]. |
| 675 /// Yields in async* are translated to a call to the [asyncStarHelper]. They, | |
| 676 /// too, need to be prepared to be interrupted in case the stream is paused or | |
| 677 /// canceled. (Currently we always suspend - this is different from the spec, | |
| 678 /// see `streamHelper` in `js_helper.dart`). | |
| 679 /// | 507 /// |
| 680 /// Yield/yield* in a sync* function is translated to a return of the value, | 508 /// Yields in sync*/async* are translated to a calls to helper functions. |
| 681 /// wrapped into a "IterationMarker" that signals the type (yield or yield*). | 509 /// (see [visitYield]) |
| 682 /// Sync* functions are executed on demand (when the user requests a value) by | |
| 683 /// the Iterable that knows how to handle these values. | |
| 684 /// | 510 /// |
| 685 /// Simplified examples (not the exact translation, but intended to show the | 511 /// Simplified examples (not the exact translation, but intended to show the |
| 686 /// ideas): | 512 /// ideas): |
| 687 /// | 513 /// |
| 688 /// function (x, y, z) async { | 514 /// function (x, y, z) async { |
| 689 /// var p = await foo(); | 515 /// var p = await foo(); |
| 690 /// return bar(p); | 516 /// return bar(p); |
| 691 /// } | 517 /// } |
| 692 /// | 518 /// |
| 693 /// Becomes (without error handling): | 519 /// Becomes (without error handling): |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 706 /// goto = 2; | 532 /// goto = 2; |
| 707 /// break; | 533 /// break; |
| 708 /// case 2: | 534 /// case 2: |
| 709 /// return thenHelper(returnValue, null, completer) | 535 /// return thenHelper(returnValue, null, completer) |
| 710 /// } | 536 /// } |
| 711 /// } | 537 /// } |
| 712 /// return thenHelper(null, helper, completer); | 538 /// return thenHelper(null, helper, completer); |
| 713 /// } | 539 /// } |
| 714 /// } | 540 /// } |
| 715 /// | 541 /// |
| 716 /// Try/catch is implemented by maintaining [handlerName] to contain the label | 542 /// Try/catch is implemented by maintaining [handler] to contain the label |
| 717 /// of the current handler. If [bodyName] throws, the caller should catch the | 543 /// of the current handler. If [body] throws, the caller should catch the |
| 718 /// error and recall [bodyName] with first argument [error_codes.ERROR] and | 544 /// error and recall [body] with first argument [error_codes.ERROR] and |
| 719 /// second argument the error. | 545 /// second argument the error. |
| 720 /// | 546 /// |
| 721 /// A `finally` clause is compiled similar to normal code, with the additional | 547 /// A `finally` clause is compiled similar to normal code, with the additional |
| 722 /// complexity that `finally` clauses need to know where to jump to after the | 548 /// complexity that `finally` clauses need to know where to jump to after the |
| 723 /// clause is done. In the translation, each flow-path that enters a `finally` | 549 /// clause is done. In the translation, each flow-path that enters a `finally` |
| 724 /// sets up the variable [nextName] with a stack of finally-blocks and a final | 550 /// sets up the variable [next] with a stack of finally-blocks and a final |
| 725 /// jump-target (exit, catch, ...). | 551 /// jump-target (exit, catch, ...). |
| 726 /// | 552 /// |
| 727 /// function(x, y, z) async { | 553 /// function(x, y, z) async { |
| 728 /// try { | 554 /// try { |
| 729 /// try { | 555 /// try { |
| 730 /// throw "error"; | 556 /// throw "error"; |
| 731 /// } finally { | 557 /// } finally { |
| 732 /// finalize1(); | 558 /// finalize1(); |
| 733 /// } | 559 /// } |
| 734 /// } catch (e) { | 560 /// } catch (e) { |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 794 /// case 8: // Rethrow | 620 /// case 8: // Rethrow |
| 795 /// return thenHelper(currentError, 1, completer); | 621 /// return thenHelper(currentError, 1, completer); |
| 796 /// } | 622 /// } |
| 797 /// } | 623 /// } |
| 798 /// return thenHelper(null, helper, completer); | 624 /// return thenHelper(null, helper, completer); |
| 799 /// } | 625 /// } |
| 800 /// } | 626 /// } |
| 801 /// | 627 /// |
| 802 @override | 628 @override |
| 803 js.Expression visitFun(js.Fun node) { | 629 js.Expression visitFun(js.Fun node) { |
| 804 if (isSync) return node; | |
| 805 | |
| 806 beginLabel(newLabel("Function start")); | 630 beginLabel(newLabel("Function start")); |
| 807 // AsyncStar needs a returnlabel for its handling of cancelation. See | 631 // AsyncStar needs a returnlabel for its handling of cancelation. See |
| 808 // [visitDartYield]. | 632 // [visitDartYield]. |
| 809 exitLabel = | 633 exitLabel = (analysis.hasExplicitReturns || isAsyncStar) |
| 810 analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null; | 634 ? newLabel("return") |
| 635 : null; | |
| 811 rethrowLabel = newLabel("rethrow"); | 636 rethrowLabel = newLabel("rethrow"); |
| 812 handlerLabels[node] = rethrowLabel; | 637 handlerLabels[node] = rethrowLabel; |
| 813 js.Statement body = node.body; | 638 js.Statement body = node.body; |
| 814 jumpTargets.add(node); | 639 jumpTargets.add(node); |
| 815 visitStatement(body); | 640 visitStatement(body); |
| 816 jumpTargets.removeLast(); | 641 jumpTargets.removeLast(); |
| 817 addExit(); | 642 addFunctionExits(); |
| 818 | 643 |
| 819 List<js.SwitchClause> clauses = labelledParts.keys.map((label) { | 644 List<js.SwitchClause> clauses = labelledParts.keys.map((label) { |
| 820 return new js.Case(js.number(label), new js.Block(labelledParts[label])); | 645 return new js.Case(js.number(label), new js.Block(labelledParts[label])); |
| 821 }).toList(); | 646 }).toList(); |
| 822 js.Statement helperBody = | 647 js.Statement rewrittenBody = |
| 823 new js.Switch(new js.VariableUse(gotoName), clauses); | 648 new js.Switch(goto, clauses); |
| 824 if (hasJumpThoughOuterLabel) { | 649 if (hasJumpThoughOuterLabel) { |
| 825 helperBody = new js.LabeledStatement(outerLabelName, helperBody); | 650 rewrittenBody = new js.LabeledStatement(outerLabelName, rewrittenBody); |
| 826 } | 651 } |
| 827 | 652 |
| 828 List<js.VariableInitialization> inits = <js.VariableInitialization>[]; | 653 List<js.VariableInitialization> variables = |
| 654 new List<js.VariableInitialization>(); | |
| 829 | 655 |
| 830 js.VariableInitialization makeInit(String name, js.Expression initValue) { | 656 variables.add(_makeVariableInitializer(goto, js.number(0))); |
| 831 return new js.VariableInitialization( | 657 variables.addAll(variableInitializations()); |
| 832 new js.VariableDeclaration(name), initValue); | 658 variables.add( |
| 833 } | 659 _makeVariableInitializer(handler, js.number(rethrowLabel))); |
| 834 | 660 variables.add(_makeVariableInitializer(currentError, null)); |
| 835 inits.add(makeInit(gotoName, js.number(0))); | |
| 836 if (isAsync) { | |
| 837 inits.add(makeInit(completerName, new js.New(newCompleter, []))); | |
| 838 } else if (isAsyncStar) { | |
| 839 inits.add(makeInit(controllerName, | |
| 840 js.js('#(#)', [newController, bodyName]))); | |
| 841 } | |
| 842 inits.add(makeInit(handlerName, js.number(rethrowLabel))); | |
| 843 inits.add(makeInit(currentErrorName, null)); | |
| 844 if (analysis.hasFinally || (isAsyncStar && analysis.hasYield)) { | 661 if (analysis.hasFinally || (isAsyncStar && analysis.hasYield)) { |
| 845 inits.add(makeInit(nextName, null)); | 662 variables.add(_makeVariableInitializer(next, null)); |
| 846 } | |
| 847 if (isAsyncStar && analysis.hasYield) { | |
| 848 inits.add(makeInit(nextWhenCanceledName, null)); | |
| 849 } | |
| 850 if (analysis.hasExplicitReturns && isAsync) { | |
| 851 inits.add(makeInit(returnValueName, null)); | |
| 852 } | 663 } |
| 853 if (analysis.hasThis && !isSyncStar) { | 664 if (analysis.hasThis && !isSyncStar) { |
| 854 // Sync* functions must remember `this` on the level of the outer | 665 // Sync* functions must remember `this` on the level of the outer |
| 855 // function. | 666 // function. |
| 856 inits.add(makeInit(selfName, js.js('this'))); | 667 variables.add(_makeVariableInitializer(self, js.js('this'))); |
| 857 } | 668 } |
| 858 inits.addAll(localVariables.map((js.VariableDeclaration decl) { | 669 variables.addAll(localVariables.map( |
| 859 return new js.VariableInitialization(decl, null); | 670 (js.VariableDeclaration declaration) { |
| 671 return new js.VariableInitialization(declaration, null); | |
| 860 })); | 672 })); |
| 861 inits.addAll(new Iterable.generate(tempVarHighWaterMark, | 673 variables.addAll(new Iterable.generate(tempVarHighWaterMark, |
| 862 (int i) => makeInit(useTempVar(i + 1).name, null))); | 674 (int i) => _makeVariableInitializer(useTempVar(i + 1).name, null))); |
| 863 js.VariableDeclarationList varDecl = new js.VariableDeclarationList(inits); | 675 js.VariableDeclarationList variableDeclarations = |
| 864 // TODO(sigurdm): Explain the difference between these cases. | 676 new js.VariableDeclarationList(variables); |
| 865 if (isSyncStar) { | 677 |
| 866 return js.js(""" | 678 // TODO(sigurdm): Explain the difference between syncStar and the other |
|
floitsch
2015/02/25 16:27:59
I think you can remove that todo now.
sigurdm
2015/02/27 09:46:33
Done.
| |
| 867 function (#params) { | 679 // implementations of generateFunction. |
| 868 if (#needsThis) | 680 return generateMainFunction(node.params, rewrittenBody, variableDeclarations ); |
| 869 var #self = this; | |
| 870 return new #newIterable(function () { | |
| 871 #varDecl; | |
| 872 return function #body(#errorCode, #result) { | |
| 873 if (#errorCode === #ERROR) { | |
| 874 #currentError = #result; | |
| 875 #goto = #handler; | |
| 876 } | |
| 877 while (true) | |
| 878 #helperBody; | |
| 879 }; | |
| 880 }); | |
| 881 } | |
| 882 """, { | |
| 883 "params": node.params, | |
| 884 "needsThis": analysis.hasThis, | |
| 885 "helperBody": helperBody, | |
| 886 "varDecl": varDecl, | |
| 887 "errorCode": errorCodeName, | |
| 888 "newIterable": newIterable, | |
| 889 "body": bodyName, | |
| 890 "self": selfName, | |
| 891 "result": resultName, | |
| 892 "goto": gotoName, | |
| 893 "handler": handlerName, | |
| 894 "currentError": currentErrorName, | |
| 895 "ERROR": js.number(error_codes.ERROR), | |
| 896 }); | |
| 897 } | |
| 898 return js.js(""" | |
| 899 function (#params) { | |
| 900 #varDecl; | |
| 901 function #bodyName(#errorCode, #result) { | |
| 902 if (#hasYield) | |
| 903 switch (#errorCode) { | |
| 904 case #STREAM_WAS_CANCELED: | |
| 905 #next = #nextWhenCanceled; | |
| 906 #goto = #next.pop(); | |
| 907 break; | |
| 908 case #ERROR: | |
| 909 #currentError = #result; | |
| 910 #goto = #handler; | |
| 911 } | |
| 912 else | |
| 913 if (#errorCode === #ERROR) { | |
| 914 #currentError = #result; | |
| 915 #goto = #handler; | |
| 916 } | |
| 917 while (true) | |
| 918 #helperBody; | |
| 919 } | |
| 920 #init; | |
| 921 }""", { | |
| 922 "params": node.params, | |
| 923 "varDecl": varDecl, | |
| 924 "STREAM_WAS_CANCELED": js.number(error_codes.STREAM_WAS_CANCELED), | |
| 925 "ERROR": js.number(error_codes.ERROR), | |
| 926 "hasYield": analysis.hasYield, | |
| 927 "helperBody": helperBody, | |
| 928 "init": generateInitializer(), | |
| 929 "bodyName": bodyName, | |
| 930 "currentError": currentErrorName, | |
| 931 "goto": gotoName, | |
| 932 "handler": handlerName, | |
| 933 "next": nextName, | |
| 934 "nextWhenCanceled": nextWhenCanceledName, | |
| 935 "errorCode": errorCodeName, | |
| 936 "result": resultName, | |
| 937 }); | |
| 938 } | 681 } |
| 939 | 682 |
| 940 @override | 683 @override |
| 941 js.Expression visitAccess(js.PropertyAccess node) { | 684 js.Expression visitAccess(js.PropertyAccess node) { |
| 942 return withExpression2(node.receiver, node.selector, | 685 return withExpression2(node.receiver, node.selector, |
| 943 (receiver, selector) => js.js('#[#]', [receiver, selector])); | 686 (receiver, selector) => js.js('#[#]', [receiver, selector])); |
| 944 } | 687 } |
| 945 | 688 |
| 946 @override | 689 @override |
| 947 js.Expression visitArrayHole(js.ArrayHole node) { | 690 js.Expression visitArrayHole(js.ArrayHole node) { |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 974 ], (evaluated) { | 717 ], (evaluated) { |
| 975 return new js.Assignment.compound( | 718 return new js.Assignment.compound( |
| 976 new js.PropertyAccess(evaluated[0], evaluated[1]), node.op, | 719 new js.PropertyAccess(evaluated[0], evaluated[1]), node.op, |
| 977 evaluated[2]); | 720 evaluated[2]); |
| 978 }); | 721 }); |
| 979 } else { | 722 } else { |
| 980 throw "Unexpected assignment left hand side $leftHandSide"; | 723 throw "Unexpected assignment left hand side $leftHandSide"; |
| 981 } | 724 } |
| 982 } | 725 } |
| 983 | 726 |
| 984 /// An await is translated to a call to [asyncHelper]/[streamHelper]. | 727 js.Statement awaitStatement(js.Expression value); |
| 728 | |
| 729 /// An await is translated to a [awaitStatement. | |
| 985 /// | 730 /// |
| 986 /// See the comments of [visitFun] for an example. | 731 /// See the comments of [visitFun] for an example. |
| 987 @override | 732 @override |
| 988 js.Expression visitAwait(js.Await node) { | 733 js.Expression visitAwait(js.Await node) { |
|
floitsch
2015/02/25 16:27:59
This should probably go into an AsyncRewriterBase.
sigurdm
2015/02/27 09:46:33
Left it for now
| |
| 989 assert(isAsync || isAsyncStar); | 734 assert(isAsync || isAsyncStar); |
| 990 int afterAwait = newLabel("returning from await."); | 735 int afterAwait = newLabel("returning from await."); |
| 991 withExpression(node.expression, (js.Expression value) { | 736 withExpression(node.expression, (js.Expression value) { |
| 992 addStatement(setGotoVariable(afterAwait)); | 737 addStatement(setGotoVariable(afterAwait)); |
| 993 addStatement(js.js.statement(""" | 738 addStatement(awaitStatement(value)); |
| 994 return #asyncHelper(#value, | |
| 995 #body, | |
| 996 #controller); | |
| 997 """, { | |
| 998 "asyncHelper": isAsync ? asyncHelper : streamHelper, | |
| 999 "value": value, | |
| 1000 "body": bodyName, | |
| 1001 "controller": isAsync ? completerName : controllerName, | |
| 1002 })); | |
| 1003 }, store: false); | 739 }, store: false); |
| 1004 beginLabel(afterAwait); | 740 beginLabel(afterAwait); |
| 1005 return new js.VariableUse(resultName); | 741 return result; |
| 1006 } | 742 } |
| 1007 | 743 |
| 1008 /// Checks if [node] is the variable named [resultName]. | 744 /// Checks if [node] is the variable named [resultName]. |
| 1009 /// | 745 /// |
| 1010 /// [resultName] is used to hold the result of a transformed computation | 746 /// [result] is used to hold the result of a transformed computation |
| 1011 /// for example the result of awaiting, or the result of a conditional or | 747 /// for example the result of awaiting, or the result of a conditional or |
| 1012 /// short-circuiting expression. | 748 /// short-circuiting expression. |
| 1013 /// If the subexpression of some transformed node already is transformed and | 749 /// If the subexpression of some transformed node already is transformed and |
| 1014 /// visiting it returns [resultName], it is not redundantly assigned to itself | 750 /// visiting it returns [result], it is not redundantly assigned to itself |
| 1015 /// again. | 751 /// again. |
| 1016 bool isResult(js.Expression node) { | 752 bool isResult(js.Expression node) { |
| 1017 return node is js.VariableUse && node.name == resultName; | 753 return node is js.VariableUse && node.name == resultName; |
| 1018 } | 754 } |
| 1019 | 755 |
| 1020 @override | 756 @override |
| 1021 js.Expression visitBinary(js.Binary node) { | 757 js.Expression visitBinary(js.Binary node) { |
| 1022 if (shouldTransform(node.right) && (node.op == "||" || node.op == "&&")) { | 758 if (shouldTransform(node.right) && (node.op == "||" || node.op == "&&")) { |
| 1023 int thenLabel = newLabel("then"); | 759 int thenLabel = newLabel("then"); |
| 1024 int joinLabel = newLabel("join"); | 760 int joinLabel = newLabel("join"); |
| 1025 withExpression(node.left, (js.Expression left) { | 761 withExpression(node.left, (js.Expression left) { |
| 1026 js.Statement assignLeft = isResult(left) | 762 js.Statement assignLeft = isResult(left) |
| 1027 ? new js.Block.empty() | 763 ? new js.Block.empty() |
| 1028 : js.js.statement('# = #;', [resultName, left]); | 764 : js.js.statement('# = #;', [result, left]); |
| 1029 if (node.op == "||") { | 765 if (node.op == "||") { |
| 1030 addStatement(js.js.statement('if (#) {#} else #', | 766 addStatement(js.js.statement('if (#) {#} else #', |
| 1031 [left, gotoAndBreak(thenLabel), assignLeft])); | 767 [left, gotoAndBreak(thenLabel), assignLeft])); |
| 1032 } else { | 768 } else { |
| 1033 assert(node.op == "&&"); | 769 assert(node.op == "&&"); |
| 1034 addStatement(js.js.statement('if (#) {#} else #', | 770 addStatement(js.js.statement('if (#) {#} else #', |
| 1035 [left, assignLeft, gotoAndBreak(thenLabel)])); | 771 [left, assignLeft, gotoAndBreak(thenLabel)])); |
| 1036 } | 772 } |
| 1037 }, store: true); | 773 }, store: true); |
| 1038 addGoto(joinLabel); | 774 addGoto(joinLabel); |
| 1039 beginLabel(thenLabel); | 775 beginLabel(thenLabel); |
| 1040 withExpression(node.right, (js.Expression value) { | 776 withExpression(node.right, (js.Expression value) { |
| 1041 if (!isResult(value)) { | 777 if (!isResult(value)) { |
| 1042 addStatement(js.js.statement('# = #;', [resultName, value])); | 778 addStatement(js.js.statement('# = #;', [result, value])); |
| 1043 } | 779 } |
| 1044 }, store: false); | 780 }, store: false); |
| 1045 beginLabel(joinLabel); | 781 beginLabel(joinLabel); |
| 1046 return new js.VariableUse(resultName); | 782 return result; |
| 1047 } | 783 } |
| 1048 | 784 |
| 1049 return withExpression2(node.left, node.right, | 785 return withExpression2(node.left, node.right, |
| 1050 (left, right) => new js.Binary(node.op, left, right)); | 786 (left, right) => new js.Binary(node.op, left, right)); |
| 1051 } | 787 } |
| 1052 | 788 |
| 1053 @override | 789 @override |
| 1054 void visitBlock(js.Block node) { | 790 void visitBlock(js.Block node) { |
| 1055 for (js.Statement statement in node.statements) { | 791 for (js.Statement statement in node.statements) { |
| 1056 visitStatement(statement); | 792 visitStatement(statement); |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1097 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { | 833 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { |
| 1098 return withExpression(node.condition, (js.Expression condition) { | 834 return withExpression(node.condition, (js.Expression condition) { |
| 1099 return js.js('# ? # : #', [condition, node.then, node.otherwise]); | 835 return js.js('# ? # : #', [condition, node.then, node.otherwise]); |
| 1100 }); | 836 }); |
| 1101 } | 837 } |
| 1102 int thenLabel = newLabel("then"); | 838 int thenLabel = newLabel("then"); |
| 1103 int joinLabel = newLabel("join"); | 839 int joinLabel = newLabel("join"); |
| 1104 int elseLabel = newLabel("else"); | 840 int elseLabel = newLabel("else"); |
| 1105 withExpression(node.condition, (js.Expression condition) { | 841 withExpression(node.condition, (js.Expression condition) { |
| 1106 addStatement(js.js.statement('# = # ? # : #;', | 842 addStatement(js.js.statement('# = # ? # : #;', |
| 1107 [gotoName, condition, js.number(thenLabel), js.number(elseLabel)])); | 843 [goto, condition, js.number(thenLabel), js.number(elseLabel)])); |
| 1108 }, store: false); | 844 }, store: false); |
| 1109 addBreak(); | 845 addBreak(); |
| 1110 beginLabel(thenLabel); | 846 beginLabel(thenLabel); |
| 1111 withExpression(node.then, (js.Expression value) { | 847 withExpression(node.then, (js.Expression value) { |
| 1112 if (!isResult(value)) { | 848 if (!isResult(value)) { |
| 1113 addStatement(js.js.statement('# = #;', [resultName, value])); | 849 addStatement(js.js.statement('# = #;', [result, value])); |
| 1114 } | 850 } |
| 1115 }, store: false); | 851 }, store: false); |
| 1116 addGoto(joinLabel); | 852 addGoto(joinLabel); |
| 1117 beginLabel(elseLabel); | 853 beginLabel(elseLabel); |
| 1118 withExpression(node.otherwise, (js.Expression value) { | 854 withExpression(node.otherwise, (js.Expression value) { |
| 1119 if (!isResult(value)) { | 855 if (!isResult(value)) { |
| 1120 addStatement(js.js.statement('# = #;', [resultName, value])); | 856 addStatement(js.js.statement('# = #;', [result, value])); |
| 1121 } | 857 } |
| 1122 }, store: false); | 858 }, store: false); |
| 1123 beginLabel(joinLabel); | 859 beginLabel(joinLabel); |
| 1124 return new js.VariableUse(resultName); | 860 return result; |
| 1125 } | 861 } |
| 1126 | 862 |
| 1127 @override | 863 @override |
| 1128 void visitContinue(js.Continue node) { | 864 void visitContinue(js.Continue node) { |
| 1129 js.Node target = analysis.targets[node]; | 865 js.Node target = analysis.targets[node]; |
| 1130 if (!shouldTransform(target)) { | 866 if (!shouldTransform(target)) { |
| 1131 addStatement(node); | 867 addStatement(node); |
| 1132 return; | 868 return; |
| 1133 } | 869 } |
| 1134 translateJump(target, continueLabels[target]); | 870 translateJump(target, continueLabels[target]); |
| 1135 } | 871 } |
| 1136 | 872 |
| 1137 /// Emits a break statement that exits the big switch statement. | 873 /// Emits a break statement that exits the big switch statement. |
| 1138 void addBreak() { | 874 void addBreak() { |
| 1139 if (insideUntranslatedBreakable) { | 875 if (insideUntranslatedBreakable) { |
| 1140 hasJumpThoughOuterLabel = true; | 876 hasJumpThoughOuterLabel = true; |
| 1141 addStatement(new js.Break(outerLabelName)); | 877 addStatement(new js.Break(outerLabelName)); |
| 1142 } else { | 878 } else { |
| 1143 addStatement(new js.Break(null)); | 879 addStatement(new js.Break(null)); |
| 1144 } | 880 } |
| 1145 } | 881 } |
| 1146 | 882 |
| 1147 /// Common code for handling break, continue, return. | 883 /// Common code for handling break, continue, return. |
| 1148 /// | 884 /// |
| 1149 /// It is necessary to run all nesting finally-handlers between the jump and | 885 /// It is necessary to run all nesting finally-handlers between the jump and |
| 1150 /// the target. For that [nextName] is used as a stack of places to go. | 886 /// the target. For that [next] is used as a stack of places to go. |
| 1151 /// | 887 /// |
| 1152 /// See also [visitFun]. | 888 /// See also [visitFun]. |
| 1153 void translateJump(js.Node target, int targetLabel) { | 889 void translateJump(js.Node target, int targetLabel) { |
| 1154 // Compute a stack of all the 'finally' nodes that must be visited before | 890 // Compute a stack of all the 'finally' nodes that must be visited before |
| 1155 // the jump. | 891 // the jump. |
| 1156 // The bottom of the stack is the label where the jump goes to. | 892 // The bottom of the stack is the label where the jump goes to. |
| 1157 List<int> jumpStack = new List<int>(); | 893 List<int> jumpStack = new List<int>(); |
| 1158 for (js.Node node in jumpTargets.reversed) { | 894 for (js.Node node in jumpTargets.reversed) { |
| 1159 if (finallyLabels[node] != null) { | 895 if (finallyLabels[node] != null) { |
| 1160 jumpStack.add(finallyLabels[node]); | 896 jumpStack.add(finallyLabels[node]); |
| 1161 } else if (node == target) { | 897 } else if (node == target) { |
| 1162 jumpStack.add(targetLabel); | 898 jumpStack.add(targetLabel); |
| 1163 break; | 899 break; |
| 1164 } | 900 } |
| 1165 // Ignore other nodes. | 901 // Ignore other nodes. |
| 1166 } | 902 } |
| 1167 jumpStack = jumpStack.reversed.toList(); | 903 jumpStack = jumpStack.reversed.toList(); |
| 1168 // As the program jumps directly to the top of the stack, it is taken off | 904 // As the program jumps directly to the top of the stack, it is taken off |
| 1169 // now. | 905 // now. |
| 1170 int firstTarget = jumpStack.removeLast(); | 906 int firstTarget = jumpStack.removeLast(); |
| 1171 if (jumpStack.isNotEmpty) { | 907 if (jumpStack.isNotEmpty) { |
| 1172 js.Expression jsJumpStack = new js.ArrayInitializer( | 908 js.Expression jsJumpStack = new js.ArrayInitializer( |
| 1173 jumpStack.map((int label) => js.number(label)).toList()); | 909 jumpStack.map((int label) => js.number(label)).toList()); |
| 1174 addStatement(js.js.statement("# = #;", [nextName, jsJumpStack])); | 910 addStatement(js.js.statement("# = #;", [next, jsJumpStack])); |
| 1175 } | 911 } |
| 1176 addGoto(firstTarget); | 912 addGoto(firstTarget); |
| 1177 } | 913 } |
| 1178 | 914 |
| 1179 @override | 915 @override |
| 1180 void visitDefault(js.Default node) => unreachable(node); | 916 void visitDefault(js.Default node) => unreachable(node); |
| 1181 | 917 |
| 1182 @override | 918 @override |
| 1183 void visitDo(js.Do node) { | 919 void visitDo(js.Do node) { |
| 1184 if (!shouldTransform(node)) { | 920 if (!shouldTransform(node)) { |
| (...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1309 void visitIf(js.If node) { | 1045 void visitIf(js.If node) { |
| 1310 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { | 1046 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { |
| 1311 withExpression(node.condition, (js.Expression condition) { | 1047 withExpression(node.condition, (js.Expression condition) { |
| 1312 addStatement(new js.If(condition, translateInBlock(node.then), | 1048 addStatement(new js.If(condition, translateInBlock(node.then), |
| 1313 translateInBlock(node.otherwise))); | 1049 translateInBlock(node.otherwise))); |
| 1314 }, store: false); | 1050 }, store: false); |
| 1315 return; | 1051 return; |
| 1316 } | 1052 } |
| 1317 int thenLabel = newLabel("then"); | 1053 int thenLabel = newLabel("then"); |
| 1318 int joinLabel = newLabel("join"); | 1054 int joinLabel = newLabel("join"); |
| 1319 int elseLabel = | 1055 int elseLabel = (node.otherwise is js.EmptyStatement) |
| 1320 node.otherwise is js.EmptyStatement ? joinLabel : newLabel("else"); | 1056 ? joinLabel |
| 1057 : newLabel("else"); | |
| 1321 | 1058 |
| 1322 withExpression(node.condition, (js.Expression condition) { | 1059 withExpression(node.condition, (js.Expression condition) { |
| 1323 addExpressionStatement( | 1060 addExpressionStatement( |
| 1324 new js.Assignment( | 1061 new js.Assignment( |
| 1325 new js.VariableUse(gotoName), | 1062 goto, |
| 1326 new js.Conditional( | 1063 new js.Conditional( |
| 1327 condition, | 1064 condition, |
| 1328 js.number(thenLabel), | 1065 js.number(thenLabel), |
| 1329 js.number(elseLabel)))); | 1066 js.number(elseLabel)))); |
| 1330 }, store: false); | 1067 }, store: false); |
| 1331 addBreak(); | 1068 addBreak(); |
| 1332 beginLabel(thenLabel); | 1069 beginLabel(thenLabel); |
| 1333 visitStatement(node.then); | 1070 visitStatement(node.then); |
| 1334 if (node.otherwise is! js.EmptyStatement) { | 1071 if (node.otherwise is! js.EmptyStatement) { |
| 1335 addGoto(joinLabel); | 1072 addGoto(joinLabel); |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1486 return withExpression( | 1223 return withExpression( |
| 1487 node.value, (js.Expression value) => new js.Property(node.name, value), | 1224 node.value, (js.Expression value) => new js.Property(node.name, value), |
| 1488 store: false); | 1225 store: false); |
| 1489 } | 1226 } |
| 1490 | 1227 |
| 1491 @override | 1228 @override |
| 1492 js.Expression visitRegExpLiteral(js.RegExpLiteral node) => node; | 1229 js.Expression visitRegExpLiteral(js.RegExpLiteral node) => node; |
| 1493 | 1230 |
| 1494 @override | 1231 @override |
| 1495 void visitReturn(js.Return node) { | 1232 void visitReturn(js.Return node) { |
| 1496 assert(node.value == null || !isSyncStar && !isAsyncStar); | 1233 assert(node.value == null || (!isSyncStar && !isAsyncStar)); |
| 1497 js.Node target = analysis.targets[node]; | 1234 js.Node target = analysis.targets[node]; |
| 1498 if (node.value != null) { | 1235 if (node.value != null) { |
| 1499 withExpression(node.value, (js.Expression value) { | 1236 withExpression(node.value, (js.Expression value) { |
| 1500 addStatement(js.js.statement("# = #;", [returnValueName, value])); | 1237 addStatement(js.js.statement("# = #;", [returnValue, value])); |
| 1501 }, store: false); | 1238 }, store: false); |
| 1502 } | 1239 } |
| 1503 translateJump(target, exitLabel); | 1240 translateJump(target, exitLabel); |
| 1504 } | 1241 } |
| 1505 | 1242 |
| 1506 @override | 1243 @override |
| 1507 void visitSwitch(js.Switch node) { | 1244 void visitSwitch(js.Switch node) { |
| 1508 if (!node.cases.any(shouldTransform)) { | 1245 if (!node.cases.any(shouldTransform)) { |
| 1509 // If only the key has an await, translation can be simplified. | 1246 // If only the key has an await, translation can be simplified. |
| 1510 bool oldInsideUntranslated = insideUntranslatedBreakable; | 1247 bool oldInsideUntranslated = insideUntranslatedBreakable; |
| (...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1592 for (int i = 0; i < labels.length; i++) { | 1329 for (int i = 0; i < labels.length; i++) { |
| 1593 beginLabel(labels[i]); | 1330 beginLabel(labels[i]); |
| 1594 visitStatement(node.cases[i].body); | 1331 visitStatement(node.cases[i].body); |
| 1595 } | 1332 } |
| 1596 beginLabel(after); | 1333 beginLabel(after); |
| 1597 jumpTargets.removeLast(); | 1334 jumpTargets.removeLast(); |
| 1598 } | 1335 } |
| 1599 | 1336 |
| 1600 @override | 1337 @override |
| 1601 js.Expression visitThis(js.This node) { | 1338 js.Expression visitThis(js.This node) { |
| 1602 return new js.VariableUse(selfName); | 1339 return self; |
| 1603 } | 1340 } |
| 1604 | 1341 |
| 1605 @override | 1342 @override |
| 1606 void visitThrow(js.Throw node) { | 1343 void visitThrow(js.Throw node) { |
| 1607 withExpression(node.expression, (js.Expression expression) { | 1344 withExpression(node.expression, (js.Expression expression) { |
| 1608 addStatement(new js.Throw(expression)); | 1345 addStatement(new js.Throw(expression)); |
| 1609 }, store: false); | 1346 }, store: false); |
| 1610 } | 1347 } |
| 1611 | 1348 |
| 1612 setErrorHandler([int errorHandler]) { | 1349 setErrorHandler([int errorHandler]) { |
| 1613 addExpressionStatement(new js.Assignment( | 1350 js.Expression label = (errorHandler == null) |
| 1614 new js.VariableUse(handlerName), | 1351 ? currentErrorHandler |
| 1615 errorHandler == null ? currentErrorHandler : js.number(errorHandler))); | 1352 : js.number(errorHandler); |
| 1353 addStatement(js.js.statement('# = #;',[handler, label])); | |
| 1616 } | 1354 } |
| 1617 | 1355 |
| 1618 List<int> _finalliesUpToAndEnclosingHandler() { | 1356 List<int> _finalliesUpToAndEnclosingHandler() { |
| 1619 List<int> result = new List<int>(); | 1357 List<int> result = new List<int>(); |
| 1620 for (int i = jumpTargets.length - 1; i >= 0; i--) { | 1358 for (int i = jumpTargets.length - 1; i >= 0; i--) { |
| 1621 js.Node node = jumpTargets[i]; | 1359 js.Node node = jumpTargets[i]; |
| 1622 int handlerLabel = handlerLabels[node]; | 1360 int handlerLabel = handlerLabels[node]; |
| 1623 if (handlerLabel != null) { | 1361 if (handlerLabel != null) { |
| 1624 result.add(handlerLabel); | 1362 result.add(handlerLabel); |
| 1625 break; | 1363 break; |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1671 | 1409 |
| 1672 js.Node last = jumpTargets.removeLast(); | 1410 js.Node last = jumpTargets.removeLast(); |
| 1673 assert(last == node); | 1411 assert(last == node); |
| 1674 | 1412 |
| 1675 if (node.finallyPart == null) { | 1413 if (node.finallyPart == null) { |
| 1676 setErrorHandler(); | 1414 setErrorHandler(); |
| 1677 addGoto(afterFinallyLabel); | 1415 addGoto(afterFinallyLabel); |
| 1678 } else { | 1416 } else { |
| 1679 // The handler is reset as the first thing in the finally block. | 1417 // The handler is reset as the first thing in the finally block. |
| 1680 addStatement( | 1418 addStatement( |
| 1681 js.js.statement("# = [#];", | 1419 js.js.statement("# = [#];", [next, js.number(afterFinallyLabel)])); |
| 1682 [nextName, js.number(afterFinallyLabel)])); | |
| 1683 addGoto(finallyLabel); | 1420 addGoto(finallyLabel); |
| 1684 } | 1421 } |
| 1685 | 1422 |
| 1686 if (node.catchPart != null) { | 1423 if (node.catchPart != null) { |
| 1687 beginLabel(handlerLabel); | 1424 beginLabel(handlerLabel); |
| 1688 // [uncaughtLabel] is the handler for the code in the catch-part. | 1425 // [uncaughtLabel] is the handler for the code in the catch-part. |
| 1689 // It ensures that [nextName] is set up to run the right finally blocks. | 1426 // It ensures that [nextName] is set up to run the right finally blocks. |
| 1690 handlerLabels[node.catchPart] = uncaughtLabel; | 1427 handlerLabels[node.catchPart] = uncaughtLabel; |
| 1691 jumpTargets.add(node.catchPart); | 1428 jumpTargets.add(node.catchPart); |
| 1692 setErrorHandler(); | 1429 setErrorHandler(); |
| 1693 // The catch declaration name can shadow outer variables, so a fresh name | 1430 // The catch declaration name can shadow outer variables, so a fresh name |
| 1694 // is needed to avoid collisions. See Ecma 262, 3rd edition, | 1431 // is needed to avoid collisions. See Ecma 262, 3rd edition, |
| 1695 // section 12.14. | 1432 // section 12.14. |
| 1696 String errorRename = freshName(node.catchPart.declaration.name); | 1433 String errorRename = freshName(node.catchPart.declaration.name); |
| 1697 localVariables.add(new js.VariableDeclaration(errorRename)); | 1434 localVariables.add(new js.VariableDeclaration(errorRename)); |
| 1698 variableRenamings | 1435 variableRenamings |
| 1699 .add(new Pair(node.catchPart.declaration.name, errorRename)); | 1436 .add(new Pair(node.catchPart.declaration.name, errorRename)); |
| 1700 addExpressionStatement(new js.Assignment( | 1437 addStatement(js.js.statement("# = #;", [errorRename, currentError])); |
| 1701 new js.VariableUse(errorRename), | |
| 1702 new js.VariableUse(currentErrorName))); | |
| 1703 visitStatement(node.catchPart.body); | 1438 visitStatement(node.catchPart.body); |
| 1704 variableRenamings.removeLast(); | 1439 variableRenamings.removeLast(); |
| 1705 if (node.finallyPart != null) { | 1440 if (node.finallyPart != null) { |
| 1706 // The error has been caught, so after the finally, continue after the | 1441 // The error has been caught, so after the finally, continue after the |
| 1707 // try. | 1442 // try. |
| 1708 addStatement(js.js.statement("# = [#];", | 1443 addStatement(js.js.statement("# = [#];", |
| 1709 [nextName, js.number(afterFinallyLabel)])); | 1444 [next, js.number(afterFinallyLabel)])); |
| 1710 addGoto(finallyLabel); | 1445 addGoto(finallyLabel); |
| 1711 } else { | 1446 } else { |
| 1712 addGoto(afterFinallyLabel); | 1447 addGoto(afterFinallyLabel); |
| 1713 } | 1448 } |
| 1714 js.Node last = jumpTargets.removeLast(); | 1449 js.Node last = jumpTargets.removeLast(); |
| 1715 assert(last == node.catchPart); | 1450 assert(last == node.catchPart); |
| 1716 } | 1451 } |
| 1717 | 1452 |
| 1718 // The "uncaught"-handler tells the finally-block to continue with | 1453 // The "uncaught"-handler tells the finally-block to continue with |
| 1719 // the enclosing finally-blocks until the current catch-handler. | 1454 // the enclosing finally-blocks until the current catch-handler. |
| 1720 beginLabel(uncaughtLabel); | 1455 beginLabel(uncaughtLabel); |
| 1721 | 1456 |
| 1722 List<int> enclosingFinallies = _finalliesUpToAndEnclosingHandler(); | 1457 List<int> enclosingFinallies = _finalliesUpToAndEnclosingHandler(); |
| 1723 | 1458 |
| 1724 int nextLabel = enclosingFinallies.removeLast(); | 1459 int nextLabel = enclosingFinallies.removeLast(); |
| 1725 if (enclosingFinallies.isNotEmpty) { | 1460 if (enclosingFinallies.isNotEmpty) { |
| 1726 // [enclosingFinallies] can be empty if there is no surrounding finally | 1461 // [enclosingFinallies] can be empty if there is no surrounding finally |
| 1727 // blocks. Then [nextLabel] will be [rethrowLabel]. | 1462 // blocks. Then [nextLabel] will be [rethrowLabel]. |
| 1728 addStatement( | 1463 addStatement( |
| 1729 js.js.statement("# = #;", [nextName, new js.ArrayInitializer( | 1464 js.js.statement("# = #;", [next, new js.ArrayInitializer( |
| 1730 enclosingFinallies.map(js.number).toList())])); | 1465 enclosingFinallies.map(js.number).toList())])); |
| 1731 } | 1466 } |
| 1732 if (node.finallyPart == null) { | 1467 if (node.finallyPart == null) { |
| 1733 // The finally-block belonging to [node] will be visited because of | 1468 // The finally-block belonging to [node] will be visited because of |
| 1734 // fallthrough. If it does not exist, add an explicit goto. | 1469 // fallthrough. If it does not exist, add an explicit goto. |
| 1735 addGoto(nextLabel); | 1470 addGoto(nextLabel); |
| 1736 } | 1471 } |
| 1737 if (node.finallyPart != null) { | 1472 if (node.finallyPart != null) { |
| 1738 js.Node last = jumpTargets.removeLast(); | 1473 js.Node last = jumpTargets.removeLast(); |
| 1739 assert(last == node.finallyPart); | 1474 assert(last == node.finallyPart); |
| 1740 | 1475 |
| 1741 beginLabel(finallyLabel); | 1476 beginLabel(finallyLabel); |
| 1742 setErrorHandler(); | 1477 setErrorHandler(); |
| 1743 visitStatement(node.finallyPart); | 1478 visitStatement(node.finallyPart); |
| 1744 addStatement(new js.Comment("// goto the next finally handler")); | 1479 addStatement(new js.Comment("// goto the next finally handler")); |
| 1745 addStatement(js.js.statement("# = #.pop();", [gotoName, nextName])); | 1480 addStatement(js.js.statement("# = #.pop();", [goto, next])); |
| 1746 addBreak(); | 1481 addBreak(); |
| 1747 } | 1482 } |
| 1748 beginLabel(afterFinallyLabel); | 1483 beginLabel(afterFinallyLabel); |
| 1749 } | 1484 } |
| 1750 | 1485 |
| 1751 @override | 1486 @override |
| 1752 visitVariableDeclaration(js.VariableDeclaration node) { | 1487 visitVariableDeclaration(js.VariableDeclaration node) { |
| 1753 unreachable(node); | 1488 unreachable(node); |
| 1754 } | 1489 } |
| 1755 | 1490 |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1807 new js.Prefix("!", condition), gotoAndBreak(afterLabel))); | 1542 new js.Prefix("!", condition), gotoAndBreak(afterLabel))); |
| 1808 }, store: false); | 1543 }, store: false); |
| 1809 } | 1544 } |
| 1810 jumpTargets.add(node); | 1545 jumpTargets.add(node); |
| 1811 visitStatement(node.body); | 1546 visitStatement(node.body); |
| 1812 jumpTargets.removeLast(); | 1547 jumpTargets.removeLast(); |
| 1813 addGoto(continueLabel); | 1548 addGoto(continueLabel); |
| 1814 beginLabel(afterLabel); | 1549 beginLabel(afterLabel); |
| 1815 } | 1550 } |
| 1816 | 1551 |
| 1552 addYield(js.DartYield node, js.Expression expression); | |
| 1553 | |
| 1554 @override | |
| 1555 void visitDartYield(js.DartYield node) { | |
|
floitsch
2015/02/25 16:27:59
Ideally this one, too, would only be shared by the
sigurdm
2015/02/27 09:46:33
Left it for now
| |
| 1556 print(this.runtimeType); | |
|
floitsch
2015/02/25 16:27:59
debug print.
sigurdm
2015/02/27 09:46:33
Done.
| |
| 1557 assert(isSyncStar || isAsyncStar); | |
| 1558 int label = newLabel("after yield"); | |
| 1559 // Don't do a break here for the goto, but instead a return in either | |
| 1560 // addSynYield or addAsyncYield. | |
| 1561 withExpression(node.expression, (js.Expression expression) { | |
| 1562 addStatement(setGotoVariable(label)); | |
| 1563 addYield(node, expression); | |
| 1564 }, store: false); | |
| 1565 beginLabel(label); | |
| 1566 } | |
| 1567 } | |
| 1568 | |
| 1569 js.VariableInitialization | |
| 1570 _makeVariableInitializer(dynamic variable, js.Expression initValue) { | |
| 1571 js.VariableDeclaration declaration; | |
| 1572 if (variable is js.VariableUse) { | |
| 1573 declaration = new js.VariableDeclaration(variable.name); | |
| 1574 } else if (variable is String) { | |
| 1575 declaration = new js.VariableDeclaration(variable); | |
| 1576 } else { | |
| 1577 assert(variable is js.VariableDeclaration); | |
| 1578 declaration = variable; | |
| 1579 } | |
| 1580 return new js.VariableInitialization(declaration, initValue); | |
| 1581 } | |
| 1582 | |
| 1583 class AsyncRewriter extends AsyncRewriterBase { | |
| 1584 | |
| 1585 bool get isAsync => true; | |
| 1586 | |
| 1587 /// The Completer that will finish an async function. | |
| 1588 /// | |
| 1589 /// Not used for sync* or async* functions. | |
| 1590 String completerName; | |
| 1591 js.VariableUse get completer => new js.VariableUse(completerName); | |
| 1592 | |
| 1593 /// The function called by an async function to simulate an await or return. | |
| 1594 /// | |
| 1595 /// For an await it is called with: | |
| 1596 /// | |
| 1597 /// - The value to await | |
| 1598 /// - The body function [bodyName] | |
| 1599 /// - The completer object [completer] | |
| 1600 /// | |
| 1601 /// For a return it is called with: | |
| 1602 /// | |
| 1603 /// - The value to complete the completer with. | |
| 1604 /// - [error_codes.SUCCESS] | |
| 1605 /// - The completer object [completer] | |
| 1606 /// | |
| 1607 /// For a throw it is called with: | |
| 1608 /// | |
| 1609 /// - The error to complete the completer with. | |
| 1610 /// - [error_codes.ERROR] | |
| 1611 /// - The completer object [completer] | |
| 1612 final js.Expression asyncHelper; | |
| 1613 | |
| 1614 /// Contructor used to initialize the [completer] variable. | |
| 1615 /// | |
| 1616 /// Specific to async methods. | |
| 1617 final js.Expression newCompleter; | |
| 1618 | |
| 1619 | |
| 1620 AsyncRewriter(DiagnosticListener diagnosticListener, | |
| 1621 spannable, | |
| 1622 {this.asyncHelper, | |
| 1623 this.newCompleter, | |
| 1624 safeVariableName}) | |
| 1625 : super(diagnosticListener, | |
| 1626 spannable, | |
| 1627 safeVariableName); | |
| 1628 | |
| 1629 @override | |
| 1630 void addYield(js.DartYield node, js.Expression expression) { | |
| 1631 diagnosticListener.internalError(spannable, | |
| 1632 "Yield in non-generating async function"); | |
| 1633 } | |
| 1634 | |
| 1635 void addErrorExit() { | |
| 1636 beginLabel(rethrowLabel); | |
| 1637 addStatement(js.js.statement( | |
| 1638 "return #thenHelper(#currentError, #errorCode, #completer);", { | |
| 1639 "thenHelper": asyncHelper, | |
| 1640 "errorCode": js.number(error_codes.ERROR), | |
| 1641 "currentError": currentError, | |
| 1642 "completer": completer})); | |
| 1643 } | |
| 1644 | |
| 1645 /// Returning from an async method calls the [asyncStarHelper] with the result . | |
|
floitsch
2015/02/25 16:27:59
long line.
sigurdm
2015/02/27 09:46:33
Done.
| |
| 1646 /// (the result might have been stored in [returnValue] by some finally | |
| 1647 /// block). | |
| 1648 void addSuccesExit() { | |
| 1649 if (analysis.hasExplicitReturns) { | |
| 1650 beginLabel(exitLabel); | |
| 1651 } else { | |
| 1652 addStatement(new js.Comment("implicit return")); | |
| 1653 } | |
| 1654 addStatement(js.js.statement( | |
| 1655 "return #runtimeHelper(#returnValue, #successCode, " | |
| 1656 "#completer, null);", { | |
| 1657 "runtimeHelper": asyncHelper, | |
| 1658 "successCode": js.number(error_codes.SUCCESS), | |
| 1659 "returnValue": analysis.hasExplicitReturns | |
| 1660 ? returnValue | |
| 1661 : new js.LiteralNull(), | |
| 1662 "completer": completer})); | |
| 1663 } | |
| 1664 | |
| 1665 @override | |
| 1666 Iterable<js.VariableInitialization> variableInitializations() { | |
| 1667 List<js.VariableInitialization> variables = | |
| 1668 new List<js.VariableInitialization>(); | |
| 1669 variables.add(_makeVariableInitializer(completer, | |
| 1670 new js.New(newCompleter, []))); | |
| 1671 if (analysis.hasExplicitReturns) { | |
| 1672 variables.add(_makeVariableInitializer(returnValue, null)); | |
| 1673 } | |
| 1674 return variables; | |
| 1675 } | |
| 1676 | |
| 1677 @override | |
| 1678 void initializeNames() { | |
| 1679 completerName = freshName("completer"); | |
| 1680 } | |
| 1681 | |
| 1682 @override | |
| 1683 js.Statement awaitStatement(js.Expression value) { | |
| 1684 return js.js.statement(""" | |
| 1685 return #asyncHelper(#value, | |
| 1686 #body, | |
| 1687 #completer); | |
| 1688 """, { | |
| 1689 "asyncHelper": asyncHelper, | |
| 1690 "value": value, | |
| 1691 "body": body, | |
| 1692 "completer": completer}); | |
| 1693 } | |
| 1694 | |
| 1695 @override | |
| 1696 js.Fun generateMainFunction(List<js.Parameter> parameters, | |
| 1697 js.Statement rewrittenBody, | |
|
floitsch
2015/02/25 16:27:59
indentation.
sigurdm
2015/02/27 09:46:33
Done.
| |
| 1698 js.VariableDeclarationList variableDeclarations) { | |
| 1699 return js.js(""" | |
| 1700 function (#parameters) { | |
| 1701 #variableDeclarations; | |
| 1702 function #bodyName(#errorCode, #result) { | |
| 1703 if (#errorCode === #ERROR) { | |
| 1704 #currentError = #result; | |
| 1705 #goto = #handler; | |
| 1706 } | |
| 1707 while (true) | |
|
floitsch
2015/02/25 16:27:59
In the next? CL please move the "while" to the rew
sigurdm
2015/02/27 09:46:33
Did it here.
| |
| 1708 #rewrittenBody; | |
| 1709 } | |
| 1710 return #asyncHelper(null, #bodyName, #completer, null); | |
| 1711 }""", { | |
| 1712 "parameters": parameters, | |
| 1713 "variableDeclarations": variableDeclarations, | |
| 1714 "ERROR": js.number(error_codes.ERROR), | |
| 1715 "rewrittenBody": rewrittenBody, | |
| 1716 "bodyName": bodyName, | |
| 1717 "currentError": currentError, | |
| 1718 "goto": goto, | |
| 1719 "handler": handler, | |
| 1720 "errorCode": errorCodeName, | |
| 1721 "result": resultName, | |
| 1722 "asyncHelper": asyncHelper, | |
| 1723 "completer": completer, | |
| 1724 }); | |
| 1725 } | |
| 1726 } | |
| 1727 | |
| 1728 class SyncStarRewriter extends AsyncRewriterBase { | |
| 1729 | |
| 1730 bool get isSyncStar => true; | |
| 1731 | |
| 1732 /// Contructor creating the Iterable for a sync* method. Called with | |
| 1733 /// [bodyName]. | |
| 1734 final js.Expression newIterable; | |
| 1735 | |
| 1736 /// A JS Expression that creates a marker showing that iteration is over. | |
| 1737 /// | |
| 1738 /// Called without arguments. | |
| 1739 final js.Expression endOfIteration; | |
| 1740 | |
| 1741 /// A JS Expression that creates a marker indication a 'yield*' statement. | |
| 1742 /// | |
| 1743 /// Called with the stream to yield from. | |
| 1744 final js.Expression yieldStarExpression; | |
| 1745 | |
| 1746 /// Used by sync* functions to throw exeptions. | |
| 1747 final js.Expression uncaughtErrorExpression; | |
| 1748 | |
| 1749 SyncStarRewriter(DiagnosticListener diagnosticListener, | |
| 1750 spannable, | |
| 1751 {this.endOfIteration, | |
| 1752 this.newIterable, | |
| 1753 this.yieldStarExpression, | |
| 1754 this.uncaughtErrorExpression, | |
| 1755 safeVariableName}) | |
| 1756 : super(diagnosticListener, | |
| 1757 spannable, | |
| 1758 safeVariableName); | |
| 1759 | |
| 1817 /// Translates a yield/yield* in an sync*. | 1760 /// Translates a yield/yield* in an sync*. |
| 1818 /// | 1761 /// |
| 1819 /// `yield` in a sync* function just returns [value]. | 1762 /// `yield` in a sync* function just returns [value]. |
| 1820 /// `yield*` wraps [value] in a [yieldStarExpression] and returns it. | 1763 /// `yield*` wraps [value] in a [yieldStarExpression] and returns it. |
| 1821 void addSyncYield(js.DartYield node, js.Expression expression) { | 1764 @override |
| 1822 assert(isSyncStar); | 1765 void addYield(js.DartYield node, js.Expression expression) { |
| 1823 if (node.hasStar) { | 1766 if (node.hasStar) { |
| 1824 addStatement( | 1767 addStatement( |
| 1825 new js.Return(new js.Call(yieldStarExpression, [expression]))); | 1768 new js.Return(new js.Call(yieldStarExpression, [expression]))); |
| 1826 } else { | 1769 } else { |
| 1827 addStatement(new js.Return(expression)); | 1770 addStatement(new js.Return(expression)); |
| 1828 } | 1771 } |
| 1829 } | 1772 } |
| 1830 | 1773 |
| 1774 @override | |
| 1775 js.Fun generateMainFunction(List<js.Parameter> params, | |
| 1776 js.Statement rewrittenBody, | |
| 1777 js.VariableDeclarationList variableDeclarations) { | |
| 1778 return js.js(""" | |
| 1779 function (#params) { | |
| 1780 if (#needsThis) | |
| 1781 var #self = this; | |
| 1782 return new #newIterable(function () { | |
| 1783 #varDecl; | |
| 1784 return function #body(#errorCode, #result) { | |
| 1785 if (#errorCode === #ERROR) { | |
| 1786 #currentError = #result; | |
| 1787 #goto = #handler; | |
| 1788 } | |
| 1789 while (true) | |
| 1790 #helperBody; | |
| 1791 }; | |
| 1792 }); | |
| 1793 } | |
| 1794 """, { | |
| 1795 "params": params, | |
| 1796 "needsThis": analysis.hasThis, | |
| 1797 "helperBody": rewrittenBody, | |
| 1798 "varDecl": variableDeclarations, | |
| 1799 "errorCode": errorCodeName, | |
| 1800 "newIterable": newIterable, | |
| 1801 "body": bodyName, | |
| 1802 "self": selfName, | |
| 1803 "result": resultName, | |
| 1804 "goto": goto, | |
| 1805 "handler": handler, | |
| 1806 "currentError": currentErrorName, | |
| 1807 "ERROR": js.number(error_codes.ERROR), | |
| 1808 }); | |
| 1809 } | |
| 1810 | |
| 1811 void addErrorExit() { | |
| 1812 beginLabel(rethrowLabel); | |
| 1813 addStatement(js.js.statement('return #(#);', | |
| 1814 [uncaughtErrorExpression, currentError])); | |
| 1815 } | |
| 1816 | |
| 1817 /// Returning from a sync* function returns an [endOfIteration] marker. | |
| 1818 void addSuccesExit() { | |
| 1819 if (analysis.hasExplicitReturns) { | |
| 1820 beginLabel(exitLabel); | |
| 1821 } else { | |
| 1822 addStatement(new js.Comment("implicit return")); | |
| 1823 } | |
| 1824 addStatement(js.js.statement('return #();', [endOfIteration])); | |
| 1825 } | |
| 1826 | |
| 1827 @override | |
| 1828 Iterable<js.VariableInitialization> variableInitializations() { | |
| 1829 List<js.VariableInitialization> variables = | |
| 1830 new List<js.VariableInitialization>(); | |
| 1831 return variables; | |
| 1832 } | |
| 1833 | |
| 1834 @override | |
| 1835 js.Statement awaitStatement(js.Expression value) { | |
| 1836 throw diagnosticListener.internalError(spannable, | |
| 1837 "Sync* functions cannot contain await statements."); | |
| 1838 } | |
| 1839 | |
| 1840 @override | |
| 1841 void initializeNames() {} | |
| 1842 } | |
| 1843 | |
| 1844 class AsyncStarRewriter extends AsyncRewriterBase { | |
| 1845 | |
| 1846 bool get isAsyncStar => true; | |
| 1847 | |
| 1848 /// The stack of labels of finally blocks to assign to [next] if the | |
| 1849 /// async* [StreamSubscription] was canceled during a yield. | |
| 1850 js.VariableUse get nextWhenCanceled { | |
| 1851 return new js.VariableUse(nextWhenCanceledName); | |
| 1852 } | |
| 1853 String nextWhenCanceledName; | |
| 1854 | |
| 1855 /// The StreamController that controls an async* function. | |
| 1856 String controllerName; | |
| 1857 js.VariableUse get controller => new js.VariableUse(controllerName); | |
| 1858 | |
| 1859 /// The function called by an async* function to simulate an await, yield or | |
| 1860 /// yield*. | |
| 1861 /// | |
| 1862 /// For an await/yield/yield* it is called with: | |
| 1863 /// | |
| 1864 /// - The value to await/yieldExpression(value to yield)/ | |
| 1865 /// yieldStarExpression(stream to yield) | |
| 1866 /// - The body function [bodyName] | |
| 1867 /// - The controller object [controllerName] | |
| 1868 /// | |
| 1869 /// For a return it is called with: | |
| 1870 /// | |
| 1871 /// - null | |
| 1872 /// - null | |
| 1873 /// - The [controllerName] | |
| 1874 /// - null. | |
| 1875 final js.Expression asyncStarHelper; | |
| 1876 | |
| 1877 /// Contructor used to initialize the [controllerName] variable. | |
| 1878 /// | |
| 1879 /// Specific to async* methods. | |
| 1880 final js.Expression newController; | |
| 1881 | |
| 1882 /// Used to get the `Stream` out of the [controllerName] variable. | |
| 1883 final js.Expression streamOfController; | |
| 1884 | |
| 1885 /// A JS Expression that creates a marker indicating a 'yield' statement. | |
| 1886 /// | |
| 1887 /// Called with the value to yield. | |
| 1888 final js.Expression yieldExpression; | |
| 1889 | |
| 1890 /// A JS Expression that creates a marker indication a 'yield*' statement. | |
| 1891 /// | |
| 1892 /// Called with the stream to yield from. | |
| 1893 final js.Expression yieldStarExpression; | |
| 1894 | |
| 1895 AsyncStarRewriter(DiagnosticListener diagnosticListener, | |
| 1896 spannable, | |
| 1897 {this.asyncStarHelper, | |
| 1898 this.streamOfController, | |
| 1899 this.newController, | |
| 1900 this.yieldExpression, | |
| 1901 this.yieldStarExpression, | |
| 1902 String safeVariableName(String original)}) | |
| 1903 : super(diagnosticListener, | |
| 1904 spannable, | |
| 1905 safeVariableName); | |
| 1906 | |
| 1907 | |
| 1831 /// Translates a yield/yield* in an async* function. | 1908 /// Translates a yield/yield* in an async* function. |
| 1832 /// | 1909 /// |
| 1833 /// yield/yield* in an async* function is translated much like the `await` is | 1910 /// yield/yield* in an async* function is translated much like the `await` is |
| 1834 /// translated in [visitAwait], only the object is wrapped in a | 1911 /// translated in [visitAwait], only the object is wrapped in a |
| 1835 /// [yieldExpression]/[yieldStarExpression] to let [asyncStarHelper] | 1912 /// [yieldExpression]/[yieldStarExpression] to let [asyncStarHelper] |
| 1836 /// distinguish them. | 1913 /// distinguish them. |
| 1837 /// Also [nextWhenCanceledName] is set up to contain the finally blocks that | 1914 /// Also [nextWhenCanceled] is set up to contain the finally blocks that |
| 1838 /// must be run in case the stream was canceled. | 1915 /// must be run in case the stream was canceled. |
| 1839 void addAsyncYield(js.DartYield node, js.Expression expression) { | 1916 @override |
| 1840 assert(isAsyncStar); | 1917 void addYield(js.DartYield node, js.Expression expression) { |
| 1841 // Find all the finally blocks that should be performed if the stream is | 1918 // Find all the finally blocks that should be performed if the stream is |
| 1842 // canceled during the yield. | 1919 // canceled during the yield. |
| 1843 // At the bottom of the stack is the return label. | 1920 // At the bottom of the stack is the return label. |
| 1844 List<int> enclosingFinallyLabels = <int>[exitLabel]; | 1921 List<int> enclosingFinallyLabels = <int>[exitLabel]; |
| 1845 enclosingFinallyLabels.addAll(jumpTargets | 1922 enclosingFinallyLabels.addAll(jumpTargets |
| 1846 .where((js.Node node) => finallyLabels[node] != null) | 1923 .where((js.Node node) => finallyLabels[node] != null) |
| 1847 .map((js.Block node) => finallyLabels[node])); | 1924 .map((js.Block node) => finallyLabels[node])); |
| 1848 addStatement(js.js.statement("# = #;", | 1925 addStatement(js.js.statement("# = #;", |
| 1849 [nextWhenCanceledName, new js.ArrayInitializer( | 1926 [nextWhenCanceled, new js.ArrayInitializer( |
| 1850 enclosingFinallyLabels.map(js.number).toList())])); | 1927 enclosingFinallyLabels.map(js.number).toList())])); |
| 1851 addStatement(js.js.statement(""" | 1928 addStatement(js.js.statement(""" |
| 1852 return #streamHelper(#yieldExpression(#expression), #body, | 1929 return #asyncStarHelper(#yieldExpression(#expression), #body, |
| 1853 #controller);""", { | 1930 #controller);""", { |
| 1854 "streamHelper": streamHelper, | 1931 "asyncStarHelper": asyncStarHelper, |
| 1855 "yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression, | 1932 "yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression, |
| 1856 "expression": expression, | 1933 "expression": expression, |
| 1857 "body": bodyName, | 1934 "body": body, |
| 1858 "controller": controllerName, | 1935 "controller": controllerName, |
| 1859 })); | 1936 })); |
| 1860 } | 1937 } |
| 1861 | 1938 |
| 1862 @override | 1939 @override |
| 1863 void visitDartYield(js.DartYield node) { | 1940 js.Fun generateMainFunction(List<js.Parameter> parameters, |
| 1864 assert(isSyncStar || isAsyncStar); | 1941 js.Statement rewrittenBody, |
| 1865 int label = newLabel("after yield"); | 1942 js.VariableDeclarationList variableDeclarations) { |
| 1866 // Don't do a break here for the goto, but instead a return in either | 1943 return js.js(""" |
| 1867 // addSynYield or addAsyncYield. | 1944 function (#parameters) { |
| 1868 withExpression(node.expression, (js.Expression expression) { | 1945 #variableDeclarations; |
| 1869 addStatement(setGotoVariable(label)); | 1946 function #bodyName(#errorCode, #result) { |
| 1870 if (isSyncStar) { | 1947 if (#hasYield) |
| 1871 addSyncYield(node, expression); | 1948 switch (#errorCode) { |
| 1872 } else { | 1949 case #STREAM_WAS_CANCELED: |
| 1873 addAsyncYield(node, expression); | 1950 #next = #nextWhenCanceled; |
| 1874 } | 1951 #goto = #next.pop(); |
| 1875 }, store: false); | 1952 break; |
| 1876 beginLabel(label); | 1953 case #ERROR: |
| 1954 #currentError = #result; | |
| 1955 #goto = #handler; | |
| 1956 } | |
| 1957 else | |
| 1958 if (#errorCode === #ERROR) { | |
| 1959 #currentError = #result; | |
| 1960 #goto = #handler; | |
| 1961 } | |
| 1962 while (true) | |
| 1963 #rewrittenBody; | |
| 1964 } | |
| 1965 return #streamOfController(#controller); | |
| 1966 }""", { | |
| 1967 "parameters": parameters, | |
| 1968 "variableDeclarations": variableDeclarations, | |
| 1969 "STREAM_WAS_CANCELED": js.number(error_codes.STREAM_WAS_CANCELED), | |
| 1970 "ERROR": js.number(error_codes.ERROR), | |
| 1971 "hasYield": analysis.hasYield, | |
| 1972 "rewrittenBody": rewrittenBody, | |
| 1973 "bodyName": bodyName, | |
| 1974 "currentError": currentError, | |
| 1975 "goto": goto, | |
| 1976 "handler": handler, | |
| 1977 "next": next, | |
| 1978 "nextWhenCanceled": nextWhenCanceled, | |
| 1979 "errorCode": errorCodeName, | |
| 1980 "result": resultName, | |
| 1981 "streamOfController": streamOfController, | |
| 1982 "controller": controllerName, | |
| 1983 }); | |
| 1984 } | |
| 1985 | |
| 1986 @override | |
| 1987 void addErrorExit() { | |
| 1988 beginLabel(rethrowLabel); | |
| 1989 addStatement(js.js.statement( | |
| 1990 "return #asyncHelper(#currentError, #errorCode, #controller);", { | |
| 1991 "asyncHelper": asyncStarHelper, | |
| 1992 "errorCode": js.number(error_codes.ERROR), | |
| 1993 "currentError": currentError, | |
| 1994 "controller": controllerName})); | |
| 1995 } | |
| 1996 | |
| 1997 /// Returning from an async* function calls the [streamHelper] with an | |
| 1998 /// [endOfIteration] marker. | |
| 1999 @override | |
| 2000 void addSuccesExit() { | |
| 2001 beginLabel(exitLabel); | |
| 2002 | |
| 2003 addStatement(js.js.statement( | |
| 2004 "return #streamHelper(null, #successCode, #controller);", { | |
| 2005 "streamHelper": asyncStarHelper, | |
| 2006 "successCode": js.number(error_codes.SUCCESS), | |
| 2007 "controller": controllerName})); | |
| 2008 } | |
| 2009 | |
| 2010 @override | |
| 2011 Iterable<js.VariableInitialization> variableInitializations() { | |
| 2012 List<js.VariableInitialization> variables = | |
| 2013 new List<js.VariableInitialization>(); | |
| 2014 variables.add(_makeVariableInitializer(controller, | |
| 2015 js.js('#(#)', [newController, bodyName]))); | |
| 2016 if (analysis.hasYield) { | |
| 2017 variables.add(_makeVariableInitializer(nextWhenCanceled, null)); | |
| 2018 } | |
| 2019 return variables; | |
| 2020 } | |
| 2021 | |
| 2022 @override | |
| 2023 void initializeNames() { | |
| 2024 controllerName = freshName("controller"); | |
| 2025 nextWhenCanceledName = freshName("nextWhenCanceled"); | |
| 2026 } | |
| 2027 | |
| 2028 @override | |
| 2029 js.Statement awaitStatement(js.Expression value) { | |
| 2030 return js.js.statement(""" | |
| 2031 return #asyncHelper(#value, | |
| 2032 #body, | |
| 2033 #controller); | |
| 2034 """, { | |
| 2035 "asyncHelper": asyncStarHelper, | |
| 2036 "value": value, | |
| 2037 "body": body, | |
| 2038 "controller": controllerName}); | |
| 1877 } | 2039 } |
| 1878 } | 2040 } |
| 1879 | 2041 |
| 1880 /// Finds out | 2042 /// Finds out |
| 1881 /// | 2043 /// |
| 1882 /// - which expressions have yield or await nested in them. | 2044 /// - which expressions have yield or await nested in them. |
| 1883 /// - targets of jumps | 2045 /// - targets of jumps |
| 1884 /// - a set of used names. | 2046 /// - a set of used names. |
| 1885 /// - if any [This]-expressions are used. | 2047 /// - if any [This]-expressions are used. |
| 1886 class PreTranslationAnalysis extends js.NodeVisitor<bool> { | 2048 class PreTranslationAnalysis extends js.NodeVisitor<bool> { |
| (...skipping 407 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2294 return condition || body; | 2456 return condition || body; |
| 2295 } | 2457 } |
| 2296 | 2458 |
| 2297 @override | 2459 @override |
| 2298 bool visitDartYield(js.DartYield node) { | 2460 bool visitDartYield(js.DartYield node) { |
| 2299 hasYield = true; | 2461 hasYield = true; |
| 2300 visit(node.expression); | 2462 visit(node.expression); |
| 2301 return true; | 2463 return true; |
| 2302 } | 2464 } |
| 2303 } | 2465 } |
| OLD | NEW |