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

Unified 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: Address comments. Fix setting of the handler in finallies... 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 side-by-side diff with in-line comments
Download patch
Index: pkg/compiler/lib/src/js/rewrite_async.dart
diff --git a/pkg/compiler/lib/src/js/rewrite_async.dart b/pkg/compiler/lib/src/js/rewrite_async.dart
new file mode 100644
index 0000000000000000000000000000000000000000..89762dd82359129508727f8b2d3b0bd788195b75
--- /dev/null
+++ b/pkg/compiler/lib/src/js/rewrite_async.dart
@@ -0,0 +1,2161 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library rewrite_async;
+
+// TODO(sigurdm): Avoid using variables in templates. It could blow up memory
+// use.
+
+import "dart:math" show max;
+import 'dart:collection';
+
+import "js.dart" show
+ ArrayHole,
+ ArrayInitializer,
+ Assignment,
+ AsyncModifier,
+ Await,
+ Binary,
+ Blob,
+ Block,
+ Break,
+ Call,
+ Case,
+ Catch,
+ Comment,
+ Conditional,
+ Continue,
+ DartYield,
+ Default,
+ Do,
+ EmptyStatement,
+ Expression,
+ ExpressionStatement,
+ For,
+ ForIn,
+ Fun,
+ FunctionDeclaration,
+ If,
+ InterpolatedExpression,
+ InterpolatedLiteral,
+ InterpolatedParameter,
+ InterpolatedSelector,
+ InterpolatedStatement,
+ LabeledStatement,
+ Literal,
+ LiteralBool,
+ LiteralExpression,
+ LiteralNull,
+ LiteralNumber,
+ LiteralStatement,
+ LiteralString,
+ Loop,
+ NamedFunction,
+ New,
+ Node,
+ NodeVisitor,
+ ObjectInitializer,
+ Parameter,
+ Postfix,
+ Prefix,
+ Program,
+ Property,
+ PropertyAccess,
+ RegExpLiteral,
+ Return,
+ Statement,
+ Switch,
+ SwitchClause,
+ This,
+ Throw,
+ Try,
+ VariableDeclaration,
+ VariableDeclarationList,
+ VariableInitialization,
+ VariableUse,
+ While,
+ js,
+ number;
+
+import '../util/util.dart';
+import '../dart2jslib.dart' show DiagnosticListener;
+
+import "../helpers/helpers.dart";
+
+/// Rewrites a js Fun with async/sync*/async* functions and await and yield
+/// (with dart-like semantics) to an equivalent function without these.
+/// await-for is not handled and must be rewritten before. (Currently handled
+/// in ssa/builder.dart).
+///
+/// When generating the input to this, special care must be taken that
+/// parameters to sync* functions that are mutated in the body must be boxed.
+/// (Currently handled in closure.dart).
+///
+/// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation.
+class AsyncRewriter extends NodeVisitor {
+
+ // Local variables are hoisted to the top of the function, so they are
+ // collected here.
+ List<VariableDeclaration> localVariables = new List<VariableDeclaration>();
+
+ Map<Node, int> continueLabels = new Map<Node, int>();
+ Map<Node, int> breakLabels = new Map<Node, int>();
+ Map<Node, int> finallyLabels = new Map<Node, int>();
+ int returnLabel;
+
+ // A stack of all enclosing jump targets (including the function for
+ // representing the target of a return, and all enclosing try-blocks that have
+ // finally part, this way ensuring all the finally blocks between a jump and
+ // its target are run before the jump.
+ List<Node> targetsAndTries = new List<Node>();
+
+ List<int> continueStack = new List<int>();
+ List<int> breakStack = new List<int>();
+ List<int> returnStack = new List<int>();
+
+ List<Pair<String, String>> variableRenamings =
+ new List<Pair<String, String>>();
+
+ PreTranslationAnalysis analysis;
+
+ List<int> errorHandlerLabels = new List<int>();
+
+ final Function safeVariableName;
+
+ // All the <x>Name variables are names of Javascript variables used in the
+ // transformed code.
+
+ /// Contains the result of an awaited expression, or a conditional or
+ /// lazy boolean operator.
+ ///
+ /// It is a parameter to the [helperName] function.
floitsch 2015/02/05 20:17:22 Doesn't really help in understanding what it's for
sigurdm 2015/02/06 14:26:32 Tried to give an example.
floitsch 2015/02/06 14:58:43 Never mind. I was again confused by the similarity
sigurdm 2015/02/06 15:21:20 Acknowledged.
+ String resultName;
floitsch 2015/02/05 20:17:22 Future CL: consider: js.Expression result = unca
sigurdm 2015/02/06 14:26:33 I started doing something like this, but one probl
+
+ /// The name of the inner function that is scheduled to do each await/yield,
+ /// and called to do a new iteation for sync*.
floitsch 2015/02/05 20:17:21 iteration
sigurdm 2015/02/06 14:26:32 Done.
+ String helperName;
+
+ /// The Completer that will finish an async function.
floitsch 2015/02/05 20:17:21 What if we are in a sync* or async* ?
sigurdm 2015/02/06 14:26:33 Done.
+ String completerName;
+
+ /// The StreamController that controls an async* function.
floitsch 2015/02/05 20:17:21 What if we are in a sync* or async function?
sigurdm 2015/02/06 14:26:33 Done.
+ String controllerName;
+
+
+ /// Used to simulate a goto.
+ ///
+ /// To "goto" a label, the label is assigned to this
+ /// variable, and break out of the switch to take another iteration in the
+ /// while loop. See [addGoto]
+ String gotoName;
+
+ /// The label of the current error handler.
+ String handlerName;
+
+ /// Current caught error.
+ String errorName;
+
+ /// A stack of labels of finally blocks to visit, and the label to go to after
+ /// the last.
+ String nextName;
+
+ /// The current returned value (a finally block may overwrite it).
+ String returnValueName;
+
+ /// The label of the outer loop.
+ ///
+ /// Used if there are untransformed loops containing break or continues to
+ /// targets outside the loop.
+ String outerLabelName;
+
+ /// If javascript `this` is used, it is accessed via this variable, in the
+ /// [helperName] function.
+ String selfName;
+
+ // These expressions are hooks for communicating with the runtime.
+
+ /// The function called by an async function to simulate an await or return.
+ ///
+ /// For an await it is called with:
+ ///
+ /// - The value to await
+ /// - The [helperName]
+ /// - The [completerName]
+ /// - A Javascript function that will take the program to the right error
floitsch 2015/02/05 20:17:22 that takes... It's not just taking the program to
sigurdm 2015/02/06 14:26:32 Done.
+ /// handler in case the future completes with an error.
+ ///
+ /// For a return it is called with:
+ ///
+ /// - The value to complete the completer with.
+ /// - null
+ /// - The [completerName]
+ /// - null.
+ final Expression thenHelper;
+
+ /// The function called by an async* function to simulate an await, yield or
+ /// yield*.
+ ///
+ /// For an await/yield/yield* it is called with:
+ ///
+ /// - The value to await/yieldExpression(value to yield)/
+ /// yieldStarExpression(stream to yield)
+ /// - The [helperName]
+ /// - The [completerName]
floitsch 2015/02/05 20:17:20 controllerName ?
sigurdm 2015/02/06 14:26:33 Done.
+ /// - A Javascript function that will take the program to the right error
floitsch 2015/02/05 20:17:21 ditto.
sigurdm 2015/02/06 14:26:32 Done.
+ /// handler in case the future completes with an error.
+ ///
+ /// For a return it is called with:
+ ///
+ /// - null
+ /// - null
+ /// - The [controllerName]
+ /// - null.
+ final Expression streamHelper;
+
+ /// How to initialize the [completerName] variable.
floitsch 2015/02/05 20:17:22 It's not a "how". The constructor that is used to
sigurdm 2015/02/06 14:26:32 Done.
+ ///
+ /// Specific to async methods.
+ final Expression newCompleter;
+
+ /// How to initialize the [controllerName] variable.
floitsch 2015/02/05 20:17:21 ditto.
sigurdm 2015/02/06 14:26:32 Done.
+ ///
+ /// Specific to async* methods.
+ final Expression newController;
floitsch 2015/02/05 20:17:21 It's inconsistent that "newCompleter" and newItera
sigurdm 2015/02/06 14:26:33 true. Made them all constructors.
+
+ /// Creates an Iterable for a sync* method. Called with [helperName]
floitsch 2015/02/05 20:17:22 Missing trailing ".".
sigurdm 2015/02/06 14:26:33 Done.
+ final Expression newIterable;
+
+ /// Creates a marker showing that iteration is over.
floitsch 2015/02/05 20:17:21 A JS Expression that creates ...
sigurdm 2015/02/06 14:26:32 Done.
+ ///
+ /// Called without arguments.
+ final Expression endOfIteration;
+
+ /// Creates a marker indicating a 'yield' statement.
floitsch 2015/02/05 20:17:21 ditto.
sigurdm 2015/02/06 14:26:33 Done.
+ ///
+ /// Called with the value to yield.
+ final Expression yieldExpression;
+
+ /// Creates a marker indication a 'yield*' statement.
floitsch 2015/02/05 20:17:21 ditto.
sigurdm 2015/02/06 14:26:34 Done.
+ ///
+ /// Called with the stream to yield from.
+ final Expression yieldStarExpression;
+
+ final DiagnosticListener diagnosticListener;
+ // For error reporting only.
+ Spannable get spannable {
+ return (_spannable == null) ? NO_LOCATION_SPANNABLE : _spannable;
+ }
+
+ Spannable _spannable;
+
+ int _currentLabel = 0;
+
+ // The highest temporary variable index currently in use.
+ int currentTempVarIndex = 0;
+ // The highest temporary variable index ever in use in this function.
+ int tempVarHighWaterMark = 0;
+ Map<int, Expression> tempVarNames = new Map<int, Expression>();
+
+ AsyncModifier async;
+
+ bool get isSync => async == const AsyncModifier.sync();
+ bool get isAsync => async == const AsyncModifier.async();
+ bool get isSyncStar => async == const AsyncModifier.syncStar();
+ bool get isAsyncStar => async == const AsyncModifier.asyncStar();
+
+ AsyncRewriter(this.diagnosticListener,
+ spannable,
+ {this.thenHelper,
+ this.streamHelper,
+ this.newCompleter,
+ this.newController,
+ this.endOfIteration,
+ this.newIterable,
+ this.yieldExpression,
+ this.yieldStarExpression,
+ this.safeVariableName})
+ : _spannable = spannable;
+
+ /// Main entry point.
+ /// Rewrites a sync*/async/async* function to an equivalent normal function.
+ ///
+ /// [spannable] can be passed to have a location for error messages.
+ Fun rewrite(Fun node, [Spannable spannable]) {
+ _spannable = spannable;
+
+ async = node.asyncModifier;
+ assert(!isSync);
+
+ analysis = new PreTranslationAnalysis(unsupported);
+ analysis.analyze(node);
+
+ // To avoid name collisions with existing names, the fresh names are
+ // generated after the analysis.
+ resultName = freshName("result");
+ completerName = freshName("completer");
+ controllerName = freshName("controller");
+ helperName = freshName("helper");
+ gotoName = freshName("goto");
+ handlerName = freshName("handler");
+ errorName = freshName("error");
+ nextName = freshName("next");
+ returnValueName = freshName("returnValue");
+ outerLabelName = freshName("outer");
+ selfName = freshName("self");
+
+ return node.accept(this);
+ }
+
+ Expression get currentErrorHandler {
+ return errorHandlerLabels.isEmpty ?
+ new LiteralNull() :
+ number(errorHandlerLabels.last);
+ }
+
+ int allocateTempVar() {
+ assert(tempVarHighWaterMark >= currentTempVarIndex);
+ currentTempVarIndex++;
+ tempVarHighWaterMark = max(currentTempVarIndex, tempVarHighWaterMark);
+ return currentTempVarIndex;
+ }
+
+ VariableUse useTempVar(int i) {
+ return tempVarNames.putIfAbsent(i, () =>
+ new VariableUse(freshName("temp$i")));
+ }
+
+ /// Generates a variable name with [safeVariableName] based on [originalName]
+ /// with a suffix to guarantee it does not collide with already used names.
+ String freshName(String originalName) {
+ String safeName = safeVariableName(originalName);
+ String result = safeName;
+ int counter = 1;
+ while (analysis.usedNames.contains(result)) {
+ result = "$safeName$counter";
+ ++counter;
+ }
+ analysis.usedNames.add(result);
+ return result;
+ }
+
+ /// All the pieces are collected in this map, to create a switch with a case
+ /// for each label.
+ ///
+ /// The order is important, therefore the type is explicitly LinkedHashMap.
+ LinkedHashMap<int, List<Statement>> labelledParts =
+ new LinkedHashMap<int, List<Statement>>();
+
+ /// Description of each label for readability of the non-minified output.
+ Map<int, String> labelComments = new Map<int, String>();
+
+ /// True if the function has any try blocks containing await.
+ bool hasTryBlocks = false;
+
+ /// True if any return, break or continue passes through a finally.
+ bool hasJumpThroughFinally = false;
+
+ /// True if the traversion currently is inside a loop or switch for which
+ /// [shouldTransform] is false.
+ bool insideUntranslatedBreakable = false;
+
+ /// True if a label is used to break to an outer switch-statement.
+ bool hasJumpThoughOuterLabel = false;
+
+ /// Buffer for collecting translated statements belonging to the same switch
+ /// case.
+ List<Statement> currentStatementBuffer;
+
+ // Labels will become cases in the big switch expression, and `goto label`
+ // is expressed by assigning to the switch key [gotoName] and breaking out of
+ // the switch.
+
+ int newLabel([String comment]) {
+ int result = _currentLabel;
+ _currentLabel++;
+ if (comment != null) {
+ labelComments[result] = comment;
+ }
+ return result;
+ }
+
+ /// Begins outputting statements to a new buffer with label [label].
+ ///
+ /// Each buffer ends up as its own case part in the big state-switch.
+ void beginLabel(int label) {
+ assert(!labelledParts.containsKey(label));
+ currentStatementBuffer = new List<Statement>();
+ labelledParts[label] = currentStatementBuffer;
+ addStatement(new Comment(labelComments[label]));
+ }
+
+ /// Returns a statement assigning to the variable named [gotoName].
+ /// This should be followed by a break for the goto to be executed. Use
+ /// [gotoWithBreak] or [addGoto] for this.
+ Statement setGotoVariable(int label) {
+ return new ExpressionStatement(
+ new Assignment(new VariableUse(gotoName), number(label)));
+ }
+
+ /// Return a block that has a goto to [label] including the break.
floitsch 2015/02/05 20:17:22 Returns
sigurdm 2015/02/06 14:26:33 Done.
+ ///
+ /// Also inserts a comment describing the label if available.
+ Block gotoAndBreak(int label) {
+ List<Statement> statements = new List<Statement>();
+ if (labelComments.containsKey(label)) {
+ statements.add(new Comment("goto ${labelComments[label]}"));
+ }
+ statements.add(setGotoVariable(label));
+ if (insideUntranslatedBreakable) {
+ hasJumpThoughOuterLabel = true;
+ statements.add(new Break(outerLabelName));
+ } else {
+ statements.add(new Break(null));
+ }
+ return new Block(statements);
+ }
+
+ /// Add a goto to [label] including the break.
floitsch 2015/02/05 20:17:22 Adds
sigurdm 2015/02/06 14:26:33 Done.
+ ///
+ /// Also inserts a comment describing the label if available.
+ void addGoto(int label) {
+ if (labelComments.containsKey(label)) {
+ addStatement(new Comment("goto ${labelComments[label]}"));
+ }
+ addStatement(setGotoVariable(label));
+
+ addBreak();
+ }
+
+ void addStatement(Statement node) {
+ currentStatementBuffer.add(node);
+ }
+
+ void addExpressionStatement(Expression node) {
+ addStatement(new ExpressionStatement(node));
+ }
+
+ /// True if there is an await or yield in [node] or some subexpression.
+ bool shouldTransform(Node node) {
+ return analysis.hasAwaitOrYield.contains(node);
+ }
+
+ void unsupported(Node node) {
+ throw new UnsupportedError(
+ "Node $node cannot be transformed by the await-sync transformer");
+ }
+
+ void unreachable(Node node) {
+ diagnosticListener.internalError(spannable,
+ "Internal error, trying to visit $node");
+ }
+
+ visitStatement(Statement node) {
+ node.accept(this);
+ }
+
+ /// Visits [node] to ensure its sideeffects are performed, but throwing away
+ /// the result.
+ ///
+ /// If the return value of visiting [node] is an expression guaranteed to have
+ /// no side effect, it is dropped.
+ void visitExpressionIgnoreResult(Expression node) {
+ Expression result = node.accept(this);
+ if (!(result is Literal || result is VariableUse)) {
+ addExpressionStatement(result);
+ }
+ }
+
+ Expression visitExpression(Expression node) {
+ return node.accept(this);
+ }
+
+ /// Emits an assignment of [result] to a temporary variable unless it can
+ /// be guaranteed that evaluating [result] has no side-effect, and has
+ /// constant value.
+ ///
+ /// Returns the temporary variable or the result itself.
+ ///
+ /// For example:
+ ///
+ /// - _storeIfNecessary(someLiteral) returns someLiteral.
+ /// - _storeIfNecessary(someVariable)
+ /// inserts: var tempX = someVariable
+ /// returns: tempX
+ /// where tempX is a fresh temporary variable.
+ Expression _storeIfNecessary(Expression result) {
+ // Note that RegExes, ArrayInitializer and ObjectInitializer are not
+ // [Literal]s.
+ if (result is Literal) return result;
+ Expression tempVar = useTempVar(allocateTempVar());
+ addExpressionStatement(new Assignment(tempVar, result));
+ return tempVar;
+ }
+
+ withExpression(Expression node, fn(Expression result), {bool store}) {
+ int oldTempVarIndex = currentTempVarIndex;
+ Expression visited = visitExpression(node);
+ if (store) {
+ visited = _storeIfNecessary(visited);
+ }
+ var result = fn(visited);
+ currentTempVarIndex = oldTempVarIndex;
+ return result;
+ }
+
+ /// Calls [fn] with the value of evaluating [node1] and [node2].
+ ///
+ /// If `shouldTransform(node2)` the first expression is stored in a temporary
+ /// variable.
+ ///
+ /// This is because node1 must be evaluated before visiting node2,
floitsch 2015/02/05 20:17:21 I'm not sure I fully understand the comment. My be
sigurdm 2015/02/06 14:26:33 Better. Thanks.
+ /// because the evaluation of an await or yield cannot be expressed as
+ /// an expression, visiting node2 it will output statements that
+ /// might have an influence on the value of node1.
+ withExpression2(Expression node1, Expression node2, fn(
+ Expression result1,
+ Expression result2)) {
+ int oldTempVarIndex = currentTempVarIndex;
+ Expression r1 = visitExpression(node1);
+ if (shouldTransform(node2)) {
+ r1 = _storeIfNecessary(r1);
+ }
+ Expression r2 = visitExpression(node2);
+ var result = fn(r1, r2);
+ currentTempVarIndex = oldTempVarIndex;
+ return result;
+ }
+
+ /// Calls [fn] with the value of evaluating all [nodes].
+ ///
+ /// All results before the last node where `shouldTransform(node)` are stored
+ /// in temporary variables.
+ ///
+ /// See more explanation on [withExpression2].
+ ///
+ /// If any of the nodes are null, they are ignored, and a null is passed to
+ /// [fn] in that place.
+ withExpressions(List<Expression> nodes, fn(List<Expression> results)) {
+ int oldTempVarIndex = currentTempVarIndex;
+ // Find last occurence of a 'transform' expression in [nodes].
+ // All expressions before that must be stored in temp-vars.
+ int lastTransformIndex = 0;
+ for (int i = nodes.length - 1; i >= 0; --i) {
+ if (nodes[i] == null) continue;
+ if (shouldTransform(nodes[i])) {
+ lastTransformIndex = i;
+ break;
+ }
+ }
+ List<Node> visited = nodes.take(lastTransformIndex).map((Node node) {
+ return node == null
+ ? null
+ : _storeIfNecessary(visitExpression(node));
+ }).toList();
+ visited.addAll(nodes.skip(lastTransformIndex).map((Node node) {
+ return node == null
+ ? null
+ : visitExpression(node);
+ }));
+ var result = fn(visited);
+ currentTempVarIndex = oldTempVarIndex;
+ return result;
+ }
+
+ /// Emits the return block that all returns should jump to (after going
+ /// through all the enclosing finally blocks) the jump to here is made in
floitsch 2015/02/05 20:17:22 blocks). The jump ...
sigurdm 2015/02/06 14:26:32 Done.
+ /// [visitReturn].
+ ///
+ /// Returning from an async method calls the [thenHelper] with the result.
+ /// (the result might have been stored in [returnValueName] by some finally
+ /// block).
+ ///
+ /// Returning from a sync* function returns an [endOfIteration] marker.
+ ///
+ /// Returning from an async* function calls the [streamHelper] with an
+ /// [endOfIteration] marker.
+ void addReturn() {
floitsch 2015/02/05 20:17:22 addFooter ? addExit ? addFunctionExit ? (`addRetu
sigurdm 2015/02/06 14:26:32 I go with addExit.
+ if (analysis.hasExplicitReturns || isAsyncStar) {
+ beginLabel(returnLabel);
+ } else {
+ addStatement(new Comment("implicit return"));
+ }
+ switch (async) {
+ case const AsyncModifier.async():
+ String returnValue = analysis.hasExplicitReturns
+ ? returnValueName
+ : "null";
+ addStatement(js.statement(
+ "return #thenHelper($returnValue, null, $completerName, null)",
+ {"thenHelper": thenHelper}));
+ break;
+ case const AsyncModifier.syncStar():
+ addStatement(new Return(new Call(endOfIteration, [])));
+ break;
+ case const AsyncModifier.asyncStar():
+ addStatement(js.statement(
+ "return #streamHelper(null, null, $controllerName, null)",
+ {"streamHelper": streamHelper}));
+ break;
+ default:
+ diagnosticListener.internalError(spannable,
+ "Internal error, unexpected asyncmodifier $async");
+ }
+ }
+
+ /// The initial call to [thenHelper]/[streamHelper].
+ ///
+ /// There is no value to await/yield, so the first argument is `null` and
+ /// also the errorCallback is `null`.
+ ///
+ /// Returns the [Future]/[Stream] coming from [completerName]/
+ /// [controllerName].
+ Statement generateInitializer() {
+ if (isAsync) {
+ return js.statement(
+ "return #thenHelper(null, $helperName, $completerName, null);",
+ {"thenHelper": thenHelper});
+ } else if (isAsyncStar) {
+ return js.statement(
+ "return #streamHelper(null, $helperName, $controllerName, null);",
+ {"streamHelper": streamHelper});
+ } else {
+ throw diagnosticListener.internalError(spannable,
+ "Unexpected asyncModifier: $async");
+ }
+ }
+
+ /// Rewrites an async/sync*/async* function to a normal Javascript function.
+ ///
+ /// The control flow is flattened by simulating 'goto' using a switch in a
+ /// loop and a state variable [gotoName] inside a helper-function that can be
+ /// called back by [thenHelper]/[streamHelper]/the [Iterator].
+ ///
+ /// Local variables are hoisted outside the helper.
+ ///
+ /// Awaits and yields in async/async* are simulated by calling
floitsch 2015/02/05 20:17:21 are translated to a call to ... Similarly remove
floitsch 2015/02/05 20:17:21 Maybe: Awaits in async/async* are translated to co
sigurdm 2015/02/06 14:26:33 Done.
sigurdm 2015/02/06 14:26:33 Better, thanks.
+ /// [thenHelper]/[streamHelper].
+ ///
+ /// Yield/yield* in a sync* function is simulated by returning the value.
floitsch 2015/02/05 20:17:21 Yield/yield* in a sync* function is translated to
sigurdm 2015/02/06 14:26:32 Done.
+ ///
+ /// Simplified examples (not the exact translation, but intended to show the
+ /// ideas):
+ ///
+ /// function (x, y, z) async {
+ /// var p = await foo();
+ /// return bar(p);
+ /// }
+ ///
+ /// Becomes:
+ ///
+ /// function(x, y, z) {
+ /// var goto = 0, returnValue, completer = new Completer(), p;
+ /// function helper(result) {
+ /// while (true) {
+ /// switch (goto) {
+ /// case 0:
+ /// goto = 1
floitsch 2015/02/05 20:17:21 Add comment, that "goto = 1" remembers the locatio
sigurdm 2015/02/06 14:26:33 Done.
+ /// return thenHelper(foo(), helper, completer, null);
+ /// case 1:
+ /// p = result;
+ /// returnValue = bar(p);
+ /// goto = 2;
+ /// break;
+ /// case 2:
+ /// return thenHelper(returnValue, null, completer, null)
+ /// }
+ /// }
+ /// return thenHelper(null, helper, completer, null);
+ /// }
+ /// }
+ ///
+ /// Try/catch is implemented by maintaining [handlerName] to contain the label
+ /// of the current handler. The switch is nested inside a try/catch that will
+ /// redirect the flow to the current handler.
+ ///
+ /// Finally is implemented by letting the translation of each flow-path that
floitsch 2015/02/05 20:17:21 A `finally` clause is compiled similar to normal c
floitsch 2015/02/05 20:17:22 `Finally`
sigurdm 2015/02/06 14:26:32 Acknowledged.
sigurdm 2015/02/06 14:26:32 Done.
+ /// will enter a finally set up the variable [nextName] with a stack of
+ /// finally blocks to exit via.
+ ///
+ /// function (x, y, z) async {
+ /// try {
+ /// try {
+ /// throw "error";
+ /// } finally {
+ /// finalize1();
+ /// }
+ /// } catch (e) {
+ /// handle(e);
+ /// } finally {
+ /// finalize2();
+ /// }
+ /// }
+ ///
+ /// Translates into (besides the fact that structures not containing
+ /// await/yield/yield* are left intact):
+ ///
+ /// function(x, y, z) {
+ /// var goto = 0;
+ /// var returnValue;
+ /// var completer = new Completer();
+ /// var handler = null;
+ /// var p;
+ /// function helper(result) {
floitsch 2015/02/05 20:17:23 comment that "result" could be either the value of
sigurdm 2015/02/06 14:26:34 Done.
+ /// while (true) {
+ /// try {
+ /// switch (goto) {
+ /// case 0:
+ /// handler = 4; // The outer catch-handler
+ /// handler = 1; // The inner (implicit) catch-handler
+ /// throw "error";
+ /// next = [3];
floitsch 2015/02/05 20:17:20 // After the finally (2) continue normally after t
sigurdm 2015/02/06 14:26:33 Done.
+ /// goto = 2;
+ /// break;
+ /// case 1: // catch handler for inner try (implicit).
floitsch 2015/02/05 20:17:20 (implicit) catch handler for the inner try.
sigurdm 2015/02/06 14:26:33 Done.
+ /// next = [3]; // destination after the finally.
floitsch 2015/02/05 20:17:22 why is this not "next = [4]" ? If we enter this ex
sigurdm 2015/02/06 14:26:33 Good point, this is a bug indeed.
+ /// // fall-though to the finally handler.
+ /// case 2: // finally for inner try
+ /// handler = 4; // catch-handler for outer try.
+ /// finalize1();
+ /// goto = next.pop();
+ /// break;
+ /// case 3: // exiting inner try.
+ /// next = [6];
+ /// goto = 5; // finally handler for outer try.
floitsch 2015/02/05 20:17:22 Why is there no break?
sigurdm 2015/02/06 14:26:32 It is a mistake.
+ /// case 4: // catch handler for outer try.
+ /// e = result;
+ /// handle(e);
+ /// goto = 4;
floitsch 2015/02/05 20:17:21 why is this here? It looks unnecessary anyway. Eve
sigurdm 2015/02/06 14:26:32 Another mistake
+ /// case 5: // finally handler for outer try.
+ /// handler = null;
+ /// finalize2();
+ /// goto = next.pop();
+ /// break;
+ /// case 6: // Exiting outer try.
+ /// case 7: // return
+ /// return thenHelper(returnValue, null, completer, null);
+ /// }
+ /// } catch (error) {
+ /// result = error;
+ /// goto = handler;
+ /// }
+ /// }
+ /// return thenHelper(null, helper, completer, null);
+ /// }
+ /// }
+ ///
+ @override
+ Expression visitFun(Fun node) {
+ if (isSync) return node;
+
+ beginLabel(newLabel("Function start"));
+ // AsyncStar needs a returnlabel for its handling of cancelation. See
+ // [visitDartYield].
+ returnLabel = analysis.hasExplicitReturns || isAsyncStar
+ ? newLabel("Return")
+ : null;
+ Statement body = node.body;
+ targetsAndTries.add(node);
+ visitStatement(body);
+ targetsAndTries.removeLast();
+ addReturn();
+
+ List<SwitchClause> clauses = labelledParts.keys.map((label) {
+ return new Case(
+ number(label),
+ new Block(labelledParts[label]));
+ }).toList();
+ Statement helperBody = new Switch(new VariableUse(gotoName), clauses);
+ if (hasJumpThoughOuterLabel) {
+ helperBody = js.statement("$outerLabelName: #", [helperBody]);
+ }
+ if (hasTryBlocks) {
+ helperBody = js.statement("""
+ try {
+ #body
+ } catch ($errorName){
+ if ($handlerName === null)
+ throw $errorName;
+ $resultName = $errorName;
+ $gotoName = $handlerName;
+ }""", {"body": helperBody});
+ }
+ List<VariableInitialization> inits = <VariableInitialization>[];
+
+ VariableInitialization makeInit(String name, Expression initValue) {
+ return new VariableInitialization(new VariableDeclaration(name),
+ initValue);
+ }
+
+ inits.add(makeInit(gotoName, number(0)));
+ if (isAsync) {
+ inits.add(makeInit(completerName, new New(newCompleter, [])));
+ } else if (isAsyncStar) {
+ inits.add(makeInit(controllerName,
+ new Call(newController,
+ [new VariableUse(helperName)])));
+ }
+ if (hasTryBlocks) {
+ inits.add(makeInit(handlerName, new LiteralNull()));
+ }
+ if (hasJumpThroughFinally) {
+ inits.add(makeInit(nextName, null));
+ }
+ if (analysis.hasExplicitReturns && isAsync) {
+ inits.add(makeInit(returnValueName, null));
+ }
+ if (analysis.hasThis) {
+ inits.add(makeInit(selfName, new This()));
+ }
+ inits.addAll(localVariables.map(
+ (VariableDeclaration decl) => new VariableInitialization(decl, null)));
+ inits.addAll(new Iterable.generate(tempVarHighWaterMark,
+ (int i) => makeInit(useTempVar(i + 1).name, null)));
+ VariableDeclarationList varDecl = new VariableDeclarationList(inits);
+ if (isSyncStar) {
+ return js("""
+ function (#params) {
+ return new #newIterable(function () {
+ #varDecl;
+ return function $helperName($resultName) {
+ while (true)
+ #helperBody;
+ };
+ });
+ }
+ """, {"params": node.params,
+ "helperBody": helperBody,
+ "varDecl": varDecl,
+ "newIterable": newIterable});
+ }
+ return js("""
+ function (#params) {
+ #varDecl;
+ function $helperName($resultName) {
+ while (true)
+ #helperBody;
+ }
+ #init;
+ }""", {"params": node.params,
+ "helperBody": helperBody,
+ "varDecl": varDecl,
+ "init": generateInitializer()});
+ }
+
+ @override
+ Expression visitAccess(PropertyAccess node) {
+ return withExpression2(
+ node.receiver,
+ node.selector,
+ (receiver, selector) => new PropertyAccess(receiver, selector));
+ }
+
+ @override
+ Expression visitArrayHole(ArrayHole node) {
+ return node;
+ }
+
+ @override
+ Expression visitArrayInitializer(ArrayInitializer node) {
+ return withExpressions(node.elements, (elements) {
+ return new ArrayInitializer(elements);
+ });
+ }
+
+ @override
+ Expression visitAssignment(Assignment node) {
+ if (!shouldTransform(node)) {
+ return new Assignment.compound(visitExpression(node.leftHandSide),
+ node.op,
+ visitExpression(node.value));
+ }
+ Expression leftHandSide = node.leftHandSide;
+ if (leftHandSide is VariableUse) {
+ return withExpression(node.value, (Expression value) {
+ return new Assignment(leftHandSide, value);
+ }, store: false);
+ } else if (leftHandSide is PropertyAccess) {
+ return withExpressions(
+ [leftHandSide.receiver, leftHandSide.selector, node.value],
+ (evaluated) {
+ return new Assignment.compound(
+ new PropertyAccess(evaluated[0], evaluated[1]),
+ node.op,
+ evaluated[2]);
+ });
+ } else {
+ throw "Unexpected assignment left hand side $leftHandSide";
+ }
+ }
+
+ /// An await is translated to a call to [thenHelper]/[streamHelper].
+ ///
+ /// See the comments of [visitFun] for an example.
+ @override
+ Expression visitAwait(Await node) {
+ assert(isAsync || isAsyncStar);
+ int afterAwait = newLabel("returning from await.");
+ withExpression(node.expression, (Expression value) {
+ addStatement(setGotoVariable(afterAwait));
+ Expression errorCallback = errorHandlerLabels.isEmpty
+ ? new LiteralNull()
+ : js("""
+ function($errorName) {
+ $gotoName = #currentHandler;
+ $helperName($errorName);
+ }""", {"currentHandler": currentErrorHandler});
+
+ addStatement(js.statement("""
+ return #thenHelper(#value,
+ $helperName,
+ ${isAsync ? completerName : controllerName},
+ #errorCallback);
+ """, {"thenHelper": isAsync ? thenHelper : streamHelper,
+ "value": value,
+ "errorCallback": errorCallback}));
+ }, store: false);
+ beginLabel(afterAwait);
+ return new VariableUse(resultName);
+ }
+
+ /// Checks if [node] is the variable named [resultName].
+ ///
+ /// [resultName] is used to hold the result of a transformed computation
+ /// for example the result of awaiting, or the result of a conditional or
+ /// short-circuiting expression.
+ /// If the subexpression of some transformed node already is transformed and
+ /// visiting it returns [resultName], it is not redundantly assigned to itself
+ /// again.
+ bool isResult(Expression node) {
+ return node is VariableUse && node.name == resultName;
+ }
+
+ @override
+ Expression visitBinary(Binary node) {
+ if (shouldTransform(node.right) &&
+ (node.op == "||" || node.op == "&&")) {
+ int thenLabel = newLabel("then");
+ int joinLabel = newLabel("join");
+ withExpression(node.left, (Expression left) {
+ Statement assignLeft = isResult(left)
+ ? new Block.empty()
+ : new ExpressionStatement(
+ new Assignment(new VariableUse(resultName), left));
+ if (node.op == "||") {
+ addStatement(new If(left, gotoAndBreak(thenLabel), assignLeft));
+ } else {
+ assert(node.op == "&&");
+ addStatement(new If(left, assignLeft, gotoAndBreak(thenLabel)));
+ }
+ }, store: true);
+ addGoto(joinLabel);
+ beginLabel(thenLabel);
+ withExpression(node.right, (Expression value) {
+ if (!isResult(value)) {
+ addExpressionStatement(
+ new Assignment(new VariableUse(resultName), value));
+ }
+ }, store: false);
+ beginLabel(joinLabel);
+ return new VariableUse(resultName);
+ }
+
+ return withExpression2(
+ node.left,
+ node.right,
+ (left, right) => new Binary(node.op, left, right));
+ }
+
+ @override
+ Expression visitBlob(Blob node) {
+ return node;
+ }
+
+ @override
+ void visitBlock(Block node) {
+ for (Statement statement in node.statements) {
+ visitStatement(statement);
+ }
+ }
+
+ @override
+ void visitBreak(Break node) {
+ Node target = analysis.targets[node];
+ if (!shouldTransform(target)) {
+ addStatement(node);
+ return;
+ }
+ translateJump(target, breakLabels[target]);
+ }
+
+ @override
+ Expression visitCall(Call node) {
+ bool storeTarget = node.arguments.any(shouldTransform);
+ return withExpression(node.target, (target) {
+ return withExpressions(node.arguments, (List<Expression> arguments) {
+ return new Call(target, arguments);
+ });
+ }, store: storeTarget);
+ }
+
+ @override
+ void visitCase(Case node) {
+ return unreachable(node);
+ }
+
+ @override
+ void visitCatch(Catch node) {
+ return unreachable(node);
+ }
+
+ @override
+ void visitComment(Comment node) {
+ addStatement(node);
+ }
+
+ @override
+ Expression visitConditional(Conditional node) {
+ if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) {
+ return withExpression(node.condition, (Expression condition) {
+ return new Conditional(condition, node.then, node.otherwise);
+ });
+ }
+ int thenLabel = newLabel("then");
+ int joinLabel = newLabel("join");
+ int elseLabel = newLabel("else");
+ withExpression(node.condition, (Expression condition) {
+ addExpressionStatement(
+ new Assignment(
+ new VariableUse(gotoName),
+ new Conditional(
+ condition,
+ number(thenLabel),
+ number(elseLabel))));
+ }, store: false);
+ addBreak();
+ beginLabel(thenLabel);
+ withExpression(node.then, (Expression value) {
+ if (!isResult(value)) {
+ addExpressionStatement(
+ new Assignment(new VariableUse(resultName), value));
+ }
+ }, store: false);
+ addGoto(joinLabel);
+ beginLabel(elseLabel);
+ withExpression(node.otherwise, (Expression value) {
+ if (!isResult(value)) {
+ addExpressionStatement(
+ new Assignment(new VariableUse(resultName), value));
+ }
+ }, store: false);
+ beginLabel(joinLabel);
+ return new VariableUse(resultName);
+ }
+
+ @override
+ void visitContinue(Continue node) {
+ Node target = analysis.targets[node];
+ if (!shouldTransform(target)) {
+ addStatement(node);
+ return;
+ }
+ translateJump(target, continueLabels[target]);
+ }
+
+ /// Emits a break statement that exits the big switch statement.
+ void addBreak() {
+ if (insideUntranslatedBreakable) {
+ hasJumpThoughOuterLabel = true;
+ addStatement(new Break(outerLabelName));
+ } else {
+ addStatement(new Break(null));
+ }
+ }
+
+ /// Common code for handling break, continue, return.
+ ///
+ /// It is necessary to run all nesting finally-handlers between the jump and
+ /// the target. For that [nextName] is used as a stack of places to go.
floitsch 2015/02/05 20:17:21 Maybe reference visitFun.
sigurdm 2015/02/06 14:26:32 Done.
+ void translateJump(Node target, int targetLabel) {
+ // Compute a stack of all the 'finally' nodes that must be visited before
+ // the jump.
+ // The bottom of the stack is the label where the jump goes to.
+ List<int> jumpStack = new List<int>();
+ for (Node node in targetsAndTries.reversed) {
+ if (node is Try) {
+ assert(node.finallyPart != null);
+ jumpStack.add(finallyLabels[node]);
+ } else if (node == target) {
+ jumpStack.add(targetLabel);
+ break;
+ }
+ // Ignore other nodes.
+ }
+ jumpStack = jumpStack.reversed.toList();
+ // As the program jumps directly to the top of the stack, it is taken off
+ // now.
+ int firstTarget = jumpStack.removeLast();
+ if (jumpStack.isNotEmpty) {
+ hasJumpThroughFinally = true;
+ Expression jsJumpStack = new ArrayInitializer(
+ jumpStack.map((int label) => number(label))
+ .toList());
+ addStatement(js.statement("$nextName = #", [jsJumpStack]));
+ }
+ addGoto(firstTarget);
+ }
+
+ @override
+ void visitDefault(Default node) => unreachable(node);
+
+ @override
+ void visitDo(Do node) {
+ if (!shouldTransform(node)) {
+ bool oldInsideUntranslatedBreakable = insideUntranslatedBreakable;
+ insideUntranslatedBreakable = true;
+ withExpression(node.condition, (Expression condition) {
+ addStatement(new Do(translateInBlock(node.body), condition));
+ }, store: false);
+ insideUntranslatedBreakable = oldInsideUntranslatedBreakable;
+ return;
+ }
+ int startLabel = newLabel("do body");
+
+ int continueLabel = newLabel("do condition");
+ continueLabels[node] = continueLabel;
+
+ int afterLabel = newLabel("after do");
+ breakLabels[node] = afterLabel;
+
+ beginLabel(startLabel);
+
+ targetsAndTries.add(node);
+ visitStatement(node.body);
+ targetsAndTries.removeLast();
+
+ beginLabel(continueLabel);
+ withExpression(node.condition, (Expression condition) {
+ addStatement(new If.noElse(condition,
+ gotoAndBreak(startLabel)));
+ }, store: false);
+ beginLabel(afterLabel);
+ }
+
+ @override
+ void visitEmptyStatement(EmptyStatement node) {
+ addStatement(node);
+ }
+
+ void visitExpressionInStatementContext(Expression node) {
+ if (node is VariableDeclarationList) {
+ // Treat VariableDeclarationList as a statement.
+ visitVariableDeclarationList(node);
+ } else {
+ visitExpressionIgnoreResult(node);
+ }
+ }
+
+ @override
+ void visitExpressionStatement(ExpressionStatement node) {
+ visitExpressionInStatementContext(node.expression);
+ }
+
+ @override
+ void visitFor(For node) {
+ if (!shouldTransform(node) ) {
+ bool oldInsideUntranslated = insideUntranslatedBreakable;
+ insideUntranslatedBreakable = true;
+ // Note that node.init, node.condition, node.update all can be null, but
+ // withExpressions handles that.
+ withExpressions([node.init, node.condition, node.update],
+ (List<Expression> transformed) {
+ addStatement(new For(transformed[0],
+ transformed[1],
+ transformed[2],
+ translateInBlock(node.body)));
+ });
+ insideUntranslatedBreakable = oldInsideUntranslated;
+ return;
+ }
+
+ if (node.init != null) {
+ visitExpressionInStatementContext(node.init);
+ }
+ int startLabel = newLabel("for condition");
+ // If there is no update, continuing the loop is the same as going to the
+ // start.
+ int continueLabel = (node.update == null)
+ ? startLabel
+ : newLabel("for update");
+ continueLabels[node] = continueLabel;
+ int afterLabel = newLabel("after for");
+ breakLabels[node] = afterLabel;
+ beginLabel(startLabel);
+ Expression condition = node.condition;
+ if (condition == null ||
+ (condition is LiteralBool && condition.value == true)) {
+ // No condition is the same as true. So the check is not needed.
floitsch 2015/02/05 20:17:20 I'm not sure I understand the comment. Remove?
sigurdm 2015/02/06 14:26:33 Done.
+ addStatement(new Comment("trivial condition"));
+ } else {
+ withExpression(condition, (Expression condition) {
+ addStatement(new If.noElse(new Prefix("!", condition),
+ gotoAndBreak(afterLabel)));
+ }, store: false);
+ }
+ targetsAndTries.add(node);
+ visitStatement(node.body);
+ targetsAndTries.removeLast();
+ if (node.update != null) {
+ beginLabel(continueLabel);
+ visitExpressionIgnoreResult(node.update);
+ }
+ addGoto(startLabel);
+ beginLabel(afterLabel);
+ }
+
+ @override
+ void visitForIn(ForIn node) {
+ // The dart output currently never uses for-in loops.
+ throw "Javascript for-in not implemented yet in the await transformation";
+ }
+
+ @override
+ void visitFunctionDeclaration(FunctionDeclaration node) {
+ unsupported(node);
+ }
+
+ // Only used for code where `!shouldTransform(node)`.
+ Block translateInBlock(Statement node) {
+ assert(!shouldTransform(node));
+ List<Statement> oldBuffer = currentStatementBuffer;
+ currentStatementBuffer = new List();
+ List<Statement> resultBuffer = currentStatementBuffer;
+ visitStatement(node);
+ currentStatementBuffer = oldBuffer;
+ return new Block(resultBuffer);
+ }
+
+ @override
+ void visitIf(If node) {
+ if (!shouldTransform(node.then) && !shouldTransform(node.otherwise)) {
+ withExpression(node.condition, (Expression condition) {
+ addStatement(
+ new If(
+ condition,
+ translateInBlock(node.then),
+ translateInBlock(node.otherwise)));
+ }, store: false);
+ return;
+ }
+ int thenLabel = newLabel("then");
+ int joinLabel = newLabel("join");
+ int elseLabel =
+ node.otherwise is EmptyStatement ? joinLabel : newLabel("else");
+
+ withExpression(node.condition, (Expression condition) {
+ addExpressionStatement(
+ new Assignment(
+ new VariableUse(gotoName),
+ new Conditional(
+ condition,
+ number(thenLabel),
+ number(elseLabel))));
+ }, store: false);
+ addBreak();
+ beginLabel(thenLabel);
+ visitStatement(node.then);
+ if (node.otherwise is! EmptyStatement) {
+ addGoto(joinLabel);
+ beginLabel(elseLabel);
+ visitStatement(node.otherwise);
+ }
+ beginLabel(joinLabel);
+ }
+
+ @override
+ visitInterpolatedExpression(InterpolatedExpression node) => unsupported(node);
+
+ @override
+ visitInterpolatedLiteral(InterpolatedLiteral node) => unsupported(node);
+
+ @override
+ visitInterpolatedParameter(InterpolatedParameter node) => unsupported(node);
+
+ @override
+ visitInterpolatedSelector(InterpolatedSelector node) => unsupported(node);
+
+ @override
+ visitInterpolatedStatement(InterpolatedStatement node) => unsupported(node);
+
+ @override
+ void visitLabeledStatement(LabeledStatement node) {
+ if (!shouldTransform(node)) {
+ addStatement(
+ new LabeledStatement(node.label, translateInBlock(node.body)));
+ return;
+ }
+ int breakLabel = newLabel("break ${node.label}");
+ int continueLabel = newLabel("continue ${node.label}");
+ breakLabels[node] = breakLabel;
+ continueLabels[node] = continueLabel;
+
+ beginLabel(continueLabel);
+ targetsAndTries.add(node);
+ visitStatement(node.body);
+ targetsAndTries.removeLast();
+ beginLabel(breakLabel);
+ }
+
+ @override
+ Expression visitLiteralBool(LiteralBool node) => node;
+
+ @override
+ visitLiteralExpression(LiteralExpression node) => unsupported(node);
+
+ @override
+ Expression visitLiteralNull(LiteralNull node) => node;
+
+ @override
+ Expression visitLiteralNumber(LiteralNumber node) => node;
+
+ @override
+ visitLiteralStatement(LiteralStatement node) => unsupported(node);
+
+ @override
+ Expression visitLiteralString(LiteralString node) => node;
+
+ @override
+ visitNamedFunction(NamedFunction node) {
+ unsupported(node);
+ }
+
+ @override
+ Expression visitNew(New node) {
+ bool storeTarget = node.arguments.any(shouldTransform);
+ return withExpression(node.target, (target) {
+ return withExpressions(node.arguments, (List<Expression> arguments) {
+ return new New(target, arguments);
+ });
+ }, store: storeTarget);
+ }
+
+ @override
+ Expression visitObjectInitializer(ObjectInitializer node) {
+ return withExpressions(
+ node.properties.map((Property property) => property.value).toList(),
+ (List<Node> values) {
+ List<Property> properties = new List.generate(values.length, (int i) {
+ return new Property(node.properties[i].name, values[i]);
+ });
+ return new ObjectInitializer(properties);
+ });
+ }
+
+ @override
+ visitParameter(Parameter node) => unreachable(node);
+
+ @override
+ Expression visitPostfix(Postfix node) {
+ if (node.op == "++" || node.op == "--") {
+ Expression argument = node.argument;
+ if (argument is VariableUse) {
+ return new Postfix(node.op, argument);
+ } else if (argument is PropertyAccess) {
+ return withExpression2(argument.receiver, argument.selector,
+ (receiver, selector) {
+ return new Postfix(node.op, new PropertyAccess(receiver, selector));
+ });
+ } else {
+ throw "Unexpected postfix ${node.op} "
+ "operator argument ${node.argument}";
+ }
+ }
+ return withExpression(
+ node.argument,
+ (Expression argument) => new Postfix(node.op, argument),
+ store: false);
+ }
+
+ @override
+ Expression visitPrefix(Prefix node) {
+ if (node.op == "++" || node.op == "--") {
+ Expression argument = node.argument;
+ if (argument is VariableUse) {
+ return new Prefix(node.op, argument);
+ } else if (argument is PropertyAccess) {
+ return withExpression2(argument.receiver, argument.selector,
+ (receiver, selector) {
+ return new Prefix(node.op, new PropertyAccess(receiver, selector));
+ });
+ } else {
+ throw "Unexpected prefix ${node.op} operator "
+ "argument ${node.argument}";
+ }
+ }
+ return withExpression(
+ node.argument,
+ (Expression argument) => new Prefix(node.op, argument),
+ store: false);
+ }
+
+ @override
+ visitProgram(Program node) => unsupported(node);
+
+ @override
+ Property visitProperty(Property node) {
+ return withExpression(
+ node.value,
+ (Expression value) => new Property(node.name, value),
+ store: false);
+ }
+
+ @override
+ Expression visitRegExpLiteral(RegExpLiteral node) => node;
+
+ @override
+ void visitReturn(Return node) {
+ assert(node.value == null || !isSyncStar && !isAsyncStar);
+ Node target = analysis.targets[node];
+ if (node.value != null) {
+ withExpression(node.value, (Expression value) {
+ addStatement(js.statement("$returnValueName = #", [value]));
+ }, store: false);
+ }
+ translateJump(target, returnLabel);
+ }
+
+ @override
+ void visitSwitch(Switch node) {
+ if (!node.cases.any(shouldTransform)) {
+ // If only the key has an await, translation can be simplified.
+ bool oldInsideUntranslated = insideUntranslatedBreakable;
+ insideUntranslatedBreakable = true;
+ withExpression(node.key, (Expression key) {
+ List<SwitchClause> cases = node.cases.map((SwitchClause clause) {
+ if (clause is Case) {
+ return new Case(clause.expression, translateInBlock(clause.body));
+ } else if (clause is Default) {
+ return new Default(translateInBlock(clause.body));
+ }
+ }).toList();
+ addStatement(new Switch(key, cases));
+ }, store: false);
+ insideUntranslatedBreakable = oldInsideUntranslated;
+ return;
+ }
+ int before = newLabel("switch");
+ int after = newLabel("after switch");
+ breakLabels[node] = after;
+
+ beginLabel(before);
+ List<int> labels = new List<int>(node.cases.length);
+
+ if (!node.cases.every(
+ (SwitchClause x) => !(x is Case && shouldTransform(x.expression)))) {
+ int defaultIndex = null; // Null means no default was found.
+ // If there is an await in one of the keys, a chain of ifs has to be used.
+
+ withExpression(node.key, (Expression key) {
+ int i = 0;
+ for (SwitchClause clause in node.cases) {
+ if (clause is Default) {
+ // The goto for the default case is added after all non-default
+ // clauses have been handled.
+ defaultIndex = i;
+ labels[i] = newLabel("default");
+ continue;
+ } else if (clause is Case) {
+ labels[i] = newLabel("case");
+ withExpression(clause.expression, (expression) {
+ addStatement(
+ new If.noElse(new Binary("===", key, expression),
+ gotoAndBreak(labels[i])));
+ }, store: false);
+ }
+ i++;
+ }
+ }, store: true);
+
+ if (defaultIndex == null) {
+ addGoto(after);
+ } else {
+ addGoto(labels[defaultIndex]);
+ }
+
+ } else {
+ bool hasDefault = false;
+ int i = 0;
+ List<SwitchClause> clauses = new List<SwitchClause>();
+ for (SwitchClause clause in node.cases) {
+ if (clause is Case) {
+ labels[i] = newLabel("case");
+ clauses.add(new Case(clause.expression, gotoAndBreak(labels[i])));
+ } else if (i is Default) {
+ labels[i] = newLabel("default");
+ clauses.add(new Default(gotoAndBreak(labels[i])));
+ hasDefault = true;
+ } else {
+ diagnosticListener.internalError(spannable,
+ "Unknown clause type $clause");
+ }
+ i++;
+ }
+ withExpression(node.key, (Expression key) {
+ addStatement(new Switch(key, clauses));
+ }, store: false);
+ if (!hasDefault) {
+ addGoto(after);
+ }
+ }
+
+ targetsAndTries.add(node);
+ for (int i = 0; i < labels.length; i++) {
+ beginLabel(labels[i]);
+ visitStatement(node.cases[i].body);
+ }
+ beginLabel(after);
+ targetsAndTries.removeLast();
+ }
+
+ @override
+ Expression visitThis(This node) {
+ return new VariableUse(selfName);
+ }
+
+ @override
+ void visitThrow(Throw node) {
+ withExpression(node.expression, (Expression expression) {
+ addStatement(new Throw(expression));
+ }, store: false);
+ }
+
+ setHandler() {
+ addExpressionStatement(
+ new Assignment(new VariableUse(handlerName), currentErrorHandler));
+ }
+
+ @override
+ void visitTry(Try node) {
floitsch 2015/02/05 20:17:21 Reference visitFun.
sigurdm 2015/02/06 14:26:32 Done.
+ if (!shouldTransform(node)) {
+ Block body = translateInBlock(node.body);
+ Catch catchPart = (node.catchPart == null)
+ ? null
+ : new Catch(node.catchPart.declaration,
+ translateInBlock(node.catchPart.body));
+ Block finallyPart = (node.finallyPart == null)
+ ? null
+ : translateInBlock(node.finallyPart);
+ addStatement(new Try(body, catchPart, finallyPart));
+ return;
+ }
+ hasTryBlocks = true;
+ int handlerLabel = newLabel("catch");
+ int finallyLabel = newLabel("finally");
+ int afterFinallyLabel = newLabel("after finally");
+ errorHandlerLabels.add(handlerLabel);
+ // Set the error handler here. It must be cleared on every path out;
+ // normal and error exit.
+ setHandler();
floitsch 2015/02/05 20:17:22 maybe rename to setErrorHandler
sigurdm 2015/02/06 14:26:33 Done.
+ if (node.finallyPart != null) {
+ finallyLabels[node] = finallyLabel;
+ targetsAndTries.add(node);
+ }
+ visitStatement(node.body);
+ errorHandlerLabels.removeLast();
+ addStatement(js.statement("$nextName = [#];",
+ [number(afterFinallyLabel)]));
+ if (node.finallyPart != null) {
+ // The handler is set as the first thing in the finally block.
+ addGoto(finallyLabel);
+ } else {
+ setHandler();
+ addGoto(afterFinallyLabel);
+ }
+ beginLabel(handlerLabel);
+ if (node.catchPart != null) {
+ setHandler();
+ // The catch declaration name can shadow outer variables, so a fresh name
+ // is needed to avoid collisions.
floitsch 2015/02/05 20:17:22 collisions. See Ecma 262, 3rd edition, section 12.
sigurdm 2015/02/06 14:26:33 Done.
+ String errorRename = freshName(node.catchPart.declaration.name);
+ localVariables.add(new VariableDeclaration(errorRename));
+ variableRenamings.add(
+ new Pair(node.catchPart.declaration.name, errorRename));
+ addExpressionStatement(
+ new Assignment(new VariableUse(errorRename),
+ new VariableUse(resultName)));
+ visitStatement(node.catchPart.body);
+ variableRenamings.removeLast();
+ }
+ if (node.finallyPart != null) {
+ setHandler();
+ targetsAndTries.removeLast();
+ // This belongs to the catch-part, but is only needed if there is a
+ // finally therefore it is in this branch.
+ // This is needed it even if there is no explicit catch-branch, because
+ // if an exception is raised the finally part has to be run.
floitsch 2015/02/05 20:17:21 I don't get this. If the try-body executes withou
sigurdm 2015/02/06 14:26:34 I agree - this is a bug. Thanks for spotting.
+ addStatement(js.statement("$nextName = [#];",
+ [number(afterFinallyLabel)]));
+ beginLabel(finallyLabel);
+ setHandler();
+ visitStatement(node.finallyPart);
+ addStatement(new Comment("// goto the next finally handler"));
+ addStatement(js.statement("$gotoName = $nextName.pop();"));
+ addBreak();
+ }
+ beginLabel(afterFinallyLabel);
+ }
+
+ @override
+ visitVariableDeclaration(VariableDeclaration node) {
+ unreachable(node);
+ }
+
+ @override
+ void visitVariableDeclarationList(VariableDeclarationList node) {
+ // Declaration of local variables is hoisted outside the helper but the
+ // initialization is done here.
+ for (VariableInitialization initialization in node.declarations) {
+ VariableDeclaration declaration = initialization.declaration;
+ localVariables.add(declaration);
+ if (initialization.value != null) {
+ withExpression(initialization.value, (Expression value) {
+ addStatement(
+ new ExpressionStatement(new Assignment(declaration, value)));
+ }, store: false);
+ }
+ }
+ }
+
+ @override
+ void visitVariableInitialization(VariableInitialization node) {
+ unreachable(node);
+ }
+
+ @override
+ Expression visitVariableUse(VariableUse node) {
+ Pair<String, String> renaming = variableRenamings.lastWhere(
+ (Pair renaming) => renaming.a == node.name,
+ orElse: () => null);
+ if (renaming == null) return node;
+ return new VariableUse(renaming.b);
+ }
+
+ @override
+ void visitWhile(While node) {
+ if (!shouldTransform(node)) {
+ bool oldInsideUntranslated = insideUntranslatedBreakable;
+ insideUntranslatedBreakable = true;
+ withExpression(node.condition, (Expression condition) {
+ addStatement(new While(condition, translateInBlock(node.body)));
+ }, store: false);
+ insideUntranslatedBreakable = oldInsideUntranslated;
+ return;
+ }
+ int continueLabel = newLabel("while condition");
+ continueLabels[node] = continueLabel;
+ beginLabel(continueLabel);
+
+ int afterLabel = newLabel("after while");
+ breakLabels[node] = afterLabel;
+ Expression condition = node.condition;
+ // If the condition is `true`, a test is not needed.
+ if (!(condition is LiteralBool && condition.value == true)) {
+ withExpression(node.condition, (Expression condition) {
+ addStatement(new If.noElse(new Prefix("!", condition),
+ gotoAndBreak(afterLabel)));
+ }, store: false);
+ }
+ targetsAndTries.add(node);
+ visitStatement(node.body);
+ targetsAndTries.removeLast();
+ addGoto(continueLabel);
+ beginLabel(afterLabel);
+ }
+
+ /// Translates a yield/yield* in an sync* function.
floitsch 2015/02/05 20:17:21 in a sync*
sigurdm 2015/02/06 14:26:34 Done.
+ ///
+ /// `yield` in a sync* function just returns [value].
+ /// `yield*` wraps [value] in a [yieldStarExpression] and returns it.
+ void addSyncYield(DartYield node, Expression expression) {
+ assert(isSyncStar);
+ if (node.hasStar) {
+ addStatement(new Return(new Call(yieldStarExpression, [expression])));
+ } else {
+ addStatement(new Return(expression));
+ }
+ }
+
+ /// Translates a yield/yield* in an async* function.
+ ///
+ /// yield/yield* in an async* function is translated much like the `await` is
+ /// translated in [visitAwait], only the object is wrapped in a
+ /// [yieldExpression]/[yieldStarExpression] to let [streamHelper] distinguish.
+ ///
+ /// Because there is no Future that can fail (as there is in await) null is
+ /// passed as the errorCallback.
+ void addAsyncYield(DartYield node, Expression expression) {
+ assert(isAsyncStar);
+ // Find all the finally blocks that should be performed if the stream is
+ // canceled during the yield.
+ // At the bottom of the stack is the return label.
+ List<int> enclosingFinallyLabels = <int>[returnLabel];
floitsch 2015/02/05 20:17:22 I think "exitLabel" would read easier. (Here in th
sigurdm 2015/02/06 14:26:33 Done.
+ enclosingFinallyLabels.addAll(targetsAndTries
+ .where((Node node) => node is Try)
+ .map((Try node) => finallyLabels[node]));
+ int destinationOnCancel = enclosingFinallyLabels.removeLast();
+ ArrayInitializer finallyListInitializer =
+ new ArrayInitializer(enclosingFinallyLabels
+ .map((int label) => number(label))
floitsch 2015/02/05 20:17:22 .map(number).toList() and yes: we should have a p
sigurdm 2015/02/06 14:26:32 Done.
+ .toList());
+ addStatement(js.statement("""
+ return #streamHelper(#yieldExpression(#expression),
+ $helperName, $controllerName, function () {
+ if (#notEmptyFinallyList)
+ $nextName = #finallyList;
+ $gotoName = #destinationOnCancel;
+ $helperName();
+ });""",
+ {"streamHelper": streamHelper,
+ "yieldExpression": node.hasStar
+ ? yieldStarExpression
+ : yieldExpression,
+ "expression": expression,
+ "notEmptyFinallyList": enclosingFinallyLabels.isNotEmpty,
+ "finallyList": finallyListInitializer,
+ "destinationOnCancel": number(destinationOnCancel)}));
+ }
+
+ @override
+ void visitDartYield(DartYield node) {
+ assert(isSyncStar || isAsyncStar);
+ int label = newLabel("after yield");
+ // Don't do a break here for the goto, but instead a return in either
+ // addSynYield or addAsyncYield.
+ withExpression(node.expression, (Expression expression) {
+ addStatement(setGotoVariable(label));
+ if (isSyncStar) {
+ addSyncYield(node, expression);
+ } else {
+ addAsyncYield(node, expression);
+ }
+ }, store: false);
+ beginLabel(label);
+ }
+}
+
+/// Finds out
+///
+/// - which expressions have yield or await nested in them.
+/// - targets of jumps
+/// - a set of used names.
+/// - if any [This]-expressions are used.
+class PreTranslationAnalysis extends NodeVisitor<bool> {
+ Set<Node> hasAwaitOrYield = new Set<Node>();
+
+ Map<Node, Node> targets = new Map<Node, Node>();
+ List<Node> loopsAndSwitches = new List<Node>();
+ List<LabeledStatement> labelledStatements = new List<LabeledStatement>();
+ Set<String> usedNames = new Set<String>();
+
+ bool hasExplicitReturns = false;
+
+ bool hasThis = false;
+
+ // The function currently being analyzed.
+ Fun currentFunction;
+
+ // For error messages.
+ final Function unsupported;
+
+ PreTranslationAnalysis(void this.unsupported(Node node));
+
+ bool visit(Node node) {
+ bool containsAwait = node.accept(this);
+ if (containsAwait) {
+ hasAwaitOrYield.add(node);
+ }
+ return containsAwait;
+ }
+
+ analyze(Fun node) {
+ currentFunction = node;
+ node.params.forEach(visit);
+ visit(node.body);
+ }
+
+ @override
+ bool visitAccess(PropertyAccess node) {
+ bool receiver = visit(node.receiver);
+ bool selector = visit(node.selector);
+ return receiver || selector;
+ }
+
+ @override
+ bool visitArrayHole(ArrayHole node) {
+ return false;
+ }
+
+ @override
+ bool visitArrayInitializer(ArrayInitializer node) {
+ bool containsAwait = false;
+ for (Expression element in node.elements) {
+ if (visit(element)) containsAwait = true;
+ }
+ return containsAwait;
+ }
+
+ @override
+ bool visitAssignment(Assignment node) {
+ bool leftHandSide = visit(node.leftHandSide);
+ bool value = (node.value == null) ? false : visit(node.value);
+ return leftHandSide || value;
+ }
+
+ @override
+ bool visitAwait(Await node) {
+ visit(node.expression);
+ return true;
+ }
+
+ @override
+ bool visitBinary(Binary node) {
+ bool left = visit(node.left);
+ bool right = visit(node.right);
+ return left || right;
+ }
+
+ @override
+ bool visitBlob(Blob node) {
+ return false;
+ }
+
+ @override
+ bool visitBlock(Block node) {
+ bool containsAwait = false;
+ for (Statement statement in node.statements) {
+ if (visit(statement)) containsAwait = true;
+ }
+ return containsAwait;
+ }
+
+ @override
+ bool visitBreak(Break node) {
+ if (node.targetLabel != null) {
+ targets[node] = labelledStatements.lastWhere(
+ (LabeledStatement statement) => statement.label == node.targetLabel);
+ } else {
+ targets[node] = loopsAndSwitches.last;
+ }
+ return false;
+ }
+
+ @override
+ bool visitCall(Call node) {
+ bool containsAwait = visit(node.target);
+ for (Expression argument in node.arguments) {
+ if (visit(argument)) containsAwait = true;
+ }
+ return containsAwait;
+ }
+
+ @override
+ bool visitCase(Case node) {
+ bool expression = visit(node.expression);
+ bool body = visit(node.body);
+ return expression || body;
+ }
+
+ @override
+ bool visitCatch(Catch node) {
+ bool declaration = visit(node.declaration);
+ bool body = visit(node.body);
+ return declaration || body;
+ }
+
+ @override
+ bool visitComment(Comment node) {
+ return false;
+ }
+
+ @override
+ bool visitConditional(Conditional node) {
+ bool condition = visit(node.condition);
+ bool then = visit(node.then);
+ bool otherwise = visit(node.otherwise);
+ return condition || then || otherwise;
+ }
+
+ @override
+ bool visitContinue(Continue node) {
+ if (node.targetLabel != null) {
+ targets[node] = labelledStatements.lastWhere(
+ (LabeledStatement stm) => stm.label == node.targetLabel);
+ } else {
+ targets[node] =
+ loopsAndSwitches.lastWhere((Node node) => node is! Switch);
+ }
+ assert(() {
+ Node target = targets[node];
+ return target is Loop ||
+ (target is LabeledStatement && target.body is Loop);
+ });
+ return false;
+ }
+
+ @override
+ bool visitDefault(Default node) {
+ return visit(node.body);
+ }
+
+ @override
+ bool visitDo(Do node) {
+ loopsAndSwitches.add(node);
+ bool body = visit(node.body);
+ bool condition = visit(node.condition);
+ loopsAndSwitches.removeLast();
+ return body || condition;
+ }
+
+ @override
+ bool visitEmptyStatement(EmptyStatement node) {
+ return false;
+ }
+
+ @override
+ bool visitExpressionStatement(ExpressionStatement node) {
+ return visit(node.expression);
+ }
+
+ @override
+ bool visitFor(For node) {
+ bool init = (node.init == null) ? false : visit(node.init);
+ bool condition = (node.condition == null) ? false : visit(node.condition);
+ bool update = (node.update == null) ? false : visit(node.update);
+ loopsAndSwitches.add(node);
+ bool body = visit(node.body);
+ loopsAndSwitches.removeLast();
+ return init || condition || update || body;
+ }
+
+ @override
+ bool visitForIn(ForIn node) {
+ bool object = visit(node.object);
+ loopsAndSwitches.add(node);
+ bool body = visit(node.body);
+ loopsAndSwitches.removeLast();
+ return object || body;
+ }
+
+ @override
+ bool visitFun(Fun node) {
+ return false;
+ }
+
+ @override
+ bool visitFunctionDeclaration(FunctionDeclaration node) {
+ return false;
+ }
+
+ @override
+ bool visitIf(If node) {
+ bool condition = visit(node.condition);
+ bool then = visit(node.then);
+ bool otherwise = visit(node.otherwise);
+ return condition || then || otherwise;
+ }
+
+ @override
+ bool visitInterpolatedExpression(InterpolatedExpression node) {
+ return unsupported(node);
+ }
+
+ @override
+ bool visitInterpolatedLiteral(InterpolatedLiteral node) {
+ return unsupported(node);
+ }
+
+ @override
+ bool visitInterpolatedParameter(InterpolatedParameter node) {
+ return unsupported(node);
+ }
+
+ @override
+ bool visitInterpolatedSelector(InterpolatedSelector node) {
+ return unsupported(node);
+ }
+
+ @override
+ bool visitInterpolatedStatement(InterpolatedStatement node) {
+ return unsupported(node);
+ }
+
+ @override
+ bool visitLabeledStatement(LabeledStatement node) {
+ usedNames.add(node.label);
+ labelledStatements.add(node);
+ bool containsAwait = visit(node.body);
+ labelledStatements.removeLast();
+ return containsAwait;
+ }
+
+ @override
+ bool visitLiteralBool(LiteralBool node) {
+ return false;
+ }
+
+ @override
+ bool visitLiteralExpression(LiteralExpression node) {
+ return unsupported(node);
+ }
+
+ @override
+ bool visitLiteralNull(LiteralNull node) {
+ return false;
+ }
+
+ @override
+ bool visitLiteralNumber(LiteralNumber node) {
+ return false;
+ }
+
+ @override
+ bool visitLiteralStatement(LiteralStatement node) {
+ return unsupported(node);
+ }
+
+ @override
+ bool visitLiteralString(LiteralString node) {
+ return false;
+ }
+
+ @override
+ bool visitNamedFunction(NamedFunction node) {
+ return false;
+ }
+
+ @override
+ bool visitNew(New node) {
+ return visitCall(node);
+ }
+
+ @override
+ bool visitObjectInitializer(ObjectInitializer node) {
+ bool containsAwait = false;
+ for (Property property in node.properties) {
+ if (visit(property)) containsAwait = true;
+ }
+ return containsAwait;
+ }
+
+ @override
+ bool visitParameter(Parameter node) {
+ usedNames.add(node.name);
+ return false;
+ }
+
+ @override
+ bool visitPostfix(Postfix node) {
+ return visit(node.argument);
+ }
+
+ @override
+ bool visitPrefix(Prefix node) {
+ return visit(node.argument);
+ }
+
+ @override
+ bool visitProgram(Program node) {
+ throw "Unexpected";
+ }
+
+ @override
+ bool visitProperty(Property node) {
+ return visit(node.value);
+ }
+
+ @override
+ bool visitRegExpLiteral(RegExpLiteral node) {
+ return false;
+ }
+
+ @override
+ bool visitReturn(Return node) {
+ hasExplicitReturns = true;
+ targets[node] = currentFunction;
+ if (node.value == null) return false;
+ return visit(node.value);
+ }
+
+ @override
+ bool visitSwitch(Switch node) {
+ loopsAndSwitches.add(node);
+ bool result = visit(node.key);
+ for (SwitchClause clause in node.cases) {
+ if (visit(clause)) result = true;
+ }
+ loopsAndSwitches.removeLast();
+ return result;
+ }
+
+ @override
+ bool visitThis(This node) {
+ hasThis = true;
+ return false;
+ }
+
+ @override
+ bool visitThrow(Throw node) {
+ return visit(node.expression);
+ }
+
+ @override
+ bool visitTry(Try node) {
+ bool body = visit(node.body);
+ bool catchPart = (node.catchPart == null) ? false : visit(node.catchPart);
+ bool finallyPart = (node.finallyPart == null)
+ ? false
+ : visit(node.finallyPart);
+ return body || catchPart || finallyPart;
+ }
+
+ @override
+ bool visitVariableDeclaration(VariableDeclaration node) {
+ usedNames.add(node.name);
+ return false;
+ }
+
+ @override
+ bool visitVariableDeclarationList(VariableDeclarationList node) {
+ bool result = false;
+ for (VariableInitialization init in node.declarations) {
+ if (visit(init)) result = true;
+ }
+ return result;
+ }
+
+ @override
+ bool visitVariableInitialization(VariableInitialization node) {
+ return visitAssignment(node);
+ }
+
+ @override
+ bool visitVariableUse(VariableUse node) {
+ usedNames.add(node.name);
+ return false;
+ }
+
+ @override
+ bool visitWhile(While node) {
+ loopsAndSwitches.add(node);
+ bool condition = visit(node.condition);
+ bool body = visit(node.body);
+ loopsAndSwitches.removeLast();
+ return condition || body;
+ }
+
+ @override
+ bool visitDartYield(DartYield node) {
+ visit(node.expression);
+ return true;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698