| 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>();
|
| +
|
| + 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;
|
| + 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.
|
| + Fun rewrite(Fun node) {
|
| + async = node.asyncModifier;
|
| + assert(!isSync);
|
| +
|
| + analysis = new Analysis();
|
| + 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]) {
|
| + 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;
|
| +
|
| + /// True if the traversion currently is inside a loop or switch for which
|
| + /// `shouldTranslate` is false.
|
| + bool insideUntranslatedBreakable = false;
|
| +
|
| + /// True if we use a label to break to the outer switch-statement.
|
| + bool hasJumpViaOuterLabel = false;
|
| +
|
| + /// Buffer for collecting translated statements belonging to the same switch
|
| + /// case.
|
| + List<Statement> currentStatementBuffer;
|
| +
|
| + // Labels will become cases in the big switch expression, and `goto label`
|
| + // is expressed by assigning to the switch key [gotoName] and breaking out of
|
| + // the switch.
|
| +
|
| + int newLabel([String comment]) {
|
| + int result = _currentLabel;
|
| + _currentLabel++;
|
| + if (comment != null) {
|
| + labelComments[result] = comment;
|
| + }
|
| + return result;
|
| + }
|
| +
|
| + 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));
|
| + }
|
| +
|
| + 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) {
|
| + 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
|
| + /// be guaranteed that evaluating [result] has no side-effect.
|
| + 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
|
| + // second contains an await.
|
| + 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)) {
|
| + 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)",
|
| + {"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)",
|
| + {"thenHelper": thenHelper}));
|
| + break;
|
| + default:
|
| + throw "Internal error, unexpected asyncmodifier";
|
| + }
|
| + }
|
| +
|
| + /// Initializing a controller. (Will be a completer for async,
|
| + /// a streamController for async* and nothing for a sync*).
|
| + /// The controller will be passed to each call to [thenHelper].
|
| + Statement initializer() {
|
| + 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 {
|
| + #body
|
| +} catch ($errorName){
|
| + if ($handlerName === null)
|
| + throw $errorName;
|
| + $resultName = $errorName;
|
| + $gotoName = $handlerName;
|
| +}""", {"body": helperBody});
|
| + }
|
| + List<VariableInitialization> inits = <VariableInitialization>[];
|
| +
|
| + // Helper.
|
| + 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) {
|
| + #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);
|
| +""", {"thenHelper": thenHelper, "expression": expression}));
|
| + } else {
|
| + addStatement(js.statement("""
|
| +return #thenHelper(#expression, $helperName, $controllerName,
|
| + function($errorName) {
|
| + $gotoName = #currentHandler;
|
| + $helperName($errorName);
|
| +});
|
| +""", {"thenHelper": thenHelper,
|
| + "expression": expression,
|
| + "currentHandler": currentErrorHandler}));
|
| + }
|
| + }, store: false);
|
| + beginLabel(afterWait);
|
| + return new VariableUse(resultName);
|
| + }
|
| +
|
| + bool isResult(Expression node) {
|
| + return node is VariableUse && node.name == resultName;
|
| + }
|
| +
|
| + @override
|
| + Expression visitBinary(Binary node) {
|
| + if (shouldTransform(node.right)) {
|
| + 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>();
|
| + for (Node n in targetsAndTries.reversed) {
|
| + if (n is Try) {
|
| + finallyStack.add(finallyLabels[n]);
|
| + } else if (n == target) {
|
| + finallyStack.add(targetLabel);
|
| + 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.
|
| + : 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
|
| + // start.
|
| + int continueLabel = node.update == null
|
| + ? startLabel
|
| + : newLabel("for update");
|
| + continueLabels[node] = continueLabel;
|
| + int afterLabel = newLabel("after for");
|
| + breakLabels[node] = afterLabel;
|
| + beginLabel(startLabel);
|
| + Expression condition = node.condition;
|
| + if (condition != null ||
|
| + condition is LiteralBool && condition.value == true) {
|
| + // No condition is the same as true. 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();
|
| + 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";
|
| + }
|
| +
|
| + @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);
|
| + 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";
|
| + }
|
| +
|
| + @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.
|
| + 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);
|
| + }
|
| + }
|
| +
|
| + 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 ?
|
| + null :
|
| + new Catch(node.catchPart.declaration,
|
| + translateInBlock(node.catchPart.body));
|
| + Block finallyPart =
|
| + node.finallyPart == null ? null : translateInBlock(node.finallyPart);
|
| + addStatement(new Try(body, catchPart, finallyPart));
|
| + return;
|
| + }
|
| + hasTryBlocks = true;
|
| + int handlerLabel = newLabel("catch");
|
| + int finallyLabel = newLabel("finally");
|
| + int afterFinallyLabel = newLabel("after finally");
|
| + errorHandlerLabels.add(handlerLabel);
|
| + 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);
|
| + 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 = [#];",
|
| + [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.
|
| + 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 (!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),
|
| + 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));
|
| + 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";
|
| + }
|
| + int label = newLabel("after yield");
|
| + withExpression(node.expression, (Expression expression) {
|
| + addStatement(goto(label));
|
| + addStatement(js.statement("""
|
| +return #thenHelper(#yieldExpression(#expression),
|
| + $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 {
|
| + 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;
|
| +
|
| + bool visit(Node node) {
|
| + bool containsAwait = node.accept(this);
|
| + if (containsAwait) {
|
| + hasAwaitOrYield.add(node);
|
| + }
|
| + return containsAwait;
|
| + }
|
| +
|
| + analyze(Fun node) {
|
| + 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);
|
| + 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);
|
| + } 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);
|
| + 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.";
|
| + }
|
| +
|
| + @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.";
|
| + }
|
| +
|
| + @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
|
| + ? 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
|
| + // 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;
|
| + }
|
| +}
|
|
|