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)); |
+} |
+ |