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 7a75198df3ccaab7fb7ed24c9c56433c0fcc11ed..7f75fb91162b9162de78c370d61244a4b59afccc 100644 |
--- a/sdk/lib/_internal/compiler/js_lib/js_helper.dart |
+++ b/sdk/lib/_internal/compiler/js_lib/js_helper.dart |
@@ -25,7 +25,14 @@ import 'dart:_isolate_helper' show |
enterJsAsync, |
isWorker; |
-import 'dart:async' show Future, DeferredLoadException, Completer; |
+import 'dart:async' |
floitsch
2015/02/05 20:17:23
maybe not your code, but should be easy:
move "sho
sigurdm
2015/02/06 14:26:34
Done.
|
+ show Future, |
+ DeferredLoadException, |
+ Completer, |
+ StreamController, |
+ Stream, |
+ StreamSubscription, |
+ scheduleMicrotask; |
import 'dart:_foreign_helper' show |
DART_CLOSURE_TO_JS, |
@@ -3462,3 +3469,249 @@ 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 call this with |
floitsch
2015/02/05 20:17:23
calls this functions. The streamHelper takes this
sigurdm
2015/02/06 14:26:34
Done.
|
+/// [helperCallback] == null, and the stream is closed. |
+/// |
+/// If the async* function wants to do a yield or yield* it will call this with |
floitsch
2015/02/05 20:17:23
it calls this function
sigurdm
2015/02/06 14:26:34
Done.
|
+/// [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], add the value of the |
floitsch
2015/02/05 20:17:23
adds
sigurdm
2015/02/06 14:26:34
Done.
|
+/// [IterationMarker] to the stream, next: if the stream subscription has been |
floitsch
2015/02/05 20:17:23
to the stream. If the stream...
(unless I'm misun
sigurdm
2015/02/06 14:26:34
Done.
|
+/// paused, return early. Otherwise schedule the helper function to be |
+/// executed again. |
+/// |
+/// If [object] is a yield-star [IterationMarker], start listening to the |
floitsch
2015/02/05 20:17:23
starts ... and adds ... schedules
sigurdm
2015/02/06 14:26:34
Done.
|
+/// 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, schedule [helperCallback] again. |
+/// |
+/// If the async* function wants to do an await it will call this with [object] |
floitsch
2015/02/05 20:17:23
calls this function with [object] not an [Iteratio
sigurdm
2015/02/06 14:26:34
Done.
|
+/// not being an [IterationMarker]. |
+/// |
+/// 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 |
floitsch
2015/02/05 20:17:23
The [helperCallback] is called on successful compl
sigurdm
2015/02/06 14:26:34
Done.
|
+/// 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 (controller.isPaused) { |
+ return null; |
+ } |
+ new Future.value(null).then( |
floitsch
2015/02/05 20:17:23
Why?
Do we have to wait a microtask before we can
sigurdm
2015/02/06 14:26:35
I thought that it was necessary to suspend executi
|
+ _wrapJsFunctionForStream(helperCallback, controller)); |
+ return; |
+ } else if (object.state == IterationMarker.YIELD_STAR) { |
+ Stream stream = object.value; |
+ controller.isAdding = true; |
+ controller.addStream(stream).then((_) { |
floitsch
2015/02/05 20:17:23
You might need to listen to the error too. I'm not
sigurdm
2015/02/06 14:26:34
As you saw below, I explicitly ask errors to be pa
|
+ 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); |
floitsch
2015/02/05 20:17:23
I see. So I guess the future cannot have an error.
sigurdm
2015/02/06 14:26:34
Done.
|
+ } |
+ addError(error, stackTrace) => controller.addError(error, stackTrace); |
+ close() => controller.close(); |
+ |
+ AsyncStarStreamController(helperCallback) { |
+ controller = new StreamController( |
+ onResume: () { |
+ if (!isAdding) { |
floitsch
2015/02/05 20:17:23
Check with Lasse, if 'isPaused' on the controller
floitsch
2015/02/05 20:17:23
Add comment.
sigurdm
2015/02/06 14:26:34
Done.
sigurdm
2015/02/06 14:26:34
According to him, it is just as good.
|
+ streamHelper(null, helperCallback, this, null); |
+ } |
+ }, onCancel: () { |
+ stopRunning = true; |
floitsch
2015/02/05 20:17:23
Please make sure (test), that an incoming stream i
sigurdm
2015/02/06 14:26:34
Modified asyncstar_yieldstar_test to do that.
|
+ }); |
+ } |
+} |
+ |
+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 */ yielded) { |
floitsch
2015/02/05 20:17:23
maybe just "values" ?
sigurdm
2015/02/06 14:26:34
Done.
|
+ return new IterationMarker._(YIELD_STAR, yielded); |
+ } |
+ |
+ 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)); |
+} |
+ |