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