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 |
deleted file mode 100644 |
index db063051d69a2051c6777e647bceae0fcb717f71..0000000000000000000000000000000000000000 |
--- a/third_party/pkg/angular/lib/core/scope.dart |
+++ /dev/null |
@@ -1,1020 +0,0 @@ |
-part of angular.core; |
- |
-NOT_IMPLEMENTED() { |
- throw new StateError('Not Implemented'); |
-} |
- |
-typedef EvalFunction0(); |
-typedef EvalFunction1(context); |
- |
-/** |
- * Injected into the listener function within [Scope.on] to provide |
- * event-specific details to the scope listener. |
- */ |
-class ScopeEvent { |
- static final String DESTROY = 'ng-destroy'; |
- |
- /** |
- * Data attached to the event. This would be the optional parameter |
- * from [Scope.emit] and [Scope.broadcast]. |
- */ |
- final data; |
- |
- /** |
- * The name of the intercepted scope event. |
- */ |
- final String name; |
- |
- /** |
- * The origin scope that triggered the event (via broadcast or emit). |
- */ |
- final Scope targetScope; |
- |
- /** |
- * The destination scope that intercepted the event. As |
- * the event traverses the scope hierarchy the the event instance |
- * stays the same, but the [currentScope] reflects the scope |
- * of the current listener which is firing. |
- */ |
- Scope get currentScope => _currentScope; |
- Scope _currentScope; |
- |
- /** |
- * true or false depending on if [stopPropagation] was executed. |
- */ |
- bool get propagationStopped => _propagationStopped; |
- bool _propagationStopped = false; |
- |
- /** |
- * true or false depending on if [preventDefault] was executed. |
- */ |
- bool get defaultPrevented => _defaultPrevented; |
- bool _defaultPrevented = false; |
- |
- /** |
- * [name] - The name of the scope event. |
- * [targetScope] - The destination scope that is listening on the event. |
- */ |
- ScopeEvent(this.name, this.targetScope, this.data); |
- |
- /** |
- * Prevents the intercepted event from propagating further to successive |
- * scopes. |
- */ |
- void stopPropagation () { |
- _propagationStopped = true; |
- } |
- |
- /** |
- * Sets the defaultPrevented flag to true. |
- */ |
- void preventDefault() { |
- _defaultPrevented = true; |
- } |
-} |
- |
-/** |
- * Allows the configuration of [Scope.digest] iteration maximum time-to-live |
- * value. Digest keeps checking the state of the watcher getters until it |
- * can execute one full iteration with no watchers triggering. TTL is used |
- * to prevent an infinite loop where watch A triggers watch B which in turn |
- * triggers watch A. If the system does not stabilize in TTL iterations then |
- * the digest is stopped and an exception is thrown. |
- */ |
-@NgInjectableService() |
-class ScopeDigestTTL { |
- final int ttl; |
- ScopeDigestTTL(): ttl = 5; |
- ScopeDigestTTL.value(this.ttl); |
-} |
- |
-//TODO(misko): I don't think this should be in scope. |
-class ScopeLocals implements Map { |
- static wrapper(scope, Map<String, Object> locals) => |
- new ScopeLocals(scope, locals); |
- |
- Map _scope; |
- Map<String, Object> _locals; |
- |
- ScopeLocals(this._scope, this._locals); |
- |
- void operator []=(String name, value) { |
- _scope[name] = value; |
- } |
- dynamic operator [](String name) => |
- (_locals.containsKey(name) ? _locals : _scope)[name]; |
- |
- bool get isEmpty => _scope.isEmpty && _locals.isEmpty; |
- bool get isNotEmpty => _scope.isNotEmpty || _locals.isNotEmpty; |
- List<String> get keys => _scope.keys; |
- List get values => _scope.values; |
- int get length => _scope.length; |
- |
- void forEach(fn) { |
- _scope.forEach(fn); |
- } |
- dynamic remove(key) => _scope.remove(key); |
- void clear() { |
- _scope.clear; |
- } |
- bool containsKey(key) => _scope.containsKey(key); |
- bool containsValue(key) => _scope.containsValue(key); |
- void addAll(map) { |
- _scope.addAll(map); |
- } |
- dynamic putIfAbsent(key, fn) => _scope.putIfAbsent(key, fn); |
-} |
- |
-/** |
- * [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 |
- * for change detection, change processing and memory management. |
- */ |
-class Scope { |
- |
- /** |
- * The default execution context for [watch]es [observe]ers, and [eval]uation. |
- */ |
- final context; |
- |
- /** |
- * The [RootScope] of the application. |
- */ |
- final RootScope rootScope; |
- |
- Scope _parentScope; |
- |
- /** |
- * The parent [Scope]. |
- */ |
- Scope get parentScope => _parentScope; |
- |
- /** |
- * 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) { |
- if (scope == rootScope) return false; |
- scope = scope._parentScope; |
- } |
- return true; |
- } |
- |
- /** |
- * Returns true if the scope is still attached to the [RootScope]. |
- */ |
- bool get isAttached => !isDestroyed; |
- |
- // TODO(misko): WatchGroup should be private. |
- // Instead we should expose performance stats about the watches |
- // such as # of watches, checks/1ms, field checks, function checks, etc |
- final WatchGroup _readWriteGroup; |
- final WatchGroup _readOnlyGroup; |
- |
- Scope _childHead, _childTail, _next, _prev; |
- _Streams _streams; |
- |
- /// Do not use. Exposes internal state for testing. |
- bool get hasOwnStreams => _streams != null && _streams._scope == this; |
- |
- Scope(Object this.context, this.rootScope, this._parentScope, |
- this._readWriteGroup, this._readOnlyGroup); |
- |
- /** |
- * A [watch] sets up a watch in the [digest] phase of the [apply] cycle. |
- * |
- * Use [watch] if the reaction function can cause updates to model. In your |
- * controller code you will most likely use [watch]. |
- */ |
- Watch watch(expression, ReactionFn reactionFn, |
- {context, FilterMap filters, bool readOnly: false}) { |
- assert(isAttached); |
- assert(expression != null); |
- AST ast; |
- Watch watch; |
- ReactionFn fn = reactionFn; |
- if (expression is AST) { |
- ast = expression; |
- } else if (expression is String) { |
- if (expression.startsWith('::')) { |
- expression = expression.substring(2); |
- fn = (value, last) { |
- if (value != null) { |
- watch.remove(); |
- return reactionFn(value, last); |
- } |
- }; |
- } else if (expression.startsWith(':')) { |
- expression = expression.substring(1); |
- fn = (value, last) => value == null ? 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); |
- } |
- |
- dynamic eval(expression, [Map locals]) { |
- assert(isAttached); |
- assert(expression == null || |
- expression is String || |
- expression is Function); |
- if (expression is String && expression.isNotEmpty) { |
- var obj = locals == null ? context : new ScopeLocals(context, locals); |
- return rootScope._parser(expression).eval(obj); |
- } |
- |
- assert(locals == null); |
- if (expression is EvalFunction1) return expression(context); |
- if (expression is EvalFunction0) return expression(); |
- 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); |
- try { |
- return eval(expression, locals); |
- } catch (e, s) { |
- rootScope._exceptionHandler(e, s); |
- } finally { |
- rootScope |
- .._transitionState(RootScope.STATE_APPLY, null) |
- ..digest() |
- ..flush(); |
- } |
- } |
- |
- ScopeEvent emit(String name, [data]) { |
- 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); |
- } |
- |
- Scope createChild(Object childContext) { |
- assert(isAttached); |
- var child = new Scope(childContext, rootScope, this, |
- _readWriteGroup.newGroup(childContext), |
- _readOnlyGroup.newGroup(childContext)); |
- var next = null; |
- 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; |
- return child; |
- } |
- |
- void destroy() { |
- assert(isAttached); |
- broadcast(ScopeEvent.DESTROY); |
- _Streams.destroy(this); |
- |
- if (_prev == null) { |
- _parentScope._childHead = _next; |
- } else { |
- _prev._next = _next; |
- } |
- if (_next == null) { |
- _parentScope._childTail = _prev; |
- } else { |
- _next._prev = _prev; |
- } |
- |
- _next = _prev = null; |
- |
- _readWriteGroup.remove(); |
- _readOnlyGroup.remove(); |
- _parentScope = null; |
- _assertInternalStateConsistency(); |
- } |
- |
- _assertInternalStateConsistency() { |
- assert((() { |
- rootScope._verifyStreams(null, '', []); |
- return true; |
- })()); |
- } |
- |
- Map<bool,int> _verifyStreams(parentScope, prefix, log) { |
- assert(_parentScope == parentScope); |
- var counts = {}; |
- var typeCounts = _streams == null ? {} : _streams._typeCounts; |
- var connection = _streams != null && _streams._scope == this ? '=' : '-'; |
- log..add(prefix)..add(hashCode)..add(connection)..add(typeCounts)..add('\n'); |
- if (_streams == null) { |
- } else if (_streams._scope == this) { |
- _streams._streams.forEach((k, ScopeStream stream){ |
- if (stream.subscriptions.isNotEmpty) { |
- counts[k] = 1 + (counts.containsKey(k) ? counts[k] : 0); |
- } |
- }); |
- } |
- var childScope = _childHead; |
- while(childScope != null) { |
- childScope._verifyStreams(this, ' $prefix', log).forEach((k, v) { |
- counts[k] = v + (counts.containsKey(k) ? counts[k] : 0); |
- }); |
- childScope = childScope._next; |
- } |
- if (!_mapEqual(counts, typeCounts)) { |
- throw 'Streams actual: $counts != bookkeeping: $typeCounts\n' |
- 'Offending scope: [scope: ${this.hashCode}]\n' |
- '${log.join('')}'; |
- } |
- return counts; |
- } |
-} |
- |
-_mapEqual(Map a, Map b) => a.length == b.length && |
- a.keys.every((k) => b.containsKey(k) && a[k] == b[k]); |
- |
-class ScopeStats { |
- bool report = true; |
- final nf = new NumberFormat.decimalPattern(); |
- |
- final digestFieldStopwatch = new AvgStopwatch(); |
- final digestEvalStopwatch = new AvgStopwatch(); |
- final digestProcessStopwatch = new AvgStopwatch(); |
- int _digestLoopNo = 0; |
- |
- final flushFieldStopwatch = new AvgStopwatch(); |
- final flushEvalStopwatch = new AvgStopwatch(); |
- final flushProcessStopwatch = new AvgStopwatch(); |
- |
- ScopeStats({this.report: false}) { |
- nf.maximumFractionDigits = 0; |
- } |
- |
- void digestStart() { |
- _digestStopwatchReset(); |
- _digestLoopNo = 0; |
- } |
- |
- _digestStopwatchReset() { |
- digestFieldStopwatch.reset(); |
- digestEvalStopwatch.reset(); |
- digestProcessStopwatch.reset(); |
- } |
- |
- void digestLoop(int changeCount) { |
- _digestLoopNo++; |
- if (report) { |
- print(this); |
- } |
- _digestStopwatchReset(); |
- } |
- |
- String _stat(AvgStopwatch s) { |
- return '${nf.format(s.count)}' |
- ' / ${nf.format(s.elapsedMicroseconds)} us' |
- ' = ${nf.format(s.ratePerMs)} #/ms'; |
- } |
- |
- void digestEnd() { |
- } |
- |
- toString() => |
- 'digest #$_digestLoopNo:' |
- 'Field: ${_stat(digestFieldStopwatch)} ' |
- 'Eval: ${_stat(digestEvalStopwatch)} ' |
- 'Process: ${_stat(digestProcessStopwatch)}'; |
-} |
- |
- |
-class RootScope extends Scope { |
- static final STATE_APPLY = 'apply'; |
- static final STATE_DIGEST = 'digest'; |
- static final STATE_FLUSH = 'digest'; |
- |
- final ExceptionHandler _exceptionHandler; |
- final AstParser _astParser; |
- final Parser _parser; |
- final ScopeDigestTTL _ttl; |
- final ExpressionVisitor visitor = new ExpressionVisitor(); // TODO(misko): delete me |
- final NgZone _zone; |
- |
- _FunctionChain _runAsyncHead, _runAsyncTail; |
- _FunctionChain _domWriteHead, _domWriteTail; |
- _FunctionChain _domReadHead, _domReadTail; |
- |
- final ScopeStats _scopeStats; |
- |
- 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)) |
- { |
- _zone.onTurnDone = apply; |
- _zone.onError = (e, s, ls) => _exceptionHandler(e, s); |
- } |
- |
- RootScope get rootScope => this; |
- bool get isAttached => true; |
- |
- void digest() { |
- _transitionState(null, STATE_DIGEST); |
- try { |
- var rootWatchGroup = (_readWriteGroup as RootWatchGroup); |
- |
- int digestTTL = _ttl.ttl; |
- const int LOG_COUNT = 3; |
- List log; |
- List digestLog; |
- var count; |
- ChangeLog changeLog; |
- _scopeStats.digestStart(); |
- do { |
- while(_runAsyncHead != null) { |
- try { |
- _runAsyncHead.fn(); |
- } catch (e, s) { |
- _exceptionHandler(e, s); |
- } |
- _runAsyncHead = _runAsyncHead._next; |
- } |
- |
- digestTTL--; |
- count = rootWatchGroup.detectChanges( |
- exceptionHandler: _exceptionHandler, |
- changeLog: changeLog, |
- fieldStopwatch: _scopeStats.digestFieldStopwatch, |
- evalStopwatch: _scopeStats.digestEvalStopwatch, |
- processStopwatch: _scopeStats.digestProcessStopwatch); |
- |
- if (digestTTL <= LOG_COUNT) { |
- if (changeLog == null) { |
- log = []; |
- digestLog = []; |
- changeLog = (e, c, p) => digestLog.add('$e: $c <= $p'); |
- } else { |
- log.add(digestLog.join(', ')); |
- digestLog.clear(); |
- } |
- } |
- if (digestTTL == 0) { |
- throw 'Model did not stabilize in ${_ttl.ttl} digests. ' |
- 'Last $LOG_COUNT iterations:\n${log.join('\n')}'; |
- } |
- _scopeStats.digestLoop(count); |
- } while (count > 0); |
- } finally { |
- _scopeStats.digestEnd(); |
- _transitionState(STATE_DIGEST, null); |
- } |
- } |
- |
- void flush() { |
- _transitionState(null, STATE_FLUSH); |
- var observeGroup = this._readOnlyGroup as RootWatchGroup; |
- bool runObservers = true; |
- try { |
- do { |
- while(_domWriteHead != null) { |
- try { |
- _domWriteHead.fn(); |
- } catch (e, s) { |
- _exceptionHandler(e, s); |
- } |
- _domWriteHead = _domWriteHead._next; |
- } |
- if (runObservers) { |
- runObservers = false; |
- observeGroup.detectChanges(exceptionHandler:_exceptionHandler); |
- } |
- while(_domReadHead != null) { |
- try { |
- _domReadHead.fn(); |
- } catch (e, s) { |
- _exceptionHandler(e, s); |
- } |
- _domReadHead = _domReadHead._next; |
- } |
- } while (_domWriteHead != null || _domReadHead != null); |
- assert((() { |
- var watchLog = []; |
- var observeLog = []; |
- (_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) { |
- throw 'Observer reaction functions should not change model. \n' |
- 'These watch changes were detected: ${watchLog.join('; ')}\n' |
- 'These observe changes were detected: ${observeLog.join('; ')}'; |
- } |
- return true; |
- })()); |
- } finally { |
- _transitionState(STATE_FLUSH, null); |
- } |
- |
- } |
- |
- // QUEUES |
- void runAsync(fn()) { |
- var chain = new _FunctionChain(fn); |
- if (_runAsyncHead == null) { |
- _runAsyncHead = _runAsyncTail = chain; |
- } else { |
- _runAsyncTail = _runAsyncTail._next = chain; |
- } |
- } |
- |
- void domWrite(fn()) { |
- var chain = new _FunctionChain(fn); |
- if (_domWriteHead == null) { |
- _domWriteHead = _domWriteTail = chain; |
- } else { |
- _domWriteTail = _domWriteTail._next = chain; |
- } |
- } |
- |
- void domRead(fn()) { |
- var chain = new _FunctionChain(fn); |
- if (_domReadHead == null) { |
- _domReadHead = _domReadTail = chain; |
- } else { |
- _domReadTail = _domReadTail._next = chain; |
- } |
- } |
- |
- void destroy() {} |
- |
- void _transitionState(String from, String to) { |
- assert(isAttached); |
- if (_state != from) throw "$_state already in progress can not enter $to."; |
- _state = to; |
- } |
-} |
- |
-/** |
- * Keeps track of Streams for each Scope. When emitting events |
- * we would need to walk the whole tree. Its faster if we can prune |
- * the Scopes we have to visit. |
- * |
- * Scope with no [_ScopeStreams] has no events registered on itself or children |
- * |
- * We keep track of [Stream]s, and also child scope [Stream]s. To save |
- * memory we use the same stream object on all of our parents if they don't |
- * have one. But that means that we have to keep track if the stream belongs |
- * to the node. |
- * |
- * Scope with [_ScopeStreams] but who's [_scope] does not match the scope |
- * is only inherited |
- * |
- * Only [Scope] with [_ScopeStreams] who's [_scope] matches the [Scope] |
- * instance is the actual scope. |
- * |
- * Once the [Stream] is created it can not be removed even if all listeners |
- * are canceled. That is because we don't know if someone still has reference |
- * to it. |
- */ |
-class _Streams { |
- final ExceptionHandler _exceptionHandler; |
- /// Scope we belong to. |
- final Scope _scope; |
- /// [Stream]s for [_scope] only |
- final _streams = new Map<String, ScopeStream>(); |
- /// Child [Scope] event counts. |
- final Map<String, int> _typeCounts; |
- |
- _Streams(this._scope, this._exceptionHandler, _Streams inheritStreams) |
- : _typeCounts = inheritStreams == null |
- ? <String, int>{} |
- : new Map.from(inheritStreams._typeCounts); |
- |
- static ScopeEvent emit(Scope scope, String name, data) { |
- var event = new ScopeEvent(name, scope, data); |
- var scopeCursor = scope; |
- while(scopeCursor != null) { |
- if (scopeCursor._streams != null && |
- scopeCursor._streams._scope == scopeCursor) { |
- ScopeStream stream = scopeCursor._streams._streams[name]; |
- if (stream != null) { |
- event._currentScope = scopeCursor; |
- stream._fire(event); |
- if (event.propagationStopped) return event; |
- } |
- } |
- scopeCursor = scopeCursor._parentScope; |
- } |
- return event; |
- } |
- |
- static ScopeEvent broadcast(Scope scope, String name, data) { |
- _Streams scopeStreams = scope._streams; |
- var event = new ScopeEvent(name, scope, data); |
- if (scopeStreams != null && scopeStreams._typeCounts.containsKey(name)) { |
- var queue = new Queue()..addFirst(scopeStreams._scope); |
- while (queue.isNotEmpty) { |
- scope = queue.removeFirst(); |
- scopeStreams = scope._streams; |
- assert(scopeStreams._scope == scope); |
- if (scopeStreams._streams.containsKey(name)) { |
- var stream = scopeStreams._streams[name]; |
- event._currentScope = scope; |
- stream._fire(event); |
- } |
- // Reverse traversal so that when the queue is read it is correct order. |
- var childScope = scope._childTail; |
- while(childScope != null) { |
- scopeStreams = childScope._streams; |
- if (scopeStreams != null && |
- scopeStreams._typeCounts.containsKey(name)) { |
- queue.addFirst(scopeStreams._scope); |
- } |
- childScope = childScope._prev; |
- } |
- } |
- } |
- return event; |
- } |
- |
- static ScopeStream on(Scope scope, |
- ExceptionHandler _exceptionHandler, |
- String name) { |
- _forceNewScopeStream(scope, _exceptionHandler); |
- return scope._streams._get(scope, name); |
- } |
- |
- static void _forceNewScopeStream(scope, _exceptionHandler) { |
- _Streams streams = scope._streams; |
- Scope scopeCursor = scope; |
- bool splitMode = false; |
- while(scopeCursor != null) { |
- _Streams cursorStreams = scopeCursor._streams; |
- var hasStream = cursorStreams != null; |
- var hasOwnStream = hasStream && cursorStreams._scope == scopeCursor; |
- if (hasOwnStream) return; |
- |
- if (!splitMode && (streams == null || (hasStream && !hasOwnStream))) { |
- if (hasStream && !hasOwnStream) { |
- splitMode = true; |
- } |
- streams = new _Streams(scopeCursor, _exceptionHandler, cursorStreams); |
- } |
- scopeCursor._streams = streams; |
- scopeCursor = scopeCursor._parentScope; |
- } |
- } |
- |
- static void destroy(Scope scope) { |
- var toBeDeletedStreams = scope._streams; |
- if (toBeDeletedStreams == null) return; // no streams to clean up |
- var parentScope = scope._parentScope; // skip current scope as not to delete listeners |
- // find the parent-most scope which still has our stream to be deleted. |
- while (parentScope != null && parentScope._streams == toBeDeletedStreams) { |
- parentScope._streams = null; |
- parentScope = parentScope._parentScope; |
- } |
- // At this point scope is the parent-most scope which has its own typeCounts |
- if (parentScope == null) return; |
- var parentStreams = parentScope._streams; |
- assert(parentStreams != toBeDeletedStreams); |
- // remove typeCounts from the scope to be destroyed from the parent |
- // typeCounts |
- toBeDeletedStreams._typeCounts.forEach( |
- (name, count) => parentStreams._addCount(name, -count)); |
- } |
- |
- async.Stream _get(Scope scope, String name) { |
- assert(scope._streams == this); |
- assert(scope._streams._scope == scope); |
- assert(_exceptionHandler != null); |
- return _streams.putIfAbsent(name, () => |
- new ScopeStream(this, _exceptionHandler, name)); |
- } |
- |
- void _addCount(String name, int amount) { |
- // decrement the counters on all parent scopes |
- _Streams lastStreams = null; |
- var scope = _scope; |
- while (scope != null) { |
- if (lastStreams != scope._streams) { |
- // we have a transition, need to decrement it |
- lastStreams = scope._streams; |
- int count = lastStreams._typeCounts[name]; |
- count = count == null ? amount : count + amount; |
- assert(count >= 0); |
- if (count == 0) { |
- lastStreams._typeCounts.remove(name); |
- if (_scope == scope) _streams.remove(name); |
- } else { |
- lastStreams._typeCounts[name] = count; |
- } |
- } |
- scope = scope._parentScope; |
- } |
- } |
-} |
- |
-class ScopeStream extends async.Stream<ScopeEvent> { |
- final ExceptionHandler _exceptionHandler; |
- final _Streams _streams; |
- final String _name; |
- final subscriptions = <ScopeStreamSubscription>[]; |
- |
- ScopeStream(this._streams, this._exceptionHandler, this._name); |
- |
- ScopeStreamSubscription listen(void onData(ScopeEvent event), |
- { Function onError, |
- void onDone(), |
- bool cancelOnError }) { |
- if (subscriptions.isEmpty) _streams._addCount(_name, 1); |
- var subscription = new ScopeStreamSubscription(this, onData); |
- subscriptions.add(subscription); |
- return subscription; |
- } |
- |
- void _fire(ScopeEvent event) { |
- for (ScopeStreamSubscription subscription in subscriptions) { |
- try { |
- subscription._onData(event); |
- } catch (e, s) { |
- _exceptionHandler(e, s); |
- } |
- } |
- } |
- |
- void _remove(ScopeStreamSubscription subscription) { |
- assert(subscription._scopeStream == this); |
- if (subscriptions.remove(subscription)) { |
- if (subscriptions.isEmpty) _streams._addCount(_name, -1); |
- } else { |
- throw new StateError('AlreadyCanceled'); |
- } |
- } |
-} |
- |
-class ScopeStreamSubscription implements async.StreamSubscription<ScopeEvent> { |
- final ScopeStream _scopeStream; |
- final Function _onData; |
- ScopeStreamSubscription(this._scopeStream, this._onData); |
- |
- // TODO(vbe) should return a Future |
- cancel() => _scopeStream._remove(this); |
- |
- 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(); |
-} |
- |
-class _FunctionChain { |
- final Function fn; |
- _FunctionChain _next; |
- |
- _FunctionChain(fn()) |
- : fn = fn |
- { |
- assert(fn != null); |
- } |
-} |
- |
-class AstParser { |
- final Parser _parser; |
- int _id = 0; |
- ExpressionVisitor _visitor = new ExpressionVisitor(); |
- |
- AstParser(this._parser); |
- |
- AST call(String exp, { FilterMap filters, |
- bool collection:false, |
- Object context:null }) { |
- _visitor.filters = filters; |
- 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); |
- } finally { |
- _visitor.contextRef = contextRef; |
- _visitor.filters = null; |
- } |
- } |
-} |
- |
-class ExpressionVisitor implements Visitor { |
- static final ContextReferenceAST scopeContextRef = new ContextReferenceAST(); |
- AST contextRef = scopeContextRef; |
- |
- AST ast; |
- FilterMap filters; |
- |
- AST visit(Expression exp) { |
- exp.accept(this); |
- assert(this.ast != null); |
- try { |
- return ast; |
- } finally { |
- ast = null; |
- } |
- } |
- |
- AST visitCollection(Expression exp) => new CollectionAST(visit(exp)); |
- AST _mapToAst(Expression expression) => visit(expression); |
- |
- List<AST> _toAst(List<Expression> expressions) => |
- expressions.map(_mapToAst).toList(); |
- |
- void visitCallScope(CallScope exp) { |
- ast = new MethodAST(contextRef, exp.name, _toAst(exp.arguments)); |
- } |
- void visitCallMember(CallMember exp) { |
- ast = new MethodAST(visit(exp.object), exp.name, _toAst(exp.arguments)); |
- } |
- visitAccessScope(AccessScope exp) { |
- ast = new FieldReadAST(contextRef, exp.name); |
- } |
- visitAccessMember(AccessMember exp) { |
- ast = new FieldReadAST(visit(exp.object), exp.name); |
- } |
- visitBinary(Binary exp) { |
- ast = new PureFunctionAST(exp.operation, |
- _operationToFunction(exp.operation), |
- [visit(exp.left), visit(exp.right)]); |
- } |
- void visitPrefix(Prefix exp) { |
- ast = new PureFunctionAST(exp.operation, |
- _operationToFunction(exp.operation), |
- [visit(exp.expression)]); |
- } |
- void visitConditional(Conditional exp) { |
- ast = new PureFunctionAST('?:', _operation_ternary, |
- [visit(exp.condition), visit(exp.yes), |
- visit(exp.no)]); |
- } |
- void visitAccessKeyed(AccessKeyed exp) { |
- ast = new PureFunctionAST('[]', _operation_bracket, |
- [visit(exp.object), visit(exp.key)]); |
- } |
- void visitLiteralPrimitive(LiteralPrimitive exp) { |
- ast = new ConstantAST(exp.value); |
- } |
- void visitLiteralString(LiteralString exp) { |
- ast = new ConstantAST(exp.value); |
- } |
- void visitLiteralArray(LiteralArray exp) { |
- List<AST> items = _toAst(exp.elements); |
- ast = new PureFunctionAST('[${items.join(', ')}]', new ArrayFn(), items); |
- } |
- |
- void visitLiteralObject(LiteralObject exp) { |
- List<String> keys = exp.keys; |
- List<AST> values = _toAst(exp.values); |
- assert(keys.length == values.length); |
- var kv = <String>[]; |
- for (var i = 0; i < keys.length; i++) { |
- kv.add('${keys[i]}: ${values[i]}'); |
- } |
- ast = new PureFunctionAST('{${kv.join(', ')}}', new MapFn(keys), values); |
- } |
- |
- void visitFilter(Filter exp) { |
- Function filterFunction = filters(exp.name); |
- List<AST> args = [visitCollection(exp.expression)]; |
- args.addAll(_toAst(exp.arguments).map((ast) => new CollectionAST(ast))); |
- ast = new PureFunctionAST('|${exp.name}', |
- new _FilterWrapper(filterFunction, args.length), args); |
- } |
- |
- // TODO(misko): this is a corner case. Choosing not to implement for now. |
- void visitCallFunction(CallFunction exp) { |
- _notSupported("function's returing functions"); |
- } |
- void visitAssign(Assign exp) { |
- _notSupported('assignement'); |
- } |
- void visitLiteral(Literal exp) { |
- _notSupported('literal'); |
- } |
- void visitExpression(Expression exp) { |
- _notSupported('?'); |
- } |
- void visitChain(Chain exp) { |
- _notSupported(';'); |
- } |
- |
- void _notSupported(String name) { |
- throw new StateError("Can not watch expression containing '$name'."); |
- } |
-} |
- |
-Function _operationToFunction(String operation) { |
- switch(operation) { |
- case '!' : return _operation_negate; |
- case '+' : return _operation_add; |
- case '-' : return _operation_subtract; |
- case '*' : return _operation_multiply; |
- case '/' : return _operation_divide; |
- case '~/' : return _operation_divide_int; |
- case '%' : return _operation_remainder; |
- case '==' : return _operation_equals; |
- case '!=' : return _operation_not_equals; |
- case '<' : return _operation_less_then; |
- case '>' : return _operation_greater_then; |
- case '<=' : return _operation_less_or_equals_then; |
- case '>=' : return _operation_greater_or_equals_then; |
- case '^' : return _operation_power; |
- case '&' : return _operation_bitwise_and; |
- case '&&' : return _operation_logical_and; |
- case '||' : return _operation_logical_or; |
- default: throw new StateError(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_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; |
-// 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); |
- |
-_operation_ternary(condition, yes, no) => toBool(condition) ? yes : no; |
-_operation_bracket(obj, key) => obj == null ? null : obj[key]; |
- |
-class ArrayFn extends FunctionApply { |
- // TODO(misko): figure out why do we need to make a copy? |
- apply(List args) => new List.from(args); |
-} |
- |
-class MapFn extends FunctionApply { |
- final List<String> keys; |
- |
- MapFn(this.keys); |
- |
- 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); |
- } |
-} |
- |
-class _FilterWrapper extends FunctionApply { |
- final Function filterFn; |
- final List args; |
- final List<Watch> argsWatches; |
- _FilterWrapper(this.filterFn, length): |
- args = new List(length), |
- argsWatches = new List(length); |
- |
- apply(List values) { |
- for (var i=0; i < values.length; i++) { |
- var value = values[i]; |
- var lastValue = args[i]; |
- if (!identical(value, lastValue)) { |
- if (value is CollectionChangeRecord) { |
- args[i] = (value as CollectionChangeRecord).iterable; |
- } else { |
- args[i] = value; |
- } |
- } |
- } |
- var value = Function.apply(filterFn, args); |
- if (value is Iterable) { |
- // Since filters 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); |
- } |
- return value; |
- } |
-} |