Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(175)

Side by Side Diff: pkg/compiler/lib/src/js/rewrite_async.dart

Issue 839323003: Implementation of async-await transformation on js ast. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Rebase + fix issue with this-rebinding in sync*. Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/compiler/lib/src/js/printer.dart ('k') | pkg/compiler/lib/src/js/template.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « pkg/compiler/lib/src/js/printer.dart ('k') | pkg/compiler/lib/src/js/template.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698