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

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: Implement new ssa-nodes in ssa-tracer. Created 5 years, 11 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..401799c038d53ccc9369560f0793dece2d0cff17
--- /dev/null
+++ b/pkg/compiler/lib/src/js/rewrite_async.dart
@@ -0,0 +1,1713 @@
+// 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;
+
+import "dart:math" show max;
+import 'dart:collection';
+
+import "js.dart";
+import '../util/util.dart';
+
+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).
+
+class AsyncRewriter extends NodeVisitor {
+
+ // Local variables are hoisted to the top of the function, so we collect them
+ // 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;
+
+ List<Node> targetsAndTries = new List<Node>();
floitsch 2015/02/02 22:00:07 Only contains tries if they have a finally. At the
sigurdm 2015/02/03 16:59:28 Done.
+
+ 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>>();
+
+ Analysis analysis;
+
+ List<int> errorHandlerLabels = new List<int>();
+
+ final Function safeVariableName;
+
+ String resultName;
+ String helperName;
+ String controllerName;
+ String gotoName;
+ String handlerName;
+ String errorName;
+ String nextName;
+ String returnValueName;
+ String outerLabelName;
+ String selfName;
+
+ final Expression thenHelper;
+ final Expression newController;
+ final Expression endOfIteration;
+ final Expression newIterable;
+ final Expression yieldExpression;
+ final Expression yieldStarExpression;
+
+ int _currentLabel = 0;
+
+ int highWaterMark = 0;
floitsch 2015/02/02 22:00:07 comment what this is.
sigurdm 2015/02/03 16:59:30 Done.
+ int currentTempVarIndex = 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.thenHelper,
+ this.newController,
+ this.endOfIteration,
+ this.newIterable,
+ this.yieldExpression,
+ this.yieldStarExpression,
+ this.safeVariableName});
+
+ /// Main entry point.
+ /// Rewrites a sync&/async/async* function to an equivalent normal function.
floitsch 2015/02/02 22:00:09 sync*
sigurdm 2015/02/03 16:59:29 Done.
+ Fun rewrite(Fun node) {
+ async = node.asyncModifier;
+ assert(!isSync);
+
+ analysis = new Analysis();
floitsch 2015/02/02 22:00:09 too generic. What does it analyse?
sigurdm 2015/02/03 16:59:28 Called it PreTranslationAnalysis. Better suggestio
+ analysis.analyze(node);
+
+ // To avoid name collisions with existing names, the fresh names are
+ // generated after the analysis.
+ resultName = freshName("result");
+ controllerName = freshName("completer");
+ 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() :
+ new LiteralNumber("${errorHandlerLabels.last}");
+ }
+
+ int allocateTempVar() {
+ assert(highWaterMark >= currentTempVarIndex);
+ currentTempVarIndex++;
+ highWaterMark = max(currentTempVarIndex, highWaterMark);
+ return currentTempVarIndex;
+ }
+
+ deallocateTempVar([howMany = 1]) {
floitsch 2015/02/02 22:00:09 void deallocateTempVar. Add "void" to all methods
sigurdm 2015/02/03 16:59:29 This function is unused. It has been removed.
+ currentTempVarIndex -= howMany;
+ assert(currentTempVarIndex >= 0);
+ }
+
+ 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;
+ }
+
+ /// We collect all the pieces in this map, and output 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 hasJumpViaFinally = false;
floitsch 2015/02/02 22:00:08 hasJumpThroughFinally ?
sigurdm 2015/02/03 16:59:30 Done.
+
+ /// True if the traversion currently is inside a loop or switch for which
+ /// `shouldTranslate` is false.
floitsch 2015/02/02 22:00:09 where is `shouldTranslate` ? Why is it not a [shou
sigurdm 2015/02/03 16:59:29 I meant shouldTransform
+ bool insideUntranslatedBreakable = false;
+
+ /// True if we use a label to break to the outer switch-statement.
+ bool hasJumpViaOuterLabel = false;
floitsch 2015/02/02 22:00:10 hasJumpThroughOuter label ? hasJumpToOuterSwitch ?
sigurdm 2015/02/03 16:59:29 Done.
+
+ /// 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;
+ }
+
+ void beginLabel(int label) {
floitsch 2015/02/02 22:00:06 Add comment. Since it has a side-effect it is impo
sigurdm 2015/02/03 16:59:30 Done.
+ assert(!labelledParts.containsKey(label));
+ labelledParts[label] = currentStatementBuffer = new List<Statement>();
floitsch 2015/02/02 22:00:08 nit: I don't like multiple assignments in the same
sigurdm 2015/02/03 16:59:30 I am not particularly fond of them either, but her
+ addStatement(new Comment(labelComments[label]));
+ }
+
+ /// Returns a statement assigning to the `__goto` variable. This should be
floitsch 2015/02/02 22:00:07 first and only mention of "__goto". (It is pretty
sigurdm 2015/02/03 16:59:29 I changed to "variable named [gotoName]".
+ /// followed by a break for the goto to be executed. Use [gotoWithBreak] or
+ /// [addGoto] for this.
+ Statement goto(int label) {
+ return new ExpressionStatement(
+ new Assignment(new VariableUse(gotoName), new LiteralNumber("$label")));
+ }
+
+ /// Returns a block with the same effect as [addGoto].
floitsch 2015/02/02 22:00:08 Improve the comment. It doesn't talk about the "br
sigurdm 2015/02/03 16:59:29 I made it more explicit.
+ Block gotoAndBreak(int label) {
+ List<Statement> statements = new List<Statement>();
+ if (labelComments.containsKey(label)) {
+ statements.add(new Comment("goto ${labelComments[label]}"));
+ }
+ statements.add(goto(label));
+ statements.add(new Break(null));
+ return new Block(statements);
+ }
+
+ /// Add a goto to [label] including the break.
+ /// Will also insert a comment describing the label if available.
floitsch 2015/02/02 22:00:08 New line before. Also inserts a ...
sigurdm 2015/02/03 16:59:28 Done.
+ void addGoto(int label) {
+ if (labelComments.containsKey(label)) {
+ addStatement(new Comment("goto ${labelComments[label]}"));
+ }
+ addStatement(goto(label));
+ addStatement(new Break(null));
+ }
+
+ void addStatement(Statement node) {
+ currentStatementBuffer.add(node);
+ }
+
+ void addExpressionStatement(Expression node) {
+ addStatement(new ExpressionStatement(node));
+ }
+
+ Expression methodCall(Expression callee, String methodName,
+ List<Expression> args) {
+ return new Call(new PropertyAccess.field(callee, methodName), args);
+ }
+
+ /// True if there is an await or yield in [node] or some subexpression.
+ bool shouldTransform(Node node) {
+ return analysis.hasAwaitOrYield.contains(node);
+ }
+
+ visitStatement(Statement node) {
+ node.accept(this);
+ }
+
+ void visitExpressionIgnoreResult(Expression node) {
floitsch 2015/02/02 22:00:08 Add comment.
sigurdm 2015/02/03 16:59:29 Done.
+ Expression result = node.accept(this);
+ if (!(result is Literal || result is VariableUse)) {
+ addExpressionStatement(result);
+ }
+ }
+
+ Expression visitExpression(Expression node) {
+ return node.accept(this);
+ }
+
+ /// Will emit an assignment of [result] to a temporary variable unless it can
floitsch 2015/02/02 22:00:07 Emits
sigurdm 2015/02/03 16:59:29 Done.
+ /// be guaranteed that evaluating [result] has no side-effect.
floitsch 2015/02/02 22:00:09 Explain what happens otherwise. Maybe best explai
sigurdm 2015/02/03 16:59:31 Done.
+ 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;
+ }
+
+ // Stores the result of evaluation first expression in a temporary if the
floitsch 2015/02/02 22:00:09 ... evaluating the first ...
sigurdm 2015/02/03 16:59:29 Done.
+ // second contains an await.
floitsch 2015/02/02 22:00:07 await or yield
sigurdm 2015/02/03 16:59:30 Done.
+ 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;
+ }
+
+ withExpressions(List<Expression> nodes, fn(List<Expression> results)) {
floitsch 2015/02/02 22:00:08 comment.
sigurdm 2015/02/03 16:59:28 Done.
+ 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 (shouldTransform(nodes[i])) {
+ lastTransformIndex = i;
+ break;
+ }
+ }
+ List<Node> visited = nodes.take(lastTransformIndex).map((Node node) {
+ return _storeIfNecessary(visitExpression(node));
+ }).toList();
+ visited.addAll(nodes.skip(lastTransformIndex).map((Node node) {
+ return visitExpression(node);
+ }));
+ var result = fn(visited);
+ currentTempVarIndex = oldTempVarIndex;
+ return result;
+ }
+
+ /// Emits the return block that all returns should jump to.
+ ///
+ /// Returning from an async method calls the helper with the result.
+ /// (the result might been stored in [returnValueName] by some finally block).
+ ///
+ /// Returning from a sync* will return a [endOfIteration] marker.
+ void addReturn() {
+ if (analysis.hasExplicitReturns) {
+ 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, $controllerName, null)",
floitsch 2015/02/02 22:00:07 In order to use "js.statement" all non-holes must
sigurdm 2015/02/03 16:59:28 Added a TODO.
+ {"thenHelper": thenHelper}));
+ break;
+ case const AsyncModifier.syncStar():
+ addStatement(new Return(new Call(endOfIteration, [])));
+ break;
+ case const AsyncModifier.asyncStar():
+ addStatement(js.statement(
+ "return #thenHelper(null, null, $controllerName, null)",
floitsch 2015/02/02 22:00:08 ditto and the same for all other js-templates in t
sigurdm 2015/02/03 16:59:28 Acknowledged.
+ {"thenHelper": thenHelper}));
+ break;
+ default:
+ throw "Internal error, unexpected asyncmodifier";
+ }
+ }
+
+ /// Initializing a controller. (Will be a completer for async,
floitsch 2015/02/02 22:00:08 No need for future-tense.
sigurdm 2015/02/03 16:59:27 Done.
+ /// a streamController for async* and nothing for a sync*).
+ /// The controller will be passed to each call to [thenHelper].
floitsch 2015/02/02 22:00:08 The controller is passed ...
sigurdm 2015/02/03 16:59:28 Done.
+ Statement initializer() {
floitsch 2015/02/02 22:00:08 generateInitializer? "initializer" is a getter na
sigurdm 2015/02/03 16:59:29 Done.
+ switch (async) {
+ case const AsyncModifier.async():
+ return js.statement(
+ "return #thenHelper(null, $helperName, $controllerName, null);",
+ {"thenHelper": thenHelper});
+ case const AsyncModifier.syncStar():
+ throw "Not handled here";
+ case const AsyncModifier.asyncStar():
+ return js.statement(
+ "return #thenHelper(null, $helperName, $controllerName, null);",
+ {"thenHelper": thenHelper}); default:
+ throw "Internal error, unexpected asyncmodifier";
+ }
+ }
+
+ @override
+ visitFun(Fun node) {
+ if (isSync) return node;
+
+ beginLabel(newLabel("Function start"));
+ returnLabel = analysis.hasExplicitReturns ? 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(
+ new LiteralNumber("$label"),
+ new Block(labelledParts[label]));
+ }).toList();
+ Statement helperBody = new Switch(new VariableUse(gotoName), clauses);
+ if (hasJumpViaOuterLabel) {
+ helperBody = js.statement("$outerLabelName: #", [helperBody]);
+ }
+ if (hasTryBlocks) {
+ helperBody = js.statement("""
+try {
floitsch 2015/02/02 22:00:07 indent
sigurdm 2015/02/03 16:59:30 Done.
+ #body
+} catch ($errorName){
+ if ($handlerName === null)
+ throw $errorName;
+ $resultName = $errorName;
+ $gotoName = $handlerName;
+}""", {"body": helperBody});
+ }
+ List<VariableInitialization> inits = <VariableInitialization>[];
+
+ // Helper.
floitsch 2015/02/02 22:00:06 Comment does not add any value.
sigurdm 2015/02/03 16:59:28 Removed
+ VariableInitialization makeInit(String name, Expression initValue) {
+ return new VariableInitialization(new VariableDeclaration(name),
+ initValue);
+ }
+
+ inits.add(makeInit(gotoName, new LiteralNumber("0")));
+ if (isAsync || isAsyncStar) {
+ inits.add(makeInit(controllerName, newController));
+ }
+ if (hasTryBlocks) {
+ inits.add(makeInit(handlerName, new LiteralNull()));
+ }
+ if (hasJumpViaFinally) {
+ 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(highWaterMark,
+ (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) {
floitsch 2015/02/02 22:00:08 indent.
sigurdm 2015/02/03 16:59:30 Done.
+ #varDecl;
+ function $helperName($resultName) {
+ while (true)
+ #helperBody;
+ }
+ #init;
+}
+""", {"params": node.params,
+ "helperBody": helperBody,
+ "varDecl": varDecl,
+ "init": initializer()});
+ }
+
+ @override
+ visitAccess(PropertyAccess node) {
+ return withExpression2(
+ node.receiver,
+ node.selector,
+ (receiver, selector) => new PropertyAccess(receiver, selector));
+ }
+
+ unreachable(Node node) {
+ throw ("Internal error, trying to visit $node");
+ }
+
+ @override
+ visitArrayHole(ArrayHole node) {
+ return node;
+ }
+
+ @override
+ visitArrayInitializer(ArrayInitializer node) {
+ return withExpressions(node.elements, (elements) {
+ return new ArrayInitializer(elements);
+ });
+ }
+
+ @override
+ 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";
+ }
+ }
+
+ @override
+ Expression visitAwait(Await node) {
+ int afterWait = newLabel("returning from await.");
+ withExpression(node.expression, (Expression expression) {
+ addStatement(goto(afterWait));
+ if (errorHandlerLabels.isEmpty) {
+ addStatement(js.statement("""
+return #thenHelper(#expression, $helperName, $controllerName, null);
floitsch 2015/02/02 22:00:09 indent.
sigurdm 2015/02/03 16:59:30 Done.
+""", {"thenHelper": thenHelper, "expression": expression}));
+ } else {
+ addStatement(js.statement("""
+return #thenHelper(#expression, $helperName, $controllerName,
floitsch 2015/02/02 22:00:09 indent.
sigurdm 2015/02/03 16:59:30 Done.
+ function($errorName) {
+ $gotoName = #currentHandler;
+ $helperName($errorName);
+});
+""", {"thenHelper": thenHelper,
+ "expression": expression,
+ "currentHandler": currentErrorHandler}));
+ }
+ }, store: false);
+ beginLabel(afterWait);
+ return new VariableUse(resultName);
+ }
+
+ bool isResult(Expression node) {
floitsch 2015/02/02 22:00:09 what is 'result' ? It looks like it is a temporary
sigurdm 2015/02/03 16:59:28 Added explanatory comment
+ return node is VariableUse && node.name == resultName;
+ }
+
+ @override
+ Expression visitBinary(Binary node) {
+ if (shouldTransform(node.right)) {
floitsch 2015/02/02 22:00:07 if (shouldTransform(node.right) && (node.op ==
sigurdm 2015/02/03 16:59:30 Done.
+ if (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
+ 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
+ visitCase(Case node) {
+ return unreachable(node);
+ }
+
+ @override
+ visitCatch(Catch node) {
+ return unreachable(node);
+ }
+
+ @override
+ 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,
+ new LiteralNumber("$thenLabel"),
+ new LiteralNumber("$elseLabel"))));
+ }, store: false);
+ addStatement(new Break(null));
+ 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
+ visitContinue(Continue node) {
+ Node target = analysis.targets[node];
+ if (!shouldTransform(target)) {
+ addStatement(node);
+ return;
+ }
+ translateJump(target, continueLabels[target]);
+ }
+
+ /// Common code for handling break, continue, return.
+ ///
+ /// It is necessary to run all nesting finally-handlers between the jump and
+ /// the target. For that we use [nextName] as a stack of places to go.
+ void translateJump(Node target, int targetLabel) {
+ // Compute a stack of all the 'finally' nodes we must visit before the jump.
+ // The bottom of the stack is the label where the jump goes to.
+ List<int> finallyStack = new List<int>();
floitsch 2015/02/02 22:00:07 since it doesn't only contain finally's maybe rena
sigurdm 2015/02/03 16:59:30 Renamed to `jumpStack`. Better?
+ for (Node n in targetsAndTries.reversed) {
floitsch 2015/02/02 22:00:07 Node node in ...
sigurdm 2015/02/03 16:59:29 Done.
+ if (n is Try) {
+ finallyStack.add(finallyLabels[n]);
floitsch 2015/02/02 22:00:08 assert that the try has a finally.
sigurdm 2015/02/03 16:59:28 Done.
+ } else if (n == target) {
+ finallyStack.add(targetLabel);
floitsch 2015/02/02 22:00:09 indentation.
sigurdm 2015/02/03 16:59:27 Done.
+ break;
+ }
+ // Ignore other nodes.
+ }
+ finallyStack = finallyStack.reversed.toList();
+ // We jump directly to the top of the stack, so take it off now.
+ int firstTarget = finallyStack.removeLast();
+ if (finallyStack.isNotEmpty) {
+ hasJumpViaFinally = true;
+ Expression jsJumpStack = new ArrayInitializer(
+ finallyStack.map((int label) => new LiteralNumber("$label"))
+ .toList());
+ addStatement(js.statement("$nextName = #", [jsJumpStack]));
+ }
+ if (insideUntranslatedBreakable) {
+ addStatement(goto(firstTarget));
+ addStatement(new Break(outerLabelName));
+ hasJumpViaOuterLabel = true;
+ } else {
+ addGoto(firstTarget);
+ }
+ }
+
+ @override
+ visitDefault(Default node) => unreachable(node);
+
+ @override
+ 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
+ visitEmptyStatement(EmptyStatement node) => node;
+
+ visitExpressionInStatementContext(Expression node) {
+ if (node is VariableDeclarationList) {
+ // Treat VariableDeclarationList as a statement.
+ visitVariableDeclarationList(node);
+ } else {
+ visitExpressionIgnoreResult(node);
+ }
+ }
+
+ @override
+ visitExpressionStatement(ExpressionStatement node) {
+ visitExpressionInStatementContext(node.expression);
+ }
+
+ @override
+ visitFor(For node) {
+ if (!shouldTransform(node) ) {
+ Expression init = node.init == null
+ ? new LiteralNumber("0") // Dummy init.
floitsch 2015/02/02 22:00:08 Why can't you just call visitExpression instead of
sigurdm 2015/02/03 16:59:30 I have to use withExpressions to get the right val
+ : node.init;
+ Expression condition = node.condition == null
+ ? new LiteralBool(true) // Dummy condition;
+ : node.condition;
+
+ Expression update = node.update == null
+ ? new LiteralNumber("0") // Dummy update.
+ : node.update;
+
+ bool oldInsideUntranslated = insideUntranslatedBreakable;
+ insideUntranslatedBreakable = true;
+ withExpressions([init, condition, 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
floitsch 2015/02/02 22:00:09 no update, continuing ... (add comma)
sigurdm 2015/02/03 16:59:28 Done.
+ // start.
+ int continueLabel = node.update == null
floitsch 2015/02/02 22:00:09 (node.update == null)
sigurdm 2015/02/03 16:59:27 Done.
+ ? 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) {
floitsch 2015/02/02 22:00:07 This doesn't make sense. The second part will alwa
sigurdm 2015/02/03 16:59:30 Good catch. Thanks.
+ // No condition is the same as true. We don't have to check.
+ withExpression(node.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
+ visitForIn(ForIn node) {
+ // The dart output currently never uses for-in loops.
+ throw "Javascript for-in not implemented yet in the await transformation";
+ }
+
+ @override
+ visitFunctionDeclaration(FunctionDeclaration node) {
+ return node;
+ }
+
+ // Only used for code where `!shouldTransform(node)`.
+ Block translateInBlock(Statement node) {
+ assert(!shouldTransform(node));
+ List<Statement> oldBuffer = currentStatementBuffer;
+ List<Statement> resultBuffer = currentStatementBuffer = new List();
floitsch 2015/02/02 22:00:07 Nit: split assignments.
sigurdm 2015/02/03 16:59:29 Done.
+ visitStatement(node);
+ currentStatementBuffer = oldBuffer;
+ return new Block(resultBuffer);
+ }
+
+ @override
+ 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,
+ new LiteralNumber("$thenLabel"),
+ new LiteralNumber("$elseLabel"))));
+ }, store: false);
+ addStatement(new Break(null));
+ beginLabel(thenLabel);
+ visitStatement(node.then);
+ if (node.otherwise is! EmptyStatement) {
+ addGoto(joinLabel);
+ beginLabel(elseLabel);
+ visitStatement(node.otherwise);
+ }
+ beginLabel(joinLabel);
+ }
+
+ @override
+ visitInterpolatedExpression(InterpolatedExpression node) {
+ throw "Unsupported";
floitsch 2015/02/02 22:00:08 use a real error object here (and in the other uns
sigurdm 2015/02/03 16:59:28 Done.
+ }
+
+ @override
+ visitInterpolatedLiteral(InterpolatedLiteral node) => throw "Unsupported";
+
+ @override
+ visitInterpolatedParameter(InterpolatedParameter node) => throw "Unsupported";
+
+ @override
+ visitInterpolatedSelector(InterpolatedSelector node) => throw "Unsupported";
+
+ @override
+ visitInterpolatedStatement(InterpolatedStatement node) => throw "Unsupported";
+
+ @override
+ 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
+ visitLiteralBool(LiteralBool node) => node;
+
+ @override
+ visitLiteralExpression(LiteralExpression node) => throw "Unsupported";
+
+ @override
+ visitLiteralNull(LiteralNull node) => node;
+
+ @override
+ visitLiteralNumber(LiteralNumber node) => node;
+
+ @override
+ visitLiteralStatement(LiteralStatement node) => throw "Unsupported";
+
+ @override
+ visitLiteralString(LiteralString node) => node;
+
+ @override
+ visitNamedFunction(NamedFunction node) {
+ throw "Internal error: "
+ "Named functions are not handled yet by the javascript async transform";
+ }
+
+ @override
+ visitNew(New node) {
+ bool storeTarget = node.arguments.any(shouldTransform);
floitsch 2015/02/02 22:00:08 You should be able to skip this. The targets of 'n
sigurdm 2015/02/03 16:59:31 I like to keep with the general Javascript semanti
+ return withExpression(node.target, (target) {
+ return withExpressions(node.arguments, (List<Expression> arguments) {
+ return new New(target, arguments);
+ });
+ }, store: storeTarget);
+ }
+
+ @override
+ 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) {
+ throw "Unexpected";
floitsch 2015/02/02 22:00:08 Replace with real error.
sigurdm 2015/02/03 16:59:29 Done.
+ }
+
+ @override
+ 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
+ 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) => throw "Unsupported";
+
+ @override
+ visitProperty(Property node) {
+ return withExpression(
+ node.value,
+ (Expression value) => new Property(node.name, value),
+ store: false);
+ }
+
+ @override
+ visitRegExpLiteral(RegExpLiteral node) => node;
+
+ @override
+ 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
+ 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, we have to use a chain of ifs.
+
+ withExpression(node.key, (Expression key) {
+ int i = 0;
+ for (SwitchClause clause in node.cases) {
+ if (clause is Default) {
+ // The default case is handled last.
floitsch 2015/02/02 22:00:10 // The goto for the default case is added after al
sigurdm 2015/02/03 16:59:28 Done.
+ 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 {
+ throw "Unknown switchclause $i";
floitsch 2015/02/02 22:00:09 Use real exception.
sigurdm 2015/02/03 16:59:31 Done.
+ }
+ 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
+ visitThis(This node) {
+ return new VariableUse(selfName);
+ }
+
+ @override
+ 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) {
+ if (!shouldTransform(node)) {
+ Block body = translateInBlock(node.body);
+ Catch catchPart = node.catchPart == null ?
floitsch 2015/02/02 22:00:07 (node.catchPart == null)
sigurdm 2015/02/03 16:59:29 Done.
+ null :
+ new Catch(node.catchPart.declaration,
+ translateInBlock(node.catchPart.body));
+ Block finallyPart =
+ node.finallyPart == null ? null : translateInBlock(node.finallyPart);
floitsch 2015/02/02 22:00:09 (node.finallyPart == null)
sigurdm 2015/02/03 16:59:29 Done.
+ 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);
floitsch 2015/02/02 22:00:07 Add more comments. Basically we set it, when we en
sigurdm 2015/02/03 16:59:28 Done.
+ setHandler();
+ if (node.finallyPart != null) {
+ finallyLabels[node] = finallyLabel;
+ targetsAndTries.add(node);
+ }
+ visitStatement(node.body);
+ errorHandlerLabels.removeLast();
+ setHandler();
+ addStatement(js.statement("$nextName = [#];",
+ [new LiteralNumber("$afterFinallyLabel")]));
+ if (node.finallyPart != null) {
+ addGoto(finallyLabel);
+ } else {
+ addGoto(afterFinallyLabel);
+ }
+ beginLabel(handlerLabel);
+ if (node.catchPart != null) {
+ setHandler();
+ String errorRename = freshName(node.catchPart.declaration.name);
floitsch 2015/02/02 22:00:07 Why is that necessary? Why can't we just use the o
sigurdm 2015/02/03 16:59:30 Done.
+ 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) {
+ targetsAndTries.removeLast();
+ addStatement(js.statement("$nextName = [#];",
floitsch 2015/02/02 22:00:09 Shouldn't this be in the catch-part?
sigurdm 2015/02/03 16:59:29 It belongs in the catch-part before the finally la
floitsch 2015/02/04 12:31:27 But it's also only needed if there is a catch part
sigurdm 2015/02/05 14:06:03 No - it is always needed, to ensure we run the fin
+ [new LiteralNumber("$afterFinallyLabel")]));
+ beginLabel(finallyLabel);
+ visitStatement(node.finallyPart);
+ addStatement(new Comment("// goto the next finally handler"));
+ addStatement(js.statement("$gotoName = $nextName.pop();"));
+ addStatement(new Break(null));
+ }
+ beginLabel(afterFinallyLabel);
+ }
+
+ @override
+ visitVariableDeclaration(VariableDeclaration node) {
+ throw "TODO";
+ }
+
+ @override
+ void visitVariableDeclarationList(VariableDeclarationList node) {
+ // Declaration of local variables is hoisted outside the helper
+ // But the initialization is done here.
floitsch 2015/02/02 22:00:07 lowercase "but".
sigurdm 2015/02/03 16:59:30 Done.
+ 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
+ visitVariableInitialization(VariableInitialization node) {
+ // These should be handled by visitVariableDeclarationList.
+ throw "Unexpected variableInitialization";
+ }
+
+ @override
+ visitVariableUse(VariableUse node) {
+ String renaming = variableRenamings.lastWhere(
+ (Pair renaming) => renaming.a == node.name,
+ orElse: () {
floitsch 2015/02/02 22:00:06 orElse: () => new Pair(null, null)
sigurdm 2015/02/03 16:59:29 Done.
+ return new Pair(null, null);
floitsch 2015/02/02 22:00:08 Don't create a new Pair for every variable use tha
sigurdm 2015/02/03 16:59:27 Done.
floitsch 2015/02/04 12:31:27 where?
sigurdm 2015/02/05 14:06:02 Sorry was to quick on clicking done on this. Done
+ }).b;
+ if (renaming == null) return node;
+ return new VariableUse(renaming);
+ }
+
+ @override
+ 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;
+ withExpression(node.condition, (Expression condition) {
+ addStatement(new If.noElse(new Prefix("!", condition),
floitsch 2015/02/02 22:00:07 If dart2js emits While loops with `while(true)` op
sigurdm 2015/02/03 16:59:28 I think it can happen, and added special handling.
+ gotoAndBreak(afterLabel)));
+ }, store: false);
+ targetsAndTries.add(node);
+ visitStatement(node.body);
+ targetsAndTries.removeLast();
+ addGoto(continueLabel);
+ beginLabel(afterLabel);
+ }
+
+ @override
+ visitYield(Yield node) {
+ assert(isSyncStar || isAsyncStar);
+ if (isSyncStar) {
+ int label = newLabel("after yield");
+ withExpression(node.expression, (Expression expression) {
+ addStatement(goto(label));
floitsch 2015/02/02 22:00:07 Add comment explaining that this doesn't do the ju
sigurdm 2015/02/03 16:59:30 I renamed goto -> setGotoVariable and added a comm
+ if (node.hasStar) {
+ addStatement(new Return(new Call(yieldStarExpression, [expression])));
+ } else {
+ addStatement(new Return(expression));
+ }
+ beginLabel(label);
+ }, store: false);
+ } else {
+ if (node.hasStar) {
+ throw "TODO";
floitsch 2015/02/02 22:00:09 Throw "good" error.
sigurdm 2015/02/03 16:59:28 Done.
+ }
+ int label = newLabel("after yield");
+ withExpression(node.expression, (Expression expression) {
+ addStatement(goto(label));
floitsch 2015/02/02 22:00:09 add comment.
+ addStatement(js.statement("""
+return #thenHelper(#yieldExpression(#expression),
floitsch 2015/02/02 22:00:08 indent.
sigurdm 2015/02/03 16:59:28 Done.
+ $helperName, $controllerName, function () {
+ $gotoName = #currentHandler;
+ $helperName();
+ });""",
+ {"thenHelper": thenHelper,
+ "yieldExpression": yieldExpression,
+ "expression": expression,
+ "currentHandler": currentErrorHandler}));
+ beginLabel(label);
+ }, store: false);
+ }
+ }
+}
+
+/// 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 Analysis extends NodeVisitor {
floitsch 2015/02/02 22:00:09 generic type?
sigurdm 2015/02/03 16:59:29 Done.
+ 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;
+
+ Fun function;
floitsch 2015/02/02 22:00:07 currentFunction? add comment.
sigurdm 2015/02/03 16:59:30 Done.
+
+ bool visit(Node node) {
+ bool containsAwait = node.accept(this);
+ if (containsAwait) {
+ hasAwaitOrYield.add(node);
+ }
+ return containsAwait;
+ }
+
+ analyze(Fun node) {
floitsch 2015/02/02 22:00:08 missing return type. same for other functions.
sigurdm 2015/02/03 16:59:29 Done.
floitsch 2015/02/04 12:31:27 not for this function.
+ function = node;
+ node.params.forEach(visit);
+ visit(node.body);
+ }
+
+ @override
+ visitAccess(PropertyAccess node) {
+ bool receiver = visit(node.receiver);
+ bool selector = visit(node.selector);
+ return receiver || selector;
+ }
+
+ @override
+ visitArrayHole(ArrayHole node) {
+ return false;
+ }
+
+ @override
+ visitArrayInitializer(ArrayInitializer node) {
+ bool containsAwait = false;
+ for (Expression element in node.elements) {
+ if (visit(element)) containsAwait = true;
+ }
+ return containsAwait;
+ }
+
+ @override
+ visitAssignment(Assignment node) {
+ bool leftHandSide = visit(node.leftHandSide);
+ bool value = node.value == null ? false : visit(node.value);
floitsch 2015/02/02 22:00:07 (node.value == null)
sigurdm 2015/02/03 16:59:30 Done.
+ return leftHandSide || value;
+ }
+
+ @override
+ visitAwait(Await node) {
+ visit(node.expression);
+ return true;
+ }
+
+ @override
+ visitBinary(Binary node) {
+ bool left = visit(node.left);
+ bool right = visit(node.right);
+ return left || right;
+ }
+
+ @override
+ visitBlob(Blob node) {
+ return false;
+ }
+
+ @override
+ visitBlock(Block node) {
+ bool containsAwait = false;
+ for (Statement statement in node.statements) {
+ if (visit(statement)) containsAwait = true;
+ }
+ return containsAwait;
+ }
+
+ @override
+ 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
+ visitCall(Call node) {
+ bool containsAwait = visit(node.target);
+ for (Expression argument in node.arguments) {
+ if (visit(argument)) containsAwait = true;
+ }
+ return containsAwait;
+ }
+
+ @override
+ visitCase(Case node) {
+ bool expression = visit(node.expression);
+ bool body = visit(node.body);
+ return expression || body;
+ }
+
+ @override
+ visitCatch(Catch node) {
+ bool declaration = visit(node.declaration);
+ bool body = visit(node.body);
+ return declaration || body;
+ }
+
+ @override
+ visitComment(Comment node) {
+ return false;
+ }
+
+ @override
+ visitConditional(Conditional node) {
+ bool condition = visit(node.condition);
+ bool then = visit(node.then);
+ bool otherwise = visit(node.otherwise);
+ return condition || then || otherwise;
+ }
+
+ @override
+ visitContinue(Continue node) {
+ if (node.targetLabel != null) {
+ targets[node] = labelledStatements.lastWhere(
+ (LabeledStatement stm) => stm.label == node.targetLabel);
floitsch 2015/02/02 22:00:08 assert that the target is indeed a loop.
sigurdm 2015/02/03 16:59:30 Done.
+ } else {
+ targets[node] =
+ loopsAndSwitches.lastWhere((Node node) => node is! Switch);
+ }
+ return false;
+ }
+
+ @override
+ visitDefault(Default node) {
+ return visit(node.body);
+ }
+
+ @override
+ visitDo(Do node) {
+ loopsAndSwitches.add(node);
+ bool body = visit(node.body);
+ bool condition = visit(node.condition);
+ loopsAndSwitches.removeLast();
+ return body || condition;
+ }
+
+ @override
+ visitEmptyStatement(EmptyStatement node) {
+ return false;
+ }
+
+ @override
+ visitExpressionStatement(ExpressionStatement node) {
+ return visit(node.expression);
+ }
+
+ @override
+ visitFor(For node) {
+ bool init = node.init == null ? false : visit(node.init);
floitsch 2015/02/02 22:00:08 parenthesis around all the conditions.
sigurdm 2015/02/03 16:59:28 Done.
+ 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
+ visitForIn(ForIn node) {
+ bool object = visit(node.object);
+ loopsAndSwitches.add(node);
+ bool body = visit(node.body);
+ loopsAndSwitches.removeLast();
+ return object || body;
+ }
+
+ @override
+ visitFun(Fun node) {
+ return false;
+ }
+
+ @override
+ visitFunctionDeclaration(FunctionDeclaration node) {
+ return false;
+ }
+
+ @override
+ visitIf(If node) {
+ bool condition = visit(node.condition);
+ bool then = visit(node.then);
+ bool otherwise = visit(node.otherwise);
+ return condition || then || otherwise;
+ }
+
+ @override
+ visitInterpolatedExpression(InterpolatedExpression node) {
+ throw "Internal error: $node is not handled by the async-transform.";
floitsch 2015/02/02 22:00:09 real errors.
sigurdm 2015/02/03 16:59:29 Done.
+ }
+
+ @override
+ visitInterpolatedLiteral(InterpolatedLiteral node) {
+ throw "Internal error: $node is not handled by the async-transform.";
+ }
+
+ @override
+ visitInterpolatedParameter(InterpolatedParameter node) {
+ throw "Internal error: $node is not handled by the async-transform.";
+ }
+
+ @override
+ visitInterpolatedSelector(InterpolatedSelector node) {
+ throw "Internal error: $node is not handled by the async-transform.";
+ }
+
+ @override
+ visitInterpolatedStatement(InterpolatedStatement node) {
+ throw "Internal error: $node is not handled by the async-transform.";
+ }
+
+ @override
+ visitLabeledStatement(LabeledStatement node) {
+ usedNames.add(node.label);
+ labelledStatements.add(node);
+ bool containsAwait = visit(node.body);
+ labelledStatements.removeLast();
+ return containsAwait;
+ }
+
+ @override
+ visitLiteralBool(LiteralBool node) {
+ return false;
+ }
+
+ @override
+ visitLiteralExpression(LiteralExpression node) {
+ throw "Internal error: $node is not handled by the async-transform.";
floitsch 2015/02/02 22:00:09 real error (and below).
sigurdm 2015/02/03 16:59:31 Done.
sigurdm 2015/02/03 16:59:31 Done.
+ }
+
+ @override
+ visitLiteralNull(LiteralNull node) {
+ return false;
+ }
+
+ @override
+ visitLiteralNumber(LiteralNumber node) {
+ return false;
+ }
+
+ @override
+ visitLiteralStatement(LiteralStatement node) {
+ throw "Internal error: $node is not handled by the async-transform.";
+ }
+
+ @override
+ visitLiteralString(LiteralString node) {
+ return false;
+ }
+
+ @override
+ visitNamedFunction(NamedFunction node) {
+ return false;
+ }
+
+ @override
+ visitNew(New node) {
+ return visitCall(node);
+ }
+
+ @override
+ visitObjectInitializer(ObjectInitializer node) {
+ bool containsAwait = false;
+ for (Property property in node.properties) {
+ if (visit(property)) containsAwait = true;
+ }
+ return containsAwait;
+ }
+
+ @override
+ visitParameter(Parameter node) {
+ usedNames.add(node.name);
+ return false;
+ }
+
+ @override
+ visitPostfix(Postfix node) {
+ return visit(node.argument);
+ }
+
+ @override
+ visitPrefix(Prefix node) {
+ return visit(node.argument);
+ }
+
+ @override
+ visitProgram(Program node) {
+ throw "Unexpected";
+ }
+
+ @override
+ visitProperty(Property node) {
+ return visit(node.value);
+ }
+
+ @override
+ visitRegExpLiteral(RegExpLiteral node) {
+ return false;
+ }
+
+ @override
+ visitReturn(Return node) {
+ hasExplicitReturns = true;
+ targets[node] = function;
+ if (node.value == null) return false;
+ return visit(node.value);
+ }
+
+ @override
+ 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
+ visitThis(This node) {
+ hasThis = true;
+ return false;
+ }
+
+ @override
+ visitThrow(Throw node) {
+ return visit(node.expression);
+ }
+
+ @override
+ visitTry(Try node) {
+ bool body = visit(node.body);
+ bool catchPart = node.catchPart == null ? false : visit(node.catchPart);
+ bool finallyPart = node.finallyPart == null
floitsch 2015/02/02 22:00:06 parenthesis.
sigurdm 2015/02/03 16:59:29 Done.
+ ? false
+ : visit(node.finallyPart);
+ return body || catchPart || finallyPart;
+ }
+
+ @override
+ visitVariableDeclaration(VariableDeclaration node) {
+ usedNames.add(node.name);
+ return false;
+ }
+
+ @override
+ visitVariableDeclarationList(VariableDeclarationList node) {
+ bool result = false;
+ for (VariableInitialization init in node.declarations) {
+ if (visit(init)) result = true;
+ }
+ return result;
+ }
+
+ @override
+ visitVariableInitialization(VariableInitialization node) {
+ return visitAssignment(node);
+ }
+
+ @override
+ visitVariableUse(VariableUse node) {
+ usedNames.add(node.name);
+ return false;
+ }
+
+ @override
+ visitWhile(While node) {
+ loopsAndSwitches.add(node);
+ // Note that `return visit(node.condition) || visit(node.body)` would lead
floitsch 2015/02/02 22:00:09 Remove comment. That's true for all "||" parts.
sigurdm 2015/02/03 16:59:30 Done.
+ // to an unwanted short-circuit.
+ bool condition = visit(node.condition);
+ bool body = visit(node.body);
+ loopsAndSwitches.removeLast();
+ return condition || body;
+ }
+
+ @override
+ visitYield(Yield node) {
+ visit(node.expression);
+ return true;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698