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; |
+ } |
+} |