| 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 // TODO(sigurdm): Throws in catch-handlers are handled wrong. | |
| 8 // TODO(sigurdm): Avoid using variables in templates. It could blow up memory | 7 // TODO(sigurdm): Avoid using variables in templates. It could blow up memory |
| 9 // use. | 8 // use. |
| 9 // TODO(sigurdm): Move the try/catch expression to a js_helper function. |
| 10 // That would also simplify the sync* case, where the error can just be thrown. |
| 10 | 11 |
| 11 import "dart:math" show max; | 12 import "dart:math" show max; |
| 12 import 'dart:collection'; | 13 import 'dart:collection'; |
| 13 | 14 |
| 15 import 'package:_internal/compiler/js_lib/shared/async_await_error_codes.dart' |
| 16 as error_codes; |
| 17 |
| 14 import "js.dart" as js; | 18 import "js.dart" as js; |
| 15 | 19 |
| 16 import '../util/util.dart'; | 20 import '../util/util.dart'; |
| 17 import '../dart2jslib.dart' show DiagnosticListener; | 21 import '../dart2jslib.dart' show DiagnosticListener; |
| 18 | 22 |
| 19 import "../helpers/helpers.dart"; | 23 import "../helpers/helpers.dart"; |
| 20 | 24 |
| 21 /// Rewrites a [js.Fun] with async/sync*/async* functions and await and yield | 25 /// Rewrites a [js.Fun] with async/sync*/async* functions and await and yield |
| 22 /// (with dart-like semantics) to an equivalent function without these. | 26 /// (with dart-like semantics) to an equivalent function without these. |
| 23 /// await-for is not handled and must be rewritten before. (Currently handled | 27 /// await-for is not handled and must be rewritten before. (Currently handled |
| 24 /// in ssa/builder.dart). | 28 /// in ssa/builder.dart). |
| 25 /// | 29 /// |
| 26 /// When generating the input to this, special care must be taken that | 30 /// When generating the input to this, special care must be taken that |
| 27 /// parameters to sync* functions that are mutated in the body must be boxed. | 31 /// parameters to sync* functions that are mutated in the body must be boxed. |
| 28 /// (Currently handled in closure.dart). | 32 /// (Currently handled in closure.dart). |
| 29 /// | 33 /// |
| 30 /// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation. | 34 /// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation. |
| 31 class AsyncRewriter extends js.NodeVisitor { | 35 class AsyncRewriter extends js.NodeVisitor { |
| 32 | 36 |
| 33 // Local variables are hoisted to the top of the function, so they are | 37 // Local variables are hoisted to the top of the function, so they are |
| 34 // collected here. | 38 // collected here. |
| 35 List<js.VariableDeclaration> localVariables = | 39 List<js.VariableDeclaration> localVariables = |
| 36 new List<js.VariableDeclaration>(); | 40 new List<js.VariableDeclaration>(); |
| 37 | 41 |
| 38 Map<js.Node, int> continueLabels = new Map<js.Node, int>(); | 42 Map<js.Node, int> continueLabels = new Map<js.Node, int>(); |
| 39 Map<js.Node, int> breakLabels = new Map<js.Node, int>(); | 43 Map<js.Node, int> breakLabels = new Map<js.Node, int>(); |
| 40 Map<js.Node, int> finallyLabels = new Map<js.Node, int>(); | 44 |
| 45 /// The label of a finally part. |
| 46 Map<js.Block, int> finallyLabels = new Map<js.Block, int>(); |
| 47 |
| 48 /// The label of the catch handler of a [js.Try] or a [js.Fun] or [js.Catch]. |
| 49 /// |
| 50 /// These mark the points an error can be consumed. |
| 51 /// |
| 52 /// - The handler of a [js.Fun] is the outermost and will rethrow the error. |
| 53 /// - The handler of a [js.Try] will run the catch handler. |
| 54 /// - The handler of a [js.Catch] is a synthetic handler that ensures the |
| 55 /// right finally blocks are run if an error is thrown inside a |
| 56 /// catch-handler. |
| 57 Map<js.Node, int> handlerLabels = new Map<js.Node, int>(); |
| 58 |
| 41 int exitLabel; | 59 int exitLabel; |
| 60 int rethrowLabel; |
| 42 | 61 |
| 43 // A stack of all enclosing jump targets (including the function for | 62 /// A stack of all (surrounding) jump targets. |
| 44 // representing the target of a return, and all enclosing try-blocks that have | 63 /// |
| 45 // finally part, this way ensuring all the finally blocks between a jump and | 64 /// Jump targets are: |
| 46 // its target are run before the jump. | 65 /// |
| 47 List<js.Node> targetsAndTries = new List<js.Node>(); | 66 /// * The function, signalling a return or uncaught throw. |
| 67 /// * Loops. |
| 68 /// * LabeledStatements (also used for 'continue' when attached to loops). |
| 69 /// * Try statements, for catch and finally handlers. |
| 70 /// * Catch handlers, when inside a catch-part of a try, the catch-handler is |
| 71 /// used to associate with a synthetic handler that will ensure the right |
| 72 /// finally blocks are visited. |
| 73 /// |
| 74 /// When jumping to a target it is necessary to visit all finallies that |
| 75 /// are on the way to target (i.e. more nested than the jump target). |
| 76 List<js.Node> jumpTargets = new List<js.Node>(); |
| 48 | 77 |
| 49 List<int> continueStack = new List<int>(); | 78 List<int> continueStack = new List<int>(); |
| 50 List<int> breakStack = new List<int>(); | 79 List<int> breakStack = new List<int>(); |
| 51 List<int> returnStack = new List<int>(); | 80 List<int> returnStack = new List<int>(); |
| 52 | 81 |
| 53 List<Pair<String, String>> variableRenamings = | 82 List<Pair<String, String>> variableRenamings = |
| 54 new List<Pair<String, String>>(); | 83 new List<Pair<String, String>>(); |
| 55 | 84 |
| 56 PreTranslationAnalysis analysis; | 85 PreTranslationAnalysis analysis; |
| 57 | 86 |
| 58 List<int> errorHandlerLabels = new List<int>(); | |
| 59 | |
| 60 final Function safeVariableName; | 87 final Function safeVariableName; |
| 61 | 88 |
| 62 // All the <x>Name variables are names of Javascript variables used in the | 89 // All the <x>Name variables are names of Javascript variables used in the |
| 63 // transformed code. | 90 // transformed code. |
| 64 | 91 |
| 65 /// Contains the result of an awaited expression, or a conditional or | 92 /// Contains the result of an awaited expression, or a conditional or |
| 66 /// lazy boolean operator. | 93 /// lazy boolean operator. |
| 67 /// | 94 /// |
| 68 /// For example a conditional expression is roughly translated like: | 95 /// For example a conditional expression is roughly translated like: |
| 69 /// [[cond ? a : b]] | 96 /// [[cond ? a : b]] |
| 70 /// | 97 /// |
| 71 /// Becomes: | 98 /// Becomes: |
| 72 /// | 99 /// |
| 73 /// while true { // outer while loop | 100 /// while true { // outer while loop |
| 74 /// switch (goto) { // Simulates goto | 101 /// switch (goto) { // Simulates goto |
| 75 /// ... | 102 /// ... |
| 76 /// goto = [[cond]] ? thenLabel : elseLabel | 103 /// goto = [[cond]] ? thenLabel : elseLabel |
| 77 /// break; | 104 /// break; |
| 78 /// case thenLabel: | 105 /// case thenLabel: |
| 79 /// result = [[a]]; | 106 /// result = [[a]]; |
| 80 /// goto = joinLabel; | 107 /// goto = joinLabel; |
| 81 /// case elseLabel: | 108 /// case elseLabel: |
| 82 /// result = [[b]]; | 109 /// result = [[b]]; |
| 83 /// case joinLabel: | 110 /// case joinLabel: |
| 84 /// // Now the result of computing the condition is in result. | 111 /// // Now the result of computing the condition is in result. |
| 85 /// .... | 112 /// .... |
| 86 /// } | 113 /// } |
| 87 /// } | 114 /// } |
| 88 /// | 115 /// |
| 89 /// It is a parameter to the [helperName] function, so that [thenHelper] and | 116 /// It is a parameter to the [bodyName] function, so that [asyncHelper] and |
| 90 /// [streamHelper] can call [helperName] with the result of an awaited Future. | 117 /// [streamHelper] can call [bodyName] with the result of an awaited Future. |
| 91 String resultName; | 118 String resultName; |
| 92 | 119 |
| 120 /// A parameter to the [bodyName] function. Indicating if we are in success |
| 121 /// or error case. |
| 122 String errorCodeName; |
| 123 |
| 93 /// The name of the inner function that is scheduled to do each await/yield, | 124 /// The name of the inner function that is scheduled to do each await/yield, |
| 94 /// and called to do a new iteration for sync*. | 125 /// and called to do a new iteration for sync*. |
| 95 String helperName; | 126 String bodyName; |
| 96 | 127 |
| 97 /// The Completer that will finish an async function. | 128 /// The Completer that will finish an async function. |
| 98 /// | 129 /// |
| 99 /// Not used for sync* or async* functions. | 130 /// Not used for sync* or async* functions. |
| 100 String completerName; | 131 String completerName; |
| 101 | 132 |
| 102 /// The StreamController that controls an async* function. | 133 /// The StreamController that controls an async* function. |
| 103 /// | 134 /// |
| 104 /// Not used for async and sync* functions | 135 /// Not used for async and sync* functions |
| 105 String controllerName; | 136 String controllerName; |
| 106 | 137 |
| 107 /// Used to simulate a goto. | 138 /// Used to simulate a goto. |
| 108 /// | 139 /// |
| 109 /// To "goto" a label, the label is assigned to this | 140 /// To "goto" a label, the label is assigned to this |
| 110 /// variable, and break out of the switch to take another iteration in the | 141 /// variable, and break out of the switch to take another iteration in the |
| 111 /// while loop. See [addGoto] | 142 /// while loop. See [addGoto] |
| 112 String gotoName; | 143 String gotoName; |
| 113 | 144 |
| 114 /// The label of the current error handler. | 145 /// The label of the current error handler. |
| 115 String handlerName; | 146 String handlerName; |
| 116 | 147 |
| 117 /// Current caught error. | 148 /// Current caught error. |
| 118 String errorName; | 149 String errorName; |
| 119 | 150 |
| 120 /// A stack of labels of finally blocks to visit, and the label to go to after | 151 /// A stack of labels of finally blocks to visit, and the label to go to after |
| 121 /// the last. | 152 /// the last. |
| 122 String nextName; | 153 String nextName; |
| 123 | 154 |
| 155 /// The stack of labels of finally blocks to assign to [nextName] if the |
| 156 /// async* [StreamSubscription] was canceled during a yield. |
| 157 String nextWhenCanceledName; |
| 158 |
| 124 /// The current returned value (a finally block may overwrite it). | 159 /// The current returned value (a finally block may overwrite it). |
| 125 String returnValueName; | 160 String returnValueName; |
| 126 | 161 |
| 162 /// If we are in the process of handling an error, stores the current error. |
| 163 String currentErrorName; |
| 164 |
| 127 /// The label of the outer loop. | 165 /// The label of the outer loop. |
| 128 /// | 166 /// |
| 129 /// Used if there are untransformed loops containing break or continues to | 167 /// Used if there are untransformed loops containing break or continues to |
| 130 /// targets outside the loop. | 168 /// targets outside the loop. |
| 131 String outerLabelName; | 169 String outerLabelName; |
| 132 | 170 |
| 133 /// If javascript `this` is used, it is accessed via this variable, in the | 171 /// If javascript `this` is used, it is accessed via this variable, in the |
| 134 /// [helperName] function. | 172 /// [bodyName] function. |
| 135 String selfName; | 173 String selfName; |
| 136 | 174 |
| 137 // These expressions are hooks for communicating with the runtime. | 175 // These expressions are hooks for communicating with the runtime. |
| 138 | 176 |
| 139 /// The function called by an async function to simulate an await or return. | 177 /// The function called by an async function to simulate an await or return. |
| 140 /// | 178 /// |
| 141 /// For an await it is called with: | 179 /// For an await it is called with: |
| 142 /// | 180 /// |
| 143 /// - The value to await | 181 /// - The value to await |
| 144 /// - The [helperName] | 182 /// - The body function [bodyName] |
| 145 /// - The [completerName] | 183 /// - The completer object [completerName] |
| 146 /// - A JavaScript function that is executed if the future completed with | |
| 147 /// an error. That function is responsible for executing the right error | |
| 148 /// handler and/or finally blocks). | |
| 149 /// | 184 /// |
| 150 /// For a return it is called with: | 185 /// For a return it is called with: |
| 151 /// | 186 /// |
| 152 /// - The value to complete the completer with. | 187 /// - The value to complete the completer with. |
| 153 /// - null | 188 /// - [error_codes.SUCCESS] |
| 154 /// - The [completerName] | 189 /// - The completer object [completerName] |
| 155 /// - null. | 190 /// |
| 156 final js.Expression thenHelper; | 191 /// For a throw it is called with: |
| 192 /// |
| 193 /// - The error to complete the completer with. |
| 194 /// - [error_codes.ERROR] |
| 195 /// - The completer object [completerName] |
| 196 final js.Expression asyncHelper; |
| 157 | 197 |
| 158 /// The function called by an async* function to simulate an await, yield or | 198 /// The function called by an async* function to simulate an await, yield or |
| 159 /// yield*. | 199 /// yield*. |
| 160 /// | 200 /// |
| 161 /// For an await/yield/yield* it is called with: | 201 /// For an await/yield/yield* it is called with: |
| 162 /// | 202 /// |
| 163 /// - The value to await/yieldExpression(value to yield)/ | 203 /// - The value to await/yieldExpression(value to yield)/ |
| 164 /// yieldStarExpression(stream to yield) | 204 /// yieldStarExpression(stream to yield) |
| 165 /// - The [helperName] | 205 /// - The body function [bodyName] |
| 166 /// - The [controllerName] | 206 /// - The controller object [controllerName] |
| 167 /// - A JavaScript function that is executed if the future completed with | |
| 168 /// an error. That function is responsible for executing the right error | |
| 169 /// handler and/or finally blocks). | |
| 170 /// | 207 /// |
| 171 /// For a return it is called with: | 208 /// For a return it is called with: |
| 172 /// | 209 /// |
| 173 /// - null | 210 /// - null |
| 174 /// - null | 211 /// - null |
| 175 /// - The [controllerName] | 212 /// - The [controllerName] |
| 176 /// - null. | 213 /// - null. |
| 177 final js.Expression streamHelper; | 214 final js.Expression streamHelper; |
| 178 | 215 |
| 179 /// Contructor used to initialize the [completerName] variable. | 216 /// Contructor used to initialize the [completerName] variable. |
| 180 /// | 217 /// |
| 181 /// Specific to async methods. | 218 /// Specific to async methods. |
| 182 final js.Expression newCompleter; | 219 final js.Expression newCompleter; |
| 183 | 220 |
| 184 /// Contructor used to initialize the [controllerName] variable. | 221 /// Contructor used to initialize the [controllerName] variable. |
| 185 /// | 222 /// |
| 186 /// Specific to async* methods. | 223 /// Specific to async* methods. |
| 187 final js.Expression newController; | 224 final js.Expression newController; |
| 188 | 225 |
| 189 /// Used to get the `Stream` out of the [controllerName] variable. | 226 /// Used to get the `Stream` out of the [controllerName] variable. |
| 190 /// | 227 /// |
| 191 /// Specific to async* methods. | 228 /// Specific to async* methods. |
| 192 final js.Expression streamOfController; | 229 final js.Expression streamOfController; |
| 193 | 230 |
| 194 /// Contructor creating the Iterable for a sync* method. Called with | 231 /// Contructor creating the Iterable for a sync* method. Called with |
| 195 /// [helperName]. | 232 /// [bodyName]. |
| 196 final js.Expression newIterable; | 233 final js.Expression newIterable; |
| 197 | 234 |
| 198 /// A JS Expression that creates a marker showing that iteration is over. | 235 /// A JS Expression that creates a marker showing that iteration is over. |
| 199 /// | 236 /// |
| 200 /// Called without arguments. | 237 /// Called without arguments. |
| 201 final js.Expression endOfIteration; | 238 final js.Expression endOfIteration; |
| 202 | 239 |
| 203 /// A JS Expression that creates a marker indicating a 'yield' statement. | 240 /// A JS Expression that creates a marker indicating a 'yield' statement. |
| 204 /// | 241 /// |
| 205 /// Called with the value to yield. | 242 /// Called with the value to yield. |
| 206 final js.Expression yieldExpression; | 243 final js.Expression yieldExpression; |
| 207 | 244 |
| 208 /// A JS Expression that creates a marker indication a 'yield*' statement. | 245 /// A JS Expression that creates a marker indication a 'yield*' statement. |
| 209 /// | 246 /// |
| 210 /// Called with the stream to yield from. | 247 /// Called with the stream to yield from. |
| 211 final js.Expression yieldStarExpression; | 248 final js.Expression yieldStarExpression; |
| 212 | 249 |
| 250 /// Used by sync* functions to throw exeptions. |
| 251 final js.Expression uncaughtErrorExpression; |
| 252 |
| 213 final DiagnosticListener diagnosticListener; | 253 final DiagnosticListener diagnosticListener; |
| 214 // For error reporting only. | 254 // For error reporting only. |
| 215 Spannable get spannable { | 255 Spannable get spannable { |
| 216 return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable; | 256 return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable; |
| 217 } | 257 } |
| 218 | 258 |
| 219 Spannable _spannable; | 259 Spannable _spannable; |
| 220 | 260 |
| 221 int _currentLabel = 0; | 261 int _currentLabel = 0; |
| 222 | 262 |
| 223 // The highest temporary variable index currently in use. | 263 // The highest temporary variable index currently in use. |
| 224 int currentTempVarIndex = 0; | 264 int currentTempVarIndex = 0; |
| 225 // The highest temporary variable index ever in use in this function. | 265 // The highest temporary variable index ever in use in this function. |
| 226 int tempVarHighWaterMark = 0; | 266 int tempVarHighWaterMark = 0; |
| 227 Map<int, js.Expression> tempVarNames = new Map<int, js.Expression>(); | 267 Map<int, js.Expression> tempVarNames = new Map<int, js.Expression>(); |
| 228 | 268 |
| 229 js.AsyncModifier async; | 269 js.AsyncModifier async; |
| 230 | 270 |
| 231 bool get isSync => async == const js.AsyncModifier.sync(); | 271 bool get isSync => async == const js.AsyncModifier.sync(); |
| 232 bool get isAsync => async == const js.AsyncModifier.async(); | 272 bool get isAsync => async == const js.AsyncModifier.async(); |
| 233 bool get isSyncStar => async == const js.AsyncModifier.syncStar(); | 273 bool get isSyncStar => async == const js.AsyncModifier.syncStar(); |
| 234 bool get isAsyncStar => async == const js.AsyncModifier.asyncStar(); | 274 bool get isAsyncStar => async == const js.AsyncModifier.asyncStar(); |
| 235 | 275 |
| 236 AsyncRewriter(this.diagnosticListener, | 276 AsyncRewriter(this.diagnosticListener, |
| 237 spannable, | 277 spannable, |
| 238 {this.thenHelper, | 278 {this.asyncHelper, |
| 239 this.streamHelper, | 279 this.streamHelper, |
| 240 this.streamOfController, | 280 this.streamOfController, |
| 241 this.newCompleter, | 281 this.newCompleter, |
| 242 this.newController, | 282 this.newController, |
| 243 this.endOfIteration, | 283 this.endOfIteration, |
| 244 this.newIterable, | 284 this.newIterable, |
| 245 this.yieldExpression, | 285 this.yieldExpression, |
| 246 this.yieldStarExpression, | 286 this.yieldStarExpression, |
| 287 this.uncaughtErrorExpression, |
| 247 this.safeVariableName}) | 288 this.safeVariableName}) |
| 248 : _spannable = spannable; | 289 : _spannable = spannable; |
| 249 | 290 |
| 250 /// Main entry point. | 291 /// Main entry point. |
| 251 /// Rewrites a sync*/async/async* function to an equivalent normal function. | 292 /// Rewrites a sync*/async/async* function to an equivalent normal function. |
| 252 /// | 293 /// |
| 253 /// [spannable] can be passed to have a location for error messages. | 294 /// [spannable] can be passed to have a location for error messages. |
| 254 js.Fun rewrite(js.Fun node, [Spannable spannable]) { | 295 js.Fun rewrite(js.Fun node, [Spannable spannable]) { |
| 255 _spannable = spannable; | 296 _spannable = spannable; |
| 256 | 297 |
| 257 async = node.asyncModifier; | 298 async = node.asyncModifier; |
| 258 assert(!isSync); | 299 assert(!isSync); |
| 259 | 300 |
| 260 analysis = new PreTranslationAnalysis(unsupported); | 301 analysis = new PreTranslationAnalysis(unsupported); |
| 261 analysis.analyze(node); | 302 analysis.analyze(node); |
| 262 | 303 |
| 263 // To avoid name collisions with existing names, the fresh names are | 304 // To avoid name collisions with existing names, the fresh names are |
| 264 // generated after the analysis. | 305 // generated after the analysis. |
| 265 resultName = freshName("result"); | 306 resultName = freshName("result"); |
| 307 errorCodeName = freshName("errorCode"); |
| 266 completerName = freshName("completer"); | 308 completerName = freshName("completer"); |
| 267 controllerName = freshName("controller"); | 309 controllerName = freshName("controller"); |
| 268 helperName = freshName("helper"); | 310 bodyName = freshName("body"); |
| 269 gotoName = freshName("goto"); | 311 gotoName = freshName("goto"); |
| 270 handlerName = freshName("handler"); | 312 handlerName = freshName("handler"); |
| 271 errorName = freshName("error"); | 313 errorName = freshName("error"); |
| 272 nextName = freshName("next"); | 314 nextName = freshName("next"); |
| 315 nextWhenCanceledName = freshName("nextWhenCanceled"); |
| 273 returnValueName = freshName("returnValue"); | 316 returnValueName = freshName("returnValue"); |
| 317 currentErrorName = freshName("currentError"); |
| 274 outerLabelName = freshName("outer"); | 318 outerLabelName = freshName("outer"); |
| 275 selfName = freshName("self"); | 319 selfName = freshName("self"); |
| 276 | 320 |
| 277 return node.accept(this); | 321 return node.accept(this); |
| 278 } | 322 } |
| 279 | 323 |
| 280 js.Expression get currentErrorHandler { | 324 js.Expression get currentErrorHandler { |
| 281 return errorHandlerLabels.isEmpty | 325 return js.number(handlerLabels[jumpTargets.lastWhere( |
| 282 ? new js.LiteralNull() | 326 (node) => handlerLabels[node] != null)]); |
| 283 : js.number(errorHandlerLabels.last); | |
| 284 } | 327 } |
| 285 | 328 |
| 286 int allocateTempVar() { | 329 int allocateTempVar() { |
| 287 assert(tempVarHighWaterMark >= currentTempVarIndex); | 330 assert(tempVarHighWaterMark >= currentTempVarIndex); |
| 288 currentTempVarIndex++; | 331 currentTempVarIndex++; |
| 289 tempVarHighWaterMark = max(currentTempVarIndex, tempVarHighWaterMark); | 332 tempVarHighWaterMark = max(currentTempVarIndex, tempVarHighWaterMark); |
| 290 return currentTempVarIndex; | 333 return currentTempVarIndex; |
| 291 } | 334 } |
| 292 | 335 |
| 293 js.VariableUse useTempVar(int i) { | 336 js.VariableUse useTempVar(int i) { |
| (...skipping 241 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 535 })); | 578 })); |
| 536 var result = fn(visited); | 579 var result = fn(visited); |
| 537 currentTempVarIndex = oldTempVarIndex; | 580 currentTempVarIndex = oldTempVarIndex; |
| 538 return result; | 581 return result; |
| 539 } | 582 } |
| 540 | 583 |
| 541 /// Emits the return block that all returns should jump to (after going | 584 /// Emits the return block that all returns should jump to (after going |
| 542 /// through all the enclosing finally blocks). The jump to here is made in | 585 /// through all the enclosing finally blocks). The jump to here is made in |
| 543 /// [visitReturn]. | 586 /// [visitReturn]. |
| 544 /// | 587 /// |
| 545 /// Returning from an async method calls the [thenHelper] with the result. | 588 /// Returning from an async method calls the [asyncHelper] with the result. |
| 546 /// (the result might have been stored in [returnValueName] by some finally | 589 /// (the result might have been stored in [returnValueName] by some finally |
| 547 /// block). | 590 /// block). |
| 548 /// | 591 /// |
| 549 /// Returning from a sync* function returns an [endOfIteration] marker. | 592 /// Returning from a sync* function returns an [endOfIteration] marker. |
| 550 /// | 593 /// |
| 551 /// Returning from an async* function calls the [streamHelper] with an | 594 /// Returning from an async* function calls the [streamHelper] with an |
| 552 /// [endOfIteration] marker. | 595 /// [endOfIteration] marker. |
| 553 void addExit() { | 596 void addExit() { |
| 554 if (analysis.hasExplicitReturns || isAsyncStar) { | 597 if (analysis.hasExplicitReturns || isAsyncStar) { |
| 555 beginLabel(exitLabel); | 598 beginLabel(exitLabel); |
| 556 } else { | 599 } else { |
| 557 addStatement(new js.Comment("implicit return")); | 600 addStatement(new js.Comment("implicit return")); |
| 558 } | 601 } |
| 559 switch (async) { | 602 switch (async) { |
| 560 case const js.AsyncModifier.async(): | 603 case const js.AsyncModifier.async(): |
| 561 String returnValue = | 604 String returnValue = |
| 562 analysis.hasExplicitReturns ? returnValueName : "null"; | 605 analysis.hasExplicitReturns ? returnValueName : "null"; |
| 563 addStatement(js.js.statement( | 606 addStatement(js.js.statement( |
| 564 "return #thenHelper($returnValue, null, $completerName, null)", { | 607 "return #thenHelper($returnValue, #successCode, " |
| 565 "thenHelper": thenHelper | 608 "$completerName, null)", { |
| 566 })); | 609 "thenHelper": asyncHelper, |
| 610 "successCode": js.number(error_codes.SUCCESS)})); |
| 567 break; | 611 break; |
| 568 case const js.AsyncModifier.syncStar(): | 612 case const js.AsyncModifier.syncStar(): |
| 569 addStatement(new js.Return(new js.Call(endOfIteration, []))); | 613 addStatement(new js.Return(new js.Call(endOfIteration, []))); |
| 570 break; | 614 break; |
| 571 case const js.AsyncModifier.asyncStar(): | 615 case const js.AsyncModifier.asyncStar(): |
| 572 addStatement(js.js.statement( | 616 addStatement(js.js.statement( |
| 573 "return #streamHelper(null, null, $controllerName, null)", { | 617 "return #streamHelper(null, #successCode, $controllerName)", { |
| 574 "streamHelper": streamHelper | 618 "streamHelper": streamHelper, |
| 575 })); | 619 "successCode": js.number(error_codes.SUCCESS)})); |
| 576 break; | 620 break; |
| 577 default: | 621 default: |
| 578 diagnosticListener.internalError( | 622 diagnosticListener.internalError( |
| 579 spannable, "Internal error, unexpected asyncmodifier $async"); | 623 spannable, "Internal error, unexpected asyncmodifier $async"); |
| 580 } | 624 } |
| 625 if (isAsync || isAsyncStar) { |
| 626 beginLabel(rethrowLabel); |
| 627 addStatement(js.js.statement( |
| 628 "return #thenHelper($currentErrorName, #errorCode, " |
| 629 "${isAsync ? completerName : controllerName})", { |
| 630 "thenHelper": isAsync ? asyncHelper : streamHelper, |
| 631 "errorCode": js.number(error_codes.ERROR)})); |
| 632 } else { |
| 633 assert(isSyncStar); |
| 634 beginLabel(rethrowLabel); |
| 635 addStatement(new js.Return(new js.Call(uncaughtErrorExpression, |
| 636 [new js.VariableUse(currentErrorName)]))); |
| 637 } |
| 581 } | 638 } |
| 582 | 639 |
| 583 /// The initial call to [thenHelper]/[streamHelper]. | 640 /// The initial call to [asyncHelper]/[streamHelper]. |
| 584 /// | 641 /// |
| 585 /// There is no value to await/yield, so the first argument is `null` and | 642 /// There is no value to await/yield, so the first argument is `null` and |
| 586 /// also the errorCallback is `null`. | 643 /// also the errorCallback is `null`. |
| 587 /// | 644 /// |
| 588 /// Returns the [Future]/[Stream] coming from [completerName]/ | 645 /// Returns the [Future]/[Stream] coming from [completerName]/ |
| 589 /// [controllerName]. | 646 /// [controllerName]. |
| 590 js.Statement generateInitializer() { | 647 js.Statement generateInitializer() { |
| 591 if (isAsync) { | 648 if (isAsync) { |
| 592 return js.js.statement( | 649 return js.js.statement( |
| 593 "return #thenHelper(null, $helperName, $completerName, null);", { | 650 "return #asyncHelper(null, $bodyName, $completerName, null);", { |
| 594 "thenHelper": thenHelper | 651 "asyncHelper": asyncHelper |
| 595 }); | 652 }); |
| 596 } else if (isAsyncStar) { | 653 } else if (isAsyncStar) { |
| 597 return js.js.statement( | 654 return js.js.statement( |
| 598 "return #streamOfController($controllerName);", { | 655 "return #streamOfController($controllerName);", { |
| 599 "streamOfController": streamOfController | 656 "streamOfController": streamOfController |
| 600 }); | 657 }); |
| 601 } else { | 658 } else { |
| 602 throw diagnosticListener.internalError( | 659 throw diagnosticListener.internalError( |
| 603 spannable, "Unexpected asyncModifier: $async"); | 660 spannable, "Unexpected asyncModifier: $async"); |
| 604 } | 661 } |
| 605 } | 662 } |
| 606 | 663 |
| 607 /// Rewrites an async/sync*/async* function to a normal Javascript function. | 664 /// Rewrites an async/sync*/async* function to a normal Javascript function. |
| 608 /// | 665 /// |
| 609 /// The control flow is flattened by simulating 'goto' using a switch in a | 666 /// The control flow is flattened by simulating 'goto' using a switch in a |
| 610 /// loop and a state variable [gotoName] inside a helper-function that can be | 667 /// loop and a state variable [gotoName] inside a nested function [bodyName] |
| 611 /// called back by [thenHelper]/[streamHelper]/the [Iterator]. | 668 /// that can be called back by [asyncHelper]/[asyncStarHelper]/the [Iterator]. |
| 612 /// | 669 /// |
| 613 /// Local variables are hoisted outside the helper. | 670 /// Local variables are hoisted outside the helper. |
| 614 /// | 671 /// |
| 615 /// Awaits in async/async* are translated to code that remembers the current | 672 /// Awaits in async/async* are translated to code that remembers the current |
| 616 /// location (so the function can resume from where it was) followed by a call | 673 /// location (so the function can resume from where it was) followed by a call |
| 617 /// to the [thenHelper]. The helper sets up the waiting for the awaited value | 674 /// to the [asyncHelper]. The helper sets up the waiting for the awaited value |
| 618 /// and returns a future which is immediately returned by the translated | 675 /// and returns a future which is immediately returned by the translated |
| 619 /// await. | 676 /// await. |
| 620 /// Yields in async* are translated to a call to the [streamHelper]. They, | 677 /// Yields in async* are translated to a call to the [asyncStarHelper]. They, |
| 621 /// too, need to be prepared to be interrupted in case the stream is paused or | 678 /// too, need to be prepared to be interrupted in case the stream is paused or |
| 622 /// canceled. (Currently we always suspend - this is different from the spec, | 679 /// canceled. (Currently we always suspend - this is different from the spec, |
| 623 /// see `streamHelper` in `js_helper.dart`). | 680 /// see `streamHelper` in `js_helper.dart`). |
| 624 /// | 681 /// |
| 625 /// Yield/yield* in a sync* function is translated to a return of the value, | 682 /// Yield/yield* in a sync* function is translated to a return of the value, |
| 626 /// wrapped into a "IterationMarker" that signals the type (yield or yield*). | 683 /// wrapped into a "IterationMarker" that signals the type (yield or yield*). |
| 627 /// Sync* functions are executed on demand (when the user requests a value) by | 684 /// Sync* functions are executed on demand (when the user requests a value) by |
| 628 /// the Iterable that knows how to handle these values. | 685 /// the Iterable that knows how to handle these values. |
| 629 /// | 686 /// |
| 630 /// Simplified examples (not the exact translation, but intended to show the | 687 /// Simplified examples (not the exact translation, but intended to show the |
| 631 /// ideas): | 688 /// ideas): |
| 632 /// | 689 /// |
| 633 /// function (x, y, z) async { | 690 /// function (x, y, z) async { |
| 634 /// var p = await foo(); | 691 /// var p = await foo(); |
| 635 /// return bar(p); | 692 /// return bar(p); |
| 636 /// } | 693 /// } |
| 637 /// | 694 /// |
| 638 /// Becomes: | 695 /// Becomes (without error handling): |
| 639 /// | 696 /// |
| 640 /// function(x, y, z) { | 697 /// function(x, y, z) { |
| 641 /// var goto = 0, returnValue, completer = new Completer(), p; | 698 /// var goto = 0, returnValue, completer = new Completer(), p; |
| 642 /// function helper(result) { | 699 /// function helper(result) { |
| 643 /// while (true) { | 700 /// while (true) { |
| 644 /// switch (goto) { | 701 /// switch (goto) { |
| 645 /// case 0: | 702 /// case 0: |
| 646 /// goto = 1 // Remember where to continue when the future succeeds. | 703 /// goto = 1 // Remember where to continue when the future succeeds. |
| 647 /// return thenHelper(foo(), helper, completer, null); | 704 /// return thenHelper(foo(), helper, completer); |
| 648 /// case 1: | 705 /// case 1: |
| 649 /// p = result; | 706 /// p = result; |
| 650 /// returnValue = bar(p); | 707 /// returnValue = bar(p); |
| 651 /// goto = 2; | 708 /// goto = 2; |
| 652 /// break; | 709 /// break; |
| 653 /// case 2: | 710 /// case 2: |
| 654 /// return thenHelper(returnValue, null, completer, null) | 711 /// return thenHelper(returnValue, null, completer) |
| 655 /// } | 712 /// } |
| 656 /// } | 713 /// } |
| 657 /// return thenHelper(null, helper, completer, null); | 714 /// return thenHelper(null, helper, completer); |
| 658 /// } | 715 /// } |
| 659 /// } | 716 /// } |
| 660 /// | 717 /// |
| 661 /// Try/catch is implemented by maintaining [handlerName] to contain the label | 718 /// Try/catch is implemented by maintaining [handlerName] to contain the label |
| 662 /// of the current handler. The switch is nested inside a try/catch that will | 719 /// of the current handler. The switch is nested inside a try/catch that will |
| 663 /// redirect the flow to the current handler. | 720 /// redirect the flow to the current handler. |
| 664 /// | 721 /// |
| 665 /// A `finally` clause is compiled similar to normal code, with the additional | 722 /// A `finally` clause is compiled similar to normal code, with the additional |
| 666 /// complexity that `finally` clauses need to know where to jump to after the | 723 /// complexity that `finally` clauses need to know where to jump to after the |
| 667 /// clause is done. In the translation, each flow-path that enters a `finally` | 724 /// clause is done. In the translation, each flow-path that enters a `finally` |
| (...skipping 14 matching lines...) Expand all Loading... |
| 682 /// } | 739 /// } |
| 683 /// } | 740 /// } |
| 684 /// | 741 /// |
| 685 /// Translates into (besides the fact that structures not containing | 742 /// Translates into (besides the fact that structures not containing |
| 686 /// await/yield/yield* are left intact): | 743 /// await/yield/yield* are left intact): |
| 687 /// | 744 /// |
| 688 /// function(x, y, z) { | 745 /// function(x, y, z) { |
| 689 /// var goto = 0; | 746 /// var goto = 0; |
| 690 /// var returnValue; | 747 /// var returnValue; |
| 691 /// var completer = new Completer(); | 748 /// var completer = new Completer(); |
| 692 /// var handler = null; | 749 /// var handler = 8; // Outside try-blocks go to the rethrow label. |
| 693 /// var p; | 750 /// var p; |
| 751 /// var storedError; |
| 694 /// // The result can be either the result of an awaited future, or an | 752 /// // The result can be either the result of an awaited future, or an |
| 695 /// // error if the future completed with an error. | 753 /// // error if the future completed with an error. |
| 696 /// function helper(result) { | 754 /// function helper(errorCode, result) { |
| 755 /// if (errorCode == 1) { |
| 756 /// storedError = result; |
| 757 /// goto = handler; |
| 758 /// } |
| 697 /// while (true) { | 759 /// while (true) { |
| 698 /// try { | 760 /// try { |
| 699 /// switch (goto) { | 761 /// switch (goto) { |
| 700 /// case 0: | 762 /// case 0: |
| 701 /// handler = 4; // The outer catch-handler | 763 /// handler = 4; // The outer catch-handler |
| 702 /// handler = 1; // The inner (implicit) catch-handler | 764 /// handler = 1; // The inner (implicit) catch-handler |
| 703 /// throw "error"; | 765 /// throw "error"; |
| 704 /// next = [3]; | 766 /// next = [3]; |
| 705 /// // After the finally (2) continue normally after the try. | 767 /// // After the finally (2) continue normally after the try. |
| 706 /// goto = 2; | 768 /// goto = 2; |
| 707 /// break; | 769 /// break; |
| 708 /// case 1: // (implicit) catch handler for inner try. | 770 /// case 1: // (implicit) catch handler for inner try. |
| 709 /// next = [3]; // destination after the finally. | 771 /// next = [3]; // destination after the finally. |
| 710 /// // fall-though to the finally handler. | 772 /// // fall-though to the finally handler. |
| 711 /// case 2: // finally for inner try | 773 /// case 2: // finally for inner try |
| 712 /// handler = 4; // catch-handler for outer try. | 774 /// handler = 4; // catch-handler for outer try. |
| 713 /// finalize1(); | 775 /// finalize1(); |
| 714 /// goto = next.pop(); | 776 /// goto = next.pop(); |
| 715 /// break; | 777 /// break; |
| 716 /// case 3: // exiting inner try. | 778 /// case 3: // exiting inner try. |
| 717 /// next = [6]; | 779 /// next = [6]; |
| 718 /// goto = 5; // finally handler for outer try. | 780 /// goto = 5; // finally handler for outer try. |
| 719 /// break; | 781 /// break; |
| 720 /// case 4: // catch handler for outer try. | 782 /// case 4: // catch handler for outer try. |
| 721 /// e = result; | 783 /// handler = 5; // If the handler throws, do the finally .. |
| 784 /// next = [8] // ... and rethrow. |
| 785 /// e = storedError; |
| 722 /// handle(e); | 786 /// handle(e); |
| 723 /// // Fall through to finally. | 787 /// // Fall through to finally. |
| 724 /// case 5: // finally handler for outer try. | 788 /// case 5: // finally handler for outer try. |
| 725 /// handler = null; | 789 /// handler = null; |
| 726 /// finalize2(); | 790 /// finalize2(); |
| 727 /// goto = next.pop(); | 791 /// goto = next.pop(); |
| 728 /// break; | 792 /// break; |
| 729 /// case 6: // Exiting outer try. | 793 /// case 6: // Exiting outer try. |
| 730 /// case 7: // return | 794 /// case 7: // return |
| 731 /// return thenHelper(returnValue, null, completer, null); | 795 /// return thenHelper(returnValue, 0, completer); |
| 796 /// case 8: // Rethrow |
| 797 /// return thenHelper(storedError, 1, completer); |
| 732 /// } | 798 /// } |
| 733 /// } catch (error) { | 799 /// } catch (error) { |
| 734 /// result = error; | 800 /// storedError = error; |
| 735 /// goto = handler; | 801 /// goto = handler; |
| 736 /// } | 802 /// } |
| 737 /// } | 803 /// } |
| 738 /// return thenHelper(null, helper, completer, null); | 804 /// return thenHelper(null, helper, completer); |
| 739 /// } | 805 /// } |
| 740 /// } | 806 /// } |
| 741 /// | 807 /// |
| 742 @override | 808 @override |
| 743 js.Expression visitFun(js.Fun node) { | 809 js.Expression visitFun(js.Fun node) { |
| 744 if (isSync) return node; | 810 if (isSync) return node; |
| 745 | 811 |
| 746 beginLabel(newLabel("Function start")); | 812 beginLabel(newLabel("Function start")); |
| 747 // AsyncStar needs a returnlabel for its handling of cancelation. See | 813 // AsyncStar needs a returnlabel for its handling of cancelation. See |
| 748 // [visitDartYield]. | 814 // [visitDartYield]. |
| 749 exitLabel = | 815 exitLabel = |
| 750 analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null; | 816 analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null; |
| 817 rethrowLabel = newLabel("rethrow"); |
| 818 handlerLabels[node] = rethrowLabel; |
| 751 js.Statement body = node.body; | 819 js.Statement body = node.body; |
| 752 targetsAndTries.add(node); | 820 jumpTargets.add(node); |
| 753 visitStatement(body); | 821 visitStatement(body); |
| 754 targetsAndTries.removeLast(); | 822 jumpTargets.removeLast(); |
| 755 addExit(); | 823 addExit(); |
| 756 | 824 |
| 757 List<js.SwitchClause> clauses = labelledParts.keys.map((label) { | 825 List<js.SwitchClause> clauses = labelledParts.keys.map((label) { |
| 758 return new js.Case(js.number(label), new js.Block(labelledParts[label])); | 826 return new js.Case(js.number(label), new js.Block(labelledParts[label])); |
| 759 }).toList(); | 827 }).toList(); |
| 760 js.Statement helperBody = | 828 js.Statement helperBody = |
| 761 new js.Switch(new js.VariableUse(gotoName), clauses); | 829 new js.Switch(new js.VariableUse(gotoName), clauses); |
| 762 if (hasJumpThoughOuterLabel) { | 830 if (hasJumpThoughOuterLabel) { |
| 763 helperBody = js.js.statement("$outerLabelName: #", [helperBody]); | 831 helperBody = js.js.statement("$outerLabelName: #", [helperBody]); |
| 764 } | 832 } |
| 765 if (hasTryBlocks) { | 833 helperBody = js.js.statement(""" |
| 766 helperBody = js.js.statement(""" | 834 try { |
| 767 try { | 835 #body |
| 768 #body | 836 } catch ($errorName){ |
| 769 } catch ($errorName){ | 837 $currentErrorName = $errorName; |
| 770 if ($handlerName === null) | 838 $gotoName = $handlerName; |
| 771 throw $errorName; | 839 }""", {"body": helperBody}); |
| 772 $resultName = $errorName; | |
| 773 $gotoName = $handlerName; | |
| 774 }""", {"body": helperBody}); | |
| 775 } | |
| 776 List<js.VariableInitialization> inits = <js.VariableInitialization>[]; | 840 List<js.VariableInitialization> inits = <js.VariableInitialization>[]; |
| 777 | 841 |
| 778 js.VariableInitialization makeInit(String name, js.Expression initValue) { | 842 js.VariableInitialization makeInit(String name, js.Expression initValue) { |
| 779 return new js.VariableInitialization( | 843 return new js.VariableInitialization( |
| 780 new js.VariableDeclaration(name), initValue); | 844 new js.VariableDeclaration(name), initValue); |
| 781 } | 845 } |
| 782 | 846 |
| 783 inits.add(makeInit(gotoName, js.number(0))); | 847 inits.add(makeInit(gotoName, js.number(0))); |
| 784 if (isAsync) { | 848 if (isAsync) { |
| 785 inits.add(makeInit(completerName, new js.New(newCompleter, []))); | 849 inits.add(makeInit(completerName, new js.New(newCompleter, []))); |
| 786 } else if (isAsyncStar) { | 850 } else if (isAsyncStar) { |
| 787 inits.add(makeInit(controllerName, | 851 inits.add(makeInit(controllerName, |
| 788 new js.Call(newController, [new js.VariableUse(helperName)]))); | 852 new js.Call(newController, [new js.VariableUse(bodyName)]))); |
| 789 } | 853 } |
| 790 if (hasTryBlocks) { | 854 inits.add(makeInit(handlerName, js.number(rethrowLabel))); |
| 791 inits.add(makeInit(handlerName, new js.LiteralNull())); | 855 inits.add(makeInit(currentErrorName, null)); |
| 792 } | 856 if (hasJumpThroughFinally || analysis.hasYield) { |
| 793 if (hasJumpThroughFinally) { | |
| 794 inits.add(makeInit(nextName, null)); | 857 inits.add(makeInit(nextName, null)); |
| 795 } | 858 } |
| 796 if (analysis.hasExplicitReturns && isAsync) { | 859 if (analysis.hasExplicitReturns && isAsync) { |
| 797 inits.add(makeInit(returnValueName, null)); | 860 inits.add(makeInit(returnValueName, null)); |
| 798 } | 861 } |
| 862 if (isSyncStar) { |
| 863 inits.add(makeInit(resultName, null)); |
| 864 } |
| 799 if (analysis.hasThis && !isSyncStar) { | 865 if (analysis.hasThis && !isSyncStar) { |
| 800 // Sync* functions must remember `this` on the level of the outer | 866 // Sync* functions must remember `this` on the level of the outer |
| 801 // function. | 867 // function. |
| 802 inits.add(makeInit(selfName, new js.This())); | 868 inits.add(makeInit(selfName, new js.This())); |
| 803 } | 869 } |
| 804 inits.addAll(localVariables.map((js.VariableDeclaration decl) { | 870 inits.addAll(localVariables.map((js.VariableDeclaration decl) { |
| 805 return new js.VariableInitialization(decl, null); | 871 return new js.VariableInitialization(decl, null); |
| 806 })); | 872 })); |
| 807 inits.addAll(new Iterable.generate(tempVarHighWaterMark, | 873 inits.addAll(new Iterable.generate(tempVarHighWaterMark, |
| 808 (int i) => makeInit(useTempVar(i + 1).name, null))); | 874 (int i) => makeInit(useTempVar(i + 1).name, null))); |
| 809 js.VariableDeclarationList varDecl = new js.VariableDeclarationList(inits); | 875 js.VariableDeclarationList varDecl = new js.VariableDeclarationList(inits); |
| 876 // TODO(sigurdm): Explain the difference between these cases. |
| 810 if (isSyncStar) { | 877 if (isSyncStar) { |
| 811 return js.js(""" | 878 return js.js(""" |
| 812 function (#params) { | 879 function (#params) { |
| 813 if (#needsThis) | 880 if (#needsThis) |
| 814 var $selfName = this; | 881 var $selfName = this; |
| 815 return new #newIterable(function () { | 882 return new #newIterable(function () { |
| 816 #varDecl; | 883 #varDecl; |
| 817 return function $helperName($resultName) { | 884 return function $bodyName() { |
| 818 while (true) | 885 while (true) |
| 819 #helperBody; | 886 #helperBody; |
| 820 }; | 887 }; |
| 821 }); | 888 }); |
| 822 } | 889 } |
| 823 """, { | 890 """, { |
| 824 "params": node.params, | 891 "params": node.params, |
| 825 "needsThis": analysis.hasThis, | 892 "needsThis": analysis.hasThis, |
| 826 "helperBody": helperBody, | 893 "helperBody": helperBody, |
| 827 "varDecl": varDecl, | 894 "varDecl": varDecl, |
| 828 "newIterable": newIterable | 895 "newIterable": newIterable |
| 829 }); | 896 }); |
| 830 } | 897 } |
| 831 return js.js(""" | 898 return js.js(""" |
| 832 function (#params) { | 899 function (#params) { |
| 833 #varDecl; | 900 #varDecl; |
| 834 function $helperName($resultName) { | 901 function $bodyName($errorCodeName, $resultName) { |
| 902 if (#hasYield) |
| 903 switch ($errorCodeName) { |
| 904 case #streamWasCanceled: |
| 905 $nextName = $nextWhenCanceledName; |
| 906 $gotoName = $nextName.pop(); |
| 907 break; |
| 908 case #errorCode: |
| 909 $currentErrorName = $resultName; |
| 910 $gotoName = $handlerName; |
| 911 } |
| 912 else |
| 913 if ($errorCodeName == #errorCode) { |
| 914 $currentErrorName = $resultName; |
| 915 $gotoName = $handlerName; |
| 916 } |
| 835 while (true) | 917 while (true) |
| 836 #helperBody; | 918 #helperBody; |
| 837 } | 919 } |
| 838 #init; | 920 #init; |
| 839 }""", { | 921 }""", { |
| 840 "params": node.params, | 922 "params": node.params, |
| 923 "varDecl": varDecl, |
| 924 "streamWasCanceled": js.number(error_codes.STREAM_WAS_CANCELED), |
| 925 "errorCode": js.number(error_codes.ERROR), |
| 926 "hasYield": analysis.hasYield, |
| 841 "helperBody": helperBody, | 927 "helperBody": helperBody, |
| 842 "varDecl": varDecl, | |
| 843 "init": generateInitializer() | 928 "init": generateInitializer() |
| 844 }); | 929 }); |
| 845 } | 930 } |
| 846 | 931 |
| 847 @override | 932 @override |
| 848 js.Expression visitAccess(js.PropertyAccess node) { | 933 js.Expression visitAccess(js.PropertyAccess node) { |
| 849 return withExpression2(node.receiver, node.selector, | 934 return withExpression2(node.receiver, node.selector, |
| 850 (receiver, selector) => new js.PropertyAccess(receiver, selector)); | 935 (receiver, selector) => new js.PropertyAccess(receiver, selector)); |
| 851 } | 936 } |
| 852 | 937 |
| (...skipping 28 matching lines...) Expand all Loading... |
| 881 ], (evaluated) { | 966 ], (evaluated) { |
| 882 return new js.Assignment.compound( | 967 return new js.Assignment.compound( |
| 883 new js.PropertyAccess(evaluated[0], evaluated[1]), node.op, | 968 new js.PropertyAccess(evaluated[0], evaluated[1]), node.op, |
| 884 evaluated[2]); | 969 evaluated[2]); |
| 885 }); | 970 }); |
| 886 } else { | 971 } else { |
| 887 throw "Unexpected assignment left hand side $leftHandSide"; | 972 throw "Unexpected assignment left hand side $leftHandSide"; |
| 888 } | 973 } |
| 889 } | 974 } |
| 890 | 975 |
| 891 /// An await is translated to a call to [thenHelper]/[streamHelper]. | 976 /// An await is translated to a call to [asyncHelper]/[streamHelper]. |
| 892 /// | 977 /// |
| 893 /// See the comments of [visitFun] for an example. | 978 /// See the comments of [visitFun] for an example. |
| 894 @override | 979 @override |
| 895 js.Expression visitAwait(js.Await node) { | 980 js.Expression visitAwait(js.Await node) { |
| 896 assert(isAsync || isAsyncStar); | 981 assert(isAsync || isAsyncStar); |
| 897 int afterAwait = newLabel("returning from await."); | 982 int afterAwait = newLabel("returning from await."); |
| 898 withExpression(node.expression, (js.Expression value) { | 983 withExpression(node.expression, (js.Expression value) { |
| 899 addStatement(setGotoVariable(afterAwait)); | 984 addStatement(setGotoVariable(afterAwait)); |
| 900 js.Expression errorCallback = errorHandlerLabels.isEmpty | |
| 901 ? new js.LiteralNull() | |
| 902 : js.js(""" | |
| 903 function($errorName) { | |
| 904 $gotoName = #currentHandler; | |
| 905 $helperName($errorName); | |
| 906 }""", {"currentHandler": currentErrorHandler}); | |
| 907 | |
| 908 addStatement(js.js.statement(""" | 985 addStatement(js.js.statement(""" |
| 909 return #thenHelper(#value, | 986 return #asyncHelper(#value, |
| 910 $helperName, | 987 $bodyName, |
| 911 ${isAsync ? completerName : controllerName}, | 988 ${isAsync ? completerName : controllerName}); |
| 912 #errorCallback); | |
| 913 """, { | 989 """, { |
| 914 "thenHelper": isAsync ? thenHelper : streamHelper, | 990 "asyncHelper": isAsync ? asyncHelper : streamHelper, |
| 915 "value": value, | 991 "value": value, |
| 916 "errorCallback": errorCallback | |
| 917 })); | 992 })); |
| 918 }, store: false); | 993 }, store: false); |
| 919 beginLabel(afterAwait); | 994 beginLabel(afterAwait); |
| 920 return new js.VariableUse(resultName); | 995 return new js.VariableUse(resultName); |
| 921 } | 996 } |
| 922 | 997 |
| 923 /// Checks if [node] is the variable named [resultName]. | 998 /// Checks if [node] is the variable named [resultName]. |
| 924 /// | 999 /// |
| 925 /// [resultName] is used to hold the result of a transformed computation | 1000 /// [resultName] is used to hold the result of a transformed computation |
| 926 /// for example the result of awaiting, or the result of a conditional or | 1001 /// for example the result of awaiting, or the result of a conditional or |
| (...skipping 139 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1066 /// | 1141 /// |
| 1067 /// It is necessary to run all nesting finally-handlers between the jump and | 1142 /// It is necessary to run all nesting finally-handlers between the jump and |
| 1068 /// the target. For that [nextName] is used as a stack of places to go. | 1143 /// the target. For that [nextName] is used as a stack of places to go. |
| 1069 /// | 1144 /// |
| 1070 /// See also [visitFun]. | 1145 /// See also [visitFun]. |
| 1071 void translateJump(js.Node target, int targetLabel) { | 1146 void translateJump(js.Node target, int targetLabel) { |
| 1072 // Compute a stack of all the 'finally' nodes that must be visited before | 1147 // Compute a stack of all the 'finally' nodes that must be visited before |
| 1073 // the jump. | 1148 // the jump. |
| 1074 // The bottom of the stack is the label where the jump goes to. | 1149 // The bottom of the stack is the label where the jump goes to. |
| 1075 List<int> jumpStack = new List<int>(); | 1150 List<int> jumpStack = new List<int>(); |
| 1076 for (js.Node node in targetsAndTries.reversed) { | 1151 for (js.Node node in jumpTargets.reversed) { |
| 1077 if (node is js.Try) { | 1152 if (finallyLabels[node] != null) { |
| 1078 assert(node.finallyPart != null); | |
| 1079 jumpStack.add(finallyLabels[node]); | 1153 jumpStack.add(finallyLabels[node]); |
| 1080 } else if (node == target) { | 1154 } else if (node == target) { |
| 1081 jumpStack.add(targetLabel); | 1155 jumpStack.add(targetLabel); |
| 1082 break; | 1156 break; |
| 1083 } | 1157 } |
| 1084 // Ignore other nodes. | 1158 // Ignore other nodes. |
| 1085 } | 1159 } |
| 1086 jumpStack = jumpStack.reversed.toList(); | 1160 jumpStack = jumpStack.reversed.toList(); |
| 1087 // As the program jumps directly to the top of the stack, it is taken off | 1161 // As the program jumps directly to the top of the stack, it is taken off |
| 1088 // now. | 1162 // now. |
| (...skipping 24 matching lines...) Expand all Loading... |
| 1113 int startLabel = newLabel("do body"); | 1187 int startLabel = newLabel("do body"); |
| 1114 | 1188 |
| 1115 int continueLabel = newLabel("do condition"); | 1189 int continueLabel = newLabel("do condition"); |
| 1116 continueLabels[node] = continueLabel; | 1190 continueLabels[node] = continueLabel; |
| 1117 | 1191 |
| 1118 int afterLabel = newLabel("after do"); | 1192 int afterLabel = newLabel("after do"); |
| 1119 breakLabels[node] = afterLabel; | 1193 breakLabels[node] = afterLabel; |
| 1120 | 1194 |
| 1121 beginLabel(startLabel); | 1195 beginLabel(startLabel); |
| 1122 | 1196 |
| 1123 targetsAndTries.add(node); | 1197 jumpTargets.add(node); |
| 1124 visitStatement(node.body); | 1198 visitStatement(node.body); |
| 1125 targetsAndTries.removeLast(); | 1199 jumpTargets.removeLast(); |
| 1126 | 1200 |
| 1127 beginLabel(continueLabel); | 1201 beginLabel(continueLabel); |
| 1128 withExpression(node.condition, (js.Expression condition) { | 1202 withExpression(node.condition, (js.Expression condition) { |
| 1129 addStatement(new js.If.noElse(condition, gotoAndBreak(startLabel))); | 1203 addStatement(new js.If.noElse(condition, gotoAndBreak(startLabel))); |
| 1130 }, store: false); | 1204 }, store: false); |
| 1131 beginLabel(afterLabel); | 1205 beginLabel(afterLabel); |
| 1132 } | 1206 } |
| 1133 | 1207 |
| 1134 @override | 1208 @override |
| 1135 void visitEmptyStatement(js.EmptyStatement node) { | 1209 void visitEmptyStatement(js.EmptyStatement node) { |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1184 js.Expression condition = node.condition; | 1258 js.Expression condition = node.condition; |
| 1185 if (condition == null || | 1259 if (condition == null || |
| 1186 (condition is js.LiteralBool && condition.value == true)) { | 1260 (condition is js.LiteralBool && condition.value == true)) { |
| 1187 addStatement(new js.Comment("trivial condition")); | 1261 addStatement(new js.Comment("trivial condition")); |
| 1188 } else { | 1262 } else { |
| 1189 withExpression(condition, (js.Expression condition) { | 1263 withExpression(condition, (js.Expression condition) { |
| 1190 addStatement(new js.If.noElse( | 1264 addStatement(new js.If.noElse( |
| 1191 new js.Prefix("!", condition), gotoAndBreak(afterLabel))); | 1265 new js.Prefix("!", condition), gotoAndBreak(afterLabel))); |
| 1192 }, store: false); | 1266 }, store: false); |
| 1193 } | 1267 } |
| 1194 targetsAndTries.add(node); | 1268 jumpTargets.add(node); |
| 1195 visitStatement(node.body); | 1269 visitStatement(node.body); |
| 1196 targetsAndTries.removeLast(); | 1270 jumpTargets.removeLast(); |
| 1197 if (node.update != null) { | 1271 if (node.update != null) { |
| 1198 beginLabel(continueLabel); | 1272 beginLabel(continueLabel); |
| 1199 visitExpressionIgnoreResult(node.update); | 1273 visitExpressionIgnoreResult(node.update); |
| 1200 } | 1274 } |
| 1201 addGoto(startLabel); | 1275 addGoto(startLabel); |
| 1202 beginLabel(afterLabel); | 1276 beginLabel(afterLabel); |
| 1203 } | 1277 } |
| 1204 | 1278 |
| 1205 @override | 1279 @override |
| 1206 void visitForIn(js.ForIn node) { | 1280 void visitForIn(js.ForIn node) { |
| (...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1287 addStatement( | 1361 addStatement( |
| 1288 new js.LabeledStatement(node.label, translateInBlock(node.body))); | 1362 new js.LabeledStatement(node.label, translateInBlock(node.body))); |
| 1289 return; | 1363 return; |
| 1290 } | 1364 } |
| 1291 int breakLabel = newLabel("break ${node.label}"); | 1365 int breakLabel = newLabel("break ${node.label}"); |
| 1292 int continueLabel = newLabel("continue ${node.label}"); | 1366 int continueLabel = newLabel("continue ${node.label}"); |
| 1293 breakLabels[node] = breakLabel; | 1367 breakLabels[node] = breakLabel; |
| 1294 continueLabels[node] = continueLabel; | 1368 continueLabels[node] = continueLabel; |
| 1295 | 1369 |
| 1296 beginLabel(continueLabel); | 1370 beginLabel(continueLabel); |
| 1297 targetsAndTries.add(node); | 1371 jumpTargets.add(node); |
| 1298 visitStatement(node.body); | 1372 visitStatement(node.body); |
| 1299 targetsAndTries.removeLast(); | 1373 jumpTargets.removeLast(); |
| 1300 beginLabel(breakLabel); | 1374 beginLabel(breakLabel); |
| 1301 } | 1375 } |
| 1302 | 1376 |
| 1303 @override | 1377 @override |
| 1304 js.Expression visitLiteralBool(js.LiteralBool node) => node; | 1378 js.Expression visitLiteralBool(js.LiteralBool node) => node; |
| 1305 | 1379 |
| 1306 @override | 1380 @override |
| 1307 visitLiteralExpression(js.LiteralExpression node) => unsupported(node); | 1381 visitLiteralExpression(js.LiteralExpression node) => unsupported(node); |
| 1308 | 1382 |
| 1309 @override | 1383 @override |
| (...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1495 i++; | 1569 i++; |
| 1496 } | 1570 } |
| 1497 withExpression(node.key, (js.Expression key) { | 1571 withExpression(node.key, (js.Expression key) { |
| 1498 addStatement(new js.Switch(key, clauses)); | 1572 addStatement(new js.Switch(key, clauses)); |
| 1499 }, store: false); | 1573 }, store: false); |
| 1500 if (!hasDefault) { | 1574 if (!hasDefault) { |
| 1501 addGoto(after); | 1575 addGoto(after); |
| 1502 } | 1576 } |
| 1503 } | 1577 } |
| 1504 | 1578 |
| 1505 targetsAndTries.add(node); | 1579 jumpTargets.add(node); |
| 1506 for (int i = 0; i < labels.length; i++) { | 1580 for (int i = 0; i < labels.length; i++) { |
| 1507 beginLabel(labels[i]); | 1581 beginLabel(labels[i]); |
| 1508 visitStatement(node.cases[i].body); | 1582 visitStatement(node.cases[i].body); |
| 1509 } | 1583 } |
| 1510 beginLabel(after); | 1584 beginLabel(after); |
| 1511 targetsAndTries.removeLast(); | 1585 jumpTargets.removeLast(); |
| 1512 } | 1586 } |
| 1513 | 1587 |
| 1514 @override | 1588 @override |
| 1515 js.Expression visitThis(js.This node) { | 1589 js.Expression visitThis(js.This node) { |
| 1516 return new js.VariableUse(selfName); | 1590 return new js.VariableUse(selfName); |
| 1517 } | 1591 } |
| 1518 | 1592 |
| 1519 @override | 1593 @override |
| 1520 void visitThrow(js.Throw node) { | 1594 void visitThrow(js.Throw node) { |
| 1521 withExpression(node.expression, (js.Expression expression) { | 1595 withExpression(node.expression, (js.Expression expression) { |
| 1522 addStatement(new js.Throw(expression)); | 1596 addStatement(new js.Throw(expression)); |
| 1523 }, store: false); | 1597 }, store: false); |
| 1524 } | 1598 } |
| 1525 | 1599 |
| 1526 setErrorHandler() { | 1600 setErrorHandler([int errorHandler]) { |
| 1527 addExpressionStatement(new js.Assignment( | 1601 addExpressionStatement(new js.Assignment( |
| 1528 new js.VariableUse(handlerName), currentErrorHandler)); | 1602 new js.VariableUse(handlerName), |
| 1603 errorHandler == null ? currentErrorHandler : js.number(errorHandler))); |
| 1529 } | 1604 } |
| 1530 | 1605 |
| 1531 @override | 1606 List<int> _finalliesUpToAndEnclosingHandler() { |
| 1607 List<int> result = new List<int>(); |
| 1608 for (int i = jumpTargets.length - 1; i >= 0; i--) { |
| 1609 js.Node node = jumpTargets[i]; |
| 1610 int handlerLabel = handlerLabels[node]; |
| 1611 if (handlerLabel != null) { |
| 1612 result.add(handlerLabel); |
| 1613 break; |
| 1614 } |
| 1615 int finallyLabel = finallyLabels[node]; |
| 1616 if (finallyLabel != null) { |
| 1617 result.add(finallyLabel); |
| 1618 } |
| 1619 } |
| 1620 return result.reversed.toList(); |
| 1621 } |
| 1622 |
| 1623 /// See the comments of [visitFun] for more explanation. |
| 1532 void visitTry(js.Try node) { | 1624 void visitTry(js.Try node) { |
| 1533 if (!shouldTransform(node)) { | 1625 if (!shouldTransform(node)) { |
| 1534 js.Block body = translateInBlock(node.body); | 1626 js.Block body = translateInBlock(node.body); |
| 1535 js.Catch catchPart = (node.catchPart == null) | 1627 js.Catch catchPart = (node.catchPart == null) |
| 1536 ? null | 1628 ? null |
| 1537 : new js.Catch(node.catchPart.declaration, | 1629 : new js.Catch(node.catchPart.declaration, |
| 1538 translateInBlock(node.catchPart.body)); | 1630 translateInBlock(node.catchPart.body)); |
| 1539 js.Block finallyPart = (node.finallyPart == null) | 1631 js.Block finallyPart = (node.finallyPart == null) |
| 1540 ? null | 1632 ? null |
| 1541 : translateInBlock(node.finallyPart); | 1633 : translateInBlock(node.finallyPart); |
| 1542 addStatement(new js.Try(body, catchPart, finallyPart)); | 1634 addStatement(new js.Try(body, catchPart, finallyPart)); |
| 1543 return; | 1635 return; |
| 1544 } | 1636 } |
| 1637 |
| 1545 hasTryBlocks = true; | 1638 hasTryBlocks = true; |
| 1546 int handlerLabel = newLabel("catch"); | 1639 int uncaughtLabel = newLabel("uncaught"); |
| 1640 int handlerLabel = (node.catchPart == null) |
| 1641 ? uncaughtLabel |
| 1642 : newLabel("catch"); |
| 1643 |
| 1547 int finallyLabel = newLabel("finally"); | 1644 int finallyLabel = newLabel("finally"); |
| 1548 int afterFinallyLabel = newLabel("after finally"); | 1645 int afterFinallyLabel = newLabel("after finally"); |
| 1549 errorHandlerLabels.add(handlerLabel); | 1646 if (node.finallyPart != null) { |
| 1647 finallyLabels[node.finallyPart] = finallyLabel; |
| 1648 jumpTargets.add(node.finallyPart); |
| 1649 } |
| 1650 |
| 1651 handlerLabels[node] = handlerLabel; |
| 1652 jumpTargets.add(node); |
| 1653 |
| 1550 // Set the error handler here. It must be cleared on every path out; | 1654 // Set the error handler here. It must be cleared on every path out; |
| 1551 // normal and error exit. | 1655 // normal and error exit. |
| 1552 setErrorHandler(); | 1656 setErrorHandler(); |
| 1553 if (node.finallyPart != null) { | 1657 |
| 1554 finallyLabels[node] = finallyLabel; | |
| 1555 targetsAndTries.add(node); | |
| 1556 } | |
| 1557 visitStatement(node.body); | 1658 visitStatement(node.body); |
| 1558 errorHandlerLabels.removeLast(); | 1659 |
| 1559 addStatement( | 1660 js.Node last = jumpTargets.removeLast(); |
| 1560 js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)])); | 1661 assert(last == node); |
| 1662 |
| 1561 if (node.finallyPart == null) { | 1663 if (node.finallyPart == null) { |
| 1562 setErrorHandler(); | 1664 setErrorHandler(); |
| 1563 addGoto(afterFinallyLabel); | 1665 addGoto(afterFinallyLabel); |
| 1564 } else { | 1666 } else { |
| 1565 // The handler is set as the first thing in the finally block. | 1667 // The handler is reset as the first thing in the finally block. |
| 1668 addStatement( |
| 1669 js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)])); |
| 1566 addGoto(finallyLabel); | 1670 addGoto(finallyLabel); |
| 1567 } | 1671 } |
| 1568 beginLabel(handlerLabel); | 1672 |
| 1569 if (node.catchPart != null) { | 1673 if (node.catchPart != null) { |
| 1674 beginLabel(handlerLabel); |
| 1675 // [uncaughtLabel] is the handler for the code in the catch-part. |
| 1676 // It ensures that [nextName] is set up to run the right finally blocks. |
| 1677 handlerLabels[node.catchPart] = uncaughtLabel; |
| 1678 jumpTargets.add(node.catchPart); |
| 1570 setErrorHandler(); | 1679 setErrorHandler(); |
| 1571 // The catch declaration name can shadow outer variables, so a fresh name | 1680 // The catch declaration name can shadow outer variables, so a fresh name |
| 1572 // is needed to avoid collisions. See Ecma 262, 3rd edition, | 1681 // is needed to avoid collisions. See Ecma 262, 3rd edition, |
| 1573 // section 12.14. | 1682 // section 12.14. |
| 1574 String errorRename = freshName(node.catchPart.declaration.name); | 1683 String errorRename = freshName(node.catchPart.declaration.name); |
| 1575 localVariables.add(new js.VariableDeclaration(errorRename)); | 1684 localVariables.add(new js.VariableDeclaration(errorRename)); |
| 1576 variableRenamings | 1685 variableRenamings |
| 1577 .add(new Pair(node.catchPart.declaration.name, errorRename)); | 1686 .add(new Pair(node.catchPart.declaration.name, errorRename)); |
| 1578 addExpressionStatement(new js.Assignment( | 1687 addExpressionStatement(new js.Assignment( |
| 1579 new js.VariableUse(errorRename), new js.VariableUse(resultName))); | 1688 new js.VariableUse(errorRename), |
| 1689 new js.VariableUse(currentErrorName))); |
| 1580 visitStatement(node.catchPart.body); | 1690 visitStatement(node.catchPart.body); |
| 1581 variableRenamings.removeLast(); | 1691 variableRenamings.removeLast(); |
| 1692 if (node.finallyPart != null) { |
| 1693 // The error has been caught, so after the finally, continue after the |
| 1694 // try. |
| 1695 addStatement(js.js.statement("$nextName = [#];", |
| 1696 [js.number(afterFinallyLabel)])); |
| 1697 addGoto(finallyLabel); |
| 1698 } else { |
| 1699 addGoto(afterFinallyLabel); |
| 1700 } |
| 1701 js.Node last = jumpTargets.removeLast(); |
| 1702 assert(last == node.catchPart); |
| 1703 } |
| 1704 |
| 1705 // The "uncaught"-handler tells the finally-block to continue with |
| 1706 // the enclosing finally-blocks until the current catch-handler. |
| 1707 beginLabel(uncaughtLabel); |
| 1708 |
| 1709 List<int> enclosingFinallies = _finalliesUpToAndEnclosingHandler(); |
| 1710 |
| 1711 int nextLabel = enclosingFinallies.removeLast(); |
| 1712 if (enclosingFinallies.isNotEmpty) { |
| 1713 // [enclosingFinallies] can be empty if there is no surrounding finally |
| 1714 // blocks. Then [nextLabel] will be [rethrowLabel]. |
| 1715 addStatement( |
| 1716 js.js.statement("$nextName = #;", new js.ArrayInitializer( |
| 1717 enclosingFinallies.map(js.number).toList()))); |
| 1718 } |
| 1719 if (node.finallyPart == null) { |
| 1720 // The finally-block belonging to [node] will be visited because of |
| 1721 // fallthrough. If it does not exist, add an explicit goto. |
| 1722 addGoto(nextLabel); |
| 1582 } | 1723 } |
| 1583 if (node.finallyPart != null) { | 1724 if (node.finallyPart != null) { |
| 1584 targetsAndTries.removeLast(); | 1725 js.Node last = jumpTargets.removeLast(); |
| 1585 setErrorHandler(); | 1726 assert(last == node.finallyPart); |
| 1586 // This belongs to the catch-part, but is only needed if there is a | 1727 |
| 1587 // `finally`. Therefore it is in this branch. | |
| 1588 // This is needed even if there is no explicit catch-branch, because | |
| 1589 // if an exception is raised the finally part has to be run. | |
| 1590 addStatement( | |
| 1591 js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)])); | |
| 1592 beginLabel(finallyLabel); | 1728 beginLabel(finallyLabel); |
| 1593 setErrorHandler(); | 1729 setErrorHandler(); |
| 1594 visitStatement(node.finallyPart); | 1730 visitStatement(node.finallyPart); |
| 1595 addStatement(new js.Comment("// goto the next finally handler")); | 1731 addStatement(new js.Comment("// goto the next finally handler")); |
| 1596 addStatement(js.js.statement("$gotoName = $nextName.pop();")); | 1732 addStatement(js.js.statement("$gotoName = $nextName.pop();")); |
| 1597 addBreak(); | 1733 addBreak(); |
| 1598 } | 1734 } |
| 1599 beginLabel(afterFinallyLabel); | 1735 beginLabel(afterFinallyLabel); |
| 1600 } | 1736 } |
| 1601 | 1737 |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1651 int afterLabel = newLabel("after while"); | 1787 int afterLabel = newLabel("after while"); |
| 1652 breakLabels[node] = afterLabel; | 1788 breakLabels[node] = afterLabel; |
| 1653 js.Expression condition = node.condition; | 1789 js.Expression condition = node.condition; |
| 1654 // If the condition is `true`, a test is not needed. | 1790 // If the condition is `true`, a test is not needed. |
| 1655 if (!(condition is js.LiteralBool && condition.value == true)) { | 1791 if (!(condition is js.LiteralBool && condition.value == true)) { |
| 1656 withExpression(node.condition, (js.Expression condition) { | 1792 withExpression(node.condition, (js.Expression condition) { |
| 1657 addStatement(new js.If.noElse( | 1793 addStatement(new js.If.noElse( |
| 1658 new js.Prefix("!", condition), gotoAndBreak(afterLabel))); | 1794 new js.Prefix("!", condition), gotoAndBreak(afterLabel))); |
| 1659 }, store: false); | 1795 }, store: false); |
| 1660 } | 1796 } |
| 1661 targetsAndTries.add(node); | 1797 jumpTargets.add(node); |
| 1662 visitStatement(node.body); | 1798 visitStatement(node.body); |
| 1663 targetsAndTries.removeLast(); | 1799 jumpTargets.removeLast(); |
| 1664 addGoto(continueLabel); | 1800 addGoto(continueLabel); |
| 1665 beginLabel(afterLabel); | 1801 beginLabel(afterLabel); |
| 1666 } | 1802 } |
| 1667 | 1803 |
| 1668 /// Translates a yield/yield* in an sync*. | 1804 /// Translates a yield/yield* in an sync*. |
| 1669 /// | 1805 /// |
| 1670 /// `yield` in a sync* function just returns [value]. | 1806 /// `yield` in a sync* function just returns [value]. |
| 1671 /// `yield*` wraps [value] in a [yieldStarExpression] and returns it. | 1807 /// `yield*` wraps [value] in a [yieldStarExpression] and returns it. |
| 1672 void addSyncYield(js.DartYield node, js.Expression expression) { | 1808 void addSyncYield(js.DartYield node, js.Expression expression) { |
| 1673 assert(isSyncStar); | 1809 assert(isSyncStar); |
| 1674 if (node.hasStar) { | 1810 if (node.hasStar) { |
| 1675 addStatement( | 1811 addStatement( |
| 1676 new js.Return(new js.Call(yieldStarExpression, [expression]))); | 1812 new js.Return(new js.Call(yieldStarExpression, [expression]))); |
| 1677 } else { | 1813 } else { |
| 1678 addStatement(new js.Return(expression)); | 1814 addStatement(new js.Return(expression)); |
| 1679 } | 1815 } |
| 1680 } | 1816 } |
| 1681 | 1817 |
| 1682 /// Translates a yield/yield* in an async* function. | 1818 /// Translates a yield/yield* in an async* function. |
| 1683 /// | 1819 /// |
| 1684 /// yield/yield* in an async* function is translated much like the `await` is | 1820 /// yield/yield* in an async* function is translated much like the `await` is |
| 1685 /// translated in [visitAwait], only the object is wrapped in a | 1821 /// translated in [visitAwait], only the object is wrapped in a |
| 1686 /// [yieldExpression]/[yieldStarExpression] to let [streamHelper] distinguish. | 1822 /// [yieldExpression]/[yieldStarExpression] to let [asyncStarHelper] |
| 1687 /// | 1823 /// distinguish them. |
| 1688 /// Because there is no Future that can fail (as there is in await) null is | 1824 /// Also [nextWhenCanceledName] is set up to contain the finally blocks that |
| 1689 /// passed as the errorCallback. | 1825 /// must be run in case the stream was canceled. |
| 1690 void addAsyncYield(js.DartYield node, js.Expression expression) { | 1826 void addAsyncYield(js.DartYield node, js.Expression expression) { |
| 1691 assert(isAsyncStar); | 1827 assert(isAsyncStar); |
| 1692 // Find all the finally blocks that should be performed if the stream is | 1828 // Find all the finally blocks that should be performed if the stream is |
| 1693 // canceled during the yield. | 1829 // canceled during the yield. |
| 1694 // At the bottom of the stack is the return label. | 1830 // At the bottom of the stack is the return label. |
| 1695 List<int> enclosingFinallyLabels = <int>[exitLabel]; | 1831 List<int> enclosingFinallyLabels = <int>[exitLabel]; |
| 1696 enclosingFinallyLabels.addAll(targetsAndTries | 1832 enclosingFinallyLabels.addAll(jumpTargets |
| 1697 .where((js.Node node) => node is js.Try) | 1833 .where((js.Node node) => finallyLabels[node] != null) |
| 1698 .map((js.Try node) => finallyLabels[node])); | 1834 .map((js.Block node) => finallyLabels[node])); |
| 1699 int destinationOnCancel = enclosingFinallyLabels.removeLast(); | 1835 addStatement(js.js.statement("$nextWhenCanceledName = #", |
| 1700 js.ArrayInitializer finallyListInitializer = new js.ArrayInitializer( | 1836 [new js.ArrayInitializer(enclosingFinallyLabels.map(js.number) |
| 1701 enclosingFinallyLabels.map(js.number).toList()); | 1837 .toList())])); |
| 1702 addStatement(js.js.statement(""" | 1838 addStatement(js.js.statement(""" |
| 1703 return #streamHelper(#yieldExpression(#expression), | 1839 return #streamHelper(#yieldExpression(#expression), |
| 1704 $helperName, $controllerName, function () { | 1840 $bodyName, $controllerName);""", { |
| 1705 if (#notEmptyFinallyList) | |
| 1706 $nextName = #finallyList; | |
| 1707 $gotoName = #destinationOnCancel; | |
| 1708 $helperName(); | |
| 1709 });""", { | |
| 1710 "streamHelper": streamHelper, | 1841 "streamHelper": streamHelper, |
| 1711 "yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression, | 1842 "yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression, |
| 1712 "expression": expression, | 1843 "expression": expression, |
| 1713 "notEmptyFinallyList": enclosingFinallyLabels.isNotEmpty, | |
| 1714 "finallyList": finallyListInitializer, | |
| 1715 "destinationOnCancel": js.number(destinationOnCancel) | |
| 1716 })); | 1844 })); |
| 1717 } | 1845 } |
| 1718 | 1846 |
| 1719 @override | 1847 @override |
| 1720 void visitDartYield(js.DartYield node) { | 1848 void visitDartYield(js.DartYield node) { |
| 1721 assert(isSyncStar || isAsyncStar); | 1849 assert(isSyncStar || isAsyncStar); |
| 1722 int label = newLabel("after yield"); | 1850 int label = newLabel("after yield"); |
| 1723 // Don't do a break here for the goto, but instead a return in either | 1851 // Don't do a break here for the goto, but instead a return in either |
| 1724 // addSynYield or addAsyncYield. | 1852 // addSynYield or addAsyncYield. |
| 1725 withExpression(node.expression, (js.Expression expression) { | 1853 withExpression(node.expression, (js.Expression expression) { |
| (...skipping 20 matching lines...) Expand all Loading... |
| 1746 Map<js.Node, js.Node> targets = new Map<js.Node, js.Node>(); | 1874 Map<js.Node, js.Node> targets = new Map<js.Node, js.Node>(); |
| 1747 List<js.Node> loopsAndSwitches = new List<js.Node>(); | 1875 List<js.Node> loopsAndSwitches = new List<js.Node>(); |
| 1748 List<js.LabeledStatement> labelledStatements = | 1876 List<js.LabeledStatement> labelledStatements = |
| 1749 new List<js.LabeledStatement>(); | 1877 new List<js.LabeledStatement>(); |
| 1750 Set<String> usedNames = new Set<String>(); | 1878 Set<String> usedNames = new Set<String>(); |
| 1751 | 1879 |
| 1752 bool hasExplicitReturns = false; | 1880 bool hasExplicitReturns = false; |
| 1753 | 1881 |
| 1754 bool hasThis = false; | 1882 bool hasThis = false; |
| 1755 | 1883 |
| 1884 bool hasYield = false; |
| 1885 |
| 1756 // The function currently being analyzed. | 1886 // The function currently being analyzed. |
| 1757 js.Fun currentFunction; | 1887 js.Fun currentFunction; |
| 1758 | 1888 |
| 1759 // For error messages. | 1889 // For error messages. |
| 1760 final Function unsupported; | 1890 final Function unsupported; |
| 1761 | 1891 |
| 1762 PreTranslationAnalysis(void this.unsupported(js.Node node)); | 1892 PreTranslationAnalysis(void this.unsupported(js.Node node)); |
| 1763 | 1893 |
| 1764 bool visit(js.Node node) { | 1894 bool visit(js.Node node) { |
| 1765 bool containsAwait = node.accept(this); | 1895 bool containsAwait = node.accept(this); |
| (...skipping 370 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2136 bool visitWhile(js.While node) { | 2266 bool visitWhile(js.While node) { |
| 2137 loopsAndSwitches.add(node); | 2267 loopsAndSwitches.add(node); |
| 2138 bool condition = visit(node.condition); | 2268 bool condition = visit(node.condition); |
| 2139 bool body = visit(node.body); | 2269 bool body = visit(node.body); |
| 2140 loopsAndSwitches.removeLast(); | 2270 loopsAndSwitches.removeLast(); |
| 2141 return condition || body; | 2271 return condition || body; |
| 2142 } | 2272 } |
| 2143 | 2273 |
| 2144 @override | 2274 @override |
| 2145 bool visitDartYield(js.DartYield node) { | 2275 bool visitDartYield(js.DartYield node) { |
| 2276 hasYield = true; |
| 2146 visit(node.expression); | 2277 visit(node.expression); |
| 2147 return true; | 2278 return true; |
| 2148 } | 2279 } |
| 2149 } | 2280 } |
| OLD | NEW |