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 |