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