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

Unified Diff: third_party/pkg/angular/lib/core/scope.dart

Issue 257423008: Update all Angular libs (run update_all.sh). (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 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: third_party/pkg/angular/lib/core/scope.dart
diff --git a/third_party/pkg/angular/lib/core/scope.dart b/third_party/pkg/angular/lib/core/scope.dart
index db063051d69a2051c6777e647bceae0fcb717f71..128cf77e992708f299924df3eb814feb3b376eb9 100644
--- a/third_party/pkg/angular/lib/core/scope.dart
+++ b/third_party/pkg/angular/lib/core/scope.dart
@@ -1,8 +1,4 @@
-part of angular.core;
-
-NOT_IMPLEMENTED() {
- throw new StateError('Not Implemented');
-}
+part of angular.core_internal;
typedef EvalFunction0();
typedef EvalFunction1(context);
@@ -81,7 +77,7 @@ class ScopeEvent {
* triggers watch A. If the system does not stabilize in TTL iterations then
* the digest is stopped and an exception is thrown.
*/
-@NgInjectableService()
+@Injectable()
class ScopeDigestTTL {
final int ttl;
ScopeDigestTTL(): ttl = 5;
@@ -102,7 +98,8 @@ class ScopeLocals implements Map {
_scope[name] = value;
}
dynamic operator [](String name) =>
- (_locals.containsKey(name) ? _locals : _scope)[name];
+ // as Map needed to clear Dart2js warning
+ ((_locals.containsKey(name) ? _locals : _scope) as Map)[name];
bool get isEmpty => _scope.isEmpty && _locals.isEmpty;
bool get isNotEmpty => _scope.isNotEmpty || _locals.isNotEmpty;
@@ -128,11 +125,13 @@ class ScopeLocals implements Map {
/**
* [Scope] is represents a collection of [watch]es [observe]ers, and [context]
* for the watchers, observers and [eval]uations. Scopes structure loosely
- * mimics the DOM structure. Scopes and [Block]s are bound to each other.
- * As scopes are created and destroyed by [BlockFactory] they are responsible
+ * mimics the DOM structure. Scopes and [View]s are bound to each other.
+ * As scopes are created and destroyed by [ViewFactory] they are responsible
* for change detection, change processing and memory management.
*/
class Scope {
+ final String id;
+ int _childScopeNextId = 0;
/**
* The default execution context for [watch]es [observe]ers, and [eval]uation.
@@ -151,13 +150,15 @@ class Scope {
*/
Scope get parentScope => _parentScope;
+ final ScopeStats _stats;
+
/**
* Return `true` if the scope has been destroyed. Once scope is destroyed
* No operations are allowed on it.
*/
bool get isDestroyed {
var scope = this;
- while(scope != null) {
+ while (scope != null) {
if (scope == rootScope) return false;
scope = scope._parentScope;
}
@@ -182,24 +183,38 @@ class Scope {
bool get hasOwnStreams => _streams != null && _streams._scope == this;
Scope(Object this.context, this.rootScope, this._parentScope,
- this._readWriteGroup, this._readOnlyGroup);
+ this._readWriteGroup, this._readOnlyGroup, this.id,
+ this._stats);
/**
- * A [watch] sets up a watch in the [digest] phase of the [apply] cycle.
+ * Use [watch] to set up change detection on an expression.
*
- * Use [watch] if the reaction function can cause updates to model. In your
- * controller code you will most likely use [watch].
+ * * [expression]: The expression to watch for changes.
+ * * [reactionFn]: The reaction function to execute when a change is detected in the watched
+ * expression.
+ * * [context]: The object against which the expression is evaluated. This defaults to the
+ * [Scope.context] if no context is specified.
+ * * [formatters]: If the watched expression contains formatters,
+ * this map specifies the set of formatters that are used by the expression.
+ * * [canChangeModel]: Specifies whether the [reactionFn] changes the model. Reaction
+ * functions that change the model are processed as part of the [digest] cycle. Otherwise,
+ * they are processed as part of the [flush] cycle.
+ * * [collection]: If [:true:], then the expression points to a collection (a list or a map),
+ * and the collection should be shallow watched. If [:false:] then the expression is watched
+ * by reference. When watching a collection, the reaction function receives a
+ * [CollectionChangeItem] that lists all the changes.
*/
- Watch watch(expression, ReactionFn reactionFn,
- {context, FilterMap filters, bool readOnly: false}) {
+ Watch watch(String expression, ReactionFn reactionFn, {context,
+ FormatterMap formatters, bool canChangeModel: true, bool collection: false}) {
assert(isAttached);
- assert(expression != null);
- AST ast;
+ assert(expression is String);
+ assert(canChangeModel is bool);
+
Watch watch;
ReactionFn fn = reactionFn;
- if (expression is AST) {
- ast = expression;
- } else if (expression is String) {
+ if (expression.isEmpty) {
+ expression = '""';
+ } else {
if (expression.startsWith('::')) {
expression = expression.substring(2);
fn = (value, last) {
@@ -210,13 +225,17 @@ class Scope {
};
} else if (expression.startsWith(':')) {
expression = expression.substring(1);
- fn = (value, last) => value == null ? null : reactionFn(value, last);
+ fn = (value, last) {
+ if (value != null) reactionFn(value, last);
+ };
}
- ast = rootScope._astParser(expression, context: context, filters: filters);
- } else {
- throw 'expressions must be String or AST got $expression.';
}
- return watch = (readOnly ? _readOnlyGroup : _readWriteGroup).watch(ast, fn);
+
+ AST ast = rootScope._astParser(expression, context: context,
+ formatters: formatters, collection: collection);
+
+ WatchGroup group = canChangeModel ? _readWriteGroup : _readOnlyGroup;
+ return watch = group.watch(ast, fn);
}
dynamic eval(expression, [Map locals]) {
@@ -235,9 +254,6 @@ class Scope {
return null;
}
- dynamic applyInZone([expression, Map locals]) =>
- rootScope._zone.run(() => apply(expression, locals));
-
dynamic apply([expression, Map locals]) {
_assertInternalStateConsistency();
rootScope._transitionState(null, RootScope.STATE_APPLY);
@@ -246,10 +262,9 @@ class Scope {
} catch (e, s) {
rootScope._exceptionHandler(e, s);
} finally {
- rootScope
- .._transitionState(RootScope.STATE_APPLY, null)
- ..digest()
- ..flush();
+ rootScope.._transitionState(RootScope.STATE_APPLY, null)
+ ..digest()
+ ..flush();
}
}
@@ -257,10 +272,12 @@ class Scope {
assert(isAttached);
return _Streams.emit(this, name, data);
}
+
ScopeEvent broadcast(String name, [data]) {
assert(isAttached);
return _Streams.broadcast(this, name, data);
}
+
ScopeStream on(String name) {
assert(isAttached);
return _Streams.on(this, rootScope._exceptionHandler, name);
@@ -270,13 +287,14 @@ class Scope {
assert(isAttached);
var child = new Scope(childContext, rootScope, this,
_readWriteGroup.newGroup(childContext),
- _readOnlyGroup.newGroup(childContext));
- var next = null;
+ _readOnlyGroup.newGroup(childContext),
+ '$id:${_childScopeNextId++}',
+ _stats);
+
var prev = _childTail;
- child._next = next;
child._prev = prev;
if (prev == null) _childHead = child; else prev._next = child;
- if (next == null) _childTail = child; else next._prev = child;
+ _childTail = child;
return child;
}
@@ -301,7 +319,6 @@ class Scope {
_readWriteGroup.remove();
_readOnlyGroup.remove();
_parentScope = null;
- _assertInternalStateConsistency();
}
_assertInternalStateConsistency() {
@@ -326,7 +343,7 @@ class Scope {
});
}
var childScope = _childHead;
- while(childScope != null) {
+ while (childScope != null) {
childScope._verifyStreams(this, ' $prefix', log).forEach((k, v) {
counts[k] = v + (counts.containsKey(k) ? counts[k] : 0);
});
@@ -344,70 +361,176 @@ class Scope {
_mapEqual(Map a, Map b) => a.length == b.length &&
a.keys.every((k) => b.containsKey(k) && a[k] == b[k]);
+/**
+ * ScopeStats collects and emits statistics about a [Scope].
+ *
+ * ScopeStats supports emitting the results. Result emission can be started or
+ * stopped at runtime. The result emission can is configured by supplying a
+ * [ScopeStatsEmitter].
+ */
+@Injectable()
class ScopeStats {
- bool report = true;
- final nf = new NumberFormat.decimalPattern();
+ final fieldStopwatch = new AvgStopwatch();
+ final evalStopwatch = new AvgStopwatch();
+ final processStopwatch = new AvgStopwatch();
- final digestFieldStopwatch = new AvgStopwatch();
- final digestEvalStopwatch = new AvgStopwatch();
- final digestProcessStopwatch = new AvgStopwatch();
- int _digestLoopNo = 0;
+ List<int> _digestLoopTimes = [];
+ int _flushPhaseDuration = 0 ;
+ int _assertFlushPhaseDuration = 0;
- final flushFieldStopwatch = new AvgStopwatch();
- final flushEvalStopwatch = new AvgStopwatch();
- final flushProcessStopwatch = new AvgStopwatch();
+ int _loopNo = 0;
+ ScopeStatsEmitter _emitter;
+ ScopeStatsConfig _config;
- ScopeStats({this.report: false}) {
- nf.maximumFractionDigits = 0;
- }
+ /**
+ * Construct a new instance of ScopeStats.
+ */
+ ScopeStats(this._emitter, this._config);
void digestStart() {
- _digestStopwatchReset();
- _digestLoopNo = 0;
+ _digestLoopTimes = [];
+ _stopwatchReset();
+ _loopNo = 0;
+ }
+
+ int _allStagesDuration() {
+ return fieldStopwatch.elapsedMicroseconds +
+ evalStopwatch.elapsedMicroseconds +
+ processStopwatch.elapsedMicroseconds;
}
- _digestStopwatchReset() {
- digestFieldStopwatch.reset();
- digestEvalStopwatch.reset();
- digestProcessStopwatch.reset();
+ _stopwatchReset() {
+ fieldStopwatch.reset();
+ evalStopwatch.reset();
+ processStopwatch.reset();
}
void digestLoop(int changeCount) {
- _digestLoopNo++;
- if (report) {
- print(this);
+ _loopNo++;
+ if (_config.emit && _emitter != null) {
+ _emitter.emit(_loopNo.toString(), fieldStopwatch, evalStopwatch,
+ processStopwatch);
}
- _digestStopwatchReset();
+ _digestLoopTimes.add( _allStagesDuration() );
+ _stopwatchReset();
}
- String _stat(AvgStopwatch s) {
- return '${nf.format(s.count)}'
- ' / ${nf.format(s.elapsedMicroseconds)} us'
- ' = ${nf.format(s.ratePerMs)} #/ms';
+ void digestEnd() {
}
- void digestEnd() {
+ void domWriteStart() {}
+ void domWriteEnd() {}
+ void domReadStart() {}
+ void domReadEnd() {}
+ void flushStart() {
+ _stopwatchReset();
+ }
+ void flushEnd() {
+ if (_config.emit && _emitter != null) {
+ _emitter.emit(RootScope.STATE_FLUSH, fieldStopwatch, evalStopwatch,
+ processStopwatch);
+ }
+ _flushPhaseDuration = _allStagesDuration();
+ }
+ void flushAssertStart() {
+ _stopwatchReset();
+ }
+ void flushAssertEnd() {
+ if (_config.emit && _emitter != null) {
+ _emitter.emit(RootScope.STATE_FLUSH_ASSERT, fieldStopwatch, evalStopwatch,
+ processStopwatch);
+ }
+ _assertFlushPhaseDuration = _allStagesDuration();
}
- toString() =>
- 'digest #$_digestLoopNo:'
- 'Field: ${_stat(digestFieldStopwatch)} '
- 'Eval: ${_stat(digestEvalStopwatch)} '
- 'Process: ${_stat(digestProcessStopwatch)}';
+ void cycleEnd() {
+ }
}
+/**
+ * ScopeStatsEmitter is in charge of formatting the [ScopeStats] and outputting
+ * a message.
+ */
+@Injectable()
+class ScopeStatsEmitter {
+ static String _PAD_ = ' ';
+ static String _HEADER_ = pad('APPLY', 7) + ':'+
+ pad('FIELD', 19) + pad('|', 20) +
+ pad('EVAL', 19) + pad('|', 20) +
+ pad('REACTION', 19) + pad('|', 20) +
+ pad('TOTAL', 10) + '\n';
+ final _nfDec = new NumberFormat("0.00", "en_US");
+ final _nfInt = new NumberFormat("0", "en_US");
+
+ static pad(String str, int size) => _PAD_.substring(0, max(size - str.length, 0)) + str;
+
+ _ms(num value) => '${pad(_nfDec.format(value), 9)} ms';
+ _us(num value) => _ms(value / 1000);
+ _tally(num value) => '${pad(_nfInt.format(value), 6)}';
+
+ /**
+ * Emit a message based on the phase and state of stopwatches.
+ */
+ void emit(String phaseOrLoopNo, AvgStopwatch fieldStopwatch,
+ AvgStopwatch evalStopwatch, AvgStopwatch processStopwatch) {
+ var total = fieldStopwatch.elapsedMicroseconds +
+ evalStopwatch.elapsedMicroseconds +
+ processStopwatch.elapsedMicroseconds;
+ print('${_formatPrefix(phaseOrLoopNo)} '
+ '${_stat(fieldStopwatch)} | '
+ '${_stat(evalStopwatch)} | '
+ '${_stat(processStopwatch)} | '
+ '${_ms(total/1000)}');
+ }
+
+ String _formatPrefix(String prefix) {
+ if (prefix == RootScope.STATE_FLUSH) return ' flush:';
+ if (prefix == RootScope.STATE_FLUSH_ASSERT) return ' assert:';
+
+ return (prefix == '1' ? _HEADER_ : '') + ' #$prefix:';
+ }
+
+ String _stat(AvgStopwatch s) {
+ return '${_tally(s.count)} / ${_us(s.elapsedMicroseconds)} @(${_tally(s.ratePerMs)} #/ms)';
+ }
+}
+
+/**
+ * ScopeStatsConfig is used to modify behavior of [ScopeStats]. You can use this
+ * object to modify behavior at runtime too.
+ */
+class ScopeStatsConfig {
+ var emit = false;
+ ScopeStatsConfig();
+ ScopeStatsConfig.enabled() {
+ emit = true;
+ }
+}
+/**
+ *
+ * Every Angular application has exactly one RootScope. RootScope extends Scope, adding
+ * services related to change detection, async unit-of-work processing, and DOM read/write queues.
+ * The RootScope can not be destroyed.
+ *
+ * ## Lifecycle
+ *
+ * All work in Angular must be done within a context of a VmTurnZone. VmTurnZone detects the end
+ * of the VM turn, and calls the Apply method to process the changes at the end of VM turn.
+ *
+ */
+@Injectable()
class RootScope extends Scope {
static final STATE_APPLY = 'apply';
static final STATE_DIGEST = 'digest';
- static final STATE_FLUSH = 'digest';
+ static final STATE_FLUSH = 'flush';
+ static final STATE_FLUSH_ASSERT = 'assert';
final ExceptionHandler _exceptionHandler;
- final AstParser _astParser;
+ final _AstParser _astParser;
final Parser _parser;
final ScopeDigestTTL _ttl;
- final ExpressionVisitor visitor = new ExpressionVisitor(); // TODO(misko): delete me
- final NgZone _zone;
+ final VmTurnZone _zone;
_FunctionChain _runAsyncHead, _runAsyncTail;
_FunctionChain _domWriteHead, _domWriteTail;
@@ -417,13 +540,68 @@ class RootScope extends Scope {
String _state;
- RootScope(Object context, this._astParser, this._parser,
- GetterCache cacheGetter, FilterMap filterMap,
- this._exceptionHandler, this._ttl, this._zone,
- this._scopeStats)
- : super(context, null, null,
- new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context),
- new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context))
+ /**
+ *
+ * While processing data bindings, Angular passes through multiple states. When testing or
+ * debugging, it can be useful to access the current `state`, which is one of the following:
+ *
+ * * null
+ * * apply
+ * * digest
+ * * flush
+ * * assert
+ *
+ * ##null
+ *
+ * Angular is not currently processing changes
+ *
+ * ##apply
+ *
+ * The apply state begins by executing the optional expression within the context of
+ * angular change detection mechanism. Any exceptions are delegated to [ExceptionHandler]. At the
+ * end of apply state RootScope enters the digest followed by flush phase (optionally if asserts
+ * enabled run assert phase.)
+ *
+ * ##digest
+ *
+ * The apply state begins by processing the async queue,
+ * followed by change detection
+ * on non-DOM listeners. Any changes detected are process using the reaction function. The digest
+ * phase is repeated as long as at least one change has been detected. By default, after 5
+ * iterations the model is considered unstable and angular exists with an exception. (See
+ * ScopeDigestTTL)
+ *
+ * ##flush
+ *
+ * The flush phase consists of these steps:
+ *
+ * 1. processing the DOM write queue
+ * 2. change detection on DOM only updates (these are reaction functions which must
+ * not change the model state and hence don't need stabilization as in digest phase).
+ * 3. processing the DOM read queue
+ * 4. repeat steps 1 and 3 (not 2) until queues are empty
+ *
+ * ##assert
+ *
+ * Optionally if Dart assert is on, verify that flush reaction functions did not make any changes
+ * to model and throw error if changes detected.
+ *
+ */
+ String get state => _state;
+
+ RootScope(Object context, Parser parser, FieldGetterFactory fieldGetterFactory,
+ FormatterMap formatters, this._exceptionHandler, this._ttl, this._zone,
+ ScopeStats _scopeStats, ClosureMap closureMap)
+ : _scopeStats = _scopeStats,
+ _parser = parser,
+ _astParser = new _AstParser(parser, closureMap),
+ super(context, null, null,
+ new RootWatchGroup(fieldGetterFactory,
+ new DirtyCheckingChangeDetector(fieldGetterFactory), context),
+ new RootWatchGroup(fieldGetterFactory,
+ new DirtyCheckingChangeDetector(fieldGetterFactory), context),
+ '',
+ _scopeStats)
{
_zone.onTurnDone = apply;
_zone.onError = (e, s, ls) => _exceptionHandler(e, s);
@@ -432,10 +610,27 @@ class RootScope extends Scope {
RootScope get rootScope => this;
bool get isAttached => true;
+/**
+ * Propagates changes between different parts of the application model. Normally called by
+ * [VMTurnZone] right before DOM rendering to initiate data binding. May also be called directly
+ * for unit testing.
+ *
+ * Before each iteration of change detection, [digest] first processes the async queue. Any
+ * work scheduled on the queue is executed before change detection. Since work scheduled on
+ * the queue may generate more async calls, [digest] must process the queue multiple times before
+ * it completes. The async queue must be empty before the model is considered stable.
+ *
+ * Next, [digest] collects the changes that have occurred in the model. For each change,
+ * [digest] calls the associated [ReactionFn]. Since a [ReactionFn] may further change the model,
+ * [digest] processes changes multiple times until no more changes are detected.
+ *
+ * If the model does not stabilize within 5 iterations, an exception is thrown. See
+ * [ScopeDigestTTL].
+ */
void digest() {
_transitionState(null, STATE_DIGEST);
try {
- var rootWatchGroup = (_readWriteGroup as RootWatchGroup);
+ var rootWatchGroup = _readWriteGroup as RootWatchGroup;
int digestTTL = _ttl.ttl;
const int LOG_COUNT = 3;
@@ -445,7 +640,7 @@ class RootScope extends Scope {
ChangeLog changeLog;
_scopeStats.digestStart();
do {
- while(_runAsyncHead != null) {
+ while (_runAsyncHead != null) {
try {
_runAsyncHead.fn();
} catch (e, s) {
@@ -453,14 +648,15 @@ class RootScope extends Scope {
}
_runAsyncHead = _runAsyncHead._next;
}
+ _runAsyncTail = null;
digestTTL--;
count = rootWatchGroup.detectChanges(
exceptionHandler: _exceptionHandler,
changeLog: changeLog,
- fieldStopwatch: _scopeStats.digestFieldStopwatch,
- evalStopwatch: _scopeStats.digestEvalStopwatch,
- processStopwatch: _scopeStats.digestProcessStopwatch);
+ fieldStopwatch: _scopeStats.fieldStopwatch,
+ evalStopwatch: _scopeStats.evalStopwatch,
+ processStopwatch: _scopeStats.processStopwatch);
if (digestTTL <= LOG_COUNT) {
if (changeLog == null) {
@@ -485,50 +681,69 @@ class RootScope extends Scope {
}
void flush() {
+ _stats.flushStart();
_transitionState(null, STATE_FLUSH);
- var observeGroup = this._readOnlyGroup as RootWatchGroup;
+ RootWatchGroup readOnlyGroup = this._readOnlyGroup as RootWatchGroup;
bool runObservers = true;
try {
do {
- while(_domWriteHead != null) {
+ if (_domWriteHead != null) _stats.domWriteStart();
+ while (_domWriteHead != null) {
try {
_domWriteHead.fn();
} catch (e, s) {
_exceptionHandler(e, s);
}
_domWriteHead = _domWriteHead._next;
+ if (_domWriteHead == null) _stats.domWriteEnd();
}
+ _domWriteTail = null;
if (runObservers) {
runObservers = false;
- observeGroup.detectChanges(exceptionHandler:_exceptionHandler);
+ readOnlyGroup.detectChanges(exceptionHandler:_exceptionHandler,
+ fieldStopwatch: _scopeStats.fieldStopwatch,
+ evalStopwatch: _scopeStats.evalStopwatch,
+ processStopwatch: _scopeStats.processStopwatch);
}
- while(_domReadHead != null) {
+ if (_domReadHead != null) _stats.domReadStart();
+ while (_domReadHead != null) {
try {
_domReadHead.fn();
} catch (e, s) {
_exceptionHandler(e, s);
}
_domReadHead = _domReadHead._next;
+ if (_domReadHead == null) _stats.domReadEnd();
}
+ _domReadTail = null;
} while (_domWriteHead != null || _domReadHead != null);
+ _stats.flushEnd();
assert((() {
- var watchLog = [];
- var observeLog = [];
+ _stats.flushAssertStart();
+ var digestLog = [];
+ var flushLog = [];
(_readWriteGroup as RootWatchGroup).detectChanges(
- changeLog: (s, c, p) => watchLog.add('$s: $c <= $p'));
- (observeGroup as RootWatchGroup).detectChanges(
- changeLog: (s, c, p) => watchLog.add('$s: $c <= $p'));
- if (watchLog.isNotEmpty || observeLog.isNotEmpty) {
+ changeLog: (s, c, p) => digestLog.add('$s: $c <= $p'),
+ fieldStopwatch: _scopeStats.fieldStopwatch,
+ evalStopwatch: _scopeStats.evalStopwatch,
+ processStopwatch: _scopeStats.processStopwatch);
+ (_readOnlyGroup as RootWatchGroup).detectChanges(
+ changeLog: (s, c, p) => flushLog.add('$s: $c <= $p'),
+ fieldStopwatch: _scopeStats.fieldStopwatch,
+ evalStopwatch: _scopeStats.evalStopwatch,
+ processStopwatch: _scopeStats.processStopwatch);
+ if (digestLog.isNotEmpty || flushLog.isNotEmpty) {
throw 'Observer reaction functions should not change model. \n'
- 'These watch changes were detected: ${watchLog.join('; ')}\n'
- 'These observe changes were detected: ${observeLog.join('; ')}';
+ 'These watch changes were detected: ${digestLog.join('; ')}\n'
+ 'These observe changes were detected: ${flushLog.join('; ')}';
}
+ _stats.flushAssertEnd();
return true;
})());
} finally {
+ _stats.cycleEnd();
_transitionState(STATE_FLUSH, null);
}
-
}
// QUEUES
@@ -607,7 +822,7 @@ class _Streams {
static ScopeEvent emit(Scope scope, String name, data) {
var event = new ScopeEvent(name, scope, data);
var scopeCursor = scope;
- while(scopeCursor != null) {
+ while (scopeCursor != null) {
if (scopeCursor._streams != null &&
scopeCursor._streams._scope == scopeCursor) {
ScopeStream stream = scopeCursor._streams._streams[name];
@@ -638,7 +853,7 @@ class _Streams {
}
// Reverse traversal so that when the queue is read it is correct order.
var childScope = scope._childTail;
- while(childScope != null) {
+ while (childScope != null) {
scopeStreams = childScope._streams;
if (scopeStreams != null &&
scopeStreams._typeCounts.containsKey(name)) {
@@ -651,9 +866,9 @@ class _Streams {
return event;
}
- static ScopeStream on(Scope scope,
- ExceptionHandler _exceptionHandler,
- String name) {
+ static async.Stream<ScopeEvent> on(Scope scope,
+ ExceptionHandler _exceptionHandler,
+ String name) {
_forceNewScopeStream(scope, _exceptionHandler);
return scope._streams._get(scope, name);
}
@@ -662,7 +877,7 @@ class _Streams {
_Streams streams = scope._streams;
Scope scopeCursor = scope;
bool splitMode = false;
- while(scopeCursor != null) {
+ while (scopeCursor != null) {
_Streams cursorStreams = scopeCursor._streams;
var hasStream = cursorStreams != null;
var hasOwnStream = hasStream && cursorStreams._scope == scopeCursor;
@@ -734,6 +949,9 @@ class ScopeStream extends async.Stream<ScopeEvent> {
final _Streams _streams;
final String _name;
final subscriptions = <ScopeStreamSubscription>[];
+ final List<Function> _work = <Function>[];
+ bool _firing = false;
+
ScopeStream(this._streams, this._exceptionHandler, this._name);
@@ -741,29 +959,46 @@ class ScopeStream extends async.Stream<ScopeEvent> {
{ Function onError,
void onDone(),
bool cancelOnError }) {
- if (subscriptions.isEmpty) _streams._addCount(_name, 1);
var subscription = new ScopeStreamSubscription(this, onData);
- subscriptions.add(subscription);
+ _concurrentSafeWork(() {
+ if (subscriptions.isEmpty) _streams._addCount(_name, 1);
+ subscriptions.add(subscription);
+ });
return subscription;
}
+ void _concurrentSafeWork([fn]) {
+ if (fn != null) _work.add(fn);
+ while(!_firing && _work.isNotEmpty) {
+ _work.removeLast()();
+ }
+ }
+
void _fire(ScopeEvent event) {
- for (ScopeStreamSubscription subscription in subscriptions) {
- try {
- subscription._onData(event);
- } catch (e, s) {
- _exceptionHandler(e, s);
+ _firing = true;
+ try {
+ for (ScopeStreamSubscription subscription in subscriptions) {
+ try {
+ subscription._onData(event);
+ } catch (e, s) {
+ _exceptionHandler(e, s);
+ }
}
+ } finally {
+ _firing = false;
+ _concurrentSafeWork();
}
}
void _remove(ScopeStreamSubscription subscription) {
- assert(subscription._scopeStream == this);
- if (subscriptions.remove(subscription)) {
- if (subscriptions.isEmpty) _streams._addCount(_name, -1);
- } else {
- throw new StateError('AlreadyCanceled');
- }
+ _concurrentSafeWork(() {
+ assert(subscription._scopeStream == this);
+ if (subscriptions.remove(subscription)) {
+ if (subscriptions.isEmpty) _streams._addCount(_name, -1);
+ } else {
+ throw new StateError('AlreadyCanceled');
+ }
+ });
}
}
@@ -772,64 +1007,74 @@ class ScopeStreamSubscription implements async.StreamSubscription<ScopeEvent> {
final Function _onData;
ScopeStreamSubscription(this._scopeStream, this._onData);
- // TODO(vbe) should return a Future
- cancel() => _scopeStream._remove(this);
+ async.Future cancel() {
+ _scopeStream._remove(this);
+ return null;
+ }
+
+ void onData(void handleData(ScopeEvent data)) => _NOT_IMPLEMENTED();
+ void onError(Function handleError) => _NOT_IMPLEMENTED();
+ void onDone(void handleDone()) => _NOT_IMPLEMENTED();
+ void pause([async.Future resumeSignal]) => _NOT_IMPLEMENTED();
+ void resume() => _NOT_IMPLEMENTED();
+ bool get isPaused => _NOT_IMPLEMENTED();
+ async.Future asFuture([var futureValue]) => _NOT_IMPLEMENTED();
+}
- void onData(void handleData(ScopeEvent data)) => NOT_IMPLEMENTED();
- void onError(Function handleError) => NOT_IMPLEMENTED();
- void onDone(void handleDone()) => NOT_IMPLEMENTED();
- void pause([async.Future resumeSignal]) => NOT_IMPLEMENTED();
- void resume() => NOT_IMPLEMENTED();
- bool get isPaused => NOT_IMPLEMENTED();
- async.Future asFuture([var futureValue]) => NOT_IMPLEMENTED();
+_NOT_IMPLEMENTED() {
+ throw new StateError('Not Implemented');
}
+
class _FunctionChain {
final Function fn;
_FunctionChain _next;
- _FunctionChain(fn())
- : fn = fn
- {
+ _FunctionChain(fn()): fn = fn {
assert(fn != null);
}
}
-class AstParser {
+class _AstParser {
final Parser _parser;
int _id = 0;
- ExpressionVisitor _visitor = new ExpressionVisitor();
+ final ExpressionVisitor _visitor;
- AstParser(this._parser);
+ _AstParser(this._parser, ClosureMap closureMap)
+ : _visitor = new ExpressionVisitor(closureMap);
- AST call(String exp, { FilterMap filters,
- bool collection:false,
- Object context:null }) {
- _visitor.filters = filters;
+ AST call(String input, {FormatterMap formatters,
+ bool collection: false,
+ Object context: null }) {
+ _visitor.formatters = formatters;
AST contextRef = _visitor.contextRef;
try {
if (context != null) {
_visitor.contextRef = new ConstantAST(context, '#${_id++}');
}
- var ast = _parser(exp);
- return collection ? _visitor.visitCollection(ast) : _visitor.visit(ast);
+ var exp = _parser(input);
+ return collection ? _visitor.visitCollection(exp) : _visitor.visit(exp);
} finally {
_visitor.contextRef = contextRef;
- _visitor.filters = null;
+ _visitor.formatters = null;
}
}
}
class ExpressionVisitor implements Visitor {
static final ContextReferenceAST scopeContextRef = new ContextReferenceAST();
+ final ClosureMap _closureMap;
AST contextRef = scopeContextRef;
+
+ ExpressionVisitor(this._closureMap);
+
AST ast;
- FilterMap filters;
+ FormatterMap formatters;
AST visit(Expression exp) {
exp.accept(this);
- assert(this.ast != null);
+ assert(ast != null);
try {
return ast;
} finally {
@@ -843,11 +1088,24 @@ class ExpressionVisitor implements Visitor {
List<AST> _toAst(List<Expression> expressions) =>
expressions.map(_mapToAst).toList();
+ Map<Symbol, AST> _toAstMap(Map<String, Expression> expressions) {
+ if (expressions.isEmpty) return const {};
+ Map<Symbol, AST> result = new Map<Symbol, AST>();
+ expressions.forEach((String name, Expression expression) {
+ result[_closureMap.lookupSymbol(name)] = _mapToAst(expression);
+ });
+ return result;
+ }
+
void visitCallScope(CallScope exp) {
- ast = new MethodAST(contextRef, exp.name, _toAst(exp.arguments));
+ List<AST> positionals = _toAst(exp.arguments.positionals);
+ Map<Symbol, AST> named = _toAstMap(exp.arguments.named);
+ ast = new MethodAST(contextRef, exp.name, positionals, named);
}
void visitCallMember(CallMember exp) {
- ast = new MethodAST(visit(exp.object), exp.name, _toAst(exp.arguments));
+ List<AST> positionals = _toAst(exp.arguments.positionals);
+ Map<Symbol, AST> named = _toAstMap(exp.arguments.named);
+ ast = new MethodAST(visit(exp.object), exp.name, positionals, named);
}
visitAccessScope(AccessScope exp) {
ast = new FieldReadAST(contextRef, exp.name);
@@ -871,7 +1129,7 @@ class ExpressionVisitor implements Visitor {
visit(exp.no)]);
}
void visitAccessKeyed(AccessKeyed exp) {
- ast = new PureFunctionAST('[]', _operation_bracket,
+ ast = new ClosureAST('[]', _operation_bracket,
[visit(exp.object), visit(exp.key)]);
}
void visitLiteralPrimitive(LiteralPrimitive exp) {
@@ -897,7 +1155,10 @@ class ExpressionVisitor implements Visitor {
}
void visitFilter(Filter exp) {
- Function filterFunction = filters(exp.name);
+ if (formatters == null) {
+ throw new Exception("No formatters have been registered");
+ }
+ Function filterFunction = formatters(exp.name);
List<AST> args = [visitCollection(exp.expression)];
args.addAll(_toAst(exp.arguments).map((ast) => new CollectionAST(ast)));
ast = new PureFunctionAST('|${exp.name}',
@@ -951,19 +1212,19 @@ Function _operationToFunction(String operation) {
_operation_negate(value) => !toBool(value);
_operation_add(left, right) => autoConvertAdd(left, right);
-_operation_subtract(left, right) => left - right;
-_operation_multiply(left, right) => left * right;
-_operation_divide(left, right) => left / right;
-_operation_divide_int(left, right) => left ~/ right;
-_operation_remainder(left, right) => left % right;
+_operation_subtract(left, right) => (left != null && right != null) ? left - right : (left != null ? left : (right != null ? 0 - right : 0));
+_operation_multiply(left, right) => (left == null || right == null) ? null : left * right;
+_operation_divide(left, right) => (left == null || right == null) ? null : left / right;
+_operation_divide_int(left, right) => (left == null || right == null) ? null : left ~/ right;
+_operation_remainder(left, right) => (left == null || right == null) ? null : left % right;
_operation_equals(left, right) => left == right;
_operation_not_equals(left, right) => left != right;
-_operation_less_then(left, right) => left < right;
-_operation_greater_then(left, right) => (left == null || right == null) ? false : left > right;
-_operation_less_or_equals_then(left, right) => left <= right;
-_operation_greater_or_equals_then(left, right) => left >= right;
-_operation_power(left, right) => left ^ right;
-_operation_bitwise_and(left, right) => left & right;
+_operation_less_then(left, right) => (left == null || right == null) ? null : left < right;
+_operation_greater_then(left, right) => (left == null || right == null) ? null : left > right;
+_operation_less_or_equals_then(left, right) => (left == null || right == null) ? null : left <= right;
+_operation_greater_or_equals_then(left, right) => (left == null || right == null) ? null : left >= right;
+_operation_power(left, right) => (left == null || right == null) ? null : left ^ right;
+_operation_bitwise_and(left, right) => (left == null || right == null) ? null : left & right;
// TODO(misko): these should short circuit the evaluation.
_operation_logical_and(left, right) => toBool(left) && toBool(right);
_operation_logical_or(left, right) => toBool(left) || toBool(right);
@@ -981,7 +1242,7 @@ class MapFn extends FunctionApply {
MapFn(this.keys);
- apply(List values) {
+ Map apply(List values) {
// TODO(misko): figure out why do we need to make a copy instead of reusing instance?
assert(values.length == keys.length);
return new Map.fromIterables(keys, values);
@@ -1003,6 +1264,8 @@ class _FilterWrapper extends FunctionApply {
if (!identical(value, lastValue)) {
if (value is CollectionChangeRecord) {
args[i] = (value as CollectionChangeRecord).iterable;
+ } else if (value is MapChangeRecord) {
+ args[i] = (value as MapChangeRecord).map;
} else {
args[i] = value;
}
@@ -1010,7 +1273,7 @@ class _FilterWrapper extends FunctionApply {
}
var value = Function.apply(filterFn, args);
if (value is Iterable) {
- // Since filters are pure we can guarantee that this well never change.
+ // Since formatters are pure we can guarantee that this well never change.
// By wrapping in UnmodifiableListView we can hint to the dirty checker
// and short circuit the iterator.
value = new UnmodifiableListView(value);
« no previous file with comments | « third_party/pkg/angular/lib/core/registry_static.dart ('k') | third_party/pkg/angular/lib/core/service.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698