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

Unified Diff: sdk/lib/_internal/compiler/js_lib/js_helper.dart

Issue 839323003: Implementation of async-await transformation on js ast. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Add missing functions to the MockCompiler's library 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
Index: sdk/lib/_internal/compiler/js_lib/js_helper.dart
diff --git a/sdk/lib/_internal/compiler/js_lib/js_helper.dart b/sdk/lib/_internal/compiler/js_lib/js_helper.dart
index dd7f4dc27e6cc0e9857ca4a82f522cec1e7c75ab..8f75db6cdc5680aec42c1a713557564e233fa14e 100644
--- a/sdk/lib/_internal/compiler/js_lib/js_helper.dart
+++ b/sdk/lib/_internal/compiler/js_lib/js_helper.dart
@@ -25,7 +25,13 @@ import 'dart:_isolate_helper' show
enterJsAsync,
isWorker;
-import 'dart:async' show Future, DeferredLoadException, Completer;
+import 'dart:async'
+ show Future,
+ DeferredLoadException,
+ Completer,
+ StreamController,
+ Stream,
+ StreamSubscription;
import 'dart:_foreign_helper' show
DART_CLOSURE_TO_JS,
@@ -3455,3 +3461,262 @@ void badMain() {
void mainHasTooManyParameters() {
throw new MainError("'main' expects too many parameters.");
}
+
+/// Runtime support for async-await transformation.
+///
+/// This function is called by a transformed function on each await and return
+/// in the untransformed function, and before starting.
+///
+/// If [object] is not a future it will be wrapped in a `new Future.value`.
+///
+/// If [helperCallback] is null it indicates a return from the async function,
+/// and we complete the completer with object.
+///
+/// Otherwise [helperCallback] is set up to be called when the future is
+/// successfull and [errorCallback] if it is completed with an error.
+///
+/// If helperCallback or errorCallback throws we complete the completer with the
+/// error.
+///
+/// Returns the future of the completer for convenience of the first call.
+dynamic thenHelper(dynamic object,
+ dynamic /* js function */ helperCallback,
+ Completer completer,
+ dynamic /* js function */ errorCallback) {
+ if (helperCallback == null) {
+ completer.complete(object);
+ return;
+ }
+ Future future = object is Future ? object : new Future.value(object);
+ future.then(_wrapJsFunction(helperCallback, completer),
+ onError: _wrapJsFunction(errorCallback, completer));
+ return completer.future;
+}
+
+// Used to avoid creating two identical closure classes in [thenHelper].
+Function _wrapJsFunction(dynamic /* js function */ function,
+ Completer completer) {
+ return (result) {
+ try {
+ JS('', '#(#)', function, result);
+ } catch (e, st) {
+ completer.completeError(e, st);
+ }
+ };
+}
+
+
+/// Runtime support for async*.
+///
+/// Called by the transformed function for each original return, await, yield,
+/// yield* and before starting the function.
+///
+/// On a return call with helperCallback == null, and we close the stream.
+///
+/// If object is an [IterationMarker] it indicates a yield or yield*.
+/// If the stream subscription has been canceled we schedule the errorCallback
+/// that should run through enclosing finally blocks before exiting.
+///
+/// If it is a single-yield we add the value to the stream.
+/// If the stream subscription has been paused we return early, otherwise
+/// we schedule the helper function to be executed again.
+///
+/// If it is a yield* we start listening to the yielded stream, and add all
+/// events and errors to our own controller (taking care if the subscription has
+/// been paused or canceled) - when the sub-stream is done we schedule the
+/// helper function again.
+///
+/// If object is not and [IterationMarker] it indicates an await.
+/// If object is not a Future, it is wrapped in a `Future.value`.
+/// Then set up helperCallBack to be called on successfull completion of the
+/// error
+///
+/// If helperCallback or errorCallback throws we add the error to the stream
+/// If the object is a YIELD_SINGLE we add the object to the stream, and if the
+/// stream has been canceled call the errorCallBack.
+dynamic streamHelper(dynamic object,
+ dynamic /* js function */ helperCallback,
+ AsyncStarStreamController controller,
+ dynamic /* js function */ errorCallback) {
+ if (helperCallback == null) {
+ // This happens on return from the async* function.
+ controller.close();
+ return;
+ }
+
+ if (object is IterationMarker) {
+ if (controller.stopRunning) {
+ _wrapJsFunctionForStream(errorCallback, controller)();
+ return;
+ }
+ if (object.state == IterationMarker.YIELD_SINGLE) {
+ controller.add(object.value);
+ } else if (object.state == IterationMarker.YIELD_STAR) {
+ Stream stream = object.value;
+ controller.subSubscription = stream.listen((value) {
+ if (controller.stopRunning) {
+ controller.subSubscription.cancel();
+ _wrapJsFunctionForStream(errorCallback, controller)(null);
+ return;
+ }
+ controller.add(value);
+ }, onError: (error, stackTrace) {
+ if (controller.stopRunning) {
+ controller.subSubscription.cancel();
+ // This will run the enclosing finally blocks.
+ _wrapJsFunctionForStream(errorCallback, controller)(null);
+ return;
+ }
+ controller.addError(error, stackTrace);
+ if (controller.isPaused) {
+ controller.subSubscription.pause();
+ }
+ }, onDone: () {
+ controller.subSubscription = null;
+ // Resume producing.
+ streamHelper(null, helperCallback, controller, null);
+ });
+ return;
+ }
+ if (controller.isPaused) {
+ return;
+ }
+ object = null;
+ }
+ Future future = object is Future ? object : new Future.value(object);
+ future.then(_wrapJsFunctionForStream(helperCallback, controller),
+ onError: errorCallback = null
+ ? null
+ : _wrapJsFunctionForStream(errorCallback, controller));
+ return controller.stream;
+}
+
+/// A wrapper around a [StreamController] that remembers if that controller
+/// got a cancel.
+///
+/// Also has a subSubscription that when not null will provide events for the
+/// stream, and will be paused and resumed along with this controller.
+class AsyncStarStreamController {
+ StreamController controller;
+ StreamSubscription subSubscription = null;
+ Stream get stream => controller.stream;
+ bool stopRunning = false;
+ bool get isPaused => controller.isPaused;
+ add(event) => controller.add(event);
+ addError(error, stackTrace) => controller.addError(error, stackTrace);
+ close() => controller.close();
+
+ AsyncStarStreamController(helperCallBack) {
+ controller = new StreamController(
+ onPause: () {
+ if (subSubscription != null) {
+ subSubscription.pause();
+ }
+ },
+ onResume: () {
+ if (subSubscription == null) {
+ helperCallBack(null, helperCallBack, controller, null);
+ } else {
+ subSubscription.resume();
+ }
+ }, onCancel: () {
+ stopRunning = true;
+ });
+ }
+}
+
+makeAsyncStarController(helperCallback) {
+ return new AsyncStarStreamController(helperCallback);
+}
+
+// Used to avoid creating identical closure classes in [streamHelper].
+Function _wrapJsFunctionForStream(dynamic /* js function */ function,
+ AsyncStarStreamController controller) {
+ return (result) {
+ try {
+ JS('', '#(#)', function, result);
+ } catch (e, st) {
+ controller.addError(e, st);
+ }
+ };
+}
+
+
+class IterationMarker {
+ static const YIELD_SINGLE = 0;
+ static const YIELD_STAR = 1;
+ static const ITERATION_ENDED = 2;
+
+ final value;
+ final int state;
+
+ IterationMarker._(this.state, this.value);
+
+ static yieldStar(Iterable iterable) {
+ return new IterationMarker._(YIELD_STAR, iterable);
+ }
+
+ static endOfIteration() {
+ return new IterationMarker._(ITERATION_ENDED, null);
+ }
+
+ static yieldSingle(value) {
+ return new IterationMarker._(YIELD_SINGLE, value);
+ }
+
+ toString() => "IterationMarker($state, $value)";
+}
+
+class SyncStarIterator implements Iterator {
+ final Function _helper;
+
+ // if [runningNested] this will be the nested iterator, otherwise it will be
+ // the current value.
+ dynamic _current = null;
+ bool _runningNested = false;
+
+ get current => _runningNested ? _current.current : _current;
+
+ SyncStarIterator(helper)
+ : _helper = ((arg) => JS('', '#(#)', helper, arg));
+
+ bool moveNext() {
+ if (_runningNested) {
+ if (_current.moveNext()) {
+ return true;
+ } else {
+ _runningNested = false;
+ }
+ }
+ _current = _helper(null);
+ if (_current is IterationMarker) {
+ if (_current.state == IterationMarker.ITERATION_ENDED) {
+ _current = null;
+ // We rely on [_helper] to repeatedly return `ITERATION_ENDED`.
+ return false;
+ } else {
+ assert(_current.state == IterationMarker.YIELD_STAR);
+ _current = _current.value.iterator;
+ _runningNested = true;
+ return moveNext();
+ }
+ }
+ return true;
+ }
+}
+
+/// An Iterable corresponding to a sync* method.
+///
+/// Each invocation of a sync* method will return a new instance of this class.
+class SyncStarIterable extends IterableBase {
+ // This is a function that will return a helper function that does the
+ // iteration of the sync*.
+ //
+ // Each invocation should give a helper with fresh state.
+ final dynamic /* js function */ _outerHelper;
+
+ SyncStarIterable(this._outerHelper);
+
+ Iterator get iterator => new SyncStarIterator(JS('', '#()', _outerHelper));
+}
+

Powered by Google App Engine
This is Rietveld 408576698