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