Chromium Code Reviews| 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..cf2a31c274685d03eabfba63ccd98344b1d63ee5 |
| --- /dev/null |
| +++ b/pkg/compiler/lib/src/js/rewrite_async.dart |
| @@ -0,0 +1,1292 @@ |
| +// 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 "js.dart"; |
| +import "../helpers/helpers.dart"; |
| +import 'dart:collection'; |
| +import "dart:math" show max; |
| +import 'package:compiler/src/util/util.dart'; |
| + |
| +class AsyncFinder extends BaseVisitor { |
| + @override |
| + visitFun(Fun node) { |
| + switch (node.asyncModifier) { |
| + case const AsyncModifier.sync(): |
| + node.visitChildren(this); |
| + break; |
| + case const AsyncModifier.async(): |
| + |
| + node.visitChildren(this); |
| + break; |
| + case const AsyncModifier.syncStar(): |
| + throw "Sync* is not supported yet"; |
| + case const AsyncModifier.asyncStar(): |
| + throw "Async* is not supported yet"; |
| + } |
| + } |
| + |
| +} |
| + |
| +class AnalysisPrinter extends BaseVisitor { |
| + int indent = 0; |
| + var awaits; |
| + visitNode(Node node) { |
| + indent ++; |
| + node.visitChildren(this); |
| + indent --; |
| + } |
| +} |
| + |
| +class Analysis extends NodeVisitor { |
| + Set<Node> awaits = new Set<Node>(); |
| + |
| + Map<Node, Node> targets = new Map<Node, Node>(); |
| + List<Node> loopsAndSwitches = new List<Node>(); |
| + List<LabeledStatement> labelledStatements = new List<LabeledStatement>(); |
| + |
| + bool visit(Node node) { |
| + bool result = node.accept(this); |
|
floitsch
2015/01/20 16:06:15
don't call it "result".
sigurdm
2015/02/02 10:20:15
Called it containsAwait
|
| + if (result) { |
| + awaits.add(node); |
| + } |
| + return result; |
| + } |
| + |
| + analyze(Fun node) { |
| + visit(node.body); |
| + } |
| + |
| + @override |
| + visitAccess(PropertyAccess node) { |
| + bool x = visit(node.receiver); |
| + bool y = visit(node.selector); |
| + return x || y; |
| + } |
| + |
| + @override |
| + visitArrayHole(ArrayHole node) { |
| + // TODO: implement visitArrayHole |
|
floitsch
2015/01/20 16:06:15
should be trivial. There is no expression in the h
sigurdm
2015/02/02 10:20:14
Done.
|
| + } |
| + |
| + @override |
| + visitArrayInitializer(ArrayInitializer node) { |
| + // TODO: implement visitArrayInitializer |
| + } |
| + |
| + @override |
| + visitAssignment(Assignment node) { |
| + bool x = visit(node.leftHandSide); |
|
floitsch
2015/01/20 16:06:15
x -> left
y -> right
sigurdm
2015/02/02 10:20:15
Done.
|
| + bool y = node.value == null |
| + ? false |
| + : visit(node.value); |
| + return x || y; |
| + } |
| + |
| + @override |
| + visitAwait(Await node) { |
| + visit(node.expression); |
| + return true; |
| + } |
| + |
| + @override |
| + visitBinary(Binary node) { |
| + bool x = visit(node.left); |
| + bool y = visit(node.right); |
| + return x || y; |
| + } |
| + |
| + @override |
| + visitBlob(Blob node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitBlock(Block node) { |
| + bool result = false; |
| + for (var s in node.statements) { |
| + if (visit(s)) result = true; |
| + } |
| + return result; |
| + } |
| + |
| + @override |
| + visitBreak(Break node) { |
| + if (node.targetLabel != null) { |
| + targets[node] = labelledStatements.lastWhere( |
| + (LabeledStatement stm) => stm.label == node.targetLabel); |
| + } else { |
| + targets[node] = loopsAndSwitches.last; |
| + } |
| + return false; |
| + } |
| + |
| + @override |
| + visitCall(Call node) { |
| + bool result = visit(node.target); |
| + for (var s in node.arguments) { |
| + if (visit(s)) result = true; |
| + } |
| + return result; |
| + } |
| + |
| + @override |
| + visitCase(Case node) { |
| + bool x = visit(node.expression); |
| + bool y = visit(node.body); |
| + return x || y; |
| + } |
| + |
| + @override |
| + visitCatch(Catch node) { |
| + bool x = visit(node.declaration); |
| + bool y = visit(node.body); |
| + return x || y; |
| + } |
| + |
| + @override |
| + visitComment(Comment node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitConditional(Conditional node) { |
| + bool x = visit(node.condition); |
| + bool y = visit(node.then); |
| + bool z = visit(node.otherwise); |
| + return x || y || z; |
| + } |
| + |
| + @override |
| + visitContinue(Continue node) { |
| + if (node.targetLabel != null) { |
| + targets[node] = labelledStatements.lastWhere( |
| + (LabeledStatement stm) => stm.label == node.targetLabel); |
| + } else { |
| + targets[node] = loopsAndSwitches.lastWhere( |
| + (Node node) => node is! Switch); |
| + } |
| + return false; |
| + } |
| + |
| + @override |
| + visitDefault(Default node) { |
| + return visit(node.body); |
| + } |
| + |
| + @override |
| + visitDo(Do node) { |
| + loopsAndSwitches.add(node); |
| + bool x = visit(node.body); |
| + bool y = visit(node.condition); |
| + loopsAndSwitches.removeLast(); |
| + return x || y; |
| + } |
| + |
| + @override |
| + visitEmptyStatement(EmptyStatement node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitExpressionStatement(ExpressionStatement node) { |
| + return visit(node.expression); |
| + } |
| + |
| + @override |
| + visitFor(For node) { |
| + loopsAndSwitches.add(node); |
|
floitsch
2015/01/20 16:06:15
I would add the loopsAndSwitches only for the body
sigurdm
2015/02/02 10:20:15
Good point
Done
|
| + bool x = visit(node.init); |
| + bool y = visit(node.condition); |
| + bool z = visit(node.update); |
| + bool u = visit(node.body); |
| + loopsAndSwitches.removeLast(); |
| + return x || y || z || u; |
| + } |
| + |
| + @override |
| + visitForIn(ForIn node) { |
| + loopsAndSwitches.add(node); |
| + loopsAndSwitches.removeLast(); |
| + // TODO: implement visitForIn |
| + throw "BADDD"; |
| + } |
| + |
| + @override |
| + visitFun(Fun node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitFunctionDeclaration(FunctionDeclaration node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitIf(If node) { |
| + bool x = visit(node.condition); |
| + bool y = visit(node.then); |
| + bool z = visit(node.otherwise); |
| + return x || y || z; |
| + } |
| + |
| + @override |
| + visitInterpolatedExpression(InterpolatedExpression node) { |
| + // TODO: implement visitInterpolatedExpression |
| + } |
| + |
| + @override |
| + visitInterpolatedLiteral(InterpolatedLiteral node) { |
| + // TODO: implement visitInterpolatedLiteral |
| + } |
| + |
| + @override |
| + visitInterpolatedParameter(InterpolatedParameter node) { |
| + // TODO: implement visitInterpolatedParameter |
| + } |
| + |
| + @override |
| + visitInterpolatedSelector(InterpolatedSelector node) { |
| + // TODO: implement visitInterpolatedSelector |
| + } |
| + |
| + @override |
| + visitInterpolatedStatement(InterpolatedStatement node) { |
| + // TODO: implement visitInterpolatedStatement |
| + } |
| + |
| + @override |
| + visitLabeledStatement(LabeledStatement node) { |
| + labelledStatements.add(node); |
| + bool result = visit(node.body); |
| + labelledStatements.removeLast(); |
| + return result; |
| + } |
| + |
| + @override |
| + visitLiteralBool(LiteralBool node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitLiteralExpression(LiteralExpression node) { |
| + // TODO: implement visitLiteralExpression |
| + } |
| + |
| + @override |
| + visitLiteralNull(LiteralNull node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitLiteralNumber(LiteralNumber node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitLiteralStatement(LiteralStatement node) { |
| + // TODO: implement visitLiteralStatement |
| + } |
| + |
| + @override |
| + visitLiteralString(LiteralString node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitNamedFunction(NamedFunction node) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitNew(New node) { |
| + return visitCall(node); |
| + } |
| + |
| + @override |
| + visitObjectInitializer(ObjectInitializer node) { |
| + bool result = false; |
| + for (Property property in node.properties) { |
| + if (visit(property)) result = true; |
| + } |
| + return result; |
| + } |
| + |
| + @override |
| + visitParameter(Parameter node) { |
| + } |
| + |
| + @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) { |
| + 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) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitThrow(Throw node) { |
| + return visit(node.expression); |
| + } |
| + |
| + @override |
| + visitTry(Try node) { |
| + bool x = visit(node.body); |
| + bool y = node.catchPart == null ? false : visit(node.catchPart); |
| + bool z = node.finallyPart == null ? false : visit(node.finallyPart); |
| + return x || y || z; |
| + } |
| + |
| + @override |
| + visitVariableDeclaration(VariableDeclaration node) { |
| + 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) { |
| + return false; |
| + } |
| + |
| + @override |
| + visitWhile(While node) { |
| + loopsAndSwitches.add(node); |
| + // Be careful to avoid short-circuit. |
| + bool x = visit(node.condition); |
| + bool y = visit(node.body); |
| + loopsAndSwitches.removeLast(); |
| + return x || y; |
| + } |
| +} |
| + |
| +class AsyncRewriter extends NodeVisitor { |
| + |
| + // The set of all nodes containing an await nested somewhere. |
| + Set<Node> __containsAwait; |
| + |
| + // Local variables are hoisted to the top of the function, so we collect them |
| + // here. |
| + List<VariableDeclaration> localVariables = new List<VariableDeclaration>(); |
| + |
| + Map<Node, Node> __targets = new Map<Node, Node>(); |
| + Map<Node, int> continueLabels = new Map<Node, int>(); |
| + Map<Node, int> breakLabels = new Map<Node, int>(); |
| + |
| + List<Pair<String, String>> variableRenamings = |
| + new List<Pair<String, String>>(); |
| + Set<String> usedNames = new Set<String>(); |
| + |
| + List<int> errorHandlerLabels = new List<int>(); |
| + |
| + // TODO(sigurdm): use namer for these. |
| + String resultName = "__result"; |
| + String helperName = "__helper"; |
| + String gotoName = "__goto"; |
| + String handlerName = "__handler"; |
| + String errorName = "__error"; |
| + |
| + int __currentLabel = 0; |
| + |
| + int highWaterMark = 0; |
| + int currentTempVarIndex = 0; |
| + |
| + int allocateTempVar() { |
| + assert(highWaterMark >= currentTempVarIndex); |
| + currentTempVarIndex++; |
| + highWaterMark = max(currentTempVarIndex, highWaterMark); |
| + return currentTempVarIndex; |
| + } |
| + |
| + deallocateTempVar([howMany = 1]) { |
| + currentTempVarIndex -= howMany; |
| + } |
| + |
| + Expression useTempVar(int i) { |
| + return new VariableUse("__temp$i"); |
| + } |
| + |
| + |
| + String freshName(String originalName) { |
| + String result = "__$originalName"; |
| + int counter = 1; |
| + while (usedNames.contains(result)) { |
| + result = "__$originalName$counter"; |
| + ++counter; |
| + } |
| + 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. |
|
floitsch
2015/01/20 16:06:15
New line before.
sigurdm
2015/02/02 10:20:15
Done.
|
| + 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 tryBlocks = false; |
| + |
| + List<Statement> currentStatementBuffer; |
| + |
| + 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); |
| + } |
| + |
| + int newLabel([String comment]) { |
| + int result = __currentLabel; |
| + __currentLabel++; |
| + if (comment != null) { |
| + labelComments[result] = comment; |
| + } |
| + return result; |
| + } |
| + |
| + void beginLabel(int label) { |
| + assert(!labelledParts.containsKey(label)); |
| + labelledParts[label] = currentStatementBuffer = new List<Statement>(); |
| + addStatement(new Comment(labelComments[label])); |
| + } |
| + |
| + /// Returns a statement assigning to the `__goto` variable. This should be |
| + /// 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]. |
| + 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. |
| + void addGoto(int label) { |
| + if (labelComments.containsKey(label)) { |
| + addStatement(new Comment("goto ${labelComments[label]}")); |
| + } |
| + addStatement(goto(label)); |
| + addStatement(new Break(null)); |
| + } |
| + |
| + bool transform(Node node) { |
|
floitsch
2015/01/20 16:06:14
shouldTransform
sigurdm
2015/02/02 10:20:15
Done.
|
| + return node is Break || node is Continue || __containsAwait.contains(node); |
| + } |
| + |
| + /// Main entry point. |
| + Fun rewrite(Fun node) { |
| + assert(node.asyncModifier == const AsyncModifier.async()); |
| + Analysis analysis = new Analysis(); |
| + analysis.analyze(node); |
| + __containsAwait = analysis.awaits; |
| + __targets = analysis.targets; |
| + new AnalysisPrinter()..awaits = analysis.awaits |
| + ..visitNode(node); |
| + return node.accept(this); |
| + } |
| + |
| + visitStatement(Statement node) { |
| + node.accept(this); |
| + } |
| + |
| + void visitExpressionIgnoreResult(Expression node) { |
| + addExpressionStatement(node.accept(this)); |
| + } |
| + |
| + Expression visitExpression(Expression node) { |
| + return node.accept(this); |
| + } |
| + |
| + Expression _storeIfNecessary(Expression result) { |
| + if (result is Literal) return result; |
|
floitsch
2015/01/20 16:06:15
I guess this will be wrong, once you have list-lit
sigurdm
2015/02/02 10:20:14
Actually list-literals are not Literal, but Object
|
| + 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; |
| + } |
| + |
| + withExpression2(Expression node1, Expression node2, |
| + fn(Expression result1, Expression result2)) { |
| + int oldTempVarIndex = currentTempVarIndex; |
| + Expression r1 = visitExpression(node1); |
| + if (transform(node2)) { |
| + r1 = _storeIfNecessary(r1); |
| + } |
| + Expression r2 = visitExpression(node2); |
| + var result = fn(r1, r2); |
| + currentTempVarIndex = oldTempVarIndex; |
| + return result; |
| + } |
| + |
| + withExpressions(List<Node> nodes, fn(List<Node> results)) { |
| + int oldTempVarIndex = currentTempVarIndex; |
| + // Find last occurence of a 'transform' expression in [nodes]. |
| + // All expressions before that must be stored in temp-vars. |
| + int lastTransformIndex = 0; |
| + for (int i = nodes.length - 1; i >= 0; --i) { |
| + if (transform(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; |
| + } |
| + |
| + // [[Fun(a1, ..., an) S ]] => |
| + // Fun(a1, ..., an) { |
| + // vars(S); // Declaration of local variables of S. |
| + // var __goto = 0; |
| + // var __handler = null; // The current error handler. |
| + // Fun helper(__result) { |
| + // while(true) { |
| + // try { |
| + // switch(__goto) { |
| + // case 0: |
| + // [[S]]; |
| + // } |
| + // } catch(__error) { |
| + // if (handler === null) throw error; |
| + // __goto = __handler; |
| + // __result = __error; |
| + // } |
| + // } |
| + // } |
| + // } |
| + visitAsyncFun(Fun node) { |
| + beginLabel(newLabel("Function start")); |
| + visitStatement(node.body); |
| + 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 (tryBlocks) { |
| + helperBody = new Try(new Block([helperBody]), |
|
floitsch
2015/01/20 16:06:15
consider using the `js` templates.
You will actual
sigurdm
2015/02/02 10:20:14
Yeah - it is better like that.
|
| + new Catch((new VariableDeclaration(errorName)), |
| + new Block([new If.noElse(new Binary("===", |
| + new VariableUse(handlerName), new LiteralNull()), |
| + new Throw(new VariableUse(errorName))), |
| + new ExpressionStatement(new Assignment( |
| + new VariableUse(resultName), |
| + new VariableUse(errorName))), |
| + new ExpressionStatement(new Assignment( |
| + new VariableUse(gotoName), |
| + new VariableUse(handlerName))), |
| + ])), null); |
| + } |
| + helperBody = new While(new LiteralBool(true), helperBody); |
| + helperBody = new Block([helperBody]); |
| + List<VariableInitialization> inits = |
| + [new VariableInitialization(new VariableDeclaration(gotoName), |
| + new LiteralNumber("0")), |
| + new VariableInitialization(new VariableDeclaration(handlerName), |
| + new LiteralNull())]; |
| + inits.addAll(localVariables.map( |
| + (VariableDeclaration decl) => new VariableInitialization(decl, null))); |
| + VariableDeclarationList varDecl = new VariableDeclarationList(inits); |
| + inits.addAll(new Iterable.generate(highWaterMark, (int i) { |
| + return new VariableInitialization( |
| + new VariableDeclaration("__temp${i + 1}"), |
| + null); |
| + })); |
| + Fun helper = new Fun([new Parameter(resultName)], helperBody); |
| + return new Fun(node.params, |
| + new Block([new ExpressionStatement(varDecl), |
| + new FunctionDeclaration(new VariableDeclaration(helperName), |
| + helper), |
| + new Return( |
| + new New(new PropertyAccess.field( |
| + new VariableUse("Future"), "microtask"), |
| + [new VariableUse(helperName)]))])); |
| + } |
| + |
| + @override |
| + visitFun(Fun node) { |
| + switch (node.asyncModifier) { |
| + case const AsyncModifier.sync(): |
| + return node; |
| + case const AsyncModifier.async(): |
| + return visitAsyncFun(node); |
| + case const AsyncModifier.syncStar(): |
| + throw "Sync* is not supported yet"; |
| + case const AsyncModifier.asyncStar(): |
| + throw "Async* is not supported yet"; |
| + } |
| + |
| + } |
| + |
| + @override |
| + visitAccess(PropertyAccess node) { |
| + return withExpression2(node.receiver, node.selector, |
| + (receiver, selector) => new PropertyAccess(receiver, selector)); |
| + } |
| + |
| + unreachable(Node node) { |
| + throw("The transformer should never meet: $node"); |
|
floitsch
2015/01/20 16:06:15
If the function is intendend to stay, make it have
sigurdm
2015/02/02 10:20:14
Done.
|
| + } |
| + |
| + @override |
| + visitArrayHole(ArrayHole node) { |
| + return node; |
| + } |
| + |
| + @override |
| + visitArrayInitializer(ArrayInitializer node) { |
| + withExpressions(node.elements, (elements) { |
| + return new ArrayInitializer(elements); |
| + }); |
| + } |
| + |
| + // C([[e1 = e2]] => |
| + // C([[e1]] = [[e2]]) |
| + @override |
| + visitAssignment(Assignment node) { |
| + 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( |
| + new PropertyAccess(evaluated[0], evaluated[1]), |
| + evaluated[2]); |
| + }); |
| + } else { |
| + throw "Unexpected assignment left hand side $leftHandSide"; |
| + } |
| + } |
| + |
| + // C([[await e]]) => |
| + // __goto = `newLabel(afterCall) |
| + // [[e]].then(__helper, onError: (error) {__goto = `currentHandler } ); |
| + // case afterCall: |
| + // C(__result); |
| + @override |
| + Expression visitAwait(Await node) { |
| + int afterWait = newLabel("Returning from await."); |
| + withExpression(node.expression, (Expression expression) { |
| + addStatement(goto(afterWait)); |
| + addStatement(new Return(new Call( |
| + new PropertyAccess.field(expression, "then"), |
| + [new VariableUse(helperName), |
| + new Fun([new Parameter(errorName)], |
| + new Block([new ExpressionStatement( |
| + new Assignment(new VariableUse(gotoName), |
| + currentErrorHandler)), |
| + new ExpressionStatement(new Call( |
| + new VariableUse(helperName), |
| + [new VariableUse(errorName)]))]))]))); |
| + }, store: false); |
| + beginLabel(afterWait); |
| + return new VariableUse(resultName); |
| + } |
| + |
| + Expression get currentErrorHandler { |
| + return errorHandlerLabels.isEmpty |
| + ? new LiteralNull() |
| + : new LiteralNumber("${errorHandlerLabels.last}"); |
| + } |
| + |
| + // TODO short-circuiting operators!! |
| + @override |
| + Expression visitBinary(Binary node) { |
| + 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) { |
| + int target = breakLabels[__targets[node]]; |
| + if (target != null) { |
| + addGoto(target); |
| + } else { |
| + addStatement(node); |
| + } |
| + } |
| + |
| + @override |
| + Expression visitCall(Call node) { |
| + bool storeTarget = node.arguments.any(transform); |
| + 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 (!transform(node.then) && !transform(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) { |
| + addExpressionStatement(new Assignment( |
| + new VariableUse(resultName), |
| + value)); |
| + }, store: false); |
| + addGoto(joinLabel); |
| + beginLabel(elseLabel); |
| + withExpression(node.otherwise, (Expression value) { |
| + addExpressionStatement(new Assignment( |
| + new VariableUse(resultName), |
| + value)); |
| + }, store: false); |
| + beginLabel(joinLabel); |
| + return new VariableUse(resultName); |
| + } |
| + |
| + @override |
| + visitContinue(Continue node) { |
| + int target = continueLabels[__targets[node]]; |
| + if (target != null) { |
| + addGoto(target); |
| + } else { |
| + addStatement(node); |
| + } |
| + } |
| + |
| + @override |
| + visitDefault(Default node) => unreachable(node); |
| + |
| + @override |
| + visitDo(Do node) { |
| + throw "TODO"; |
| + } |
| + |
| + @override |
| + visitEmptyStatement(EmptyStatement node) => node; |
| + |
| + @override |
| + visitExpressionStatement(ExpressionStatement node) { |
| + if (node.expression is VariableDeclarationList) { |
| + // Treat VariableDeclarationList as a statement. |
| + visitVariableDeclarationList(node.expression); |
| + } else { |
| + visitExpressionIgnoreResult(node.expression); |
| + } |
| + } |
| + |
| + @override |
| + visitFor(For node) { |
| + throw "TODO"; |
| + } |
| + |
| + @override |
| + visitForIn(ForIn node) { |
| + throw "TODO"; |
| + } |
| + |
| + @override |
| + visitFunctionDeclaration(FunctionDeclaration node) { |
| + return node; |
| + } |
| + |
| + Block translateInBlock(Statement node) { |
| + List<Statement> oldBuffer = currentStatementBuffer; |
| + List<Statement> resultBuffer = currentStatementBuffer = new List(); |
| + visitStatement(node); |
| + currentStatementBuffer = oldBuffer; |
| + return new Block(resultBuffer); |
| + } |
| + |
| + @override |
| + visitIf(If node) { |
| + if (!transform(node.then) && !transform(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"; |
| + } |
| + |
| + @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) { |
| + int breakLabel = newLabel("break ${node.label}"); |
| + int continueLabel = newLabel("continue ${node.label}"); |
| + breakLabels[node] = breakLabel; |
| + continueLabels[node] = continueLabel; |
| + |
| + beginLabel(continueLabel); |
| + visitStatement(node.body); |
| + 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 "TODO"; |
| + } |
| + |
| + @override |
| + visitNew(New node) { |
| + bool storeTarget = node.arguments.any(transform); |
| + 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, (List<Node> properties) { |
| + return new ObjectInitializer(properties); |
| + }); |
| + } |
| + |
| + @override |
| + visitParameter(Parameter node) { |
| + throw "Unexpected"; |
| + } |
| + |
| + @override |
| + visitPostfix(Postfix node) { |
| + return withExpression(node.argument, |
| + (Expression argument) => new Postfix(node.op, argument), store: false); |
| + } |
| + |
| + @override |
| + visitPrefix(Prefix node) { |
| + 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) { |
| + if (node.value == null) { |
| + addStatement(node); |
| + } else { |
| + withExpression(node.value, (Expression value) { |
| + addStatement(new Return(value)); |
| + }, store: false); |
| + } |
| + } |
| + |
| + // [[switch(e) { |
| + // case i: Si; |
| + // ... |
| + // default: Sd; |
| + // }]] => |
| + // case #before |
| + // switch([[e]]) { |
| + // case i: __goto = #i; |
| + // ... |
| + // default: __goto = #defaultLabel; |
| + // } |
| + // case #i: |
| + // [[Si, breakLabels|#after, continueLabels|#before]] |
| + // // Notice fallthough/ |
| + // ... |
| + // case #defaultLabel: |
| + // [[Si, breakLabels|#after, continueLabels|#before]] |
| + // case #afterSwitch: |
| + @override |
| + visitSwitch(Switch node) { |
| + if (!node.cases.any(transform)) { |
| + // If only the key has an await, translation can be simplified. |
| + 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)); |
| + }); |
| + 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 && transform(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. |
| + 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"; |
| + } |
| + i++; |
| + } |
| + withExpression(node.key, (Expression key) { |
| + addStatement(new Switch(key, clauses)); |
| + }, store: false); |
| + if (!hasDefault) { |
| + addGoto(after); |
| + } |
| + } |
| + for (int i = 0; i < labels.length; i++) { |
| + beginLabel(labels[i]); |
| + visitStatement(node.cases[i].body); |
| + } |
| + beginLabel(after); |
| + } |
| + |
| + @override |
| + visitThis(This node) => node; |
| + |
| + @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 (!transform(node)) { |
| + Block body = translateInBlock(node.body); |
| + Catch catchPart = node.catchPart == null |
| + ? null |
| + : new Catch(node.catchPart.declaration, |
| + translateInBlock(node.catchPart.body)); |
| + Block finallyPart = node.finallyPart == null |
| + ? null |
| + : translateInBlock(node.finallyPart); |
| + addStatement(new Try(body, |
| + catchPart, |
| + finallyPart)); |
| + return; |
| + } |
| + tryBlocks = true; |
| + int handlerLabel = newLabel("catch"); |
| + int finallyLabel = newLabel("finally"); |
| + errorHandlerLabels.add(handlerLabel); |
| + setHandler(); |
| + visitStatement(node.body); |
| + errorHandlerLabels.removeLast(); |
| + setHandler(); |
| + addGoto(finallyLabel); |
| + beginLabel(handlerLabel); |
| + if (node.catchPart != null) { |
| + String errorRename = freshName(node.catchPart.declaration.name); |
| + localVariables.add(new VariableDeclaration(errorRename)); |
| + variableRenamings.add( |
| + new Pair(node.catchPart.declaration.name, errorRename)); |
| + addExpressionStatement(new Assignment( |
| + new VariableUse(errorRename), |
| + new VariableUse(resultName))); |
| + visitStatement(node.catchPart.body); |
| + variableRenamings.removeLast(); |
| + } |
| + beginLabel(finallyLabel); |
| + if (node.finallyPart != null) { |
| + visitStatement(node.finallyPart); |
| + } |
| + } |
| + |
| + @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. |
| + 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: () { |
| + return new Pair(null, null); |
| + }).b; |
| + if (renaming == null) return node; |
| + return new VariableUse(renaming); |
| + } |
| + |
| + @override |
| + visitWhile(While node) { |
| + if (!transform(node.body)) { |
| + // If only the condition has an await, translation can be simplified. |
| + withExpression(node.condition, (Expression condition) { |
| + addStatement(new While(condition, translateInBlock(node.body))); |
| + }, store: false); |
| + return; |
| + } |
| + int continueLabel = newLabel("while condition"); |
| + continueLabels[node] = continueLabel; |
| + beginLabel(continueLabel); |
| + |
| + int bodyLabel = newLabel("while body"); |
| + int after = newLabel("after while"); |
| + breakLabels[node] = after; |
| + withExpression(node.condition, (Expression cond) { |
| + addExpressionStatement(new Assignment(new VariableUse(gotoName), |
| + new Conditional(cond, new LiteralNumber("$bodyLabel"), |
| + new LiteralNumber("$after")))); |
| + }, store: false); |
| + addStatement(new Break(null)); |
| + beginLabel(bodyLabel); |
| + visitStatement(node.body); |
| + addGoto(continueLabel); |
| + beginLabel(after); |
| + } |
| +} |