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 f89fe9da3e25849437a53960ce2d44db169e032f..f964d399789596eb256b995c627161e682491f74 100644 |
--- a/sdk/lib/_internal/compiler/js_lib/js_helper.dart |
+++ b/sdk/lib/_internal/compiler/js_lib/js_helper.dart |
@@ -21,11 +21,18 @@ import 'dart:_js_embedded_names' show |
import 'dart:collection'; |
import 'dart:_isolate_helper' show |
IsolateNatives, |
- leaveJsAsync, |
enterJsAsync, |
- isWorker; |
- |
-import 'dart:async' show Future, DeferredLoadException, Completer; |
+ isWorker, |
+ leaveJsAsync; |
+ |
+import 'dart:async' show |
+ Future, |
+ DeferredLoadException, |
+ Completer, |
+ StreamController, |
+ Stream, |
+ StreamSubscription, |
+ scheduleMicrotask; |
import 'dart:_foreign_helper' show |
DART_CLOSURE_TO_JS, |
@@ -3462,3 +3469,255 @@ 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(_wrapJsFunctionForThenHelper(helperCallback, completer), |
+ onError: (errorCallback == null) |
+ ? null |
+ : _wrapJsFunctionForThenHelper(errorCallback, completer)); |
+ return completer.future; |
+} |
+ |
+Function _wrapJsFunctionForThenHelper(dynamic /* js function */ function, |
+ Completer completer) { |
+ return (result) { |
+ try { |
+ JS('', '#(#)', function, result); |
+ } catch (e, st) { |
+ completer.completeError(e, st); |
+ } |
+ }; |
+} |
+ |
+ |
+/// Implements the runtime support for async* functions. |
+/// |
+/// Called by the transformed function for each original return, await, yield, |
+/// yield* and before starting the function. |
+/// |
+/// When the async* function wants to return it calls this function. with |
+/// [helperCallback] == null, the streamHelper takes this as signal to close the |
+/// stream. |
+/// |
+/// If the async* function wants to do a yield or yield* it calls this function |
+/// with [object] being an [IterationMarker]. In this case [errorCallback] has a |
+/// special meaning; it is a callback that will run all enclosing finalizers. |
+/// |
+/// In the case of a yield or yield*, if the stream subscription has been |
+/// canceled [errorCallback] is scheduled. |
+/// |
+/// If [object] is a single-yield [IterationMarker], adds the value of the |
+/// [IterationMarker] to the stream. If the stream subscription has been |
+/// paused, return early. Otherwise schedule the helper function to be |
+/// executed again. |
+/// |
+/// If [object] is a yield-star [IterationMarker], starts listening to the |
+/// yielded stream, and adds all events and errors to our own controller (taking |
+/// care if the subscription has been paused or canceled) - when the sub-stream |
+/// is done, schedules [helperCallback] again. |
+/// |
+/// If the async* function wants to do an await it calls this function with |
+/// [object] not and [IterationMarker]. |
+/// |
+/// If [object] is not a [Future], it is wrapped in a `Future.value`. |
+/// The [helperCallback] is called on successfull completion of the |
+/// future. |
+/// |
+/// If [helperCallback] or [errorCallback] throws the error is added to the |
+/// stream. |
+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 null; |
+ } |
+ |
+ if (object is IterationMarker) { |
+ if (controller.stopRunning) { |
+ _wrapJsFunctionForStream(errorCallback, controller)(); |
+ return null; |
+ } |
+ if (object.state == IterationMarker.YIELD_SINGLE) { |
+ controller.add(object.value); |
+ // If the controller is paused we stop producing more values. |
+ if (controller.isPaused) { |
+ return null; |
+ } |
+ // TODO(sigurdm): We should not suspend here according to the spec. |
+ scheduleMicrotask(() { |
+ _wrapJsFunctionForStream(helperCallback, controller)(null); |
+ }); |
+ return; |
+ } else if (object.state == IterationMarker.YIELD_STAR) { |
+ Stream stream = object.value; |
+ controller.isAdding = true; |
+ // Errors of [stream] are passed though to the main stream. (see |
+ // [AsyncStreamController.addStream]. |
+ // TODO(sigurdm): The spec is not very clear here. Clarify with Gilad. |
+ controller.addStream(stream).then((_) { |
+ controller.isAdding = false; |
+ _wrapJsFunctionForStream(helperCallback, controller)(null); |
+ }); |
+ return 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; |
+ Stream get stream => controller.stream; |
+ bool stopRunning = false; |
+ bool isAdding = false; |
+ bool get isPaused => controller.isPaused; |
+ add(event) => controller.add(event); |
+ addStream(Stream stream) { |
+ return controller.addStream(stream, cancelOnError: false); |
+ } |
+ addError(error, stackTrace) => controller.addError(error, stackTrace); |
+ close() => controller.close(); |
+ |
+ AsyncStarStreamController(helperCallback) { |
+ controller = new StreamController( |
+ onResume: () { |
+ if (!isAdding) { |
+ streamHelper(null, helperCallback, this, null); |
+ } |
+ }, onCancel: () { |
+ stopRunning = true; |
+ }); |
+ } |
+} |
+ |
+makeAsyncStarController(helperCallback) { |
+ return new AsyncStarStreamController(helperCallback); |
+} |
+ |
+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(dynamic /* Iterable or Stream */ values) { |
+ return new IterationMarker._(YIELD_STAR, values); |
+ } |
+ |
+ static endOfIteration() { |
+ return new IterationMarker._(ITERATION_ENDED, null); |
+ } |
+ |
+ static yieldSingle(dynamic value) { |
+ return new IterationMarker._(YIELD_SINGLE, value); |
+ } |
+ |
+ toString() => "IterationMarker($state, $value)"; |
+} |
+ |
+class SyncStarIterator implements Iterator { |
+ final Function _helper; |
+ |
+ // If [runningNested] this is the nested iterator, otherwise it is 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; |
+ // 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)); |
+} |