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 |