Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library rewrite_async; | |
| 6 | |
| 7 // TODO(sigurdm): Throws in catch-handlers are handled wrong. | |
| 8 // TODO(sigurdm): Avoid using variables in templates. It could blow up memory | |
| 9 // use. | |
| 10 | |
| 11 import "dart:math" show max; | |
| 12 import 'dart:collection'; | |
| 13 | |
| 14 import "js.dart" as js; | |
| 15 | |
| 16 import '../util/util.dart'; | |
| 17 import '../dart2jslib.dart' show DiagnosticListener; | |
| 18 | |
| 19 import "../helpers/helpers.dart"; | |
| 20 | |
| 21 /// Rewrites a [js.Fun] with async/sync*/async* functions and await and yield | |
| 22 /// (with dart-like semantics) to an equivalent function without these. | |
| 23 /// await-for is not handled and must be rewritten before. (Currently handled | |
| 24 /// in ssa/builder.dart). | |
| 25 /// | |
| 26 /// 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. | |
| 28 /// (Currently handled in closure.dart). | |
| 29 /// | |
| 30 /// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation. | |
| 31 class AsyncRewriter extends js.NodeVisitor { | |
| 32 | |
| 33 // Local variables are hoisted to the top of the function, so they are | |
| 34 // collected here. | |
| 35 List<js.VariableDeclaration> localVariables = | |
| 36 new List<js.VariableDeclaration>(); | |
| 37 | |
| 38 Map<js.Node, int> continueLabels = new Map<js.Node, int>(); | |
| 39 Map<js.Node, int> breakLabels = new Map<js.Node, int>(); | |
| 40 Map<js.Node, int> finallyLabels = new Map<js.Node, int>(); | |
| 41 int exitLabel; | |
| 42 | |
| 43 // A stack of all enclosing jump targets (including the function for | |
| 44 // representing the target of a return, and all enclosing try-blocks that have | |
| 45 // finally part, this way ensuring all the finally blocks between a jump and | |
| 46 // its target are run before the jump. | |
| 47 List<js.Node> targetsAndTries = new List<js.Node>(); | |
| 48 | |
| 49 List<int> continueStack = new List<int>(); | |
| 50 List<int> breakStack = new List<int>(); | |
| 51 List<int> returnStack = new List<int>(); | |
| 52 | |
| 53 List<Pair<String, String>> variableRenamings = | |
| 54 new List<Pair<String, String>>(); | |
| 55 | |
| 56 PreTranslationAnalysis analysis; | |
| 57 | |
| 58 List<int> errorHandlerLabels = new List<int>(); | |
| 59 | |
| 60 final Function safeVariableName; | |
| 61 | |
| 62 // All the <x>Name variables are names of Javascript variables used in the | |
| 63 // transformed code. | |
| 64 | |
| 65 /// Contains the result of an awaited expression, or a conditional or | |
| 66 /// lazy boolean operator. | |
| 67 /// | |
| 68 /// For example a conditional expression is roughly translated like: | |
| 69 /// [[cond ? a : b]] | |
| 70 /// | |
| 71 /// Becomes: | |
| 72 /// | |
| 73 /// while true { // outer while loop | |
| 74 /// switch (goto) { // Simulates goto | |
| 75 /// ... | |
| 76 /// goto = [[cond]] ? thenLabel : elseLabel | |
| 77 /// break; | |
| 78 /// case thenLabel: | |
| 79 /// result = [[a]]; | |
| 80 /// goto = joinLabel; | |
| 81 /// case elseLabel: | |
| 82 /// result = [[b]]; | |
| 83 /// case joinLabel: | |
| 84 /// // Now the result of computing the condition is in result. | |
| 85 /// .... | |
| 86 /// } | |
| 87 /// } | |
| 88 /// | |
| 89 /// It is a parameter to the [helperName] function, so that [thenHelper] and | |
| 90 /// [streamHelper] can call [helperName] with the result of an awaited Future. | |
| 91 String resultName; | |
| 92 | |
| 93 /// The name of the inner function that is scheduled to do each await/yield, | |
| 94 /// and called to do a new iteration for sync*. | |
| 95 String helperName; | |
| 96 | |
| 97 /// The Completer that will finish an async function. | |
| 98 /// | |
| 99 /// Not used for sync* or async* functions. | |
| 100 String completerName; | |
| 101 | |
| 102 /// The StreamController that controls an async* function. | |
| 103 /// | |
| 104 /// Not used for async and sync* functions | |
| 105 String controllerName; | |
| 106 | |
| 107 /// Used to simulate a goto. | |
| 108 /// | |
| 109 /// To "goto" a label, the label is assigned to this | |
| 110 /// variable, and break out of the switch to take another iteration in the | |
| 111 /// while loop. See [addGoto] | |
| 112 String gotoName; | |
| 113 | |
| 114 /// The label of the current error handler. | |
| 115 String handlerName; | |
| 116 | |
| 117 /// Current caught error. | |
| 118 String errorName; | |
| 119 | |
| 120 /// A stack of labels of finally blocks to visit, and the label to go to after | |
| 121 /// the last. | |
| 122 String nextName; | |
| 123 | |
| 124 /// The current returned value (a finally block may overwrite it). | |
| 125 String returnValueName; | |
| 126 | |
| 127 /// The label of the outer loop. | |
| 128 /// | |
| 129 /// Used if there are untransformed loops containing break or continues to | |
| 130 /// targets outside the loop. | |
| 131 String outerLabelName; | |
| 132 | |
| 133 /// If javascript `this` is used, it is accessed via this variable, in the | |
| 134 /// [helperName] function. | |
| 135 String selfName; | |
| 136 | |
| 137 // These expressions are hooks for communicating with the runtime. | |
| 138 | |
| 139 /// The function called by an async function to simulate an await or return. | |
| 140 /// | |
| 141 /// For an await it is called with: | |
| 142 /// | |
| 143 /// - The value to await | |
| 144 /// - The [helperName] | |
| 145 /// - The [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 /// | |
| 150 /// For a return it is called with: | |
| 151 /// | |
| 152 /// - The value to complete the completer with. | |
| 153 /// - null | |
| 154 /// - The [completerName] | |
| 155 /// - null. | |
| 156 final js.Expression thenHelper; | |
| 157 | |
| 158 /// The function called by an async* function to simulate an await, yield or | |
| 159 /// yield*. | |
| 160 /// | |
| 161 /// For an await/yield/yield* it is called with: | |
| 162 /// | |
| 163 /// - The value to await/yieldExpression(value to yield)/ | |
| 164 /// yieldStarExpression(stream to yield) | |
| 165 /// - The [helperName] | |
| 166 /// - The [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 /// | |
| 171 /// For a return it is called with: | |
| 172 /// | |
| 173 /// - null | |
| 174 /// - null | |
| 175 /// - The [controllerName] | |
| 176 /// - null. | |
| 177 final js.Expression streamHelper; | |
| 178 | |
| 179 /// Contructor used to initializes the [completerName] variable. | |
|
floitsch
2015/02/09 11:53:57
initialize
sigurdm
2015/02/11 07:54:53
Done.
| |
| 180 /// | |
| 181 /// Specific to async methods. | |
| 182 final js.Expression newCompleter; | |
| 183 | |
| 184 /// Contructor used to initializes the [controllerName] variable. | |
|
floitsch
2015/02/09 11:53:58
initialize
sigurdm
2015/02/11 07:54:53
Done.
| |
| 185 /// | |
| 186 /// Specific to async* methods. | |
| 187 final js.Expression newController; | |
| 188 | |
| 189 /// Contructor creating the Iterable for a sync* method. Called with | |
| 190 /// [helperName]. | |
| 191 final js.Expression newIterable; | |
| 192 | |
| 193 /// A JS Expression that creates a marker showing that iteration is over. | |
| 194 /// | |
| 195 /// Called without arguments. | |
| 196 final js.Expression endOfIteration; | |
| 197 | |
| 198 /// A JS Expression that creates a marker indicating a 'yield' statement. | |
| 199 /// | |
| 200 /// Called with the value to yield. | |
| 201 final js.Expression yieldExpression; | |
| 202 | |
| 203 /// A JS Expression that creates a marker indication a 'yield*' statement. | |
| 204 /// | |
| 205 /// Called with the stream to yield from. | |
| 206 final js.Expression yieldStarExpression; | |
| 207 | |
| 208 final DiagnosticListener diagnosticListener; | |
| 209 // For error reporting only. | |
| 210 Spannable get spannable { | |
| 211 return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable; | |
| 212 } | |
| 213 | |
| 214 Spannable _spannable; | |
| 215 | |
| 216 int _currentLabel = 0; | |
| 217 | |
| 218 // The highest temporary variable index currently in use. | |
| 219 int currentTempVarIndex = 0; | |
| 220 // The highest temporary variable index ever in use in this function. | |
| 221 int tempVarHighWaterMark = 0; | |
| 222 Map<int, js.Expression> tempVarNames = new Map<int, js.Expression>(); | |
| 223 | |
| 224 js.AsyncModifier async; | |
| 225 | |
| 226 bool get isSync => async == const js.AsyncModifier.sync(); | |
| 227 bool get isAsync => async == const js.AsyncModifier.async(); | |
| 228 bool get isSyncStar => async == const js.AsyncModifier.syncStar(); | |
| 229 bool get isAsyncStar => async == const js.AsyncModifier.asyncStar(); | |
| 230 | |
| 231 AsyncRewriter(this.diagnosticListener, | |
| 232 spannable, | |
| 233 {this.thenHelper, | |
| 234 this.streamHelper, | |
| 235 this.newCompleter, | |
| 236 this.newController, | |
| 237 this.endOfIteration, | |
| 238 this.newIterable, | |
| 239 this.yieldExpression, | |
| 240 this.yieldStarExpression, | |
| 241 this.safeVariableName}) | |
| 242 : _spannable = spannable; | |
| 243 | |
| 244 /// Main entry point. | |
| 245 /// Rewrites a sync*/async/async* function to an equivalent normal function. | |
| 246 /// | |
| 247 /// [spannable] can be passed to have a location for error messages. | |
| 248 js.Fun rewrite(js.Fun node, [Spannable spannable]) { | |
| 249 _spannable = spannable; | |
| 250 | |
| 251 async = node.asyncModifier; | |
| 252 assert(!isSync); | |
| 253 | |
| 254 analysis = new PreTranslationAnalysis(unsupported); | |
| 255 analysis.analyze(node); | |
| 256 | |
| 257 // To avoid name collisions with existing names, the fresh names are | |
| 258 // generated after the analysis. | |
| 259 resultName = freshName("result"); | |
| 260 completerName = freshName("completer"); | |
| 261 controllerName = freshName("controller"); | |
| 262 helperName = freshName("helper"); | |
| 263 gotoName = freshName("goto"); | |
| 264 handlerName = freshName("handler"); | |
| 265 errorName = freshName("error"); | |
| 266 nextName = freshName("next"); | |
| 267 returnValueName = freshName("returnValue"); | |
| 268 outerLabelName = freshName("outer"); | |
| 269 selfName = freshName("self"); | |
| 270 | |
| 271 return node.accept(this); | |
| 272 } | |
| 273 | |
| 274 js.Expression get currentErrorHandler { | |
| 275 return errorHandlerLabels.isEmpty | |
| 276 ? new js.LiteralNull() | |
| 277 : js.number(errorHandlerLabels.last); | |
| 278 } | |
| 279 | |
| 280 int allocateTempVar() { | |
| 281 assert(tempVarHighWaterMark >= currentTempVarIndex); | |
| 282 currentTempVarIndex++; | |
| 283 tempVarHighWaterMark = max(currentTempVarIndex, tempVarHighWaterMark); | |
| 284 return currentTempVarIndex; | |
| 285 } | |
| 286 | |
| 287 js.VariableUse useTempVar(int i) { | |
| 288 return tempVarNames.putIfAbsent( | |
| 289 i, () => new js.VariableUse(freshName("temp$i"))); | |
| 290 } | |
| 291 | |
| 292 /// Generates a variable name with [safeVariableName] based on [originalName] | |
| 293 /// with a suffix to guarantee it does not collide with already used names. | |
| 294 String freshName(String originalName) { | |
| 295 String safeName = safeVariableName(originalName); | |
| 296 String result = safeName; | |
| 297 int counter = 1; | |
| 298 while (analysis.usedNames.contains(result)) { | |
| 299 result = "$safeName$counter"; | |
| 300 ++counter; | |
| 301 } | |
| 302 analysis.usedNames.add(result); | |
| 303 return result; | |
| 304 } | |
| 305 | |
| 306 /// All the pieces are collected in this map, to create a switch with a case | |
| 307 /// for each label. | |
| 308 /// | |
| 309 /// The order is important, therefore the type is explicitly LinkedHashMap. | |
| 310 LinkedHashMap<int, List<js.Statement>> labelledParts = | |
| 311 new LinkedHashMap<int, List<js.Statement>>(); | |
| 312 | |
| 313 /// Description of each label for readability of the non-minified output. | |
| 314 Map<int, String> labelComments = new Map<int, String>(); | |
| 315 | |
| 316 /// True if the function has any try blocks containing await. | |
| 317 bool hasTryBlocks = false; | |
| 318 | |
| 319 /// True if any return, break or continue passes through a finally. | |
| 320 bool hasJumpThroughFinally = false; | |
| 321 | |
| 322 /// True if the traversion currently is inside a loop or switch for which | |
| 323 /// [shouldTransform] is false. | |
| 324 bool insideUntranslatedBreakable = false; | |
| 325 | |
| 326 /// True if a label is used to break to an outer switch-statement. | |
| 327 bool hasJumpThoughOuterLabel = false; | |
| 328 | |
| 329 /// True if there is a catch-handler protected by a finally with no enclosing | |
| 330 /// catch-handlers. | |
| 331 bool needsRethrow = false; | |
| 332 | |
| 333 /// Buffer for collecting translated statements belonging to the same switch | |
| 334 /// case. | |
| 335 List<js.Statement> currentStatementBuffer; | |
| 336 | |
| 337 // Labels will become cases in the big switch expression, and `goto label` | |
| 338 // is expressed by assigning to the switch key [gotoName] and breaking out of | |
| 339 // the switch. | |
| 340 | |
| 341 int newLabel([String comment]) { | |
| 342 int result = _currentLabel; | |
| 343 _currentLabel++; | |
| 344 if (comment != null) { | |
| 345 labelComments[result] = comment; | |
| 346 } | |
| 347 return result; | |
| 348 } | |
| 349 | |
| 350 /// Begins outputting statements to a new buffer with label [label]. | |
| 351 /// | |
| 352 /// Each buffer ends up as its own case part in the big state-switch. | |
| 353 void beginLabel(int label) { | |
| 354 assert(!labelledParts.containsKey(label)); | |
| 355 currentStatementBuffer = new List<js.Statement>(); | |
| 356 labelledParts[label] = currentStatementBuffer; | |
| 357 addStatement(new js.Comment(labelComments[label])); | |
| 358 } | |
| 359 | |
| 360 /// Returns a statement assigning to the variable named [gotoName]. | |
| 361 /// This should be followed by a break for the goto to be executed. Use | |
| 362 /// [gotoWithBreak] or [addGoto] for this. | |
| 363 js.Statement setGotoVariable(int label) { | |
| 364 return new js.ExpressionStatement( | |
| 365 new js.Assignment(new js.VariableUse(gotoName), js.number(label))); | |
| 366 } | |
| 367 | |
| 368 /// Returns a block that has a goto to [label] including the break. | |
| 369 /// | |
| 370 /// Also inserts a comment describing the label if available. | |
| 371 js.Block gotoAndBreak(int label) { | |
| 372 List<js.Statement> statements = new List<js.Statement>(); | |
| 373 if (labelComments.containsKey(label)) { | |
| 374 statements.add(new js.Comment("goto ${labelComments[label]}")); | |
| 375 } | |
| 376 statements.add(setGotoVariable(label)); | |
| 377 if (insideUntranslatedBreakable) { | |
| 378 hasJumpThoughOuterLabel = true; | |
| 379 statements.add(new js.Break(outerLabelName)); | |
| 380 } else { | |
| 381 statements.add(new js.Break(null)); | |
| 382 } | |
| 383 return new js.Block(statements); | |
| 384 } | |
| 385 | |
| 386 /// Adds a goto to [label] including the break. | |
| 387 /// | |
| 388 /// Also inserts a comment describing the label if available. | |
| 389 void addGoto(int label) { | |
| 390 if (labelComments.containsKey(label)) { | |
| 391 addStatement(new js.Comment("goto ${labelComments[label]}")); | |
| 392 } | |
| 393 addStatement(setGotoVariable(label)); | |
| 394 | |
| 395 addBreak(); | |
| 396 } | |
| 397 | |
| 398 void addStatement(js.Statement node) { | |
| 399 currentStatementBuffer.add(node); | |
| 400 } | |
| 401 | |
| 402 void addExpressionStatement(js.Expression node) { | |
| 403 addStatement(new js.ExpressionStatement(node)); | |
| 404 } | |
| 405 | |
| 406 /// True if there is an await or yield in [node] or some subexpression. | |
| 407 bool shouldTransform(js.Node node) { | |
| 408 return analysis.hasAwaitOrYield.contains(node); | |
| 409 } | |
| 410 | |
| 411 void unsupported(js.Node node) { | |
| 412 throw new UnsupportedError( | |
| 413 "Node $node cannot be transformed by the await-sync transformer"); | |
| 414 } | |
| 415 | |
| 416 void unreachable(js.Node node) { | |
| 417 diagnosticListener.internalError( | |
| 418 spannable, "Internal error, trying to visit $node"); | |
| 419 } | |
| 420 | |
| 421 visitStatement(js.Statement node) { | |
| 422 node.accept(this); | |
| 423 } | |
| 424 | |
| 425 /// Visits [node] to ensure its sideeffects are performed, but throwing away | |
| 426 /// the result. | |
| 427 /// | |
| 428 /// If the return value of visiting [node] is an expression guaranteed to have | |
| 429 /// no side effect, it is dropped. | |
| 430 void visitExpressionIgnoreResult(js.Expression node) { | |
| 431 js.Expression result = node.accept(this); | |
| 432 if (!(result is js.Literal || result is js.VariableUse)) { | |
| 433 addExpressionStatement(result); | |
| 434 } | |
| 435 } | |
| 436 | |
| 437 js.Expression visitExpression(js.Expression node) { | |
| 438 return node.accept(this); | |
| 439 } | |
| 440 | |
| 441 | |
| 442 /// Calls [fn] with the value of evaluating [node1] and [node2]. | |
| 443 /// | |
| 444 /// Both nodes are evaluated in order. | |
| 445 /// | |
| 446 /// If node2 must be transformed (see [shouldTransform]), then the evaluation | |
| 447 /// of node1 is added to the current statement-list and the result is stored | |
| 448 /// in a temporary variable. The evaluation of node2 is then free to emit | |
| 449 /// statements without affecting the result of node1. | |
| 450 /// | |
| 451 /// This is necessary, because await or yield expressions have to emit | |
| 452 /// statements, and these statements could affect the value of node1. | |
| 453 /// | |
| 454 /// For example: | |
| 455 /// | |
| 456 /// - _storeIfNecessary(someLiteral) returns someLiteral. | |
| 457 /// - _storeIfNecessary(someVariable) | |
| 458 /// inserts: var tempX = someVariable | |
| 459 /// returns: tempX | |
| 460 /// where tempX is a fresh temporary variable. | |
| 461 js.Expression _storeIfNecessary(js.Expression result) { | |
| 462 // Note that RegExes, js.ArrayInitializer and js.ObjectInitializer are not | |
| 463 // [js.Literal]s. | |
| 464 if (result is js.Literal) return result; | |
| 465 js.Expression tempVar = useTempVar(allocateTempVar()); | |
| 466 addExpressionStatement(new js.Assignment(tempVar, result)); | |
| 467 return tempVar; | |
| 468 } | |
| 469 | |
| 470 withExpression(js.Expression node, fn(js.Expression result), {bool store}) { | |
| 471 int oldTempVarIndex = currentTempVarIndex; | |
| 472 js.Expression visited = visitExpression(node); | |
| 473 if (store) { | |
| 474 visited = _storeIfNecessary(visited); | |
| 475 } | |
| 476 var result = fn(visited); | |
| 477 currentTempVarIndex = oldTempVarIndex; | |
| 478 return result; | |
| 479 } | |
| 480 | |
| 481 /// Calls [fn] with the value of evaluating [node1] and [node2]. | |
| 482 /// | |
| 483 /// If `shouldTransform(node2)` the first expression is stored in a temporary | |
| 484 /// variable. | |
| 485 /// | |
| 486 /// This is because node1 must be evaluated before visiting node2, | |
| 487 /// because the evaluation of an await or yield cannot be expressed as | |
| 488 /// an expression, visiting node2 it will output statements that | |
| 489 /// might have an influence on the value of node1. | |
| 490 withExpression2(js.Expression node1, js.Expression node2, | |
| 491 fn(js.Expression result1, js.Expression result2)) { | |
| 492 int oldTempVarIndex = currentTempVarIndex; | |
| 493 js.Expression r1 = visitExpression(node1); | |
| 494 if (shouldTransform(node2)) { | |
| 495 r1 = _storeIfNecessary(r1); | |
| 496 } | |
| 497 js.Expression r2 = visitExpression(node2); | |
| 498 var result = fn(r1, r2); | |
| 499 currentTempVarIndex = oldTempVarIndex; | |
| 500 return result; | |
| 501 } | |
| 502 | |
| 503 /// Calls [fn] with the value of evaluating all [nodes]. | |
| 504 /// | |
| 505 /// All results before the last node where `shouldTransform(node)` are stored | |
| 506 /// in temporary variables. | |
| 507 /// | |
| 508 /// See more explanation on [withExpression2]. | |
| 509 /// | |
| 510 /// If any of the nodes are null, they are ignored, and a null is passed to | |
| 511 /// [fn] in that place. | |
| 512 withExpressions(List<js.Expression> nodes, fn(List<js.Expression> results)) { | |
| 513 int oldTempVarIndex = currentTempVarIndex; | |
| 514 // Find last occurence of a 'transform' expression in [nodes]. | |
| 515 // All expressions before that must be stored in temp-vars. | |
| 516 int lastTransformIndex = 0; | |
| 517 for (int i = nodes.length - 1; i >= 0; --i) { | |
| 518 if (nodes[i] == null) continue; | |
| 519 if (shouldTransform(nodes[i])) { | |
| 520 lastTransformIndex = i; | |
| 521 break; | |
| 522 } | |
| 523 } | |
| 524 List<js.Node> visited = nodes.take(lastTransformIndex).map((js.Node node) { | |
| 525 return node == null ? null : _storeIfNecessary(visitExpression(node)); | |
| 526 }).toList(); | |
| 527 visited.addAll(nodes.skip(lastTransformIndex).map((js.Node node) { | |
| 528 return node == null ? null : visitExpression(node); | |
| 529 })); | |
| 530 var result = fn(visited); | |
| 531 currentTempVarIndex = oldTempVarIndex; | |
| 532 return result; | |
| 533 } | |
| 534 | |
| 535 /// Emits the return block that all returns should jump to (after going | |
| 536 /// through all the enclosing finally blocks). The jump to here is made in | |
| 537 /// [visitReturn]. | |
| 538 /// | |
| 539 /// Returning from an async method calls the [thenHelper] with the result. | |
| 540 /// (the result might have been stored in [returnValueName] by some finally | |
| 541 /// block). | |
| 542 /// | |
| 543 /// Returning from a sync* function returns an [endOfIteration] marker. | |
| 544 /// | |
| 545 /// Returning from an async* function calls the [streamHelper] with an | |
| 546 /// [endOfIteration] marker. | |
| 547 void addExit() { | |
| 548 if (analysis.hasExplicitReturns || isAsyncStar) { | |
| 549 beginLabel(exitLabel); | |
| 550 } else { | |
| 551 addStatement(new js.Comment("implicit return")); | |
| 552 } | |
| 553 switch (async) { | |
| 554 case const js.AsyncModifier.async(): | |
| 555 String returnValue = | |
| 556 analysis.hasExplicitReturns ? returnValueName : "null"; | |
| 557 addStatement(js.js.statement( | |
| 558 "return #thenHelper($returnValue, null, $completerName, null)", { | |
| 559 "thenHelper": thenHelper | |
| 560 })); | |
| 561 break; | |
| 562 case const js.AsyncModifier.syncStar(): | |
| 563 addStatement(new js.Return(new js.Call(endOfIteration, []))); | |
| 564 break; | |
| 565 case const js.AsyncModifier.asyncStar(): | |
| 566 addStatement(js.js.statement( | |
| 567 "return #streamHelper(null, null, $controllerName, null)", { | |
| 568 "streamHelper": streamHelper | |
| 569 })); | |
| 570 break; | |
| 571 default: | |
| 572 diagnosticListener.internalError( | |
| 573 spannable, "Internal error, unexpected asyncmodifier $async"); | |
| 574 } | |
| 575 } | |
| 576 | |
| 577 /// The initial call to [thenHelper]/[streamHelper]. | |
| 578 /// | |
| 579 /// There is no value to await/yield, so the first argument is `null` and | |
| 580 /// also the errorCallback is `null`. | |
| 581 /// | |
| 582 /// Returns the [Future]/[Stream] coming from [completerName]/ | |
| 583 /// [controllerName]. | |
| 584 js.Statement generateInitializer() { | |
| 585 if (isAsync) { | |
| 586 return js.js.statement( | |
| 587 "return #thenHelper(null, $helperName, $completerName, null);", { | |
| 588 "thenHelper": thenHelper | |
| 589 }); | |
| 590 } else if (isAsyncStar) { | |
| 591 return js.js.statement( | |
| 592 "return #streamHelper(null, $helperName, $controllerName, null);", { | |
| 593 "streamHelper": streamHelper | |
| 594 }); | |
| 595 } else { | |
| 596 throw diagnosticListener.internalError( | |
| 597 spannable, "Unexpected asyncModifier: $async"); | |
| 598 } | |
| 599 } | |
| 600 | |
| 601 /// Rewrites an async/sync*/async* function to a normal Javascript function. | |
| 602 /// | |
| 603 /// The control flow is flattened by simulating 'goto' using a switch in a | |
| 604 /// loop and a state variable [gotoName] inside a helper-function that can be | |
| 605 /// called back by [thenHelper]/[streamHelper]/the [Iterator]. | |
| 606 /// | |
| 607 /// Local variables are hoisted outside the helper. | |
| 608 /// | |
| 609 /// Awaits in async/async* are translated to code that remembers the current | |
| 610 /// location (so the function can resume from where it was) followed by a call | |
| 611 /// to the [thenHelper]. The helper sets up the waiting for the awaited value | |
| 612 /// and returns a future which is immediately returned by the translated | |
| 613 /// await. | |
| 614 /// Yields in async* are translated to a call to the [streamHelper]. They, | |
| 615 /// too, need to be prepared to be interrupted in case the stream is paused or | |
| 616 /// canceled. (Currently we always suspend - this is different from the spec, | |
|
floitsch
2015/02/09 11:53:58
Missing closing parenthesis.
sigurdm
2015/02/11 07:54:53
Done.
| |
| 617 /// see `streamHelper` in `js_helper.dart`. | |
| 618 /// | |
| 619 /// Yield/yield* in a sync* function is translated to a return of the value, | |
| 620 /// wrapped into a "IterationMarker" that signals the type (yield or yield*). | |
| 621 /// Sync* functions are executed on demand (when the user requests a value) by | |
| 622 /// the Iterable that knows how to handle these values. | |
| 623 /// | |
| 624 /// Simplified examples (not the exact translation, but intended to show the | |
| 625 /// ideas): | |
| 626 /// | |
| 627 /// function (x, y, z) async { | |
| 628 /// var p = await foo(); | |
| 629 /// return bar(p); | |
| 630 /// } | |
| 631 /// | |
| 632 /// Becomes: | |
| 633 /// | |
| 634 /// function(x, y, z) { | |
| 635 /// var goto = 0, returnValue, completer = new Completer(), p; | |
| 636 /// function helper(result) { | |
| 637 /// while (true) { | |
| 638 /// switch (goto) { | |
| 639 /// case 0: | |
| 640 /// goto = 1 // Remember where to continue when the future succeeds. | |
| 641 /// return thenHelper(foo(), helper, completer, null); | |
| 642 /// case 1: | |
| 643 /// p = result; | |
| 644 /// returnValue = bar(p); | |
| 645 /// goto = 2; | |
| 646 /// break; | |
| 647 /// case 2: | |
| 648 /// return thenHelper(returnValue, null, completer, null) | |
| 649 /// } | |
| 650 /// } | |
| 651 /// return thenHelper(null, helper, completer, null); | |
| 652 /// } | |
| 653 /// } | |
| 654 /// | |
| 655 /// Try/catch is implemented by maintaining [handlerName] to contain the label | |
| 656 /// of the current handler. The switch is nested inside a try/catch that will | |
| 657 /// redirect the flow to the current handler. | |
| 658 /// | |
| 659 /// A `finally` clause is compiled similar to normal code, with the additional | |
| 660 /// complexity that `finally` clauses need to know where to jump to after the | |
| 661 /// clause is done. In the translation, each flow-path that enters a `finally` | |
| 662 /// sets up the variable [nextName] with a stack of finally-blocks and a final | |
| 663 /// jump-target (exit, catch, ...). | |
| 664 /// | |
| 665 /// function (x, y, z) async { | |
| 666 /// try { | |
| 667 /// try { | |
| 668 /// throw "error"; | |
| 669 /// } finally { | |
| 670 /// finalize1(); | |
| 671 /// } | |
| 672 /// } catch (e) { | |
| 673 /// handle(e); | |
| 674 /// } finally { | |
| 675 /// finalize2(); | |
| 676 /// } | |
| 677 /// } | |
| 678 /// | |
| 679 /// Translates into (besides the fact that structures not containing | |
| 680 /// await/yield/yield* are left intact): | |
| 681 /// | |
| 682 /// function(x, y, z) { | |
| 683 /// var goto = 0; | |
| 684 /// var returnValue; | |
| 685 /// var completer = new Completer(); | |
| 686 /// var handler = null; | |
| 687 /// var p; | |
| 688 /// // The result can be either the result of an awaited future, or an | |
| 689 /// // error if the future completed with an error. | |
| 690 /// function helper(result) { | |
| 691 /// while (true) { | |
| 692 /// try { | |
| 693 /// switch (goto) { | |
| 694 /// case 0: | |
| 695 /// handler = 4; // The outer catch-handler | |
| 696 /// handler = 1; // The inner (implicit) catch-handler | |
| 697 /// throw "error"; | |
| 698 /// next = [3]; | |
| 699 /// // After the finally (2) continue normally after the try. | |
| 700 /// goto = 2; | |
| 701 /// break; | |
| 702 /// case 1: // (implicit) catch handler for inner try. | |
| 703 /// next = [3]; // destination after the finally. | |
| 704 /// // fall-though to the finally handler. | |
| 705 /// case 2: // finally for inner try | |
| 706 /// handler = 4; // catch-handler for outer try. | |
| 707 /// finalize1(); | |
| 708 /// goto = next.pop(); | |
| 709 /// break; | |
| 710 /// case 3: // exiting inner try. | |
| 711 /// next = [6]; | |
| 712 /// goto = 5; // finally handler for outer try. | |
| 713 /// break; | |
| 714 /// case 4: // catch handler for outer try. | |
| 715 /// e = result; | |
| 716 /// handle(e); | |
| 717 /// // Fall through to finally. | |
| 718 /// case 5: // finally handler for outer try. | |
| 719 /// handler = null; | |
| 720 /// finalize2(); | |
| 721 /// goto = next.pop(); | |
| 722 /// break; | |
| 723 /// case 6: // Exiting outer try. | |
| 724 /// case 7: // return | |
| 725 /// return thenHelper(returnValue, null, completer, null); | |
| 726 /// } | |
| 727 /// } catch (error) { | |
| 728 /// result = error; | |
| 729 /// goto = handler; | |
| 730 /// } | |
| 731 /// } | |
| 732 /// return thenHelper(null, helper, completer, null); | |
| 733 /// } | |
| 734 /// } | |
| 735 /// | |
| 736 @override | |
| 737 js.Expression visitFun(js.Fun node) { | |
| 738 if (isSync) return node; | |
| 739 | |
| 740 beginLabel(newLabel("Function start")); | |
| 741 // AsyncStar needs a returnlabel for its handling of cancelation. See | |
| 742 // [visitDartYield]. | |
| 743 exitLabel = | |
| 744 analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null; | |
| 745 js.Statement body = node.body; | |
| 746 targetsAndTries.add(node); | |
| 747 visitStatement(body); | |
| 748 targetsAndTries.removeLast(); | |
| 749 addExit(); | |
| 750 | |
| 751 List<js.SwitchClause> clauses = labelledParts.keys.map((label) { | |
| 752 return new js.Case(js.number(label), new js.Block(labelledParts[label])); | |
| 753 }).toList(); | |
| 754 js.Statement helperBody = | |
| 755 new js.Switch(new js.VariableUse(gotoName), clauses); | |
| 756 if (hasJumpThoughOuterLabel) { | |
| 757 helperBody = js.js.statement("$outerLabelName: #", [helperBody]); | |
| 758 } | |
| 759 if (hasTryBlocks) { | |
| 760 helperBody = js.js.statement(""" | |
| 761 try { | |
| 762 #body | |
| 763 } catch ($errorName){ | |
| 764 if ($handlerName === null) | |
| 765 throw $errorName; | |
| 766 $resultName = $errorName; | |
| 767 $gotoName = $handlerName; | |
| 768 }""", {"body": helperBody}); | |
| 769 } | |
| 770 List<js.VariableInitialization> inits = <js.VariableInitialization>[]; | |
| 771 | |
| 772 js.VariableInitialization makeInit(String name, js.Expression initValue) { | |
| 773 return new js.VariableInitialization( | |
| 774 new js.VariableDeclaration(name), initValue); | |
| 775 } | |
| 776 | |
| 777 inits.add(makeInit(gotoName, js.number(0))); | |
| 778 if (isAsync) { | |
| 779 inits.add(makeInit(completerName, new js.New(newCompleter, []))); | |
| 780 } else if (isAsyncStar) { | |
| 781 inits.add(makeInit(controllerName, | |
| 782 new js.Call(newController, [new js.VariableUse(helperName)]))); | |
| 783 } | |
| 784 if (hasTryBlocks) { | |
| 785 inits.add(makeInit(handlerName, new js.LiteralNull())); | |
| 786 } | |
| 787 if (hasJumpThroughFinally) { | |
| 788 inits.add(makeInit(nextName, null)); | |
| 789 } | |
| 790 if (analysis.hasExplicitReturns && isAsync) { | |
| 791 inits.add(makeInit(returnValueName, null)); | |
| 792 } | |
| 793 if (analysis.hasThis && !isSyncStar) { | |
| 794 // Sync* functions must remember this on the level of the outer function. | |
|
floitsch
2015/02/09 11:53:57
`this`
sigurdm
2015/02/11 07:54:53
Done.
| |
| 795 inits.add(makeInit(selfName, new js.This())); | |
| 796 } | |
| 797 inits.addAll(localVariables.map((js.VariableDeclaration decl) { | |
| 798 return new js.VariableInitialization(decl, null); | |
| 799 })); | |
| 800 inits.addAll(new Iterable.generate(tempVarHighWaterMark, | |
| 801 (int i) => makeInit(useTempVar(i + 1).name, null))); | |
| 802 js.VariableDeclarationList varDecl = new js.VariableDeclarationList(inits); | |
| 803 if (isSyncStar) { | |
| 804 return js.js(""" | |
| 805 function (#params) { | |
| 806 if (#needsThis) { | |
|
floitsch
2015/02/09 11:53:57
Nit: Remove '{' and '}'.
It's a limitation of the
sigurdm
2015/02/11 07:54:53
Done.
| |
| 807 var $selfName = this; | |
| 808 } | |
| 809 return new #newIterable(function () { | |
| 810 #varDecl; | |
| 811 return function $helperName($resultName) { | |
| 812 while (true) | |
| 813 #helperBody; | |
| 814 }; | |
| 815 }); | |
| 816 } | |
| 817 """, { | |
| 818 "params": node.params, | |
| 819 "needsThis": analysis.hasThis, | |
| 820 "helperBody": helperBody, | |
| 821 "varDecl": varDecl, | |
| 822 "newIterable": newIterable | |
| 823 }); | |
| 824 } | |
| 825 return js.js(""" | |
| 826 function (#params) { | |
| 827 #varDecl; | |
| 828 function $helperName($resultName) { | |
| 829 while (true) | |
| 830 #helperBody; | |
| 831 } | |
| 832 #init; | |
| 833 }""", { | |
| 834 "params": node.params, | |
| 835 "helperBody": helperBody, | |
| 836 "varDecl": varDecl, | |
| 837 "init": generateInitializer() | |
| 838 }); | |
| 839 } | |
| 840 | |
| 841 @override | |
| 842 js.Expression visitAccess(js.PropertyAccess node) { | |
| 843 return withExpression2(node.receiver, node.selector, | |
| 844 (receiver, selector) => new js.PropertyAccess(receiver, selector)); | |
| 845 } | |
| 846 | |
| 847 @override | |
| 848 js.Expression visitArrayHole(js.ArrayHole node) { | |
| 849 return node; | |
| 850 } | |
| 851 | |
| 852 @override | |
| 853 js.Expression visitArrayInitializer(js.ArrayInitializer node) { | |
| 854 return withExpressions(node.elements, (elements) { | |
| 855 return new js.ArrayInitializer(elements); | |
| 856 }); | |
| 857 } | |
| 858 | |
| 859 @override | |
| 860 js.Expression visitAssignment(js.Assignment node) { | |
| 861 if (!shouldTransform(node)) { | |
| 862 return new js.Assignment.compound(visitExpression(node.leftHandSide), | |
| 863 node.op, visitExpression(node.value)); | |
| 864 } | |
| 865 js.Expression leftHandSide = node.leftHandSide; | |
| 866 if (leftHandSide is js.VariableUse) { | |
| 867 return withExpression(node.value, (js.Expression value) { | |
| 868 return new js.Assignment(leftHandSide, value); | |
| 869 }, store: false); | |
| 870 } else if (leftHandSide is js.PropertyAccess) { | |
| 871 return withExpressions([ | |
| 872 leftHandSide.receiver, | |
| 873 leftHandSide.selector, | |
| 874 node.value | |
| 875 ], (evaluated) { | |
| 876 return new js.Assignment.compound( | |
| 877 new js.PropertyAccess(evaluated[0], evaluated[1]), node.op, | |
| 878 evaluated[2]); | |
| 879 }); | |
| 880 } else { | |
| 881 throw "Unexpected assignment left hand side $leftHandSide"; | |
| 882 } | |
| 883 } | |
| 884 | |
| 885 /// An await is translated to a call to [thenHelper]/[streamHelper]. | |
| 886 /// | |
| 887 /// See the comments of [visitFun] for an example. | |
| 888 @override | |
| 889 js.Expression visitAwait(js.Await node) { | |
| 890 assert(isAsync || isAsyncStar); | |
| 891 int afterAwait = newLabel("returning from await."); | |
| 892 withExpression(node.expression, (js.Expression value) { | |
| 893 addStatement(setGotoVariable(afterAwait)); | |
| 894 js.Expression errorCallback = errorHandlerLabels.isEmpty | |
| 895 ? new js.LiteralNull() | |
| 896 : js.js(""" | |
| 897 function($errorName) { | |
| 898 $gotoName = #currentHandler; | |
| 899 $helperName($errorName); | |
| 900 }""", {"currentHandler": currentErrorHandler}); | |
| 901 | |
| 902 addStatement(js.js.statement(""" | |
| 903 return #thenHelper(#value, | |
| 904 $helperName, | |
| 905 ${isAsync ? completerName : controllerName}, | |
| 906 #errorCallback); | |
| 907 """, { | |
| 908 "thenHelper": isAsync ? thenHelper : streamHelper, | |
| 909 "value": value, | |
| 910 "errorCallback": errorCallback | |
| 911 })); | |
| 912 }, store: false); | |
| 913 beginLabel(afterAwait); | |
| 914 return new js.VariableUse(resultName); | |
| 915 } | |
| 916 | |
| 917 /// Checks if [node] is the variable named [resultName]. | |
| 918 /// | |
| 919 /// [resultName] is used to hold the result of a transformed computation | |
| 920 /// for example the result of awaiting, or the result of a conditional or | |
| 921 /// short-circuiting expression. | |
| 922 /// If the subexpression of some transformed node already is transformed and | |
| 923 /// visiting it returns [resultName], it is not redundantly assigned to itself | |
| 924 /// again. | |
| 925 bool isResult(js.Expression node) { | |
| 926 return node is js.VariableUse && node.name == resultName; | |
| 927 } | |
| 928 | |
| 929 @override | |
| 930 js.Expression visitBinary(js.Binary node) { | |
| 931 if (shouldTransform(node.right) && (node.op == "||" || node.op == "&&")) { | |
| 932 int thenLabel = newLabel("then"); | |
| 933 int joinLabel = newLabel("join"); | |
| 934 withExpression(node.left, (js.Expression left) { | |
| 935 js.Statement assignLeft = isResult(left) | |
| 936 ? new js.Block.empty() | |
| 937 : new js.ExpressionStatement( | |
| 938 new js.Assignment(new js.VariableUse(resultName), left)); | |
| 939 if (node.op == "||") { | |
| 940 addStatement(new js.If(left, gotoAndBreak(thenLabel), assignLeft)); | |
| 941 } else { | |
| 942 assert(node.op == "&&"); | |
| 943 addStatement(new js.If(left, assignLeft, gotoAndBreak(thenLabel))); | |
| 944 } | |
| 945 }, store: true); | |
| 946 addGoto(joinLabel); | |
| 947 beginLabel(thenLabel); | |
| 948 withExpression(node.right, (js.Expression value) { | |
| 949 if (!isResult(value)) { | |
| 950 addExpressionStatement( | |
| 951 new js.Assignment(new js.VariableUse(resultName), value)); | |
| 952 } | |
| 953 }, store: false); | |
| 954 beginLabel(joinLabel); | |
| 955 return new js.VariableUse(resultName); | |
| 956 } | |
| 957 | |
| 958 return withExpression2(node.left, node.right, | |
| 959 (left, right) => new js.Binary(node.op, left, right)); | |
| 960 } | |
| 961 | |
| 962 @override | |
| 963 js.Expression visitBlob(js.Blob node) { | |
| 964 return node; | |
| 965 } | |
| 966 | |
| 967 @override | |
| 968 void visitBlock(js.Block node) { | |
| 969 for (js.Statement statement in node.statements) { | |
| 970 visitStatement(statement); | |
| 971 } | |
| 972 } | |
| 973 | |
| 974 @override | |
| 975 void visitBreak(js.Break node) { | |
| 976 js.Node target = analysis.targets[node]; | |
| 977 if (!shouldTransform(target)) { | |
| 978 addStatement(node); | |
| 979 return; | |
| 980 } | |
| 981 translateJump(target, breakLabels[target]); | |
| 982 } | |
| 983 | |
| 984 @override | |
| 985 js.Expression visitCall(js.Call node) { | |
| 986 bool storeTarget = node.arguments.any(shouldTransform); | |
| 987 return withExpression(node.target, (target) { | |
| 988 return withExpressions(node.arguments, (List<js.Expression> arguments) { | |
| 989 return new js.Call(target, arguments); | |
| 990 }); | |
| 991 }, store: storeTarget); | |
| 992 } | |
| 993 | |
| 994 @override | |
| 995 void visitCase(js.Case node) { | |
| 996 return unreachable(node); | |
| 997 } | |
| 998 | |
| 999 @override | |
| 1000 void visitCatch(js.Catch node) { | |
| 1001 return unreachable(node); | |
| 1002 } | |
| 1003 | |
| 1004 @override | |
| 1005 void visitComment(js.Comment node) { | |
| 1006 addStatement(node); | |
| 1007 } | |
| 1008 | |
| 1009 @override | |
| 1010 js.Expression visitConditional(js.Conditional node) { | |
| 1011 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { | |
| 1012 return withExpression(node.condition, (js.Expression condition) { | |
| 1013 return new js.Conditional(condition, node.then, node.otherwise); | |
| 1014 }); | |
| 1015 } | |
| 1016 int thenLabel = newLabel("then"); | |
| 1017 int joinLabel = newLabel("join"); | |
| 1018 int elseLabel = newLabel("else"); | |
| 1019 withExpression(node.condition, (js.Expression condition) { | |
| 1020 addExpressionStatement(new js.Assignment(new js.VariableUse(gotoName), | |
| 1021 new js.Conditional( | |
| 1022 condition, js.number(thenLabel), js.number(elseLabel)))); | |
| 1023 }, store: false); | |
| 1024 addBreak(); | |
| 1025 beginLabel(thenLabel); | |
| 1026 withExpression(node.then, (js.Expression value) { | |
| 1027 if (!isResult(value)) { | |
| 1028 addExpressionStatement( | |
| 1029 new js.Assignment(new js.VariableUse(resultName), value)); | |
| 1030 } | |
| 1031 }, store: false); | |
| 1032 addGoto(joinLabel); | |
| 1033 beginLabel(elseLabel); | |
| 1034 withExpression(node.otherwise, (js.Expression value) { | |
| 1035 if (!isResult(value)) { | |
| 1036 addExpressionStatement( | |
| 1037 new js.Assignment(new js.VariableUse(resultName), value)); | |
| 1038 } | |
| 1039 }, store: false); | |
| 1040 beginLabel(joinLabel); | |
| 1041 return new js.VariableUse(resultName); | |
| 1042 } | |
| 1043 | |
| 1044 @override | |
| 1045 void visitContinue(js.Continue node) { | |
| 1046 js.Node target = analysis.targets[node]; | |
| 1047 if (!shouldTransform(target)) { | |
| 1048 addStatement(node); | |
| 1049 return; | |
| 1050 } | |
| 1051 translateJump(target, continueLabels[target]); | |
| 1052 } | |
| 1053 | |
| 1054 /// Emits a break statement that exits the big switch statement. | |
| 1055 void addBreak() { | |
| 1056 if (insideUntranslatedBreakable) { | |
| 1057 hasJumpThoughOuterLabel = true; | |
| 1058 addStatement(new js.Break(outerLabelName)); | |
| 1059 } else { | |
| 1060 addStatement(new js.Break(null)); | |
| 1061 } | |
| 1062 } | |
| 1063 | |
| 1064 /// Common code for handling break, continue, return. | |
| 1065 /// | |
| 1066 /// It is necessary to run all nesting finally-handlers between the jump and | |
| 1067 /// the target. For that [nextName] is used as a stack of places to go. | |
| 1068 /// | |
| 1069 /// See also [visitFun]. | |
| 1070 void translateJump(js.Node target, int targetLabel) { | |
| 1071 // Compute a stack of all the 'finally' nodes that must be visited before | |
| 1072 // the jump. | |
| 1073 // The bottom of the stack is the label where the jump goes to. | |
| 1074 List<int> jumpStack = new List<int>(); | |
| 1075 for (js.Node node in targetsAndTries.reversed) { | |
| 1076 if (node is js.Try) { | |
| 1077 assert(node.finallyPart != null); | |
| 1078 jumpStack.add(finallyLabels[node]); | |
| 1079 } else if (node == target) { | |
| 1080 jumpStack.add(targetLabel); | |
| 1081 break; | |
| 1082 } | |
| 1083 // Ignore other nodes. | |
| 1084 } | |
| 1085 jumpStack = jumpStack.reversed.toList(); | |
| 1086 // As the program jumps directly to the top of the stack, it is taken off | |
| 1087 // now. | |
| 1088 int firstTarget = jumpStack.removeLast(); | |
| 1089 if (jumpStack.isNotEmpty) { | |
| 1090 hasJumpThroughFinally = true; | |
| 1091 js.Expression jsJumpStack = new js.ArrayInitializer( | |
| 1092 jumpStack.map((int label) => js.number(label)).toList()); | |
| 1093 addStatement(js.js.statement("$nextName = #", [jsJumpStack])); | |
| 1094 } | |
| 1095 addGoto(firstTarget); | |
| 1096 } | |
| 1097 | |
| 1098 @override | |
| 1099 void visitDefault(js.Default node) => unreachable(node); | |
| 1100 | |
| 1101 @override | |
| 1102 void visitDo(js.Do node) { | |
| 1103 if (!shouldTransform(node)) { | |
| 1104 bool oldInsideUntranslatedBreakable = insideUntranslatedBreakable; | |
| 1105 insideUntranslatedBreakable = true; | |
| 1106 withExpression(node.condition, (js.Expression condition) { | |
| 1107 addStatement(new js.Do(translateInBlock(node.body), condition)); | |
| 1108 }, store: false); | |
| 1109 insideUntranslatedBreakable = oldInsideUntranslatedBreakable; | |
| 1110 return; | |
| 1111 } | |
| 1112 int startLabel = newLabel("do body"); | |
| 1113 | |
| 1114 int continueLabel = newLabel("do condition"); | |
| 1115 continueLabels[node] = continueLabel; | |
| 1116 | |
| 1117 int afterLabel = newLabel("after do"); | |
| 1118 breakLabels[node] = afterLabel; | |
| 1119 | |
| 1120 beginLabel(startLabel); | |
| 1121 | |
| 1122 targetsAndTries.add(node); | |
| 1123 visitStatement(node.body); | |
| 1124 targetsAndTries.removeLast(); | |
| 1125 | |
| 1126 beginLabel(continueLabel); | |
| 1127 withExpression(node.condition, (js.Expression condition) { | |
| 1128 addStatement(new js.If.noElse(condition, gotoAndBreak(startLabel))); | |
| 1129 }, store: false); | |
| 1130 beginLabel(afterLabel); | |
| 1131 } | |
| 1132 | |
| 1133 @override | |
| 1134 void visitEmptyStatement(js.EmptyStatement node) { | |
| 1135 addStatement(node); | |
| 1136 } | |
| 1137 | |
| 1138 void visitExpressionInStatementContext(js.Expression node) { | |
| 1139 if (node is js.VariableDeclarationList) { | |
| 1140 // Treat js.VariableDeclarationList as a statement. | |
| 1141 visitVariableDeclarationList(node); | |
| 1142 } else { | |
| 1143 visitExpressionIgnoreResult(node); | |
| 1144 } | |
| 1145 } | |
| 1146 | |
| 1147 @override | |
| 1148 void visitExpressionStatement(js.ExpressionStatement node) { | |
| 1149 visitExpressionInStatementContext(node.expression); | |
| 1150 } | |
| 1151 | |
| 1152 @override | |
| 1153 void visitFor(js.For node) { | |
| 1154 if (!shouldTransform(node)) { | |
| 1155 bool oldInsideUntranslated = insideUntranslatedBreakable; | |
| 1156 insideUntranslatedBreakable = true; | |
| 1157 // Note that node.init, node.condition, node.update all can be null, but | |
| 1158 // withExpressions handles that. | |
| 1159 withExpressions([ | |
| 1160 node.init, | |
| 1161 node.condition, | |
| 1162 node.update | |
| 1163 ], (List<js.Expression> transformed) { | |
| 1164 addStatement(new js.For(transformed[0], transformed[1], transformed[2], | |
| 1165 translateInBlock(node.body))); | |
| 1166 }); | |
| 1167 insideUntranslatedBreakable = oldInsideUntranslated; | |
| 1168 return; | |
| 1169 } | |
| 1170 | |
| 1171 if (node.init != null) { | |
| 1172 visitExpressionInStatementContext(node.init); | |
| 1173 } | |
| 1174 int startLabel = newLabel("for condition"); | |
| 1175 // If there is no update, continuing the loop is the same as going to the | |
| 1176 // start. | |
| 1177 int continueLabel = | |
| 1178 (node.update == null) ? startLabel : newLabel("for update"); | |
| 1179 continueLabels[node] = continueLabel; | |
| 1180 int afterLabel = newLabel("after for"); | |
| 1181 breakLabels[node] = afterLabel; | |
| 1182 beginLabel(startLabel); | |
| 1183 js.Expression condition = node.condition; | |
| 1184 if (condition == null || | |
| 1185 (condition is js.LiteralBool && condition.value == true)) { | |
| 1186 addStatement(new js.Comment("trivial condition")); | |
| 1187 } else { | |
| 1188 withExpression(condition, (js.Expression condition) { | |
| 1189 addStatement(new js.If.noElse( | |
| 1190 new js.Prefix("!", condition), gotoAndBreak(afterLabel))); | |
| 1191 }, store: false); | |
| 1192 } | |
| 1193 targetsAndTries.add(node); | |
| 1194 visitStatement(node.body); | |
| 1195 targetsAndTries.removeLast(); | |
| 1196 if (node.update != null) { | |
| 1197 beginLabel(continueLabel); | |
| 1198 visitExpressionIgnoreResult(node.update); | |
| 1199 } | |
| 1200 addGoto(startLabel); | |
| 1201 beginLabel(afterLabel); | |
| 1202 } | |
| 1203 | |
| 1204 @override | |
| 1205 void visitForIn(js.ForIn node) { | |
| 1206 // The dart output currently never uses for-in loops. | |
| 1207 throw "Javascript for-in not implemented yet in the await transformation"; | |
| 1208 } | |
| 1209 | |
| 1210 @override | |
| 1211 void visitFunctionDeclaration(js.FunctionDeclaration node) { | |
| 1212 unsupported(node); | |
| 1213 } | |
| 1214 | |
| 1215 // Only used for code where `!shouldTransform(node)`. | |
| 1216 js.Block translateInBlock(js.Statement node) { | |
| 1217 assert(!shouldTransform(node)); | |
| 1218 List<js.Statement> oldBuffer = currentStatementBuffer; | |
| 1219 currentStatementBuffer = new List(); | |
| 1220 List<js.Statement> resultBuffer = currentStatementBuffer; | |
| 1221 visitStatement(node); | |
| 1222 currentStatementBuffer = oldBuffer; | |
| 1223 return new js.Block(resultBuffer); | |
| 1224 } | |
| 1225 | |
| 1226 @override | |
| 1227 void visitIf(js.If node) { | |
| 1228 if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) { | |
| 1229 withExpression(node.condition, (js.Expression condition) { | |
| 1230 addStatement(new js.If(condition, translateInBlock(node.then), | |
| 1231 translateInBlock(node.otherwise))); | |
| 1232 }, store: false); | |
| 1233 return; | |
| 1234 } | |
| 1235 int thenLabel = newLabel("then"); | |
| 1236 int joinLabel = newLabel("join"); | |
| 1237 int elseLabel = | |
| 1238 node.otherwise is js.EmptyStatement ? joinLabel : newLabel("else"); | |
| 1239 | |
| 1240 withExpression(node.condition, (js.Expression condition) { | |
| 1241 addExpressionStatement( | |
| 1242 new js.Assignment( | |
| 1243 new js.VariableUse(gotoName), | |
| 1244 new js.Conditional( | |
| 1245 condition, | |
| 1246 js.number(thenLabel), | |
| 1247 js.number(elseLabel)))); | |
| 1248 }, store: false); | |
| 1249 addBreak(); | |
| 1250 beginLabel(thenLabel); | |
| 1251 visitStatement(node.then); | |
| 1252 if (node.otherwise is! js.EmptyStatement) { | |
| 1253 addGoto(joinLabel); | |
| 1254 beginLabel(elseLabel); | |
| 1255 visitStatement(node.otherwise); | |
| 1256 } | |
| 1257 beginLabel(joinLabel); | |
| 1258 } | |
| 1259 | |
| 1260 @override | |
| 1261 visitInterpolatedExpression(js.InterpolatedExpression node) { | |
| 1262 return unsupported(node); | |
| 1263 } | |
| 1264 | |
| 1265 @override | |
| 1266 visitInterpolatedLiteral(js.InterpolatedLiteral node) => unsupported(node); | |
| 1267 | |
| 1268 @override | |
| 1269 visitInterpolatedParameter(js.InterpolatedParameter node) { | |
| 1270 return unsupported(node); | |
| 1271 } | |
| 1272 | |
| 1273 @override | |
| 1274 visitInterpolatedSelector(js.InterpolatedSelector node) { | |
| 1275 return unsupported(node); | |
| 1276 } | |
| 1277 | |
| 1278 @override | |
| 1279 visitInterpolatedStatement(js.InterpolatedStatement node) { | |
| 1280 return unsupported(node); | |
| 1281 } | |
| 1282 | |
| 1283 @override | |
| 1284 void visitLabeledStatement(js.LabeledStatement node) { | |
| 1285 if (!shouldTransform(node)) { | |
| 1286 addStatement( | |
| 1287 new js.LabeledStatement(node.label, translateInBlock(node.body))); | |
| 1288 return; | |
| 1289 } | |
| 1290 int breakLabel = newLabel("break ${node.label}"); | |
| 1291 int continueLabel = newLabel("continue ${node.label}"); | |
| 1292 breakLabels[node] = breakLabel; | |
| 1293 continueLabels[node] = continueLabel; | |
| 1294 | |
| 1295 beginLabel(continueLabel); | |
| 1296 targetsAndTries.add(node); | |
| 1297 visitStatement(node.body); | |
| 1298 targetsAndTries.removeLast(); | |
| 1299 beginLabel(breakLabel); | |
| 1300 } | |
| 1301 | |
| 1302 @override | |
| 1303 js.Expression visitLiteralBool(js.LiteralBool node) => node; | |
| 1304 | |
| 1305 @override | |
| 1306 visitLiteralExpression(js.LiteralExpression node) => unsupported(node); | |
| 1307 | |
| 1308 @override | |
| 1309 js.Expression visitLiteralNull(js.LiteralNull node) => node; | |
| 1310 | |
| 1311 @override | |
| 1312 js.Expression visitLiteralNumber(js.LiteralNumber node) => node; | |
| 1313 | |
| 1314 @override | |
| 1315 visitLiteralStatement(js.LiteralStatement node) => unsupported(node); | |
| 1316 | |
| 1317 @override | |
| 1318 js.Expression visitLiteralString(js.LiteralString node) => node; | |
| 1319 | |
| 1320 @override | |
| 1321 visitNamedFunction(js.NamedFunction node) { | |
| 1322 unsupported(node); | |
| 1323 } | |
| 1324 | |
| 1325 @override | |
| 1326 js.Expression visitNew(js.New node) { | |
| 1327 bool storeTarget = node.arguments.any(shouldTransform); | |
| 1328 return withExpression(node.target, (target) { | |
| 1329 return withExpressions(node.arguments, (List<js.Expression> arguments) { | |
| 1330 return new js.New(target, arguments); | |
| 1331 }); | |
| 1332 }, store: storeTarget); | |
| 1333 } | |
| 1334 | |
| 1335 @override | |
| 1336 js.Expression visitObjectInitializer(js.ObjectInitializer node) { | |
| 1337 return withExpressions( | |
| 1338 node.properties.map((js.Property property) => property.value).toList(), | |
| 1339 (List<js.Node> values) { | |
| 1340 List<js.Property> properties = new List.generate(values.length, (int i) { | |
| 1341 return new js.Property(node.properties[i].name, values[i]); | |
| 1342 }); | |
| 1343 return new js.ObjectInitializer(properties); | |
| 1344 }); | |
| 1345 } | |
| 1346 | |
| 1347 @override | |
| 1348 visitParameter(js.Parameter node) => unreachable(node); | |
| 1349 | |
| 1350 @override | |
| 1351 js.Expression visitPostfix(js.Postfix node) { | |
| 1352 if (node.op == "++" || node.op == "--") { | |
| 1353 js.Expression argument = node.argument; | |
| 1354 if (argument is js.VariableUse) { | |
| 1355 return new js.Postfix(node.op, argument); | |
| 1356 } else if (argument is js.PropertyAccess) { | |
| 1357 return withExpression2(argument.receiver, argument.selector, | |
| 1358 (receiver, selector) { | |
| 1359 return new js.Postfix( | |
| 1360 node.op, new js.PropertyAccess(receiver, selector)); | |
| 1361 }); | |
| 1362 } else { | |
| 1363 throw "Unexpected postfix ${node.op} " | |
| 1364 "operator argument ${node.argument}"; | |
| 1365 } | |
| 1366 } | |
| 1367 return withExpression(node.argument, | |
| 1368 (js.Expression argument) => new js.Postfix(node.op, argument), | |
| 1369 store: false); | |
| 1370 } | |
| 1371 | |
| 1372 @override | |
| 1373 js.Expression visitPrefix(js.Prefix node) { | |
| 1374 if (node.op == "++" || node.op == "--") { | |
| 1375 js.Expression argument = node.argument; | |
| 1376 if (argument is js.VariableUse) { | |
| 1377 return new js.Prefix(node.op, argument); | |
| 1378 } else if (argument is js.PropertyAccess) { | |
| 1379 return withExpression2(argument.receiver, argument.selector, | |
| 1380 (receiver, selector) { | |
| 1381 return new js.Prefix( | |
| 1382 node.op, new js.PropertyAccess(receiver, selector)); | |
| 1383 }); | |
| 1384 } else { | |
| 1385 throw "Unexpected prefix ${node.op} operator " | |
| 1386 "argument ${node.argument}"; | |
| 1387 } | |
| 1388 } | |
| 1389 return withExpression(node.argument, | |
| 1390 (js.Expression argument) => new js.Prefix(node.op, argument), | |
| 1391 store: false); | |
| 1392 } | |
| 1393 | |
| 1394 @override | |
| 1395 visitProgram(js.Program node) => unsupported(node); | |
| 1396 | |
| 1397 @override | |
| 1398 js.Property visitProperty(js.Property node) { | |
| 1399 return withExpression( | |
| 1400 node.value, (js.Expression value) => new js.Property(node.name, value), | |
| 1401 store: false); | |
| 1402 } | |
| 1403 | |
| 1404 @override | |
| 1405 js.Expression visitRegExpLiteral(js.RegExpLiteral node) => node; | |
| 1406 | |
| 1407 @override | |
| 1408 void visitReturn(js.Return node) { | |
| 1409 assert(node.value == null || !isSyncStar && !isAsyncStar); | |
| 1410 js.Node target = analysis.targets[node]; | |
| 1411 if (node.value != null) { | |
| 1412 withExpression(node.value, (js.Expression value) { | |
| 1413 addStatement(js.js.statement("$returnValueName = #", [value])); | |
| 1414 }, store: false); | |
| 1415 } | |
| 1416 translateJump(target, exitLabel); | |
| 1417 } | |
| 1418 | |
| 1419 @override | |
| 1420 void visitSwitch(js.Switch node) { | |
| 1421 if (!node.cases.any(shouldTransform)) { | |
| 1422 // If only the key has an await, translation can be simplified. | |
| 1423 bool oldInsideUntranslated = insideUntranslatedBreakable; | |
| 1424 insideUntranslatedBreakable = true; | |
| 1425 withExpression(node.key, (js.Expression key) { | |
| 1426 List<js.SwitchClause> cases = node.cases.map((js.SwitchClause clause) { | |
| 1427 if (clause is js.Case) { | |
| 1428 return new js.Case( | |
| 1429 clause.expression, translateInBlock(clause.body)); | |
| 1430 } else if (clause is js.Default) { | |
| 1431 return new js.Default(translateInBlock(clause.body)); | |
| 1432 } | |
| 1433 }).toList(); | |
| 1434 addStatement(new js.Switch(key, cases)); | |
| 1435 }, store: false); | |
| 1436 insideUntranslatedBreakable = oldInsideUntranslated; | |
| 1437 return; | |
| 1438 } | |
| 1439 int before = newLabel("switch"); | |
| 1440 int after = newLabel("after switch"); | |
| 1441 breakLabels[node] = after; | |
| 1442 | |
| 1443 beginLabel(before); | |
| 1444 List<int> labels = new List<int>(node.cases.length); | |
| 1445 | |
| 1446 bool anyCaseExpressionTransformed = node.cases.any( | |
| 1447 (js.SwitchClause x) => x is js.Case && shouldTransform(x.expression)); | |
| 1448 if (anyCaseExpressionTransformed) { | |
| 1449 int defaultIndex = null; // Null means no default was found. | |
| 1450 // If there is an await in one of the keys, a chain of ifs has to be used. | |
| 1451 | |
| 1452 withExpression(node.key, (js.Expression key) { | |
| 1453 int i = 0; | |
| 1454 for (js.SwitchClause clause in node.cases) { | |
| 1455 if (clause is js.Default) { | |
| 1456 // The goto for the default case is added after all non-default | |
| 1457 // clauses have been handled. | |
| 1458 defaultIndex = i; | |
| 1459 labels[i] = newLabel("default"); | |
| 1460 continue; | |
| 1461 } else if (clause is js.Case) { | |
| 1462 labels[i] = newLabel("case"); | |
| 1463 withExpression(clause.expression, (expression) { | |
| 1464 addStatement(new js.If.noElse( | |
| 1465 new js.Binary("===", key, expression), | |
| 1466 gotoAndBreak(labels[i]))); | |
| 1467 }, store: false); | |
| 1468 } | |
| 1469 i++; | |
| 1470 } | |
| 1471 }, store: true); | |
| 1472 | |
| 1473 if (defaultIndex == null) { | |
| 1474 addGoto(after); | |
| 1475 } else { | |
| 1476 addGoto(labels[defaultIndex]); | |
| 1477 } | |
| 1478 } else { | |
| 1479 bool hasDefault = false; | |
| 1480 int i = 0; | |
| 1481 List<js.SwitchClause> clauses = new List<js.SwitchClause>(); | |
| 1482 for (js.SwitchClause clause in node.cases) { | |
| 1483 if (clause is js.Case) { | |
| 1484 labels[i] = newLabel("case"); | |
| 1485 clauses.add(new js.Case(clause.expression, gotoAndBreak(labels[i]))); | |
| 1486 } else if (i is js.Default) { | |
| 1487 labels[i] = newLabel("default"); | |
| 1488 clauses.add(new js.Default(gotoAndBreak(labels[i]))); | |
| 1489 hasDefault = true; | |
| 1490 } else { | |
| 1491 diagnosticListener.internalError( | |
| 1492 spannable, "Unknown clause type $clause"); | |
| 1493 } | |
| 1494 i++; | |
| 1495 } | |
| 1496 withExpression(node.key, (js.Expression key) { | |
| 1497 addStatement(new js.Switch(key, clauses)); | |
| 1498 }, store: false); | |
| 1499 if (!hasDefault) { | |
| 1500 addGoto(after); | |
| 1501 } | |
| 1502 } | |
| 1503 | |
| 1504 targetsAndTries.add(node); | |
| 1505 for (int i = 0; i < labels.length; i++) { | |
| 1506 beginLabel(labels[i]); | |
| 1507 visitStatement(node.cases[i].body); | |
| 1508 } | |
| 1509 beginLabel(after); | |
| 1510 targetsAndTries.removeLast(); | |
| 1511 } | |
| 1512 | |
| 1513 @override | |
| 1514 js.Expression visitThis(js.This node) { | |
| 1515 return new js.VariableUse(selfName); | |
| 1516 } | |
| 1517 | |
| 1518 @override | |
| 1519 void visitThrow(js.Throw node) { | |
| 1520 withExpression(node.expression, (js.Expression expression) { | |
| 1521 addStatement(new js.Throw(expression)); | |
| 1522 }, store: false); | |
| 1523 } | |
| 1524 | |
| 1525 setErrorHandler() { | |
| 1526 addExpressionStatement(new js.Assignment( | |
| 1527 new js.VariableUse(handlerName), currentErrorHandler)); | |
| 1528 } | |
| 1529 | |
| 1530 @override | |
| 1531 void visitTry(js.Try node) { | |
| 1532 if (!shouldTransform(node)) { | |
| 1533 js.Block body = translateInBlock(node.body); | |
| 1534 js.Catch catchPart = (node.catchPart == null) | |
| 1535 ? null | |
| 1536 : new js.Catch(node.catchPart.declaration, | |
| 1537 translateInBlock(node.catchPart.body)); | |
| 1538 js.Block finallyPart = (node.finallyPart == null) | |
| 1539 ? null | |
| 1540 : translateInBlock(node.finallyPart); | |
| 1541 addStatement(new js.Try(body, catchPart, finallyPart)); | |
| 1542 return; | |
| 1543 } | |
| 1544 hasTryBlocks = true; | |
| 1545 int handlerLabel = newLabel("catch"); | |
| 1546 int finallyLabel = newLabel("finally"); | |
| 1547 int afterFinallyLabel = newLabel("after finally"); | |
| 1548 errorHandlerLabels.add(handlerLabel); | |
| 1549 // Set the error handler here. It must be cleared on every path out; | |
| 1550 // normal and error exit. | |
| 1551 setErrorHandler(); | |
| 1552 if (node.finallyPart != null) { | |
| 1553 finallyLabels[node] = finallyLabel; | |
| 1554 targetsAndTries.add(node); | |
| 1555 } | |
| 1556 visitStatement(node.body); | |
| 1557 errorHandlerLabels.removeLast(); | |
| 1558 addStatement( | |
| 1559 js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)])); | |
| 1560 if (node.finallyPart == null) { | |
| 1561 setErrorHandler(); | |
| 1562 addGoto(afterFinallyLabel); | |
| 1563 } else { | |
| 1564 // The handler is set as the first thing in the finally block. | |
| 1565 addGoto(finallyLabel); | |
| 1566 } | |
| 1567 beginLabel(handlerLabel); | |
| 1568 if (node.catchPart != null) { | |
| 1569 setErrorHandler(); | |
| 1570 // The catch declaration name can shadow outer variables, so a fresh name | |
| 1571 // is needed to avoid collisions. See Ecma 262, 3rd edition, | |
| 1572 // section 12.14. | |
| 1573 String errorRename = freshName(node.catchPart.declaration.name); | |
| 1574 localVariables.add(new js.VariableDeclaration(errorRename)); | |
| 1575 variableRenamings | |
| 1576 .add(new Pair(node.catchPart.declaration.name, errorRename)); | |
| 1577 addExpressionStatement(new js.Assignment( | |
| 1578 new js.VariableUse(errorRename), new js.VariableUse(resultName))); | |
| 1579 visitStatement(node.catchPart.body); | |
| 1580 variableRenamings.removeLast(); | |
| 1581 } | |
| 1582 if (node.finallyPart != null) { | |
| 1583 targetsAndTries.removeLast(); | |
| 1584 setErrorHandler(); | |
| 1585 // This belongs to the catch-part, but is only needed if there is a | |
| 1586 // `finally`. Therefore it is in this branch. | |
| 1587 // This is needed even if there is no explicit catch-branch, because | |
| 1588 // if an exception is raised the finally part has to be run. | |
| 1589 addStatement( | |
| 1590 js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)])); | |
| 1591 beginLabel(finallyLabel); | |
| 1592 setErrorHandler(); | |
| 1593 visitStatement(node.finallyPart); | |
| 1594 addStatement(new js.Comment("// goto the next finally handler")); | |
| 1595 addStatement(js.js.statement("$gotoName = $nextName.pop();")); | |
| 1596 addBreak(); | |
| 1597 } | |
| 1598 beginLabel(afterFinallyLabel); | |
| 1599 } | |
| 1600 | |
| 1601 @override | |
| 1602 visitVariableDeclaration(js.VariableDeclaration node) { | |
| 1603 unreachable(node); | |
| 1604 } | |
| 1605 | |
| 1606 @override | |
| 1607 void visitVariableDeclarationList(js.VariableDeclarationList node) { | |
| 1608 // Declaration of local variables is hoisted outside the helper but the | |
| 1609 // initialization is done here. | |
| 1610 for (js.VariableInitialization initialization in node.declarations) { | |
| 1611 js.VariableDeclaration declaration = initialization.declaration; | |
| 1612 localVariables.add(declaration); | |
| 1613 if (initialization.value != null) { | |
| 1614 withExpression(initialization.value, (js.Expression value) { | |
| 1615 addStatement(new js.ExpressionStatement( | |
| 1616 new js.Assignment(declaration, value))); | |
| 1617 }, store: false); | |
| 1618 } | |
| 1619 } | |
| 1620 } | |
| 1621 | |
| 1622 @override | |
| 1623 void visitVariableInitialization(js.VariableInitialization node) { | |
| 1624 unreachable(node); | |
| 1625 } | |
| 1626 | |
| 1627 @override | |
| 1628 js.Expression visitVariableUse(js.VariableUse node) { | |
| 1629 Pair<String, String> renaming = variableRenamings.lastWhere( | |
| 1630 (Pair renaming) => renaming.a == node.name, orElse: () => null); | |
| 1631 if (renaming == null) return node; | |
| 1632 return new js.VariableUse(renaming.b); | |
| 1633 } | |
| 1634 | |
| 1635 @override | |
| 1636 void visitWhile(js.While node) { | |
| 1637 if (!shouldTransform(node)) { | |
| 1638 bool oldInsideUntranslated = insideUntranslatedBreakable; | |
| 1639 insideUntranslatedBreakable = true; | |
| 1640 withExpression(node.condition, (js.Expression condition) { | |
| 1641 addStatement(new js.While(condition, translateInBlock(node.body))); | |
| 1642 }, store: false); | |
| 1643 insideUntranslatedBreakable = oldInsideUntranslated; | |
| 1644 return; | |
| 1645 } | |
| 1646 int continueLabel = newLabel("while condition"); | |
| 1647 continueLabels[node] = continueLabel; | |
| 1648 beginLabel(continueLabel); | |
| 1649 | |
| 1650 int afterLabel = newLabel("after while"); | |
| 1651 breakLabels[node] = afterLabel; | |
| 1652 js.Expression condition = node.condition; | |
| 1653 // If the condition is `true`, a test is not needed. | |
| 1654 if (!(condition is js.LiteralBool && condition.value == true)) { | |
| 1655 withExpression(node.condition, (js.Expression condition) { | |
| 1656 addStatement(new js.If.noElse( | |
| 1657 new js.Prefix("!", condition), gotoAndBreak(afterLabel))); | |
| 1658 }, store: false); | |
| 1659 } | |
| 1660 targetsAndTries.add(node); | |
| 1661 visitStatement(node.body); | |
| 1662 targetsAndTries.removeLast(); | |
| 1663 addGoto(continueLabel); | |
| 1664 beginLabel(afterLabel); | |
| 1665 } | |
| 1666 | |
| 1667 /// Translates a yield/yield* in an sync*. | |
| 1668 /// | |
| 1669 /// `yield` in a sync* function just returns [value]. | |
| 1670 /// `yield*` wraps [value] in a [yieldStarExpression] and returns it. | |
| 1671 void addSyncYield(js.DartYield node, js.Expression expression) { | |
| 1672 assert(isSyncStar); | |
| 1673 if (node.hasStar) { | |
| 1674 addStatement( | |
| 1675 new js.Return(new js.Call(yieldStarExpression, [expression]))); | |
| 1676 } else { | |
| 1677 addStatement(new js.Return(expression)); | |
| 1678 } | |
| 1679 } | |
| 1680 | |
| 1681 /// Translates a yield/yield* in an async* function. | |
| 1682 /// | |
| 1683 /// yield/yield* in an async* function is translated much like the `await` is | |
| 1684 /// translated in [visitAwait], only the object is wrapped in a | |
| 1685 /// [yieldExpression]/[yieldStarExpression] to let [streamHelper] distinguish. | |
| 1686 /// | |
| 1687 /// Because there is no Future that can fail (as there is in await) null is | |
| 1688 /// passed as the errorCallback. | |
| 1689 void addAsyncYield(js.DartYield node, js.Expression expression) { | |
| 1690 assert(isAsyncStar); | |
| 1691 // Find all the finally blocks that should be performed if the stream is | |
| 1692 // canceled during the yield. | |
| 1693 // At the bottom of the stack is the return label. | |
| 1694 List<int> enclosingFinallyLabels = <int>[exitLabel]; | |
| 1695 enclosingFinallyLabels.addAll(targetsAndTries | |
| 1696 .where((js.Node node) => node is js.Try) | |
| 1697 .map((js.Try node) => finallyLabels[node])); | |
| 1698 int destinationOnCancel = enclosingFinallyLabels.removeLast(); | |
| 1699 js.ArrayInitializer finallyListInitializer = new js.ArrayInitializer( | |
| 1700 enclosingFinallyLabels.map(js.number).toList()); | |
| 1701 addStatement(js.js.statement(""" | |
| 1702 return #streamHelper(#yieldExpression(#expression), | |
| 1703 $helperName, $controllerName, function () { | |
| 1704 if (#notEmptyFinallyList) | |
| 1705 $nextName = #finallyList; | |
| 1706 $gotoName = #destinationOnCancel; | |
| 1707 $helperName(); | |
| 1708 });""", { | |
| 1709 "streamHelper": streamHelper, | |
| 1710 "yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression, | |
| 1711 "expression": expression, | |
| 1712 "notEmptyFinallyList": enclosingFinallyLabels.isNotEmpty, | |
| 1713 "finallyList": finallyListInitializer, | |
| 1714 "destinationOnCancel": js.number(destinationOnCancel) | |
| 1715 })); | |
| 1716 } | |
| 1717 | |
| 1718 @override | |
| 1719 void visitDartYield(js.DartYield node) { | |
| 1720 assert(isSyncStar || isAsyncStar); | |
| 1721 int label = newLabel("after yield"); | |
| 1722 // Don't do a break here for the goto, but instead a return in either | |
| 1723 // addSynYield or addAsyncYield. | |
| 1724 withExpression(node.expression, (js.Expression expression) { | |
| 1725 addStatement(setGotoVariable(label)); | |
| 1726 if (isSyncStar) { | |
| 1727 addSyncYield(node, expression); | |
| 1728 } else { | |
| 1729 addAsyncYield(node, expression); | |
| 1730 } | |
| 1731 }, store: false); | |
| 1732 beginLabel(label); | |
| 1733 } | |
| 1734 } | |
| 1735 | |
| 1736 /// Finds out | |
| 1737 /// | |
| 1738 /// - which expressions have yield or await nested in them. | |
| 1739 /// - targets of jumps | |
| 1740 /// - a set of used names. | |
| 1741 /// - if any [This]-expressions are used. | |
| 1742 class PreTranslationAnalysis extends js.NodeVisitor<bool> { | |
| 1743 Set<js.Node> hasAwaitOrYield = new Set<js.Node>(); | |
| 1744 | |
| 1745 Map<js.Node, js.Node> targets = new Map<js.Node, js.Node>(); | |
| 1746 List<js.Node> loopsAndSwitches = new List<js.Node>(); | |
| 1747 List<js.LabeledStatement> labelledStatements = | |
| 1748 new List<js.LabeledStatement>(); | |
| 1749 Set<String> usedNames = new Set<String>(); | |
| 1750 | |
| 1751 bool hasExplicitReturns = false; | |
| 1752 | |
| 1753 bool hasThis = false; | |
| 1754 | |
| 1755 // The function currently being analyzed. | |
| 1756 js.Fun currentFunction; | |
| 1757 | |
| 1758 // For error messages. | |
| 1759 final Function unsupported; | |
| 1760 | |
| 1761 PreTranslationAnalysis(void this.unsupported(js.Node node)); | |
| 1762 | |
| 1763 bool visit(js.Node node) { | |
| 1764 bool containsAwait = node.accept(this); | |
| 1765 if (containsAwait) { | |
| 1766 hasAwaitOrYield.add(node); | |
| 1767 } | |
| 1768 return containsAwait; | |
| 1769 } | |
| 1770 | |
| 1771 analyze(js.Fun node) { | |
| 1772 currentFunction = node; | |
| 1773 node.params.forEach(visit); | |
| 1774 visit(node.body); | |
| 1775 } | |
| 1776 | |
| 1777 @override | |
| 1778 bool visitAccess(js.PropertyAccess node) { | |
| 1779 bool receiver = visit(node.receiver); | |
| 1780 bool selector = visit(node.selector); | |
| 1781 return receiver || selector; | |
| 1782 } | |
| 1783 | |
| 1784 @override | |
| 1785 bool visitArrayHole(js.ArrayHole node) { | |
| 1786 return false; | |
| 1787 } | |
| 1788 | |
| 1789 @override | |
| 1790 bool visitArrayInitializer(js.ArrayInitializer node) { | |
| 1791 bool containsAwait = false; | |
| 1792 for (js.Expression element in node.elements) { | |
| 1793 if (visit(element)) containsAwait = true; | |
| 1794 } | |
| 1795 return containsAwait; | |
| 1796 } | |
| 1797 | |
| 1798 @override | |
| 1799 bool visitAssignment(js.Assignment node) { | |
| 1800 bool leftHandSide = visit(node.leftHandSide); | |
| 1801 bool value = (node.value == null) ? false : visit(node.value); | |
| 1802 return leftHandSide || value; | |
| 1803 } | |
| 1804 | |
| 1805 @override | |
| 1806 bool visitAwait(js.Await node) { | |
| 1807 visit(node.expression); | |
| 1808 return true; | |
| 1809 } | |
| 1810 | |
| 1811 @override | |
| 1812 bool visitBinary(js.Binary node) { | |
| 1813 bool left = visit(node.left); | |
| 1814 bool right = visit(node.right); | |
| 1815 return left || right; | |
| 1816 } | |
| 1817 | |
| 1818 @override | |
| 1819 bool visitBlob(js.Blob node) { | |
| 1820 return false; | |
| 1821 } | |
| 1822 | |
| 1823 @override | |
| 1824 bool visitBlock(js.Block node) { | |
| 1825 bool containsAwait = false; | |
| 1826 for (js.Statement statement in node.statements) { | |
| 1827 if (visit(statement)) containsAwait = true; | |
| 1828 } | |
| 1829 return containsAwait; | |
| 1830 } | |
| 1831 | |
| 1832 @override | |
| 1833 bool visitBreak(js.Break node) { | |
| 1834 if (node.targetLabel != null) { | |
| 1835 targets[node] = labelledStatements.lastWhere( | |
| 1836 (js.LabeledStatement statement) { | |
| 1837 return statement.label == node.targetLabel; | |
| 1838 }); | |
| 1839 } else { | |
| 1840 targets[node] = loopsAndSwitches.last; | |
| 1841 } | |
| 1842 return false; | |
| 1843 } | |
| 1844 | |
| 1845 @override | |
| 1846 bool visitCall(js.Call node) { | |
| 1847 bool containsAwait = visit(node.target); | |
| 1848 for (js.Expression argument in node.arguments) { | |
| 1849 if (visit(argument)) containsAwait = true; | |
| 1850 } | |
| 1851 return containsAwait; | |
| 1852 } | |
| 1853 | |
| 1854 @override | |
| 1855 bool visitCase(js.Case node) { | |
| 1856 bool expression = visit(node.expression); | |
| 1857 bool body = visit(node.body); | |
| 1858 return expression || body; | |
| 1859 } | |
| 1860 | |
| 1861 @override | |
| 1862 bool visitCatch(js.Catch node) { | |
| 1863 bool declaration = visit(node.declaration); | |
| 1864 bool body = visit(node.body); | |
| 1865 return declaration || body; | |
| 1866 } | |
| 1867 | |
| 1868 @override | |
| 1869 bool visitComment(js.Comment node) { | |
| 1870 return false; | |
| 1871 } | |
| 1872 | |
| 1873 @override | |
| 1874 bool visitConditional(js.Conditional node) { | |
| 1875 bool condition = visit(node.condition); | |
| 1876 bool then = visit(node.then); | |
| 1877 bool otherwise = visit(node.otherwise); | |
| 1878 return condition || then || otherwise; | |
| 1879 } | |
| 1880 | |
| 1881 @override | |
| 1882 bool visitContinue(js.Continue node) { | |
| 1883 if (node.targetLabel != null) { | |
| 1884 targets[node] = labelledStatements.lastWhere( | |
| 1885 (js.LabeledStatement stm) => stm.label == node.targetLabel); | |
| 1886 } else { | |
| 1887 targets[node] = | |
| 1888 loopsAndSwitches.lastWhere((js.Node node) => node is! js.Switch); | |
| 1889 } | |
| 1890 assert(() { | |
| 1891 js.Node target = targets[node]; | |
| 1892 return target is js.Loop || | |
| 1893 (target is js.LabeledStatement && target.body is js.Loop); | |
| 1894 }); | |
| 1895 return false; | |
| 1896 } | |
| 1897 | |
| 1898 @override | |
| 1899 bool visitDefault(js.Default node) { | |
| 1900 return visit(node.body); | |
| 1901 } | |
| 1902 | |
| 1903 @override | |
| 1904 bool visitDo(js.Do node) { | |
| 1905 loopsAndSwitches.add(node); | |
| 1906 bool body = visit(node.body); | |
| 1907 bool condition = visit(node.condition); | |
| 1908 loopsAndSwitches.removeLast(); | |
| 1909 return body || condition; | |
| 1910 } | |
| 1911 | |
| 1912 @override | |
| 1913 bool visitEmptyStatement(js.EmptyStatement node) { | |
| 1914 return false; | |
| 1915 } | |
| 1916 | |
| 1917 @override | |
| 1918 bool visitExpressionStatement(js.ExpressionStatement node) { | |
| 1919 return visit(node.expression); | |
| 1920 } | |
| 1921 | |
| 1922 @override | |
| 1923 bool visitFor(js.For node) { | |
| 1924 bool init = (node.init == null) ? false : visit(node.init); | |
| 1925 bool condition = (node.condition == null) ? false : visit(node.condition); | |
| 1926 bool update = (node.update == null) ? false : visit(node.update); | |
| 1927 loopsAndSwitches.add(node); | |
| 1928 bool body = visit(node.body); | |
| 1929 loopsAndSwitches.removeLast(); | |
| 1930 return init || condition || update || body; | |
| 1931 } | |
| 1932 | |
| 1933 @override | |
| 1934 bool visitForIn(js.ForIn node) { | |
| 1935 bool object = visit(node.object); | |
| 1936 loopsAndSwitches.add(node); | |
| 1937 bool body = visit(node.body); | |
| 1938 loopsAndSwitches.removeLast(); | |
| 1939 return object || body; | |
| 1940 } | |
| 1941 | |
| 1942 @override | |
| 1943 bool visitFun(js.Fun node) { | |
| 1944 return false; | |
| 1945 } | |
| 1946 | |
| 1947 @override | |
| 1948 bool visitFunctionDeclaration(js.FunctionDeclaration node) { | |
| 1949 return false; | |
| 1950 } | |
| 1951 | |
| 1952 @override | |
| 1953 bool visitIf(js.If node) { | |
| 1954 bool condition = visit(node.condition); | |
| 1955 bool then = visit(node.then); | |
| 1956 bool otherwise = visit(node.otherwise); | |
| 1957 return condition || then || otherwise; | |
| 1958 } | |
| 1959 | |
| 1960 @override | |
| 1961 bool visitInterpolatedExpression(js.InterpolatedExpression node) { | |
| 1962 return unsupported(node); | |
| 1963 } | |
| 1964 | |
| 1965 @override | |
| 1966 bool visitInterpolatedLiteral(js.InterpolatedLiteral node) { | |
| 1967 return unsupported(node); | |
| 1968 } | |
| 1969 | |
| 1970 @override | |
| 1971 bool visitInterpolatedParameter(js.InterpolatedParameter node) { | |
| 1972 return unsupported(node); | |
| 1973 } | |
| 1974 | |
| 1975 @override | |
| 1976 bool visitInterpolatedSelector(js.InterpolatedSelector node) { | |
| 1977 return unsupported(node); | |
| 1978 } | |
| 1979 | |
| 1980 @override | |
| 1981 bool visitInterpolatedStatement(js.InterpolatedStatement node) { | |
| 1982 return unsupported(node); | |
| 1983 } | |
| 1984 | |
| 1985 @override | |
| 1986 bool visitLabeledStatement(js.LabeledStatement node) { | |
| 1987 usedNames.add(node.label); | |
| 1988 labelledStatements.add(node); | |
| 1989 bool containsAwait = visit(node.body); | |
| 1990 labelledStatements.removeLast(); | |
| 1991 return containsAwait; | |
| 1992 } | |
| 1993 | |
| 1994 @override | |
| 1995 bool visitLiteralBool(js.LiteralBool node) { | |
| 1996 return false; | |
| 1997 } | |
| 1998 | |
| 1999 @override | |
| 2000 bool visitLiteralExpression(js.LiteralExpression node) { | |
| 2001 return unsupported(node); | |
| 2002 } | |
| 2003 | |
| 2004 @override | |
| 2005 bool visitLiteralNull(js.LiteralNull node) { | |
| 2006 return false; | |
| 2007 } | |
| 2008 | |
| 2009 @override | |
| 2010 bool visitLiteralNumber(js.LiteralNumber node) { | |
| 2011 return false; | |
| 2012 } | |
| 2013 | |
| 2014 @override | |
| 2015 bool visitLiteralStatement(js.LiteralStatement node) { | |
| 2016 return unsupported(node); | |
| 2017 } | |
| 2018 | |
| 2019 @override | |
| 2020 bool visitLiteralString(js.LiteralString node) { | |
| 2021 return false; | |
| 2022 } | |
| 2023 | |
| 2024 @override | |
| 2025 bool visitNamedFunction(js.NamedFunction node) { | |
| 2026 return false; | |
| 2027 } | |
| 2028 | |
| 2029 @override | |
| 2030 bool visitNew(js.New node) { | |
| 2031 return visitCall(node); | |
| 2032 } | |
| 2033 | |
| 2034 @override | |
| 2035 bool visitObjectInitializer(js.ObjectInitializer node) { | |
| 2036 bool containsAwait = false; | |
| 2037 for (js.Property property in node.properties) { | |
| 2038 if (visit(property)) containsAwait = true; | |
| 2039 } | |
| 2040 return containsAwait; | |
| 2041 } | |
| 2042 | |
| 2043 @override | |
| 2044 bool visitParameter(js.Parameter node) { | |
| 2045 usedNames.add(node.name); | |
| 2046 return false; | |
| 2047 } | |
| 2048 | |
| 2049 @override | |
| 2050 bool visitPostfix(js.Postfix node) { | |
| 2051 return visit(node.argument); | |
| 2052 } | |
| 2053 | |
| 2054 @override | |
| 2055 bool visitPrefix(js.Prefix node) { | |
| 2056 return visit(node.argument); | |
| 2057 } | |
| 2058 | |
| 2059 @override | |
| 2060 bool visitProgram(js.Program node) { | |
| 2061 throw "Unexpected"; | |
| 2062 } | |
| 2063 | |
| 2064 @override | |
| 2065 bool visitProperty(js.Property node) { | |
| 2066 return visit(node.value); | |
| 2067 } | |
| 2068 | |
| 2069 @override | |
| 2070 bool visitRegExpLiteral(js.RegExpLiteral node) { | |
| 2071 return false; | |
| 2072 } | |
| 2073 | |
| 2074 @override | |
| 2075 bool visitReturn(js.Return node) { | |
| 2076 hasExplicitReturns = true; | |
| 2077 targets[node] = currentFunction; | |
| 2078 if (node.value == null) return false; | |
| 2079 return visit(node.value); | |
| 2080 } | |
| 2081 | |
| 2082 @override | |
| 2083 bool visitSwitch(js.Switch node) { | |
| 2084 loopsAndSwitches.add(node); | |
| 2085 bool result = visit(node.key); | |
| 2086 for (js.SwitchClause clause in node.cases) { | |
| 2087 if (visit(clause)) result = true; | |
| 2088 } | |
| 2089 loopsAndSwitches.removeLast(); | |
| 2090 return result; | |
| 2091 } | |
| 2092 | |
| 2093 @override | |
| 2094 bool visitThis(js.This node) { | |
| 2095 hasThis = true; | |
| 2096 return false; | |
| 2097 } | |
| 2098 | |
| 2099 @override | |
| 2100 bool visitThrow(js.Throw node) { | |
| 2101 return visit(node.expression); | |
| 2102 } | |
| 2103 | |
| 2104 @override | |
| 2105 bool visitTry(js.Try node) { | |
| 2106 bool body = visit(node.body); | |
| 2107 bool catchPart = (node.catchPart == null) ? false : visit(node.catchPart); | |
| 2108 bool finallyPart = | |
| 2109 (node.finallyPart == null) ? false : visit(node.finallyPart); | |
| 2110 return body || catchPart || finallyPart; | |
| 2111 } | |
| 2112 | |
| 2113 @override | |
| 2114 bool visitVariableDeclaration(js.VariableDeclaration node) { | |
| 2115 usedNames.add(node.name); | |
| 2116 return false; | |
| 2117 } | |
| 2118 | |
| 2119 @override | |
| 2120 bool visitVariableDeclarationList(js.VariableDeclarationList node) { | |
| 2121 bool result = false; | |
| 2122 for (js.VariableInitialization init in node.declarations) { | |
| 2123 if (visit(init)) result = true; | |
| 2124 } | |
| 2125 return result; | |
| 2126 } | |
| 2127 | |
| 2128 @override | |
| 2129 bool visitVariableInitialization(js.VariableInitialization node) { | |
| 2130 return visitAssignment(node); | |
| 2131 } | |
| 2132 | |
| 2133 @override | |
| 2134 bool visitVariableUse(js.VariableUse node) { | |
| 2135 usedNames.add(node.name); | |
| 2136 return false; | |
| 2137 } | |
| 2138 | |
| 2139 @override | |
| 2140 bool visitWhile(js.While node) { | |
| 2141 loopsAndSwitches.add(node); | |
| 2142 bool condition = visit(node.condition); | |
| 2143 bool body = visit(node.body); | |
| 2144 loopsAndSwitches.removeLast(); | |
| 2145 return condition || body; | |
| 2146 } | |
| 2147 | |
| 2148 @override | |
| 2149 bool visitDartYield(js.DartYield node) { | |
| 2150 visit(node.expression); | |
| 2151 return true; | |
| 2152 } | |
| 2153 } | |
| OLD | NEW |