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

Unified Diff: pkg/compiler/lib/src/js/rewrite_async.dart

Issue 953283003: Refactor rewrite_async (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Few more fixes Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | pkg/compiler/lib/src/ssa/builder.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
index c2948bfb56400326969932aa625213be5b75b498..28febf210108f86aa5795fa12f34d77db8af7a72 100644
--- a/pkg/compiler/lib/src/js/rewrite_async.dart
+++ b/pkg/compiler/lib/src/js/rewrite_async.dart
@@ -27,7 +27,7 @@ import "../helpers/helpers.dart";
/// (Currently handled in closure.dart).
///
/// Look at [visitFun], [visitDartYield] and [visitAwait] for more explanation.
-class AsyncRewriter extends js.NodeVisitor {
+abstract class AsyncRewriterBase extends js.NodeVisitor {
// Local variables are hoisted to the top of the function, so they are
// collected here.
@@ -108,53 +108,42 @@ class AsyncRewriter extends js.NodeVisitor {
/// }
/// }
///
- /// It is a parameter to the [bodyName] function, so that [asyncHelper] and
- /// [streamHelper] can call [bodyName] with the result of an awaited Future.
+ /// It is a parameter to the [body] function, so that [awaitStatement] can
+ /// call [body] with the result of an awaited Future.
+ js.VariableUse get result => new js.VariableUse(resultName);
String resultName;
/// A parameter to the [bodyName] function. Indicating if we are in success
/// or error case.
String errorCodeName;
- /// The name of the inner function that is scheduled to do each await/yield,
+ /// The inner function that is scheduled to do each await/yield,
/// and called to do a new iteration for sync*.
+ js.VariableUse get body => new js.VariableUse(bodyName);
String bodyName;
- /// The Completer that will finish an async function.
- ///
- /// Not used for sync* or async* functions.
- String completerName;
-
- /// The StreamController that controls an async* function.
- ///
- /// Not used for async and sync* functions
- 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]
+ /// 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]
+ js.VariableUse get goto => new js.VariableUse(gotoName);
String gotoName;
- /// The label of the current error handler.
+ /// Variable containing the label of the current error handler.
+ js.VariableUse get handler => new js.VariableUse(handlerName);
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.
+ js.VariableUse get next => new js.VariableUse(nextName);
String nextName;
- /// The stack of labels of finally blocks to assign to [nextName] if the
- /// async* [StreamSubscription] was canceled during a yield.
- String nextWhenCanceledName;
-
/// The current returned value (a finally block may overwrite it).
+ js.VariableUse get returnValue => new js.VariableUse(returnValueName);
String returnValueName;
- /// If we are in the process of handling an error, stores the current error.
+ /// Stores the current error when we are in the process of handling an error.
+ js.VariableUse get currentError => new js.VariableUse(currentErrorName);
String currentErrorName;
/// The label of the outer loop.
@@ -165,86 +154,9 @@ class AsyncRewriter extends js.NodeVisitor {
/// If javascript `this` is used, it is accessed via this variable, in the
/// [bodyName] function.
+ js.VariableUse get self => new js.VariableUse(selfName);
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 body function [bodyName]
- /// - The completer object [completerName]
- ///
- /// For a return it is called with:
- ///
- /// - The value to complete the completer with.
- /// - [error_codes.SUCCESS]
- /// - The completer object [completerName]
- ///
- /// For a throw it is called with:
- ///
- /// - The error to complete the completer with.
- /// - [error_codes.ERROR]
- /// - The completer object [completerName]
- final js.Expression asyncHelper;
-
- /// 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 body function [bodyName]
- /// - The controller object [controllerName]
- ///
- /// For a return it is called with:
- ///
- /// - null
- /// - null
- /// - The [controllerName]
- /// - null.
- final js.Expression streamHelper;
-
- /// Contructor used to initialize the [completerName] variable.
- ///
- /// Specific to async methods.
- final js.Expression newCompleter;
-
- /// Contructor used to initialize the [controllerName] variable.
- ///
- /// Specific to async* methods.
- final js.Expression newController;
-
- /// Used to get the `Stream` out of the [controllerName] variable.
- ///
- /// Specific to async* methods.
- final js.Expression streamOfController;
-
- /// Contructor creating the Iterable for a sync* method. Called with
- /// [bodyName].
- final js.Expression newIterable;
-
- /// A JS Expression that creates a marker showing that iteration is over.
- ///
- /// Called without arguments.
- final js.Expression endOfIteration;
-
- /// A JS Expression that creates a marker indicating a 'yield' statement.
- ///
- /// Called with the value to yield.
- final js.Expression yieldExpression;
-
- /// A JS Expression that creates a marker indication a 'yield*' statement.
- ///
- /// Called with the stream to yield from.
- final js.Expression yieldStarExpression;
-
- /// Used by sync* functions to throw exeptions.
- final js.Expression uncaughtErrorExpression;
-
final DiagnosticListener diagnosticListener;
// For error reporting only.
Spannable get spannable {
@@ -261,28 +173,18 @@ class AsyncRewriter extends js.NodeVisitor {
int tempVarHighWaterMark = 0;
Map<int, js.Expression> tempVarNames = new Map<int, js.Expression>();
- js.AsyncModifier async;
-
- bool get isSync => async == const js.AsyncModifier.sync();
- bool get isAsync => async == const js.AsyncModifier.async();
- bool get isSyncStar => async == const js.AsyncModifier.syncStar();
- bool get isAsyncStar => async == const js.AsyncModifier.asyncStar();
+ bool get isAsync => false;
+ bool get isSyncStar => false;
+ bool get isAsyncStar => false;
- AsyncRewriter(this.diagnosticListener,
- spannable,
- {this.asyncHelper,
- this.streamHelper,
- this.streamOfController,
- this.newCompleter,
- this.newController,
- this.endOfIteration,
- this.newIterable,
- this.yieldExpression,
- this.yieldStarExpression,
- this.uncaughtErrorExpression,
- this.safeVariableName})
+ AsyncRewriterBase(this.diagnosticListener,
+ spannable,
+ this.safeVariableName)
: _spannable = spannable;
+ /// Initialize names used by the subClass.
+ void initializeNames();
+
/// Main entry point.
/// Rewrites a sync*/async/async* function to an equivalent normal function.
///
@@ -290,9 +192,6 @@ class AsyncRewriter extends js.NodeVisitor {
js.Fun rewrite(js.Fun node, [Spannable spannable]) {
_spannable = spannable;
- async = node.asyncModifier;
- assert(!isSync);
-
analysis = new PreTranslationAnalysis(unsupported);
analysis.analyze(node);
@@ -300,18 +199,16 @@ class AsyncRewriter extends js.NodeVisitor {
// generated after the analysis.
resultName = freshName("result");
errorCodeName = freshName("errorCode");
- completerName = freshName("completer");
- controllerName = freshName("controller");
bodyName = freshName("body");
gotoName = freshName("goto");
handlerName = freshName("handler");
- errorName = freshName("error");
nextName = freshName("next");
- nextWhenCanceledName = freshName("nextWhenCanceled");
returnValueName = freshName("returnValue");
currentErrorName = freshName("currentError");
outerLabelName = freshName("outer");
selfName = freshName("self");
+ // Initialize names specific to the subclass.
+ initializeNames();
return node.accept(this);
}
@@ -402,7 +299,7 @@ class AsyncRewriter extends js.NodeVisitor {
/// This should be followed by a break for the goto to be executed. Use
/// [gotoWithBreak] or [addGoto] for this.
js.Statement setGotoVariable(int label) {
- return js.js.statement('# = #;', [gotoName, js.number(label)]);
+ return js.js.statement('# = #;', [goto, js.number(label)]);
}
/// Returns a block that has a goto to [label] including the break.
@@ -562,125 +459,54 @@ class AsyncRewriter extends js.NodeVisitor {
}
}
List<js.Node> visited = nodes.take(lastTransformIndex).map((js.Node node) {
- return node == null ? null : _storeIfNecessary(visitExpression(node));
+ return (node == null) ? null : _storeIfNecessary(visitExpression(node));
}).toList();
visited.addAll(nodes.skip(lastTransformIndex).map((js.Node node) {
- return node == null ? null : visitExpression(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
+ /// Emits the return block that all returns jumps to (after going
floitsch 2015/02/25 16:27:59 jump
sigurdm 2015/02/27 09:46:33 Done.
sigurdm 2015/02/27 09:46:33 Done.
/// through all the enclosing finally blocks). The jump to here is made in
/// [visitReturn].
- ///
- /// Returning from an async method calls the [asyncHelper] 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 addExit() {
- if (analysis.hasExplicitReturns || isAsyncStar) {
- beginLabel(exitLabel);
- } else {
- addStatement(new js.Comment("implicit return"));
- }
- switch (async) {
- case const js.AsyncModifier.async():
- addStatement(js.js.statement(
- "return #runtimeHelper(#returnValue, #successCode, "
- "#completer, null);", {
- "runtimeHelper": asyncHelper,
- "successCode": js.number(error_codes.SUCCESS),
- "returnValue": analysis.hasExplicitReturns
- ? returnValueName
- : new js.LiteralNull(),
- "completer": completerName}));
- break;
- case const js.AsyncModifier.syncStar():
- addStatement(js.js.statement('return #();', [endOfIteration]));
- break;
- case const js.AsyncModifier.asyncStar():
- addStatement(js.js.statement(
- "return #streamHelper(null, #successCode, #controller);", {
- "streamHelper": streamHelper,
- "successCode": js.number(error_codes.SUCCESS),
- "controller": controllerName}));
- break;
- default:
- diagnosticListener.internalError(
- spannable, "Internal error, unexpected asyncmodifier $async");
- }
- if (isAsync || isAsyncStar) {
- beginLabel(rethrowLabel);
- addStatement(js.js.statement(
- "return #thenHelper(#currentError, #errorCode, #controller);", {
- "thenHelper": isAsync ? asyncHelper : streamHelper,
- "errorCode": js.number(error_codes.ERROR),
- "currentError": currentErrorName,
- "controller": isAsync ? completerName : controllerName}));
- } else {
- assert(isSyncStar);
- beginLabel(rethrowLabel);
- addStatement(js.js.statement('return #(#);',
- [uncaughtErrorExpression, currentErrorName]));
- }
- }
+ void addSuccesExit();
- /// The initial call to [asyncHelper]/[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].
- js.Statement generateInitializer() {
- if (isAsync) {
- return js.js.statement(
- "return #asyncHelper(null, #body, #completer, null);", {
- "asyncHelper": asyncHelper,
- "body": bodyName,
- "completer": completerName,
- });
- } else if (isAsyncStar) {
- return js.js.statement(
- "return #streamOfController(#controller);", {
- "streamOfController": streamOfController,
- "controller": controllerName,
- });
- } else {
- throw diagnosticListener.internalError(
- spannable, "Unexpected asyncModifier: $async");
- }
+ /// Emits the block that control flows to if an error has been thrown
+ /// but not caught. (after going through all the enclosing finally blocks).
+ void addErrorExit();
+
+ void addFunctionExits() {
+ addSuccesExit();
+ addErrorExit();
}
+ /// Returns the rewritten function.
+ js.Fun generateFunction(List<js.Parameter> parameters,
sigurdm 2015/02/25 11:43:18 Needs a better name...
floitsch 2015/02/25 16:27:59 finishFunction ?
sigurdm 2015/02/27 09:46:33 Done.
+ js.Statement rewrittenBody,
+ js.VariableDeclarationList variableDeclarations);
+
+ Iterable<js.VariableInitialization> variableInitializations();
+
/// 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 nested function [bodyName]
- /// that can be called back by [asyncHelper]/[asyncStarHelper]/the [Iterator].
+ /// loop and a state variable [goto] inside a nested function [body]
+ /// that can be called back by [asyncStarHelper]/[asyncStarHelper]/the
+ /// [Iterator].
///
/// Local variables are hoisted outside the helper.
///
/// Awaits in async/async* are translated to code that remembers the current
- /// location (so the function can resume from where it was) followed by a call
- /// to the [asyncHelper]. The helper sets up the waiting for the awaited value
- /// and returns a future which is immediately returned by the translated
- /// await.
- /// Yields in async* are translated to a call to the [asyncStarHelper]. They,
- /// too, need to be prepared to be interrupted in case the stream is paused or
- /// canceled. (Currently we always suspend - this is different from the spec,
- /// see `streamHelper` in `js_helper.dart`).
- ///
- /// Yield/yield* in a sync* function is translated to a return of the value,
- /// wrapped into a "IterationMarker" that signals the type (yield or yield*).
- /// Sync* functions are executed on demand (when the user requests a value) by
- /// the Iterable that knows how to handle these values.
+ /// location (so the function can resume from where it was) followed by a
+ /// [awaitStatement]. The helper sets up the waiting for the awaited
+ /// value and returns a future which is immediately returned by the
+ /// [awaitStatement].
+ ///
+ /// Yields in sync*/async* are translated to a calls to helper functions.
+ /// (see [visitYield])
///
/// Simplified examples (not the exact translation, but intended to show the
/// ideas):
@@ -713,15 +539,15 @@ class AsyncRewriter extends js.NodeVisitor {
/// }
/// }
///
- /// Try/catch is implemented by maintaining [handlerName] to contain the label
- /// of the current handler. If [bodyName] throws, the caller should catch the
- /// error and recall [bodyName] with first argument [error_codes.ERROR] and
+ /// Try/catch is implemented by maintaining [handler] to contain the label
+ /// of the current handler. If [body] throws, the caller should catch the
+ /// error and recall [body] with first argument [error_codes.ERROR] and
/// second argument the error.
///
/// A `finally` clause is compiled similar to normal code, with the additional
/// complexity that `finally` clauses need to know where to jump to after the
/// clause is done. In the translation, each flow-path that enters a `finally`
- /// sets up the variable [nextName] with a stack of finally-blocks and a final
+ /// sets up the variable [next] with a stack of finally-blocks and a final
/// jump-target (exit, catch, ...).
///
/// function(x, y, z) async {
@@ -801,140 +627,57 @@ class AsyncRewriter extends js.NodeVisitor {
///
@override
js.Expression visitFun(js.Fun node) {
- if (isSync) return node;
-
beginLabel(newLabel("Function start"));
// AsyncStar needs a returnlabel for its handling of cancelation. See
// [visitDartYield].
- exitLabel =
- analysis.hasExplicitReturns || isAsyncStar ? newLabel("return") : null;
+ exitLabel = (analysis.hasExplicitReturns || isAsyncStar)
+ ? newLabel("return")
+ : null;
rethrowLabel = newLabel("rethrow");
handlerLabels[node] = rethrowLabel;
js.Statement body = node.body;
jumpTargets.add(node);
visitStatement(body);
jumpTargets.removeLast();
- addExit();
+ addFunctionExits();
List<js.SwitchClause> clauses = labelledParts.keys.map((label) {
return new js.Case(js.number(label), new js.Block(labelledParts[label]));
}).toList();
- js.Statement helperBody =
- new js.Switch(new js.VariableUse(gotoName), clauses);
+ js.Statement rewrittenBody =
+ new js.Switch(goto, clauses);
if (hasJumpThoughOuterLabel) {
- helperBody = new js.LabeledStatement(outerLabelName, helperBody);
+ rewrittenBody = new js.LabeledStatement(outerLabelName, rewrittenBody);
}
- List<js.VariableInitialization> inits = <js.VariableInitialization>[];
-
- js.VariableInitialization makeInit(String name, js.Expression initValue) {
- return new js.VariableInitialization(
- new js.VariableDeclaration(name), initValue);
- }
+ List<js.VariableInitialization> variables =
+ new List<js.VariableInitialization>();
- inits.add(makeInit(gotoName, js.number(0)));
- if (isAsync) {
- inits.add(makeInit(completerName, new js.New(newCompleter, [])));
- } else if (isAsyncStar) {
- inits.add(makeInit(controllerName,
- js.js('#(#)', [newController, bodyName])));
- }
- inits.add(makeInit(handlerName, js.number(rethrowLabel)));
- inits.add(makeInit(currentErrorName, null));
+ variables.add(_makeVariableInitializer(goto, js.number(0)));
+ variables.addAll(variableInitializations());
+ variables.add(
+ _makeVariableInitializer(handler, js.number(rethrowLabel)));
+ variables.add(_makeVariableInitializer(currentError, null));
if (analysis.hasFinally || (isAsyncStar && analysis.hasYield)) {
- inits.add(makeInit(nextName, null));
- }
- if (isAsyncStar && analysis.hasYield) {
- inits.add(makeInit(nextWhenCanceledName, null));
- }
- if (analysis.hasExplicitReturns && isAsync) {
- inits.add(makeInit(returnValueName, null));
+ variables.add(_makeVariableInitializer(next, null));
}
if (analysis.hasThis && !isSyncStar) {
// Sync* functions must remember `this` on the level of the outer
// function.
- inits.add(makeInit(selfName, js.js('this')));
+ variables.add(_makeVariableInitializer(self, js.js('this')));
}
- inits.addAll(localVariables.map((js.VariableDeclaration decl) {
- return new js.VariableInitialization(decl, null);
+ variables.addAll(localVariables.map(
+ (js.VariableDeclaration declaration) {
+ return new js.VariableInitialization(declaration, null);
}));
- inits.addAll(new Iterable.generate(tempVarHighWaterMark,
- (int i) => makeInit(useTempVar(i + 1).name, null)));
- js.VariableDeclarationList varDecl = new js.VariableDeclarationList(inits);
- // TODO(sigurdm): Explain the difference between these cases.
- if (isSyncStar) {
- return js.js("""
- function (#params) {
- if (#needsThis)
- var #self = this;
- return new #newIterable(function () {
- #varDecl;
- return function #body(#errorCode, #result) {
- if (#errorCode === #ERROR) {
- #currentError = #result;
- #goto = #handler;
- }
- while (true)
- #helperBody;
- };
- });
- }
- """, {
- "params": node.params,
- "needsThis": analysis.hasThis,
- "helperBody": helperBody,
- "varDecl": varDecl,
- "errorCode": errorCodeName,
- "newIterable": newIterable,
- "body": bodyName,
- "self": selfName,
- "result": resultName,
- "goto": gotoName,
- "handler": handlerName,
- "currentError": currentErrorName,
- "ERROR": js.number(error_codes.ERROR),
- });
- }
- return js.js("""
- function (#params) {
- #varDecl;
- function #bodyName(#errorCode, #result) {
- if (#hasYield)
- switch (#errorCode) {
- case #STREAM_WAS_CANCELED:
- #next = #nextWhenCanceled;
- #goto = #next.pop();
- break;
- case #ERROR:
- #currentError = #result;
- #goto = #handler;
- }
- else
- if (#errorCode === #ERROR) {
- #currentError = #result;
- #goto = #handler;
- }
- while (true)
- #helperBody;
- }
- #init;
- }""", {
- "params": node.params,
- "varDecl": varDecl,
- "STREAM_WAS_CANCELED": js.number(error_codes.STREAM_WAS_CANCELED),
- "ERROR": js.number(error_codes.ERROR),
- "hasYield": analysis.hasYield,
- "helperBody": helperBody,
- "init": generateInitializer(),
- "bodyName": bodyName,
- "currentError": currentErrorName,
- "goto": gotoName,
- "handler": handlerName,
- "next": nextName,
- "nextWhenCanceled": nextWhenCanceledName,
- "errorCode": errorCodeName,
- "result": resultName,
- });
+ variables.addAll(new Iterable.generate(tempVarHighWaterMark,
+ (int i) => _makeVariableInitializer(useTempVar(i + 1).name, null)));
+ js.VariableDeclarationList variableDeclarations =
+ new js.VariableDeclarationList(variables);
+
+ // TODO(sigurdm): Explain the difference between syncStar and the other
floitsch 2015/02/25 16:27:59 I think you can remove that todo now.
sigurdm 2015/02/27 09:46:33 Done.
+ // implementations of generateFunction.
+ return generateMainFunction(node.params, rewrittenBody, variableDeclarations);
}
@override
@@ -981,7 +724,9 @@ class AsyncRewriter extends js.NodeVisitor {
}
}
- /// An await is translated to a call to [asyncHelper]/[streamHelper].
+ js.Statement awaitStatement(js.Expression value);
+
+ /// An await is translated to a [awaitStatement.
///
/// See the comments of [visitFun] for an example.
@override
@@ -990,28 +735,19 @@ class AsyncRewriter extends js.NodeVisitor {
int afterAwait = newLabel("returning from await.");
withExpression(node.expression, (js.Expression value) {
addStatement(setGotoVariable(afterAwait));
- addStatement(js.js.statement("""
- return #asyncHelper(#value,
- #body,
- #controller);
- """, {
- "asyncHelper": isAsync ? asyncHelper : streamHelper,
- "value": value,
- "body": bodyName,
- "controller": isAsync ? completerName : controllerName,
- }));
+ addStatement(awaitStatement(value));
}, store: false);
beginLabel(afterAwait);
- return new js.VariableUse(resultName);
+ return result;
}
/// Checks if [node] is the variable named [resultName].
///
- /// [resultName] is used to hold the result of a transformed computation
+ /// [result] 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
+ /// visiting it returns [result], it is not redundantly assigned to itself
/// again.
bool isResult(js.Expression node) {
return node is js.VariableUse && node.name == resultName;
@@ -1025,7 +761,7 @@ class AsyncRewriter extends js.NodeVisitor {
withExpression(node.left, (js.Expression left) {
js.Statement assignLeft = isResult(left)
? new js.Block.empty()
- : js.js.statement('# = #;', [resultName, left]);
+ : js.js.statement('# = #;', [result, left]);
if (node.op == "||") {
addStatement(js.js.statement('if (#) {#} else #',
[left, gotoAndBreak(thenLabel), assignLeft]));
@@ -1039,11 +775,11 @@ class AsyncRewriter extends js.NodeVisitor {
beginLabel(thenLabel);
withExpression(node.right, (js.Expression value) {
if (!isResult(value)) {
- addStatement(js.js.statement('# = #;', [resultName, value]));
+ addStatement(js.js.statement('# = #;', [result, value]));
}
}, store: false);
beginLabel(joinLabel);
- return new js.VariableUse(resultName);
+ return result;
}
return withExpression2(node.left, node.right,
@@ -1104,24 +840,24 @@ class AsyncRewriter extends js.NodeVisitor {
int elseLabel = newLabel("else");
withExpression(node.condition, (js.Expression condition) {
addStatement(js.js.statement('# = # ? # : #;',
- [gotoName, condition, js.number(thenLabel), js.number(elseLabel)]));
+ [goto, condition, js.number(thenLabel), js.number(elseLabel)]));
}, store: false);
addBreak();
beginLabel(thenLabel);
withExpression(node.then, (js.Expression value) {
if (!isResult(value)) {
- addStatement(js.js.statement('# = #;', [resultName, value]));
+ addStatement(js.js.statement('# = #;', [result, value]));
}
}, store: false);
addGoto(joinLabel);
beginLabel(elseLabel);
withExpression(node.otherwise, (js.Expression value) {
if (!isResult(value)) {
- addStatement(js.js.statement('# = #;', [resultName, value]));
+ addStatement(js.js.statement('# = #;', [result, value]));
}
}, store: false);
beginLabel(joinLabel);
- return new js.VariableUse(resultName);
+ return result;
}
@override
@@ -1147,7 +883,7 @@ class AsyncRewriter extends js.NodeVisitor {
/// 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.
+ /// the target. For that [next] is used as a stack of places to go.
///
/// See also [visitFun].
void translateJump(js.Node target, int targetLabel) {
@@ -1171,7 +907,7 @@ class AsyncRewriter extends js.NodeVisitor {
if (jumpStack.isNotEmpty) {
js.Expression jsJumpStack = new js.ArrayInitializer(
jumpStack.map((int label) => js.number(label)).toList());
- addStatement(js.js.statement("# = #;", [nextName, jsJumpStack]));
+ addStatement(js.js.statement("# = #;", [next, jsJumpStack]));
}
addGoto(firstTarget);
}
@@ -1316,13 +1052,14 @@ class AsyncRewriter extends js.NodeVisitor {
}
int thenLabel = newLabel("then");
int joinLabel = newLabel("join");
- int elseLabel =
- node.otherwise is js.EmptyStatement ? joinLabel : newLabel("else");
+ int elseLabel = (node.otherwise is js.EmptyStatement)
+ ? joinLabel
+ : newLabel("else");
withExpression(node.condition, (js.Expression condition) {
addExpressionStatement(
new js.Assignment(
- new js.VariableUse(gotoName),
+ goto,
new js.Conditional(
condition,
js.number(thenLabel),
@@ -1493,11 +1230,11 @@ class AsyncRewriter extends js.NodeVisitor {
@override
void visitReturn(js.Return node) {
- assert(node.value == null || !isSyncStar && !isAsyncStar);
+ assert(node.value == null || (!isSyncStar && !isAsyncStar));
js.Node target = analysis.targets[node];
if (node.value != null) {
withExpression(node.value, (js.Expression value) {
- addStatement(js.js.statement("# = #;", [returnValueName, value]));
+ addStatement(js.js.statement("# = #;", [returnValue, value]));
}, store: false);
}
translateJump(target, exitLabel);
@@ -1599,7 +1336,7 @@ class AsyncRewriter extends js.NodeVisitor {
@override
js.Expression visitThis(js.This node) {
- return new js.VariableUse(selfName);
+ return self;
}
@override
@@ -1610,9 +1347,10 @@ class AsyncRewriter extends js.NodeVisitor {
}
setErrorHandler([int errorHandler]) {
- addExpressionStatement(new js.Assignment(
- new js.VariableUse(handlerName),
- errorHandler == null ? currentErrorHandler : js.number(errorHandler)));
+ js.Expression label = (errorHandler == null)
+ ? currentErrorHandler
+ : js.number(errorHandler);
+ addStatement(js.js.statement('# = #;',[handler, label]));
}
List<int> _finalliesUpToAndEnclosingHandler() {
@@ -1678,8 +1416,7 @@ class AsyncRewriter extends js.NodeVisitor {
} else {
// The handler is reset as the first thing in the finally block.
addStatement(
- js.js.statement("# = [#];",
- [nextName, js.number(afterFinallyLabel)]));
+ js.js.statement("# = [#];", [next, js.number(afterFinallyLabel)]));
addGoto(finallyLabel);
}
@@ -1697,16 +1434,14 @@ class AsyncRewriter extends js.NodeVisitor {
localVariables.add(new js.VariableDeclaration(errorRename));
variableRenamings
.add(new Pair(node.catchPart.declaration.name, errorRename));
- addExpressionStatement(new js.Assignment(
- new js.VariableUse(errorRename),
- new js.VariableUse(currentErrorName)));
+ addStatement(js.js.statement("# = #;", [errorRename, currentError]));
visitStatement(node.catchPart.body);
variableRenamings.removeLast();
if (node.finallyPart != null) {
// The error has been caught, so after the finally, continue after the
// try.
addStatement(js.js.statement("# = [#];",
- [nextName, js.number(afterFinallyLabel)]));
+ [next, js.number(afterFinallyLabel)]));
addGoto(finallyLabel);
} else {
addGoto(afterFinallyLabel);
@@ -1726,7 +1461,7 @@ class AsyncRewriter extends js.NodeVisitor {
// [enclosingFinallies] can be empty if there is no surrounding finally
// blocks. Then [nextLabel] will be [rethrowLabel].
addStatement(
- js.js.statement("# = #;", [nextName, new js.ArrayInitializer(
+ js.js.statement("# = #;", [next, new js.ArrayInitializer(
enclosingFinallies.map(js.number).toList())]));
}
if (node.finallyPart == null) {
@@ -1742,7 +1477,7 @@ class AsyncRewriter extends js.NodeVisitor {
setErrorHandler();
visitStatement(node.finallyPart);
addStatement(new js.Comment("// goto the next finally handler"));
- addStatement(js.js.statement("# = #.pop();", [gotoName, nextName]));
+ addStatement(js.js.statement("# = #.pop();", [goto, next]));
addBreak();
}
beginLabel(afterFinallyLabel);
@@ -1814,12 +1549,220 @@ class AsyncRewriter extends js.NodeVisitor {
beginLabel(afterLabel);
}
+ addYield(js.DartYield node, js.Expression expression);
+
+ @override
+ void visitDartYield(js.DartYield node) {
floitsch 2015/02/25 16:27:59 Ideally this one, too, would only be shared by the
sigurdm 2015/02/27 09:46:33 Left it for now
+ print(this.runtimeType);
floitsch 2015/02/25 16:27:59 debug print.
sigurdm 2015/02/27 09:46:33 Done.
+ 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, (js.Expression expression) {
+ addStatement(setGotoVariable(label));
+ addYield(node, expression);
+ }, store: false);
+ beginLabel(label);
+ }
+}
+
+js.VariableInitialization
+ _makeVariableInitializer(dynamic variable, js.Expression initValue) {
+ js.VariableDeclaration declaration;
+ if (variable is js.VariableUse) {
+ declaration = new js.VariableDeclaration(variable.name);
+ } else if (variable is String) {
+ declaration = new js.VariableDeclaration(variable);
+ } else {
+ assert(variable is js.VariableDeclaration);
+ declaration = variable;
+ }
+ return new js.VariableInitialization(declaration, initValue);
+}
+
+class AsyncRewriter extends AsyncRewriterBase {
+
+ bool get isAsync => true;
+
+ /// The Completer that will finish an async function.
+ ///
+ /// Not used for sync* or async* functions.
+ String completerName;
+ js.VariableUse get completer => new js.VariableUse(completerName);
+
+ /// 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 body function [bodyName]
+ /// - The completer object [completer]
+ ///
+ /// For a return it is called with:
+ ///
+ /// - The value to complete the completer with.
+ /// - [error_codes.SUCCESS]
+ /// - The completer object [completer]
+ ///
+ /// For a throw it is called with:
+ ///
+ /// - The error to complete the completer with.
+ /// - [error_codes.ERROR]
+ /// - The completer object [completer]
+ final js.Expression asyncHelper;
+
+ /// Contructor used to initialize the [completer] variable.
+ ///
+ /// Specific to async methods.
+ final js.Expression newCompleter;
+
+
+ AsyncRewriter(DiagnosticListener diagnosticListener,
+ spannable,
+ {this.asyncHelper,
+ this.newCompleter,
+ safeVariableName})
+ : super(diagnosticListener,
+ spannable,
+ safeVariableName);
+
+ @override
+ void addYield(js.DartYield node, js.Expression expression) {
+ diagnosticListener.internalError(spannable,
+ "Yield in non-generating async function");
+ }
+
+ void addErrorExit() {
+ beginLabel(rethrowLabel);
+ addStatement(js.js.statement(
+ "return #thenHelper(#currentError, #errorCode, #completer);", {
+ "thenHelper": asyncHelper,
+ "errorCode": js.number(error_codes.ERROR),
+ "currentError": currentError,
+ "completer": completer}));
+ }
+
+ /// Returning from an async method calls the [asyncStarHelper] with the result.
floitsch 2015/02/25 16:27:59 long line.
sigurdm 2015/02/27 09:46:33 Done.
+ /// (the result might have been stored in [returnValue] by some finally
+ /// block).
+ void addSuccesExit() {
+ if (analysis.hasExplicitReturns) {
+ beginLabel(exitLabel);
+ } else {
+ addStatement(new js.Comment("implicit return"));
+ }
+ addStatement(js.js.statement(
+ "return #runtimeHelper(#returnValue, #successCode, "
+ "#completer, null);", {
+ "runtimeHelper": asyncHelper,
+ "successCode": js.number(error_codes.SUCCESS),
+ "returnValue": analysis.hasExplicitReturns
+ ? returnValue
+ : new js.LiteralNull(),
+ "completer": completer}));
+ }
+
+ @override
+ Iterable<js.VariableInitialization> variableInitializations() {
+ List<js.VariableInitialization> variables =
+ new List<js.VariableInitialization>();
+ variables.add(_makeVariableInitializer(completer,
+ new js.New(newCompleter, [])));
+ if (analysis.hasExplicitReturns) {
+ variables.add(_makeVariableInitializer(returnValue, null));
+ }
+ return variables;
+ }
+
+ @override
+ void initializeNames() {
+ completerName = freshName("completer");
+ }
+
+ @override
+ js.Statement awaitStatement(js.Expression value) {
+ return js.js.statement("""
+ return #asyncHelper(#value,
+ #body,
+ #completer);
+ """, {
+ "asyncHelper": asyncHelper,
+ "value": value,
+ "body": body,
+ "completer": completer});
+ }
+
+ @override
+ js.Fun generateMainFunction(List<js.Parameter> parameters,
+ js.Statement rewrittenBody,
floitsch 2015/02/25 16:27:59 indentation.
sigurdm 2015/02/27 09:46:33 Done.
+ js.VariableDeclarationList variableDeclarations) {
+ return js.js("""
+ function (#parameters) {
+ #variableDeclarations;
+ function #bodyName(#errorCode, #result) {
+ if (#errorCode === #ERROR) {
+ #currentError = #result;
+ #goto = #handler;
+ }
+ while (true)
floitsch 2015/02/25 16:27:59 In the next? CL please move the "while" to the rew
sigurdm 2015/02/27 09:46:33 Did it here.
+ #rewrittenBody;
+ }
+ return #asyncHelper(null, #bodyName, #completer, null);
+ }""", {
+ "parameters": parameters,
+ "variableDeclarations": variableDeclarations,
+ "ERROR": js.number(error_codes.ERROR),
+ "rewrittenBody": rewrittenBody,
+ "bodyName": bodyName,
+ "currentError": currentError,
+ "goto": goto,
+ "handler": handler,
+ "errorCode": errorCodeName,
+ "result": resultName,
+ "asyncHelper": asyncHelper,
+ "completer": completer,
+ });
+ }
+}
+
+class SyncStarRewriter extends AsyncRewriterBase {
+
+ bool get isSyncStar => true;
+
+ /// Contructor creating the Iterable for a sync* method. Called with
+ /// [bodyName].
+ final js.Expression newIterable;
+
+ /// A JS Expression that creates a marker showing that iteration is over.
+ ///
+ /// Called without arguments.
+ final js.Expression endOfIteration;
+
+ /// A JS Expression that creates a marker indication a 'yield*' statement.
+ ///
+ /// Called with the stream to yield from.
+ final js.Expression yieldStarExpression;
+
+ /// Used by sync* functions to throw exeptions.
+ final js.Expression uncaughtErrorExpression;
+
+ SyncStarRewriter(DiagnosticListener diagnosticListener,
+ spannable,
+ {this.endOfIteration,
+ this.newIterable,
+ this.yieldStarExpression,
+ this.uncaughtErrorExpression,
+ safeVariableName})
+ : super(diagnosticListener,
+ spannable,
+ safeVariableName);
+
/// Translates a yield/yield* in an sync*.
///
/// `yield` in a sync* function just returns [value].
/// `yield*` wraps [value] in a [yieldStarExpression] and returns it.
- void addSyncYield(js.DartYield node, js.Expression expression) {
- assert(isSyncStar);
+ @override
+ void addYield(js.DartYield node, js.Expression expression) {
if (node.hasStar) {
addStatement(
new js.Return(new js.Call(yieldStarExpression, [expression])));
@@ -1828,16 +1771,150 @@ class AsyncRewriter extends js.NodeVisitor {
}
}
+ @override
+ js.Fun generateMainFunction(List<js.Parameter> params,
+ js.Statement rewrittenBody,
+ js.VariableDeclarationList variableDeclarations) {
+ return js.js("""
+ function (#params) {
+ if (#needsThis)
+ var #self = this;
+ return new #newIterable(function () {
+ #varDecl;
+ return function #body(#errorCode, #result) {
+ if (#errorCode === #ERROR) {
+ #currentError = #result;
+ #goto = #handler;
+ }
+ while (true)
+ #helperBody;
+ };
+ });
+ }
+ """, {
+ "params": params,
+ "needsThis": analysis.hasThis,
+ "helperBody": rewrittenBody,
+ "varDecl": variableDeclarations,
+ "errorCode": errorCodeName,
+ "newIterable": newIterable,
+ "body": bodyName,
+ "self": selfName,
+ "result": resultName,
+ "goto": goto,
+ "handler": handler,
+ "currentError": currentErrorName,
+ "ERROR": js.number(error_codes.ERROR),
+ });
+ }
+
+ void addErrorExit() {
+ beginLabel(rethrowLabel);
+ addStatement(js.js.statement('return #(#);',
+ [uncaughtErrorExpression, currentError]));
+ }
+
+ /// Returning from a sync* function returns an [endOfIteration] marker.
+ void addSuccesExit() {
+ if (analysis.hasExplicitReturns) {
+ beginLabel(exitLabel);
+ } else {
+ addStatement(new js.Comment("implicit return"));
+ }
+ addStatement(js.js.statement('return #();', [endOfIteration]));
+ }
+
+ @override
+ Iterable<js.VariableInitialization> variableInitializations() {
+ List<js.VariableInitialization> variables =
+ new List<js.VariableInitialization>();
+ return variables;
+ }
+
+ @override
+ js.Statement awaitStatement(js.Expression value) {
+ throw diagnosticListener.internalError(spannable,
+ "Sync* functions cannot contain await statements.");
+ }
+
+ @override
+ void initializeNames() {}
+}
+
+class AsyncStarRewriter extends AsyncRewriterBase {
+
+ bool get isAsyncStar => true;
+
+ /// The stack of labels of finally blocks to assign to [next] if the
+ /// async* [StreamSubscription] was canceled during a yield.
+ js.VariableUse get nextWhenCanceled {
+ return new js.VariableUse(nextWhenCanceledName);
+ }
+ String nextWhenCanceledName;
+
+ /// The StreamController that controls an async* function.
+ String controllerName;
+ js.VariableUse get controller => new js.VariableUse(controllerName);
+
+ /// 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 body function [bodyName]
+ /// - The controller object [controllerName]
+ ///
+ /// For a return it is called with:
+ ///
+ /// - null
+ /// - null
+ /// - The [controllerName]
+ /// - null.
+ final js.Expression asyncStarHelper;
+
+ /// Contructor used to initialize the [controllerName] variable.
+ ///
+ /// Specific to async* methods.
+ final js.Expression newController;
+
+ /// Used to get the `Stream` out of the [controllerName] variable.
+ final js.Expression streamOfController;
+
+ /// A JS Expression that creates a marker indicating a 'yield' statement.
+ ///
+ /// Called with the value to yield.
+ final js.Expression yieldExpression;
+
+ /// A JS Expression that creates a marker indication a 'yield*' statement.
+ ///
+ /// Called with the stream to yield from.
+ final js.Expression yieldStarExpression;
+
+ AsyncStarRewriter(DiagnosticListener diagnosticListener,
+ spannable,
+ {this.asyncStarHelper,
+ this.streamOfController,
+ this.newController,
+ this.yieldExpression,
+ this.yieldStarExpression,
+ String safeVariableName(String original)})
+ : super(diagnosticListener,
+ spannable,
+ safeVariableName);
+
+
/// 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 [asyncStarHelper]
/// distinguish them.
- /// Also [nextWhenCanceledName] is set up to contain the finally blocks that
+ /// Also [nextWhenCanceled] is set up to contain the finally blocks that
/// must be run in case the stream was canceled.
- void addAsyncYield(js.DartYield node, js.Expression expression) {
- assert(isAsyncStar);
+ @override
+ void addYield(js.DartYield node, js.Expression expression) {
// 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.
@@ -1846,34 +1923,119 @@ class AsyncRewriter extends js.NodeVisitor {
.where((js.Node node) => finallyLabels[node] != null)
.map((js.Block node) => finallyLabels[node]));
addStatement(js.js.statement("# = #;",
- [nextWhenCanceledName, new js.ArrayInitializer(
+ [nextWhenCanceled, new js.ArrayInitializer(
enclosingFinallyLabels.map(js.number).toList())]));
addStatement(js.js.statement("""
- return #streamHelper(#yieldExpression(#expression), #body,
+ return #asyncStarHelper(#yieldExpression(#expression), #body,
#controller);""", {
- "streamHelper": streamHelper,
+ "asyncStarHelper": asyncStarHelper,
"yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression,
"expression": expression,
- "body": bodyName,
+ "body": body,
"controller": controllerName,
}));
}
@override
- void visitDartYield(js.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, (js.Expression expression) {
- addStatement(setGotoVariable(label));
- if (isSyncStar) {
- addSyncYield(node, expression);
- } else {
- addAsyncYield(node, expression);
- }
- }, store: false);
- beginLabel(label);
+ js.Fun generateMainFunction(List<js.Parameter> parameters,
+ js.Statement rewrittenBody,
+ js.VariableDeclarationList variableDeclarations) {
+ return js.js("""
+ function (#parameters) {
+ #variableDeclarations;
+ function #bodyName(#errorCode, #result) {
+ if (#hasYield)
+ switch (#errorCode) {
+ case #STREAM_WAS_CANCELED:
+ #next = #nextWhenCanceled;
+ #goto = #next.pop();
+ break;
+ case #ERROR:
+ #currentError = #result;
+ #goto = #handler;
+ }
+ else
+ if (#errorCode === #ERROR) {
+ #currentError = #result;
+ #goto = #handler;
+ }
+ while (true)
+ #rewrittenBody;
+ }
+ return #streamOfController(#controller);
+ }""", {
+ "parameters": parameters,
+ "variableDeclarations": variableDeclarations,
+ "STREAM_WAS_CANCELED": js.number(error_codes.STREAM_WAS_CANCELED),
+ "ERROR": js.number(error_codes.ERROR),
+ "hasYield": analysis.hasYield,
+ "rewrittenBody": rewrittenBody,
+ "bodyName": bodyName,
+ "currentError": currentError,
+ "goto": goto,
+ "handler": handler,
+ "next": next,
+ "nextWhenCanceled": nextWhenCanceled,
+ "errorCode": errorCodeName,
+ "result": resultName,
+ "streamOfController": streamOfController,
+ "controller": controllerName,
+ });
+ }
+
+ @override
+ void addErrorExit() {
+ beginLabel(rethrowLabel);
+ addStatement(js.js.statement(
+ "return #asyncHelper(#currentError, #errorCode, #controller);", {
+ "asyncHelper": asyncStarHelper,
+ "errorCode": js.number(error_codes.ERROR),
+ "currentError": currentError,
+ "controller": controllerName}));
+ }
+
+ /// Returning from an async* function calls the [streamHelper] with an
+ /// [endOfIteration] marker.
+ @override
+ void addSuccesExit() {
+ beginLabel(exitLabel);
+
+ addStatement(js.js.statement(
+ "return #streamHelper(null, #successCode, #controller);", {
+ "streamHelper": asyncStarHelper,
+ "successCode": js.number(error_codes.SUCCESS),
+ "controller": controllerName}));
+ }
+
+ @override
+ Iterable<js.VariableInitialization> variableInitializations() {
+ List<js.VariableInitialization> variables =
+ new List<js.VariableInitialization>();
+ variables.add(_makeVariableInitializer(controller,
+ js.js('#(#)', [newController, bodyName])));
+ if (analysis.hasYield) {
+ variables.add(_makeVariableInitializer(nextWhenCanceled, null));
+ }
+ return variables;
+ }
+
+ @override
+ void initializeNames() {
+ controllerName = freshName("controller");
+ nextWhenCanceledName = freshName("nextWhenCanceled");
+ }
+
+ @override
+ js.Statement awaitStatement(js.Expression value) {
+ return js.js.statement("""
+ return #asyncHelper(#value,
+ #body,
+ #controller);
+ """, {
+ "asyncHelper": asyncStarHelper,
+ "value": value,
+ "body": body,
+ "controller": controllerName});
}
}
« no previous file with comments | « no previous file | pkg/compiler/lib/src/ssa/builder.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698