| OLD | NEW |
| 1 part of angular.core_internal; | 1 part of angular.core; |
| 2 | 2 |
| 3 NOT_IMPLEMENTED() { |
| 4 throw new StateError('Not Implemented'); |
| 5 } |
| 6 |
| 3 typedef EvalFunction0(); | 7 typedef EvalFunction0(); |
| 4 typedef EvalFunction1(context); | 8 typedef EvalFunction1(context); |
| 5 | 9 |
| 6 /** | 10 /** |
| 7 * Injected into the listener function within [Scope.on] to provide | 11 * Injected into the listener function within [Scope.on] to provide |
| 8 * event-specific details to the scope listener. | 12 * event-specific details to the scope listener. |
| 9 */ | 13 */ |
| 10 class ScopeEvent { | 14 class ScopeEvent { |
| 11 static final String DESTROY = 'ng-destroy'; | 15 static final String DESTROY = 'ng-destroy'; |
| 12 | 16 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 70 } | 74 } |
| 71 | 75 |
| 72 /** | 76 /** |
| 73 * Allows the configuration of [Scope.digest] iteration maximum time-to-live | 77 * Allows the configuration of [Scope.digest] iteration maximum time-to-live |
| 74 * value. Digest keeps checking the state of the watcher getters until it | 78 * value. Digest keeps checking the state of the watcher getters until it |
| 75 * can execute one full iteration with no watchers triggering. TTL is used | 79 * can execute one full iteration with no watchers triggering. TTL is used |
| 76 * to prevent an infinite loop where watch A triggers watch B which in turn | 80 * to prevent an infinite loop where watch A triggers watch B which in turn |
| 77 * triggers watch A. If the system does not stabilize in TTL iterations then | 81 * triggers watch A. If the system does not stabilize in TTL iterations then |
| 78 * the digest is stopped and an exception is thrown. | 82 * the digest is stopped and an exception is thrown. |
| 79 */ | 83 */ |
| 80 @Injectable() | 84 @NgInjectableService() |
| 81 class ScopeDigestTTL { | 85 class ScopeDigestTTL { |
| 82 final int ttl; | 86 final int ttl; |
| 83 ScopeDigestTTL(): ttl = 5; | 87 ScopeDigestTTL(): ttl = 5; |
| 84 ScopeDigestTTL.value(this.ttl); | 88 ScopeDigestTTL.value(this.ttl); |
| 85 } | 89 } |
| 86 | 90 |
| 87 //TODO(misko): I don't think this should be in scope. | 91 //TODO(misko): I don't think this should be in scope. |
| 88 class ScopeLocals implements Map { | 92 class ScopeLocals implements Map { |
| 89 static wrapper(scope, Map<String, Object> locals) => | 93 static wrapper(scope, Map<String, Object> locals) => |
| 90 new ScopeLocals(scope, locals); | 94 new ScopeLocals(scope, locals); |
| 91 | 95 |
| 92 Map _scope; | 96 Map _scope; |
| 93 Map<String, Object> _locals; | 97 Map<String, Object> _locals; |
| 94 | 98 |
| 95 ScopeLocals(this._scope, this._locals); | 99 ScopeLocals(this._scope, this._locals); |
| 96 | 100 |
| 97 void operator []=(String name, value) { | 101 void operator []=(String name, value) { |
| 98 _scope[name] = value; | 102 _scope[name] = value; |
| 99 } | 103 } |
| 100 dynamic operator [](String name) => | 104 dynamic operator [](String name) => |
| 101 // as Map needed to clear Dart2js warning | 105 (_locals.containsKey(name) ? _locals : _scope)[name]; |
| 102 ((_locals.containsKey(name) ? _locals : _scope) as Map)[name]; | |
| 103 | 106 |
| 104 bool get isEmpty => _scope.isEmpty && _locals.isEmpty; | 107 bool get isEmpty => _scope.isEmpty && _locals.isEmpty; |
| 105 bool get isNotEmpty => _scope.isNotEmpty || _locals.isNotEmpty; | 108 bool get isNotEmpty => _scope.isNotEmpty || _locals.isNotEmpty; |
| 106 List<String> get keys => _scope.keys; | 109 List<String> get keys => _scope.keys; |
| 107 List get values => _scope.values; | 110 List get values => _scope.values; |
| 108 int get length => _scope.length; | 111 int get length => _scope.length; |
| 109 | 112 |
| 110 void forEach(fn) { | 113 void forEach(fn) { |
| 111 _scope.forEach(fn); | 114 _scope.forEach(fn); |
| 112 } | 115 } |
| 113 dynamic remove(key) => _scope.remove(key); | 116 dynamic remove(key) => _scope.remove(key); |
| 114 void clear() { | 117 void clear() { |
| 115 _scope.clear; | 118 _scope.clear; |
| 116 } | 119 } |
| 117 bool containsKey(key) => _scope.containsKey(key); | 120 bool containsKey(key) => _scope.containsKey(key); |
| 118 bool containsValue(key) => _scope.containsValue(key); | 121 bool containsValue(key) => _scope.containsValue(key); |
| 119 void addAll(map) { | 122 void addAll(map) { |
| 120 _scope.addAll(map); | 123 _scope.addAll(map); |
| 121 } | 124 } |
| 122 dynamic putIfAbsent(key, fn) => _scope.putIfAbsent(key, fn); | 125 dynamic putIfAbsent(key, fn) => _scope.putIfAbsent(key, fn); |
| 123 } | 126 } |
| 124 | 127 |
| 125 /** | 128 /** |
| 126 * [Scope] is represents a collection of [watch]es [observe]ers, and [context] | 129 * [Scope] is represents a collection of [watch]es [observe]ers, and [context] |
| 127 * for the watchers, observers and [eval]uations. Scopes structure loosely | 130 * for the watchers, observers and [eval]uations. Scopes structure loosely |
| 128 * mimics the DOM structure. Scopes and [View]s are bound to each other. | 131 * mimics the DOM structure. Scopes and [Block]s are bound to each other. |
| 129 * As scopes are created and destroyed by [ViewFactory] they are responsible | 132 * As scopes are created and destroyed by [BlockFactory] they are responsible |
| 130 * for change detection, change processing and memory management. | 133 * for change detection, change processing and memory management. |
| 131 */ | 134 */ |
| 132 class Scope { | 135 class Scope { |
| 133 final String id; | |
| 134 int _childScopeNextId = 0; | |
| 135 | 136 |
| 136 /** | 137 /** |
| 137 * The default execution context for [watch]es [observe]ers, and [eval]uation. | 138 * The default execution context for [watch]es [observe]ers, and [eval]uation. |
| 138 */ | 139 */ |
| 139 final context; | 140 final context; |
| 140 | 141 |
| 141 /** | 142 /** |
| 142 * The [RootScope] of the application. | 143 * The [RootScope] of the application. |
| 143 */ | 144 */ |
| 144 final RootScope rootScope; | 145 final RootScope rootScope; |
| 145 | 146 |
| 146 Scope _parentScope; | 147 Scope _parentScope; |
| 147 | 148 |
| 148 /** | 149 /** |
| 149 * The parent [Scope]. | 150 * The parent [Scope]. |
| 150 */ | 151 */ |
| 151 Scope get parentScope => _parentScope; | 152 Scope get parentScope => _parentScope; |
| 152 | 153 |
| 153 final ScopeStats _stats; | |
| 154 | |
| 155 /** | 154 /** |
| 156 * Return `true` if the scope has been destroyed. Once scope is destroyed | 155 * Return `true` if the scope has been destroyed. Once scope is destroyed |
| 157 * No operations are allowed on it. | 156 * No operations are allowed on it. |
| 158 */ | 157 */ |
| 159 bool get isDestroyed { | 158 bool get isDestroyed { |
| 160 var scope = this; | 159 var scope = this; |
| 161 while (scope != null) { | 160 while(scope != null) { |
| 162 if (scope == rootScope) return false; | 161 if (scope == rootScope) return false; |
| 163 scope = scope._parentScope; | 162 scope = scope._parentScope; |
| 164 } | 163 } |
| 165 return true; | 164 return true; |
| 166 } | 165 } |
| 167 | 166 |
| 168 /** | 167 /** |
| 169 * Returns true if the scope is still attached to the [RootScope]. | 168 * Returns true if the scope is still attached to the [RootScope]. |
| 170 */ | 169 */ |
| 171 bool get isAttached => !isDestroyed; | 170 bool get isAttached => !isDestroyed; |
| 172 | 171 |
| 173 // TODO(misko): WatchGroup should be private. | 172 // TODO(misko): WatchGroup should be private. |
| 174 // Instead we should expose performance stats about the watches | 173 // Instead we should expose performance stats about the watches |
| 175 // such as # of watches, checks/1ms, field checks, function checks, etc | 174 // such as # of watches, checks/1ms, field checks, function checks, etc |
| 176 final WatchGroup _readWriteGroup; | 175 final WatchGroup _readWriteGroup; |
| 177 final WatchGroup _readOnlyGroup; | 176 final WatchGroup _readOnlyGroup; |
| 178 | 177 |
| 179 Scope _childHead, _childTail, _next, _prev; | 178 Scope _childHead, _childTail, _next, _prev; |
| 180 _Streams _streams; | 179 _Streams _streams; |
| 181 | 180 |
| 182 /// Do not use. Exposes internal state for testing. | 181 /// Do not use. Exposes internal state for testing. |
| 183 bool get hasOwnStreams => _streams != null && _streams._scope == this; | 182 bool get hasOwnStreams => _streams != null && _streams._scope == this; |
| 184 | 183 |
| 185 Scope(Object this.context, this.rootScope, this._parentScope, | 184 Scope(Object this.context, this.rootScope, this._parentScope, |
| 186 this._readWriteGroup, this._readOnlyGroup, this.id, | 185 this._readWriteGroup, this._readOnlyGroup); |
| 187 this._stats); | |
| 188 | 186 |
| 189 /** | 187 /** |
| 190 * Use [watch] to set up change detection on an expression. | 188 * A [watch] sets up a watch in the [digest] phase of the [apply] cycle. |
| 191 * | 189 * |
| 192 * * [expression]: The expression to watch for changes. | 190 * Use [watch] if the reaction function can cause updates to model. In your |
| 193 * * [reactionFn]: The reaction function to execute when a change is detected
in the watched | 191 * controller code you will most likely use [watch]. |
| 194 * expression. | |
| 195 * * [context]: The object against which the expression is evaluated. This def
aults to the | |
| 196 * [Scope.context] if no context is specified. | |
| 197 * * [formatters]: If the watched expression contains formatters, | |
| 198 * this map specifies the set of formatters that are used by the expression. | |
| 199 * * [canChangeModel]: Specifies whether the [reactionFn] changes the model. R
eaction | |
| 200 * functions that change the model are processed as part of the [digest] cyc
le. Otherwise, | |
| 201 * they are processed as part of the [flush] cycle. | |
| 202 * * [collection]: If [:true:], then the expression points to a collection (a
list or a map), | |
| 203 * and the collection should be shallow watched. If [:false:] then the expre
ssion is watched | |
| 204 * by reference. When watching a collection, the reaction function receives
a | |
| 205 * [CollectionChangeItem] that lists all the changes. | |
| 206 */ | 192 */ |
| 207 Watch watch(String expression, ReactionFn reactionFn, {context, | 193 Watch watch(expression, ReactionFn reactionFn, |
| 208 FormatterMap formatters, bool canChangeModel: true, bool collection: false
}) { | 194 {context, FilterMap filters, bool readOnly: false}) { |
| 209 assert(isAttached); | 195 assert(isAttached); |
| 210 assert(expression is String); | 196 assert(expression != null); |
| 211 assert(canChangeModel is bool); | 197 AST ast; |
| 212 | |
| 213 Watch watch; | 198 Watch watch; |
| 214 ReactionFn fn = reactionFn; | 199 ReactionFn fn = reactionFn; |
| 215 if (expression.isEmpty) { | 200 if (expression is AST) { |
| 216 expression = '""'; | 201 ast = expression; |
| 217 } else { | 202 } else if (expression is String) { |
| 218 if (expression.startsWith('::')) { | 203 if (expression.startsWith('::')) { |
| 219 expression = expression.substring(2); | 204 expression = expression.substring(2); |
| 220 fn = (value, last) { | 205 fn = (value, last) { |
| 221 if (value != null) { | 206 if (value != null) { |
| 222 watch.remove(); | 207 watch.remove(); |
| 223 return reactionFn(value, last); | 208 return reactionFn(value, last); |
| 224 } | 209 } |
| 225 }; | 210 }; |
| 226 } else if (expression.startsWith(':')) { | 211 } else if (expression.startsWith(':')) { |
| 227 expression = expression.substring(1); | 212 expression = expression.substring(1); |
| 228 fn = (value, last) { | 213 fn = (value, last) => value == null ? null : reactionFn(value, last); |
| 229 if (value != null) reactionFn(value, last); | |
| 230 }; | |
| 231 } | 214 } |
| 215 ast = rootScope._astParser(expression, context: context, filters: filters)
; |
| 216 } else { |
| 217 throw 'expressions must be String or AST got $expression.'; |
| 232 } | 218 } |
| 233 | 219 return watch = (readOnly ? _readOnlyGroup : _readWriteGroup).watch(ast, fn); |
| 234 AST ast = rootScope._astParser(expression, context: context, | |
| 235 formatters: formatters, collection: collection); | |
| 236 | |
| 237 WatchGroup group = canChangeModel ? _readWriteGroup : _readOnlyGroup; | |
| 238 return watch = group.watch(ast, fn); | |
| 239 } | 220 } |
| 240 | 221 |
| 241 dynamic eval(expression, [Map locals]) { | 222 dynamic eval(expression, [Map locals]) { |
| 242 assert(isAttached); | 223 assert(isAttached); |
| 243 assert(expression == null || | 224 assert(expression == null || |
| 244 expression is String || | 225 expression is String || |
| 245 expression is Function); | 226 expression is Function); |
| 246 if (expression is String && expression.isNotEmpty) { | 227 if (expression is String && expression.isNotEmpty) { |
| 247 var obj = locals == null ? context : new ScopeLocals(context, locals); | 228 var obj = locals == null ? context : new ScopeLocals(context, locals); |
| 248 return rootScope._parser(expression).eval(obj); | 229 return rootScope._parser(expression).eval(obj); |
| 249 } | 230 } |
| 250 | 231 |
| 251 assert(locals == null); | 232 assert(locals == null); |
| 252 if (expression is EvalFunction1) return expression(context); | 233 if (expression is EvalFunction1) return expression(context); |
| 253 if (expression is EvalFunction0) return expression(); | 234 if (expression is EvalFunction0) return expression(); |
| 254 return null; | 235 return null; |
| 255 } | 236 } |
| 256 | 237 |
| 238 dynamic applyInZone([expression, Map locals]) => |
| 239 rootScope._zone.run(() => apply(expression, locals)); |
| 240 |
| 257 dynamic apply([expression, Map locals]) { | 241 dynamic apply([expression, Map locals]) { |
| 258 _assertInternalStateConsistency(); | 242 _assertInternalStateConsistency(); |
| 259 rootScope._transitionState(null, RootScope.STATE_APPLY); | 243 rootScope._transitionState(null, RootScope.STATE_APPLY); |
| 260 try { | 244 try { |
| 261 return eval(expression, locals); | 245 return eval(expression, locals); |
| 262 } catch (e, s) { | 246 } catch (e, s) { |
| 263 rootScope._exceptionHandler(e, s); | 247 rootScope._exceptionHandler(e, s); |
| 264 } finally { | 248 } finally { |
| 265 rootScope.._transitionState(RootScope.STATE_APPLY, null) | 249 rootScope |
| 266 ..digest() | 250 .._transitionState(RootScope.STATE_APPLY, null) |
| 267 ..flush(); | 251 ..digest() |
| 252 ..flush(); |
| 268 } | 253 } |
| 269 } | 254 } |
| 270 | 255 |
| 271 ScopeEvent emit(String name, [data]) { | 256 ScopeEvent emit(String name, [data]) { |
| 272 assert(isAttached); | 257 assert(isAttached); |
| 273 return _Streams.emit(this, name, data); | 258 return _Streams.emit(this, name, data); |
| 274 } | 259 } |
| 275 | |
| 276 ScopeEvent broadcast(String name, [data]) { | 260 ScopeEvent broadcast(String name, [data]) { |
| 277 assert(isAttached); | 261 assert(isAttached); |
| 278 return _Streams.broadcast(this, name, data); | 262 return _Streams.broadcast(this, name, data); |
| 279 } | 263 } |
| 280 | |
| 281 ScopeStream on(String name) { | 264 ScopeStream on(String name) { |
| 282 assert(isAttached); | 265 assert(isAttached); |
| 283 return _Streams.on(this, rootScope._exceptionHandler, name); | 266 return _Streams.on(this, rootScope._exceptionHandler, name); |
| 284 } | 267 } |
| 285 | 268 |
| 286 Scope createChild(Object childContext) { | 269 Scope createChild(Object childContext) { |
| 287 assert(isAttached); | 270 assert(isAttached); |
| 288 var child = new Scope(childContext, rootScope, this, | 271 var child = new Scope(childContext, rootScope, this, |
| 289 _readWriteGroup.newGroup(childContext), | 272 _readWriteGroup.newGroup(childContext), |
| 290 _readOnlyGroup.newGroup(childContext), | 273 _readOnlyGroup.newGroup(childContext)); |
| 291 '$id:${_childScopeNextId++}', | 274 var next = null; |
| 292 _stats); | |
| 293 | |
| 294 var prev = _childTail; | 275 var prev = _childTail; |
| 276 child._next = next; |
| 295 child._prev = prev; | 277 child._prev = prev; |
| 296 if (prev == null) _childHead = child; else prev._next = child; | 278 if (prev == null) _childHead = child; else prev._next = child; |
| 297 _childTail = child; | 279 if (next == null) _childTail = child; else next._prev = child; |
| 298 return child; | 280 return child; |
| 299 } | 281 } |
| 300 | 282 |
| 301 void destroy() { | 283 void destroy() { |
| 302 assert(isAttached); | 284 assert(isAttached); |
| 303 broadcast(ScopeEvent.DESTROY); | 285 broadcast(ScopeEvent.DESTROY); |
| 304 _Streams.destroy(this); | 286 _Streams.destroy(this); |
| 305 | 287 |
| 306 if (_prev == null) { | 288 if (_prev == null) { |
| 307 _parentScope._childHead = _next; | 289 _parentScope._childHead = _next; |
| 308 } else { | 290 } else { |
| 309 _prev._next = _next; | 291 _prev._next = _next; |
| 310 } | 292 } |
| 311 if (_next == null) { | 293 if (_next == null) { |
| 312 _parentScope._childTail = _prev; | 294 _parentScope._childTail = _prev; |
| 313 } else { | 295 } else { |
| 314 _next._prev = _prev; | 296 _next._prev = _prev; |
| 315 } | 297 } |
| 316 | 298 |
| 317 _next = _prev = null; | 299 _next = _prev = null; |
| 318 | 300 |
| 319 _readWriteGroup.remove(); | 301 _readWriteGroup.remove(); |
| 320 _readOnlyGroup.remove(); | 302 _readOnlyGroup.remove(); |
| 321 _parentScope = null; | 303 _parentScope = null; |
| 304 _assertInternalStateConsistency(); |
| 322 } | 305 } |
| 323 | 306 |
| 324 _assertInternalStateConsistency() { | 307 _assertInternalStateConsistency() { |
| 325 assert((() { | 308 assert((() { |
| 326 rootScope._verifyStreams(null, '', []); | 309 rootScope._verifyStreams(null, '', []); |
| 327 return true; | 310 return true; |
| 328 })()); | 311 })()); |
| 329 } | 312 } |
| 330 | 313 |
| 331 Map<bool,int> _verifyStreams(parentScope, prefix, log) { | 314 Map<bool,int> _verifyStreams(parentScope, prefix, log) { |
| 332 assert(_parentScope == parentScope); | 315 assert(_parentScope == parentScope); |
| 333 var counts = {}; | 316 var counts = {}; |
| 334 var typeCounts = _streams == null ? {} : _streams._typeCounts; | 317 var typeCounts = _streams == null ? {} : _streams._typeCounts; |
| 335 var connection = _streams != null && _streams._scope == this ? '=' : '-'; | 318 var connection = _streams != null && _streams._scope == this ? '=' : '-'; |
| 336 log..add(prefix)..add(hashCode)..add(connection)..add(typeCounts)..add('\n')
; | 319 log..add(prefix)..add(hashCode)..add(connection)..add(typeCounts)..add('\n')
; |
| 337 if (_streams == null) { | 320 if (_streams == null) { |
| 338 } else if (_streams._scope == this) { | 321 } else if (_streams._scope == this) { |
| 339 _streams._streams.forEach((k, ScopeStream stream){ | 322 _streams._streams.forEach((k, ScopeStream stream){ |
| 340 if (stream.subscriptions.isNotEmpty) { | 323 if (stream.subscriptions.isNotEmpty) { |
| 341 counts[k] = 1 + (counts.containsKey(k) ? counts[k] : 0); | 324 counts[k] = 1 + (counts.containsKey(k) ? counts[k] : 0); |
| 342 } | 325 } |
| 343 }); | 326 }); |
| 344 } | 327 } |
| 345 var childScope = _childHead; | 328 var childScope = _childHead; |
| 346 while (childScope != null) { | 329 while(childScope != null) { |
| 347 childScope._verifyStreams(this, ' $prefix', log).forEach((k, v) { | 330 childScope._verifyStreams(this, ' $prefix', log).forEach((k, v) { |
| 348 counts[k] = v + (counts.containsKey(k) ? counts[k] : 0); | 331 counts[k] = v + (counts.containsKey(k) ? counts[k] : 0); |
| 349 }); | 332 }); |
| 350 childScope = childScope._next; | 333 childScope = childScope._next; |
| 351 } | 334 } |
| 352 if (!_mapEqual(counts, typeCounts)) { | 335 if (!_mapEqual(counts, typeCounts)) { |
| 353 throw 'Streams actual: $counts != bookkeeping: $typeCounts\n' | 336 throw 'Streams actual: $counts != bookkeeping: $typeCounts\n' |
| 354 'Offending scope: [scope: ${this.hashCode}]\n' | 337 'Offending scope: [scope: ${this.hashCode}]\n' |
| 355 '${log.join('')}'; | 338 '${log.join('')}'; |
| 356 } | 339 } |
| 357 return counts; | 340 return counts; |
| 358 } | 341 } |
| 359 } | 342 } |
| 360 | 343 |
| 361 _mapEqual(Map a, Map b) => a.length == b.length && | 344 _mapEqual(Map a, Map b) => a.length == b.length && |
| 362 a.keys.every((k) => b.containsKey(k) && a[k] == b[k]); | 345 a.keys.every((k) => b.containsKey(k) && a[k] == b[k]); |
| 363 | 346 |
| 364 /** | |
| 365 * ScopeStats collects and emits statistics about a [Scope]. | |
| 366 * | |
| 367 * ScopeStats supports emitting the results. Result emission can be started or | |
| 368 * stopped at runtime. The result emission can is configured by supplying a | |
| 369 * [ScopeStatsEmitter]. | |
| 370 */ | |
| 371 @Injectable() | |
| 372 class ScopeStats { | 347 class ScopeStats { |
| 373 final fieldStopwatch = new AvgStopwatch(); | 348 bool report = true; |
| 374 final evalStopwatch = new AvgStopwatch(); | 349 final nf = new NumberFormat.decimalPattern(); |
| 375 final processStopwatch = new AvgStopwatch(); | |
| 376 | 350 |
| 377 List<int> _digestLoopTimes = []; | 351 final digestFieldStopwatch = new AvgStopwatch(); |
| 378 int _flushPhaseDuration = 0 ; | 352 final digestEvalStopwatch = new AvgStopwatch(); |
| 379 int _assertFlushPhaseDuration = 0; | 353 final digestProcessStopwatch = new AvgStopwatch(); |
| 354 int _digestLoopNo = 0; |
| 380 | 355 |
| 381 int _loopNo = 0; | 356 final flushFieldStopwatch = new AvgStopwatch(); |
| 382 ScopeStatsEmitter _emitter; | 357 final flushEvalStopwatch = new AvgStopwatch(); |
| 383 ScopeStatsConfig _config; | 358 final flushProcessStopwatch = new AvgStopwatch(); |
| 384 | 359 |
| 385 /** | 360 ScopeStats({this.report: false}) { |
| 386 * Construct a new instance of ScopeStats. | 361 nf.maximumFractionDigits = 0; |
| 387 */ | 362 } |
| 388 ScopeStats(this._emitter, this._config); | |
| 389 | 363 |
| 390 void digestStart() { | 364 void digestStart() { |
| 391 _digestLoopTimes = []; | 365 _digestStopwatchReset(); |
| 392 _stopwatchReset(); | 366 _digestLoopNo = 0; |
| 393 _loopNo = 0; | |
| 394 } | 367 } |
| 395 | 368 |
| 396 int _allStagesDuration() { | 369 _digestStopwatchReset() { |
| 397 return fieldStopwatch.elapsedMicroseconds + | 370 digestFieldStopwatch.reset(); |
| 398 evalStopwatch.elapsedMicroseconds + | 371 digestEvalStopwatch.reset(); |
| 399 processStopwatch.elapsedMicroseconds; | 372 digestProcessStopwatch.reset(); |
| 400 } | |
| 401 | |
| 402 _stopwatchReset() { | |
| 403 fieldStopwatch.reset(); | |
| 404 evalStopwatch.reset(); | |
| 405 processStopwatch.reset(); | |
| 406 } | 373 } |
| 407 | 374 |
| 408 void digestLoop(int changeCount) { | 375 void digestLoop(int changeCount) { |
| 409 _loopNo++; | 376 _digestLoopNo++; |
| 410 if (_config.emit && _emitter != null) { | 377 if (report) { |
| 411 _emitter.emit(_loopNo.toString(), fieldStopwatch, evalStopwatch, | 378 print(this); |
| 412 processStopwatch); | |
| 413 } | 379 } |
| 414 _digestLoopTimes.add( _allStagesDuration() ); | 380 _digestStopwatchReset(); |
| 415 _stopwatchReset(); | 381 } |
| 382 |
| 383 String _stat(AvgStopwatch s) { |
| 384 return '${nf.format(s.count)}' |
| 385 ' / ${nf.format(s.elapsedMicroseconds)} us' |
| 386 ' = ${nf.format(s.ratePerMs)} #/ms'; |
| 416 } | 387 } |
| 417 | 388 |
| 418 void digestEnd() { | 389 void digestEnd() { |
| 419 } | 390 } |
| 420 | 391 |
| 421 void domWriteStart() {} | 392 toString() => |
| 422 void domWriteEnd() {} | 393 'digest #$_digestLoopNo:' |
| 423 void domReadStart() {} | 394 'Field: ${_stat(digestFieldStopwatch)} ' |
| 424 void domReadEnd() {} | 395 'Eval: ${_stat(digestEvalStopwatch)} ' |
| 425 void flushStart() { | 396 'Process: ${_stat(digestProcessStopwatch)}'; |
| 426 _stopwatchReset(); | |
| 427 } | |
| 428 void flushEnd() { | |
| 429 if (_config.emit && _emitter != null) { | |
| 430 _emitter.emit(RootScope.STATE_FLUSH, fieldStopwatch, evalStopwatch, | |
| 431 processStopwatch); | |
| 432 } | |
| 433 _flushPhaseDuration = _allStagesDuration(); | |
| 434 } | |
| 435 void flushAssertStart() { | |
| 436 _stopwatchReset(); | |
| 437 } | |
| 438 void flushAssertEnd() { | |
| 439 if (_config.emit && _emitter != null) { | |
| 440 _emitter.emit(RootScope.STATE_FLUSH_ASSERT, fieldStopwatch, evalStopwatch, | |
| 441 processStopwatch); | |
| 442 } | |
| 443 _assertFlushPhaseDuration = _allStagesDuration(); | |
| 444 } | |
| 445 | |
| 446 void cycleEnd() { | |
| 447 } | |
| 448 } | 397 } |
| 449 | 398 |
| 450 /** | |
| 451 * ScopeStatsEmitter is in charge of formatting the [ScopeStats] and outputting | |
| 452 * a message. | |
| 453 */ | |
| 454 @Injectable() | |
| 455 class ScopeStatsEmitter { | |
| 456 static String _PAD_ = ' '; | |
| 457 static String _HEADER_ = pad('APPLY', 7) + ':'+ | |
| 458 pad('FIELD', 19) + pad('|', 20) + | |
| 459 pad('EVAL', 19) + pad('|', 20) + | |
| 460 pad('REACTION', 19) + pad('|', 20) + | |
| 461 pad('TOTAL', 10) + '\n'; | |
| 462 final _nfDec = new NumberFormat("0.00", "en_US"); | |
| 463 final _nfInt = new NumberFormat("0", "en_US"); | |
| 464 | 399 |
| 465 static pad(String str, int size) => _PAD_.substring(0, max(size - str.length,
0)) + str; | |
| 466 | |
| 467 _ms(num value) => '${pad(_nfDec.format(value), 9)} ms'; | |
| 468 _us(num value) => _ms(value / 1000); | |
| 469 _tally(num value) => '${pad(_nfInt.format(value), 6)}'; | |
| 470 | |
| 471 /** | |
| 472 * Emit a message based on the phase and state of stopwatches. | |
| 473 */ | |
| 474 void emit(String phaseOrLoopNo, AvgStopwatch fieldStopwatch, | |
| 475 AvgStopwatch evalStopwatch, AvgStopwatch processStopwatch) { | |
| 476 var total = fieldStopwatch.elapsedMicroseconds + | |
| 477 evalStopwatch.elapsedMicroseconds + | |
| 478 processStopwatch.elapsedMicroseconds; | |
| 479 print('${_formatPrefix(phaseOrLoopNo)} ' | |
| 480 '${_stat(fieldStopwatch)} | ' | |
| 481 '${_stat(evalStopwatch)} | ' | |
| 482 '${_stat(processStopwatch)} | ' | |
| 483 '${_ms(total/1000)}'); | |
| 484 } | |
| 485 | |
| 486 String _formatPrefix(String prefix) { | |
| 487 if (prefix == RootScope.STATE_FLUSH) return ' flush:'; | |
| 488 if (prefix == RootScope.STATE_FLUSH_ASSERT) return ' assert:'; | |
| 489 | |
| 490 return (prefix == '1' ? _HEADER_ : '') + ' #$prefix:'; | |
| 491 } | |
| 492 | |
| 493 String _stat(AvgStopwatch s) { | |
| 494 return '${_tally(s.count)} / ${_us(s.elapsedMicroseconds)} @(${_tally(s.rate
PerMs)} #/ms)'; | |
| 495 } | |
| 496 } | |
| 497 | |
| 498 /** | |
| 499 * ScopeStatsConfig is used to modify behavior of [ScopeStats]. You can use this | |
| 500 * object to modify behavior at runtime too. | |
| 501 */ | |
| 502 class ScopeStatsConfig { | |
| 503 var emit = false; | |
| 504 | |
| 505 ScopeStatsConfig(); | |
| 506 ScopeStatsConfig.enabled() { | |
| 507 emit = true; | |
| 508 } | |
| 509 } | |
| 510 /** | |
| 511 * | |
| 512 * Every Angular application has exactly one RootScope. RootScope extends Scope,
adding | |
| 513 * services related to change detection, async unit-of-work processing, and DOM
read/write queues. | |
| 514 * The RootScope can not be destroyed. | |
| 515 * | |
| 516 * ## Lifecycle | |
| 517 * | |
| 518 * All work in Angular must be done within a context of a VmTurnZone. VmTurnZone
detects the end | |
| 519 * of the VM turn, and calls the Apply method to process the changes at the end
of VM turn. | |
| 520 * | |
| 521 */ | |
| 522 @Injectable() | |
| 523 class RootScope extends Scope { | 400 class RootScope extends Scope { |
| 524 static final STATE_APPLY = 'apply'; | 401 static final STATE_APPLY = 'apply'; |
| 525 static final STATE_DIGEST = 'digest'; | 402 static final STATE_DIGEST = 'digest'; |
| 526 static final STATE_FLUSH = 'flush'; | 403 static final STATE_FLUSH = 'digest'; |
| 527 static final STATE_FLUSH_ASSERT = 'assert'; | |
| 528 | 404 |
| 529 final ExceptionHandler _exceptionHandler; | 405 final ExceptionHandler _exceptionHandler; |
| 530 final _AstParser _astParser; | 406 final AstParser _astParser; |
| 531 final Parser _parser; | 407 final Parser _parser; |
| 532 final ScopeDigestTTL _ttl; | 408 final ScopeDigestTTL _ttl; |
| 533 final VmTurnZone _zone; | 409 final ExpressionVisitor visitor = new ExpressionVisitor(); // TODO(misko): del
ete me |
| 410 final NgZone _zone; |
| 534 | 411 |
| 535 _FunctionChain _runAsyncHead, _runAsyncTail; | 412 _FunctionChain _runAsyncHead, _runAsyncTail; |
| 536 _FunctionChain _domWriteHead, _domWriteTail; | 413 _FunctionChain _domWriteHead, _domWriteTail; |
| 537 _FunctionChain _domReadHead, _domReadTail; | 414 _FunctionChain _domReadHead, _domReadTail; |
| 538 | 415 |
| 539 final ScopeStats _scopeStats; | 416 final ScopeStats _scopeStats; |
| 540 | 417 |
| 541 String _state; | 418 String _state; |
| 542 | 419 |
| 543 /** | 420 RootScope(Object context, this._astParser, this._parser, |
| 544 * | 421 GetterCache cacheGetter, FilterMap filterMap, |
| 545 * While processing data bindings, Angular passes through multiple states. Whe
n testing or | 422 this._exceptionHandler, this._ttl, this._zone, |
| 546 * debugging, it can be useful to access the current `state`, which is one of
the following: | 423 this._scopeStats) |
| 547 * | 424 : super(context, null, null, |
| 548 * * null | 425 new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), con
text), |
| 549 * * apply | 426 new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), con
text)) |
| 550 * * digest | |
| 551 * * flush | |
| 552 * * assert | |
| 553 * | |
| 554 * ##null | |
| 555 * | |
| 556 * Angular is not currently processing changes | |
| 557 * | |
| 558 * ##apply | |
| 559 * | |
| 560 * The apply state begins by executing the optional expression within the cont
ext of | |
| 561 * angular change detection mechanism. Any exceptions are delegated to [Except
ionHandler]. At the | |
| 562 * end of apply state RootScope enters the digest followed by flush phase (opt
ionally if asserts | |
| 563 * enabled run assert phase.) | |
| 564 * | |
| 565 * ##digest | |
| 566 * | |
| 567 * The apply state begins by processing the async queue, | |
| 568 * followed by change detection | |
| 569 * on non-DOM listeners. Any changes detected are process using the reaction f
unction. The digest | |
| 570 * phase is repeated as long as at least one change has been detected. By defa
ult, after 5 | |
| 571 * iterations the model is considered unstable and angular exists with an exce
ption. (See | |
| 572 * ScopeDigestTTL) | |
| 573 * | |
| 574 * ##flush | |
| 575 * | |
| 576 * The flush phase consists of these steps: | |
| 577 * | |
| 578 * 1. processing the DOM write queue | |
| 579 * 2. change detection on DOM only updates (these are reaction functions which
must | |
| 580 * not change the model state and hence don't need stabilization as in dige
st phase). | |
| 581 * 3. processing the DOM read queue | |
| 582 * 4. repeat steps 1 and 3 (not 2) until queues are empty | |
| 583 * | |
| 584 * ##assert | |
| 585 * | |
| 586 * Optionally if Dart assert is on, verify that flush reaction functions did n
ot make any changes | |
| 587 * to model and throw error if changes detected. | |
| 588 * | |
| 589 */ | |
| 590 String get state => _state; | |
| 591 | |
| 592 RootScope(Object context, Parser parser, FieldGetterFactory fieldGetterFactory
, | |
| 593 FormatterMap formatters, this._exceptionHandler, this._ttl, this._zo
ne, | |
| 594 ScopeStats _scopeStats, ClosureMap closureMap) | |
| 595 : _scopeStats = _scopeStats, | |
| 596 _parser = parser, | |
| 597 _astParser = new _AstParser(parser, closureMap), | |
| 598 super(context, null, null, | |
| 599 new RootWatchGroup(fieldGetterFactory, | |
| 600 new DirtyCheckingChangeDetector(fieldGetterFactory), context), | |
| 601 new RootWatchGroup(fieldGetterFactory, | |
| 602 new DirtyCheckingChangeDetector(fieldGetterFactory), context), | |
| 603 '', | |
| 604 _scopeStats) | |
| 605 { | 427 { |
| 606 _zone.onTurnDone = apply; | 428 _zone.onTurnDone = apply; |
| 607 _zone.onError = (e, s, ls) => _exceptionHandler(e, s); | 429 _zone.onError = (e, s, ls) => _exceptionHandler(e, s); |
| 608 } | 430 } |
| 609 | 431 |
| 610 RootScope get rootScope => this; | 432 RootScope get rootScope => this; |
| 611 bool get isAttached => true; | 433 bool get isAttached => true; |
| 612 | 434 |
| 613 /** | |
| 614 * Propagates changes between different parts of the application model. Normall
y called by | |
| 615 * [VMTurnZone] right before DOM rendering to initiate data binding. May also b
e called directly | |
| 616 * for unit testing. | |
| 617 * | |
| 618 * Before each iteration of change detection, [digest] first processes the asyn
c queue. Any | |
| 619 * work scheduled on the queue is executed before change detection. Since work
scheduled on | |
| 620 * the queue may generate more async calls, [digest] must process the queue mul
tiple times before | |
| 621 * it completes. The async queue must be empty before the model is considered s
table. | |
| 622 * | |
| 623 * Next, [digest] collects the changes that have occurred in the model. For eac
h change, | |
| 624 * [digest] calls the associated [ReactionFn]. Since a [ReactionFn] may further
change the model, | |
| 625 * [digest] processes changes multiple times until no more changes are detected
. | |
| 626 * | |
| 627 * If the model does not stabilize within 5 iterations, an exception is thrown.
See | |
| 628 * [ScopeDigestTTL]. | |
| 629 */ | |
| 630 void digest() { | 435 void digest() { |
| 631 _transitionState(null, STATE_DIGEST); | 436 _transitionState(null, STATE_DIGEST); |
| 632 try { | 437 try { |
| 633 var rootWatchGroup = _readWriteGroup as RootWatchGroup; | 438 var rootWatchGroup = (_readWriteGroup as RootWatchGroup); |
| 634 | 439 |
| 635 int digestTTL = _ttl.ttl; | 440 int digestTTL = _ttl.ttl; |
| 636 const int LOG_COUNT = 3; | 441 const int LOG_COUNT = 3; |
| 637 List log; | 442 List log; |
| 638 List digestLog; | 443 List digestLog; |
| 639 var count; | 444 var count; |
| 640 ChangeLog changeLog; | 445 ChangeLog changeLog; |
| 641 _scopeStats.digestStart(); | 446 _scopeStats.digestStart(); |
| 642 do { | 447 do { |
| 643 while (_runAsyncHead != null) { | 448 while(_runAsyncHead != null) { |
| 644 try { | 449 try { |
| 645 _runAsyncHead.fn(); | 450 _runAsyncHead.fn(); |
| 646 } catch (e, s) { | 451 } catch (e, s) { |
| 647 _exceptionHandler(e, s); | 452 _exceptionHandler(e, s); |
| 648 } | 453 } |
| 649 _runAsyncHead = _runAsyncHead._next; | 454 _runAsyncHead = _runAsyncHead._next; |
| 650 } | 455 } |
| 651 _runAsyncTail = null; | |
| 652 | 456 |
| 653 digestTTL--; | 457 digestTTL--; |
| 654 count = rootWatchGroup.detectChanges( | 458 count = rootWatchGroup.detectChanges( |
| 655 exceptionHandler: _exceptionHandler, | 459 exceptionHandler: _exceptionHandler, |
| 656 changeLog: changeLog, | 460 changeLog: changeLog, |
| 657 fieldStopwatch: _scopeStats.fieldStopwatch, | 461 fieldStopwatch: _scopeStats.digestFieldStopwatch, |
| 658 evalStopwatch: _scopeStats.evalStopwatch, | 462 evalStopwatch: _scopeStats.digestEvalStopwatch, |
| 659 processStopwatch: _scopeStats.processStopwatch); | 463 processStopwatch: _scopeStats.digestProcessStopwatch); |
| 660 | 464 |
| 661 if (digestTTL <= LOG_COUNT) { | 465 if (digestTTL <= LOG_COUNT) { |
| 662 if (changeLog == null) { | 466 if (changeLog == null) { |
| 663 log = []; | 467 log = []; |
| 664 digestLog = []; | 468 digestLog = []; |
| 665 changeLog = (e, c, p) => digestLog.add('$e: $c <= $p'); | 469 changeLog = (e, c, p) => digestLog.add('$e: $c <= $p'); |
| 666 } else { | 470 } else { |
| 667 log.add(digestLog.join(', ')); | 471 log.add(digestLog.join(', ')); |
| 668 digestLog.clear(); | 472 digestLog.clear(); |
| 669 } | 473 } |
| 670 } | 474 } |
| 671 if (digestTTL == 0) { | 475 if (digestTTL == 0) { |
| 672 throw 'Model did not stabilize in ${_ttl.ttl} digests. ' | 476 throw 'Model did not stabilize in ${_ttl.ttl} digests. ' |
| 673 'Last $LOG_COUNT iterations:\n${log.join('\n')}'; | 477 'Last $LOG_COUNT iterations:\n${log.join('\n')}'; |
| 674 } | 478 } |
| 675 _scopeStats.digestLoop(count); | 479 _scopeStats.digestLoop(count); |
| 676 } while (count > 0); | 480 } while (count > 0); |
| 677 } finally { | 481 } finally { |
| 678 _scopeStats.digestEnd(); | 482 _scopeStats.digestEnd(); |
| 679 _transitionState(STATE_DIGEST, null); | 483 _transitionState(STATE_DIGEST, null); |
| 680 } | 484 } |
| 681 } | 485 } |
| 682 | 486 |
| 683 void flush() { | 487 void flush() { |
| 684 _stats.flushStart(); | |
| 685 _transitionState(null, STATE_FLUSH); | 488 _transitionState(null, STATE_FLUSH); |
| 686 RootWatchGroup readOnlyGroup = this._readOnlyGroup as RootWatchGroup; | 489 var observeGroup = this._readOnlyGroup as RootWatchGroup; |
| 687 bool runObservers = true; | 490 bool runObservers = true; |
| 688 try { | 491 try { |
| 689 do { | 492 do { |
| 690 if (_domWriteHead != null) _stats.domWriteStart(); | 493 while(_domWriteHead != null) { |
| 691 while (_domWriteHead != null) { | |
| 692 try { | 494 try { |
| 693 _domWriteHead.fn(); | 495 _domWriteHead.fn(); |
| 694 } catch (e, s) { | 496 } catch (e, s) { |
| 695 _exceptionHandler(e, s); | 497 _exceptionHandler(e, s); |
| 696 } | 498 } |
| 697 _domWriteHead = _domWriteHead._next; | 499 _domWriteHead = _domWriteHead._next; |
| 698 if (_domWriteHead == null) _stats.domWriteEnd(); | |
| 699 } | 500 } |
| 700 _domWriteTail = null; | |
| 701 if (runObservers) { | 501 if (runObservers) { |
| 702 runObservers = false; | 502 runObservers = false; |
| 703 readOnlyGroup.detectChanges(exceptionHandler:_exceptionHandler, | 503 observeGroup.detectChanges(exceptionHandler:_exceptionHandler); |
| 704 fieldStopwatch: _scopeStats.fieldStopwatch, | |
| 705 evalStopwatch: _scopeStats.evalStopwatch, | |
| 706 processStopwatch: _scopeStats.processStopwatch); | |
| 707 } | 504 } |
| 708 if (_domReadHead != null) _stats.domReadStart(); | 505 while(_domReadHead != null) { |
| 709 while (_domReadHead != null) { | |
| 710 try { | 506 try { |
| 711 _domReadHead.fn(); | 507 _domReadHead.fn(); |
| 712 } catch (e, s) { | 508 } catch (e, s) { |
| 713 _exceptionHandler(e, s); | 509 _exceptionHandler(e, s); |
| 714 } | 510 } |
| 715 _domReadHead = _domReadHead._next; | 511 _domReadHead = _domReadHead._next; |
| 716 if (_domReadHead == null) _stats.domReadEnd(); | |
| 717 } | 512 } |
| 718 _domReadTail = null; | |
| 719 } while (_domWriteHead != null || _domReadHead != null); | 513 } while (_domWriteHead != null || _domReadHead != null); |
| 720 _stats.flushEnd(); | |
| 721 assert((() { | 514 assert((() { |
| 722 _stats.flushAssertStart(); | 515 var watchLog = []; |
| 723 var digestLog = []; | 516 var observeLog = []; |
| 724 var flushLog = []; | |
| 725 (_readWriteGroup as RootWatchGroup).detectChanges( | 517 (_readWriteGroup as RootWatchGroup).detectChanges( |
| 726 changeLog: (s, c, p) => digestLog.add('$s: $c <= $p'), | 518 changeLog: (s, c, p) => watchLog.add('$s: $c <= $p')); |
| 727 fieldStopwatch: _scopeStats.fieldStopwatch, | 519 (observeGroup as RootWatchGroup).detectChanges( |
| 728 evalStopwatch: _scopeStats.evalStopwatch, | 520 changeLog: (s, c, p) => watchLog.add('$s: $c <= $p')); |
| 729 processStopwatch: _scopeStats.processStopwatch); | 521 if (watchLog.isNotEmpty || observeLog.isNotEmpty) { |
| 730 (_readOnlyGroup as RootWatchGroup).detectChanges( | |
| 731 changeLog: (s, c, p) => flushLog.add('$s: $c <= $p'), | |
| 732 fieldStopwatch: _scopeStats.fieldStopwatch, | |
| 733 evalStopwatch: _scopeStats.evalStopwatch, | |
| 734 processStopwatch: _scopeStats.processStopwatch); | |
| 735 if (digestLog.isNotEmpty || flushLog.isNotEmpty) { | |
| 736 throw 'Observer reaction functions should not change model. \n' | 522 throw 'Observer reaction functions should not change model. \n' |
| 737 'These watch changes were detected: ${digestLog.join('; ')}\n' | 523 'These watch changes were detected: ${watchLog.join('; ')}\n' |
| 738 'These observe changes were detected: ${flushLog.join('; ')}'; | 524 'These observe changes were detected: ${observeLog.join('; ')}'; |
| 739 } | 525 } |
| 740 _stats.flushAssertEnd(); | |
| 741 return true; | 526 return true; |
| 742 })()); | 527 })()); |
| 743 } finally { | 528 } finally { |
| 744 _stats.cycleEnd(); | |
| 745 _transitionState(STATE_FLUSH, null); | 529 _transitionState(STATE_FLUSH, null); |
| 746 } | 530 } |
| 531 |
| 747 } | 532 } |
| 748 | 533 |
| 749 // QUEUES | 534 // QUEUES |
| 750 void runAsync(fn()) { | 535 void runAsync(fn()) { |
| 751 var chain = new _FunctionChain(fn); | 536 var chain = new _FunctionChain(fn); |
| 752 if (_runAsyncHead == null) { | 537 if (_runAsyncHead == null) { |
| 753 _runAsyncHead = _runAsyncTail = chain; | 538 _runAsyncHead = _runAsyncTail = chain; |
| 754 } else { | 539 } else { |
| 755 _runAsyncTail = _runAsyncTail._next = chain; | 540 _runAsyncTail = _runAsyncTail._next = chain; |
| 756 } | 541 } |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 815 final Map<String, int> _typeCounts; | 600 final Map<String, int> _typeCounts; |
| 816 | 601 |
| 817 _Streams(this._scope, this._exceptionHandler, _Streams inheritStreams) | 602 _Streams(this._scope, this._exceptionHandler, _Streams inheritStreams) |
| 818 : _typeCounts = inheritStreams == null | 603 : _typeCounts = inheritStreams == null |
| 819 ? <String, int>{} | 604 ? <String, int>{} |
| 820 : new Map.from(inheritStreams._typeCounts); | 605 : new Map.from(inheritStreams._typeCounts); |
| 821 | 606 |
| 822 static ScopeEvent emit(Scope scope, String name, data) { | 607 static ScopeEvent emit(Scope scope, String name, data) { |
| 823 var event = new ScopeEvent(name, scope, data); | 608 var event = new ScopeEvent(name, scope, data); |
| 824 var scopeCursor = scope; | 609 var scopeCursor = scope; |
| 825 while (scopeCursor != null) { | 610 while(scopeCursor != null) { |
| 826 if (scopeCursor._streams != null && | 611 if (scopeCursor._streams != null && |
| 827 scopeCursor._streams._scope == scopeCursor) { | 612 scopeCursor._streams._scope == scopeCursor) { |
| 828 ScopeStream stream = scopeCursor._streams._streams[name]; | 613 ScopeStream stream = scopeCursor._streams._streams[name]; |
| 829 if (stream != null) { | 614 if (stream != null) { |
| 830 event._currentScope = scopeCursor; | 615 event._currentScope = scopeCursor; |
| 831 stream._fire(event); | 616 stream._fire(event); |
| 832 if (event.propagationStopped) return event; | 617 if (event.propagationStopped) return event; |
| 833 } | 618 } |
| 834 } | 619 } |
| 835 scopeCursor = scopeCursor._parentScope; | 620 scopeCursor = scopeCursor._parentScope; |
| (...skipping 10 matching lines...) Expand all Loading... |
| 846 scope = queue.removeFirst(); | 631 scope = queue.removeFirst(); |
| 847 scopeStreams = scope._streams; | 632 scopeStreams = scope._streams; |
| 848 assert(scopeStreams._scope == scope); | 633 assert(scopeStreams._scope == scope); |
| 849 if (scopeStreams._streams.containsKey(name)) { | 634 if (scopeStreams._streams.containsKey(name)) { |
| 850 var stream = scopeStreams._streams[name]; | 635 var stream = scopeStreams._streams[name]; |
| 851 event._currentScope = scope; | 636 event._currentScope = scope; |
| 852 stream._fire(event); | 637 stream._fire(event); |
| 853 } | 638 } |
| 854 // Reverse traversal so that when the queue is read it is correct order. | 639 // Reverse traversal so that when the queue is read it is correct order. |
| 855 var childScope = scope._childTail; | 640 var childScope = scope._childTail; |
| 856 while (childScope != null) { | 641 while(childScope != null) { |
| 857 scopeStreams = childScope._streams; | 642 scopeStreams = childScope._streams; |
| 858 if (scopeStreams != null && | 643 if (scopeStreams != null && |
| 859 scopeStreams._typeCounts.containsKey(name)) { | 644 scopeStreams._typeCounts.containsKey(name)) { |
| 860 queue.addFirst(scopeStreams._scope); | 645 queue.addFirst(scopeStreams._scope); |
| 861 } | 646 } |
| 862 childScope = childScope._prev; | 647 childScope = childScope._prev; |
| 863 } | 648 } |
| 864 } | 649 } |
| 865 } | 650 } |
| 866 return event; | 651 return event; |
| 867 } | 652 } |
| 868 | 653 |
| 869 static async.Stream<ScopeEvent> on(Scope scope, | 654 static ScopeStream on(Scope scope, |
| 870 ExceptionHandler _exceptionHandler, | 655 ExceptionHandler _exceptionHandler, |
| 871 String name) { | 656 String name) { |
| 872 _forceNewScopeStream(scope, _exceptionHandler); | 657 _forceNewScopeStream(scope, _exceptionHandler); |
| 873 return scope._streams._get(scope, name); | 658 return scope._streams._get(scope, name); |
| 874 } | 659 } |
| 875 | 660 |
| 876 static void _forceNewScopeStream(scope, _exceptionHandler) { | 661 static void _forceNewScopeStream(scope, _exceptionHandler) { |
| 877 _Streams streams = scope._streams; | 662 _Streams streams = scope._streams; |
| 878 Scope scopeCursor = scope; | 663 Scope scopeCursor = scope; |
| 879 bool splitMode = false; | 664 bool splitMode = false; |
| 880 while (scopeCursor != null) { | 665 while(scopeCursor != null) { |
| 881 _Streams cursorStreams = scopeCursor._streams; | 666 _Streams cursorStreams = scopeCursor._streams; |
| 882 var hasStream = cursorStreams != null; | 667 var hasStream = cursorStreams != null; |
| 883 var hasOwnStream = hasStream && cursorStreams._scope == scopeCursor; | 668 var hasOwnStream = hasStream && cursorStreams._scope == scopeCursor; |
| 884 if (hasOwnStream) return; | 669 if (hasOwnStream) return; |
| 885 | 670 |
| 886 if (!splitMode && (streams == null || (hasStream && !hasOwnStream))) { | 671 if (!splitMode && (streams == null || (hasStream && !hasOwnStream))) { |
| 887 if (hasStream && !hasOwnStream) { | 672 if (hasStream && !hasOwnStream) { |
| 888 splitMode = true; | 673 splitMode = true; |
| 889 } | 674 } |
| 890 streams = new _Streams(scopeCursor, _exceptionHandler, cursorStreams); | 675 streams = new _Streams(scopeCursor, _exceptionHandler, cursorStreams); |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 942 scope = scope._parentScope; | 727 scope = scope._parentScope; |
| 943 } | 728 } |
| 944 } | 729 } |
| 945 } | 730 } |
| 946 | 731 |
| 947 class ScopeStream extends async.Stream<ScopeEvent> { | 732 class ScopeStream extends async.Stream<ScopeEvent> { |
| 948 final ExceptionHandler _exceptionHandler; | 733 final ExceptionHandler _exceptionHandler; |
| 949 final _Streams _streams; | 734 final _Streams _streams; |
| 950 final String _name; | 735 final String _name; |
| 951 final subscriptions = <ScopeStreamSubscription>[]; | 736 final subscriptions = <ScopeStreamSubscription>[]; |
| 952 final List<Function> _work = <Function>[]; | |
| 953 bool _firing = false; | |
| 954 | |
| 955 | 737 |
| 956 ScopeStream(this._streams, this._exceptionHandler, this._name); | 738 ScopeStream(this._streams, this._exceptionHandler, this._name); |
| 957 | 739 |
| 958 ScopeStreamSubscription listen(void onData(ScopeEvent event), | 740 ScopeStreamSubscription listen(void onData(ScopeEvent event), |
| 959 { Function onError, | 741 { Function onError, |
| 960 void onDone(), | 742 void onDone(), |
| 961 bool cancelOnError }) { | 743 bool cancelOnError }) { |
| 744 if (subscriptions.isEmpty) _streams._addCount(_name, 1); |
| 962 var subscription = new ScopeStreamSubscription(this, onData); | 745 var subscription = new ScopeStreamSubscription(this, onData); |
| 963 _concurrentSafeWork(() { | 746 subscriptions.add(subscription); |
| 964 if (subscriptions.isEmpty) _streams._addCount(_name, 1); | |
| 965 subscriptions.add(subscription); | |
| 966 }); | |
| 967 return subscription; | 747 return subscription; |
| 968 } | 748 } |
| 969 | 749 |
| 970 void _concurrentSafeWork([fn]) { | |
| 971 if (fn != null) _work.add(fn); | |
| 972 while(!_firing && _work.isNotEmpty) { | |
| 973 _work.removeLast()(); | |
| 974 } | |
| 975 } | |
| 976 | |
| 977 void _fire(ScopeEvent event) { | 750 void _fire(ScopeEvent event) { |
| 978 _firing = true; | 751 for (ScopeStreamSubscription subscription in subscriptions) { |
| 979 try { | 752 try { |
| 980 for (ScopeStreamSubscription subscription in subscriptions) { | 753 subscription._onData(event); |
| 981 try { | 754 } catch (e, s) { |
| 982 subscription._onData(event); | 755 _exceptionHandler(e, s); |
| 983 } catch (e, s) { | |
| 984 _exceptionHandler(e, s); | |
| 985 } | |
| 986 } | 756 } |
| 987 } finally { | |
| 988 _firing = false; | |
| 989 _concurrentSafeWork(); | |
| 990 } | 757 } |
| 991 } | 758 } |
| 992 | 759 |
| 993 void _remove(ScopeStreamSubscription subscription) { | 760 void _remove(ScopeStreamSubscription subscription) { |
| 994 _concurrentSafeWork(() { | 761 assert(subscription._scopeStream == this); |
| 995 assert(subscription._scopeStream == this); | 762 if (subscriptions.remove(subscription)) { |
| 996 if (subscriptions.remove(subscription)) { | 763 if (subscriptions.isEmpty) _streams._addCount(_name, -1); |
| 997 if (subscriptions.isEmpty) _streams._addCount(_name, -1); | 764 } else { |
| 998 } else { | 765 throw new StateError('AlreadyCanceled'); |
| 999 throw new StateError('AlreadyCanceled'); | 766 } |
| 1000 } | |
| 1001 }); | |
| 1002 } | 767 } |
| 1003 } | 768 } |
| 1004 | 769 |
| 1005 class ScopeStreamSubscription implements async.StreamSubscription<ScopeEvent> { | 770 class ScopeStreamSubscription implements async.StreamSubscription<ScopeEvent> { |
| 1006 final ScopeStream _scopeStream; | 771 final ScopeStream _scopeStream; |
| 1007 final Function _onData; | 772 final Function _onData; |
| 1008 ScopeStreamSubscription(this._scopeStream, this._onData); | 773 ScopeStreamSubscription(this._scopeStream, this._onData); |
| 1009 | 774 |
| 1010 async.Future cancel() { | 775 // TODO(vbe) should return a Future |
| 1011 _scopeStream._remove(this); | 776 cancel() => _scopeStream._remove(this); |
| 1012 return null; | |
| 1013 } | |
| 1014 | 777 |
| 1015 void onData(void handleData(ScopeEvent data)) => _NOT_IMPLEMENTED(); | 778 void onData(void handleData(ScopeEvent data)) => NOT_IMPLEMENTED(); |
| 1016 void onError(Function handleError) => _NOT_IMPLEMENTED(); | 779 void onError(Function handleError) => NOT_IMPLEMENTED(); |
| 1017 void onDone(void handleDone()) => _NOT_IMPLEMENTED(); | 780 void onDone(void handleDone()) => NOT_IMPLEMENTED(); |
| 1018 void pause([async.Future resumeSignal]) => _NOT_IMPLEMENTED(); | 781 void pause([async.Future resumeSignal]) => NOT_IMPLEMENTED(); |
| 1019 void resume() => _NOT_IMPLEMENTED(); | 782 void resume() => NOT_IMPLEMENTED(); |
| 1020 bool get isPaused => _NOT_IMPLEMENTED(); | 783 bool get isPaused => NOT_IMPLEMENTED(); |
| 1021 async.Future asFuture([var futureValue]) => _NOT_IMPLEMENTED(); | 784 async.Future asFuture([var futureValue]) => NOT_IMPLEMENTED(); |
| 1022 } | 785 } |
| 1023 | 786 |
| 1024 _NOT_IMPLEMENTED() { | |
| 1025 throw new StateError('Not Implemented'); | |
| 1026 } | |
| 1027 | |
| 1028 | |
| 1029 class _FunctionChain { | 787 class _FunctionChain { |
| 1030 final Function fn; | 788 final Function fn; |
| 1031 _FunctionChain _next; | 789 _FunctionChain _next; |
| 1032 | 790 |
| 1033 _FunctionChain(fn()): fn = fn { | 791 _FunctionChain(fn()) |
| 792 : fn = fn |
| 793 { |
| 1034 assert(fn != null); | 794 assert(fn != null); |
| 1035 } | 795 } |
| 1036 } | 796 } |
| 1037 | 797 |
| 1038 class _AstParser { | 798 class AstParser { |
| 1039 final Parser _parser; | 799 final Parser _parser; |
| 1040 int _id = 0; | 800 int _id = 0; |
| 1041 final ExpressionVisitor _visitor; | 801 ExpressionVisitor _visitor = new ExpressionVisitor(); |
| 1042 | 802 |
| 1043 _AstParser(this._parser, ClosureMap closureMap) | 803 AstParser(this._parser); |
| 1044 : _visitor = new ExpressionVisitor(closureMap); | |
| 1045 | 804 |
| 1046 AST call(String input, {FormatterMap formatters, | 805 AST call(String exp, { FilterMap filters, |
| 1047 bool collection: false, | 806 bool collection:false, |
| 1048 Object context: null }) { | 807 Object context:null }) { |
| 1049 _visitor.formatters = formatters; | 808 _visitor.filters = filters; |
| 1050 AST contextRef = _visitor.contextRef; | 809 AST contextRef = _visitor.contextRef; |
| 1051 try { | 810 try { |
| 1052 if (context != null) { | 811 if (context != null) { |
| 1053 _visitor.contextRef = new ConstantAST(context, '#${_id++}'); | 812 _visitor.contextRef = new ConstantAST(context, '#${_id++}'); |
| 1054 } | 813 } |
| 1055 var exp = _parser(input); | 814 var ast = _parser(exp); |
| 1056 return collection ? _visitor.visitCollection(exp) : _visitor.visit(exp); | 815 return collection ? _visitor.visitCollection(ast) : _visitor.visit(ast); |
| 1057 } finally { | 816 } finally { |
| 1058 _visitor.contextRef = contextRef; | 817 _visitor.contextRef = contextRef; |
| 1059 _visitor.formatters = null; | 818 _visitor.filters = null; |
| 1060 } | 819 } |
| 1061 } | 820 } |
| 1062 } | 821 } |
| 1063 | 822 |
| 1064 class ExpressionVisitor implements Visitor { | 823 class ExpressionVisitor implements Visitor { |
| 1065 static final ContextReferenceAST scopeContextRef = new ContextReferenceAST(); | 824 static final ContextReferenceAST scopeContextRef = new ContextReferenceAST(); |
| 1066 final ClosureMap _closureMap; | |
| 1067 AST contextRef = scopeContextRef; | 825 AST contextRef = scopeContextRef; |
| 1068 | 826 |
| 1069 | |
| 1070 ExpressionVisitor(this._closureMap); | |
| 1071 | |
| 1072 AST ast; | 827 AST ast; |
| 1073 FormatterMap formatters; | 828 FilterMap filters; |
| 1074 | 829 |
| 1075 AST visit(Expression exp) { | 830 AST visit(Expression exp) { |
| 1076 exp.accept(this); | 831 exp.accept(this); |
| 1077 assert(ast != null); | 832 assert(this.ast != null); |
| 1078 try { | 833 try { |
| 1079 return ast; | 834 return ast; |
| 1080 } finally { | 835 } finally { |
| 1081 ast = null; | 836 ast = null; |
| 1082 } | 837 } |
| 1083 } | 838 } |
| 1084 | 839 |
| 1085 AST visitCollection(Expression exp) => new CollectionAST(visit(exp)); | 840 AST visitCollection(Expression exp) => new CollectionAST(visit(exp)); |
| 1086 AST _mapToAst(Expression expression) => visit(expression); | 841 AST _mapToAst(Expression expression) => visit(expression); |
| 1087 | 842 |
| 1088 List<AST> _toAst(List<Expression> expressions) => | 843 List<AST> _toAst(List<Expression> expressions) => |
| 1089 expressions.map(_mapToAst).toList(); | 844 expressions.map(_mapToAst).toList(); |
| 1090 | 845 |
| 1091 Map<Symbol, AST> _toAstMap(Map<String, Expression> expressions) { | |
| 1092 if (expressions.isEmpty) return const {}; | |
| 1093 Map<Symbol, AST> result = new Map<Symbol, AST>(); | |
| 1094 expressions.forEach((String name, Expression expression) { | |
| 1095 result[_closureMap.lookupSymbol(name)] = _mapToAst(expression); | |
| 1096 }); | |
| 1097 return result; | |
| 1098 } | |
| 1099 | |
| 1100 void visitCallScope(CallScope exp) { | 846 void visitCallScope(CallScope exp) { |
| 1101 List<AST> positionals = _toAst(exp.arguments.positionals); | 847 ast = new MethodAST(contextRef, exp.name, _toAst(exp.arguments)); |
| 1102 Map<Symbol, AST> named = _toAstMap(exp.arguments.named); | |
| 1103 ast = new MethodAST(contextRef, exp.name, positionals, named); | |
| 1104 } | 848 } |
| 1105 void visitCallMember(CallMember exp) { | 849 void visitCallMember(CallMember exp) { |
| 1106 List<AST> positionals = _toAst(exp.arguments.positionals); | 850 ast = new MethodAST(visit(exp.object), exp.name, _toAst(exp.arguments)); |
| 1107 Map<Symbol, AST> named = _toAstMap(exp.arguments.named); | |
| 1108 ast = new MethodAST(visit(exp.object), exp.name, positionals, named); | |
| 1109 } | 851 } |
| 1110 visitAccessScope(AccessScope exp) { | 852 visitAccessScope(AccessScope exp) { |
| 1111 ast = new FieldReadAST(contextRef, exp.name); | 853 ast = new FieldReadAST(contextRef, exp.name); |
| 1112 } | 854 } |
| 1113 visitAccessMember(AccessMember exp) { | 855 visitAccessMember(AccessMember exp) { |
| 1114 ast = new FieldReadAST(visit(exp.object), exp.name); | 856 ast = new FieldReadAST(visit(exp.object), exp.name); |
| 1115 } | 857 } |
| 1116 visitBinary(Binary exp) { | 858 visitBinary(Binary exp) { |
| 1117 ast = new PureFunctionAST(exp.operation, | 859 ast = new PureFunctionAST(exp.operation, |
| 1118 _operationToFunction(exp.operation), | 860 _operationToFunction(exp.operation), |
| 1119 [visit(exp.left), visit(exp.right)]); | 861 [visit(exp.left), visit(exp.right)]); |
| 1120 } | 862 } |
| 1121 void visitPrefix(Prefix exp) { | 863 void visitPrefix(Prefix exp) { |
| 1122 ast = new PureFunctionAST(exp.operation, | 864 ast = new PureFunctionAST(exp.operation, |
| 1123 _operationToFunction(exp.operation), | 865 _operationToFunction(exp.operation), |
| 1124 [visit(exp.expression)]); | 866 [visit(exp.expression)]); |
| 1125 } | 867 } |
| 1126 void visitConditional(Conditional exp) { | 868 void visitConditional(Conditional exp) { |
| 1127 ast = new PureFunctionAST('?:', _operation_ternary, | 869 ast = new PureFunctionAST('?:', _operation_ternary, |
| 1128 [visit(exp.condition), visit(exp.yes), | 870 [visit(exp.condition), visit(exp.yes), |
| 1129 visit(exp.no)]); | 871 visit(exp.no)]); |
| 1130 } | 872 } |
| 1131 void visitAccessKeyed(AccessKeyed exp) { | 873 void visitAccessKeyed(AccessKeyed exp) { |
| 1132 ast = new ClosureAST('[]', _operation_bracket, | 874 ast = new PureFunctionAST('[]', _operation_bracket, |
| 1133 [visit(exp.object), visit(exp.key)]); | 875 [visit(exp.object), visit(exp.key)]); |
| 1134 } | 876 } |
| 1135 void visitLiteralPrimitive(LiteralPrimitive exp) { | 877 void visitLiteralPrimitive(LiteralPrimitive exp) { |
| 1136 ast = new ConstantAST(exp.value); | 878 ast = new ConstantAST(exp.value); |
| 1137 } | 879 } |
| 1138 void visitLiteralString(LiteralString exp) { | 880 void visitLiteralString(LiteralString exp) { |
| 1139 ast = new ConstantAST(exp.value); | 881 ast = new ConstantAST(exp.value); |
| 1140 } | 882 } |
| 1141 void visitLiteralArray(LiteralArray exp) { | 883 void visitLiteralArray(LiteralArray exp) { |
| 1142 List<AST> items = _toAst(exp.elements); | 884 List<AST> items = _toAst(exp.elements); |
| 1143 ast = new PureFunctionAST('[${items.join(', ')}]', new ArrayFn(), items); | 885 ast = new PureFunctionAST('[${items.join(', ')}]', new ArrayFn(), items); |
| 1144 } | 886 } |
| 1145 | 887 |
| 1146 void visitLiteralObject(LiteralObject exp) { | 888 void visitLiteralObject(LiteralObject exp) { |
| 1147 List<String> keys = exp.keys; | 889 List<String> keys = exp.keys; |
| 1148 List<AST> values = _toAst(exp.values); | 890 List<AST> values = _toAst(exp.values); |
| 1149 assert(keys.length == values.length); | 891 assert(keys.length == values.length); |
| 1150 var kv = <String>[]; | 892 var kv = <String>[]; |
| 1151 for (var i = 0; i < keys.length; i++) { | 893 for (var i = 0; i < keys.length; i++) { |
| 1152 kv.add('${keys[i]}: ${values[i]}'); | 894 kv.add('${keys[i]}: ${values[i]}'); |
| 1153 } | 895 } |
| 1154 ast = new PureFunctionAST('{${kv.join(', ')}}', new MapFn(keys), values); | 896 ast = new PureFunctionAST('{${kv.join(', ')}}', new MapFn(keys), values); |
| 1155 } | 897 } |
| 1156 | 898 |
| 1157 void visitFilter(Filter exp) { | 899 void visitFilter(Filter exp) { |
| 1158 if (formatters == null) { | 900 Function filterFunction = filters(exp.name); |
| 1159 throw new Exception("No formatters have been registered"); | |
| 1160 } | |
| 1161 Function filterFunction = formatters(exp.name); | |
| 1162 List<AST> args = [visitCollection(exp.expression)]; | 901 List<AST> args = [visitCollection(exp.expression)]; |
| 1163 args.addAll(_toAst(exp.arguments).map((ast) => new CollectionAST(ast))); | 902 args.addAll(_toAst(exp.arguments).map((ast) => new CollectionAST(ast))); |
| 1164 ast = new PureFunctionAST('|${exp.name}', | 903 ast = new PureFunctionAST('|${exp.name}', |
| 1165 new _FilterWrapper(filterFunction, args.length), args); | 904 new _FilterWrapper(filterFunction, args.length), args); |
| 1166 } | 905 } |
| 1167 | 906 |
| 1168 // TODO(misko): this is a corner case. Choosing not to implement for now. | 907 // TODO(misko): this is a corner case. Choosing not to implement for now. |
| 1169 void visitCallFunction(CallFunction exp) { | 908 void visitCallFunction(CallFunction exp) { |
| 1170 _notSupported("function's returing functions"); | 909 _notSupported("function's returing functions"); |
| 1171 } | 910 } |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1205 case '^' : return _operation_power; | 944 case '^' : return _operation_power; |
| 1206 case '&' : return _operation_bitwise_and; | 945 case '&' : return _operation_bitwise_and; |
| 1207 case '&&' : return _operation_logical_and; | 946 case '&&' : return _operation_logical_and; |
| 1208 case '||' : return _operation_logical_or; | 947 case '||' : return _operation_logical_or; |
| 1209 default: throw new StateError(operation); | 948 default: throw new StateError(operation); |
| 1210 } | 949 } |
| 1211 } | 950 } |
| 1212 | 951 |
| 1213 _operation_negate(value) => !toBool(value); | 952 _operation_negate(value) => !toBool(value); |
| 1214 _operation_add(left, right) => autoConvertAdd(left, right); | 953 _operation_add(left, right) => autoConvertAdd(left, right); |
| 1215 _operation_subtract(left, right) => (left != null && right != null
) ? left - right : (left != null ? left : (right != null ? 0 - right : 0)); | 954 _operation_subtract(left, right) => left - right; |
| 1216 _operation_multiply(left, right) => (left == null || right == null
) ? null : left * right; | 955 _operation_multiply(left, right) => left * right; |
| 1217 _operation_divide(left, right) => (left == null || right == null
) ? null : left / right; | 956 _operation_divide(left, right) => left / right; |
| 1218 _operation_divide_int(left, right) => (left == null || right == null
) ? null : left ~/ right; | 957 _operation_divide_int(left, right) => left ~/ right; |
| 1219 _operation_remainder(left, right) => (left == null || right == null
) ? null : left % right; | 958 _operation_remainder(left, right) => left % right; |
| 1220 _operation_equals(left, right) => left == right; | 959 _operation_equals(left, right) => left == right; |
| 1221 _operation_not_equals(left, right) => left != right; | 960 _operation_not_equals(left, right) => left != right; |
| 1222 _operation_less_then(left, right) => (left == null || right == null
) ? null : left < right; | 961 _operation_less_then(left, right) => left < right; |
| 1223 _operation_greater_then(left, right) => (left == null || right == null
) ? null : left > right; | 962 _operation_greater_then(left, right) => (left == null || right == null
) ? false : left > right; |
| 1224 _operation_less_or_equals_then(left, right) => (left == null || right == null
) ? null : left <= right; | 963 _operation_less_or_equals_then(left, right) => left <= right; |
| 1225 _operation_greater_or_equals_then(left, right) => (left == null || right == null
) ? null : left >= right; | 964 _operation_greater_or_equals_then(left, right) => left >= right; |
| 1226 _operation_power(left, right) => (left == null || right == null
) ? null : left ^ right; | 965 _operation_power(left, right) => left ^ right; |
| 1227 _operation_bitwise_and(left, right) => (left == null || right == null
) ? null : left & right; | 966 _operation_bitwise_and(left, right) => left & right; |
| 1228 // TODO(misko): these should short circuit the evaluation. | 967 // TODO(misko): these should short circuit the evaluation. |
| 1229 _operation_logical_and(left, right) => toBool(left) && toBool(right); | 968 _operation_logical_and(left, right) => toBool(left) && toBool(right); |
| 1230 _operation_logical_or(left, right) => toBool(left) || toBool(right); | 969 _operation_logical_or(left, right) => toBool(left) || toBool(right); |
| 1231 | 970 |
| 1232 _operation_ternary(condition, yes, no) => toBool(condition) ? yes : no; | 971 _operation_ternary(condition, yes, no) => toBool(condition) ? yes : no; |
| 1233 _operation_bracket(obj, key) => obj == null ? null : obj[key]; | 972 _operation_bracket(obj, key) => obj == null ? null : obj[key]; |
| 1234 | 973 |
| 1235 class ArrayFn extends FunctionApply { | 974 class ArrayFn extends FunctionApply { |
| 1236 // TODO(misko): figure out why do we need to make a copy? | 975 // TODO(misko): figure out why do we need to make a copy? |
| 1237 apply(List args) => new List.from(args); | 976 apply(List args) => new List.from(args); |
| 1238 } | 977 } |
| 1239 | 978 |
| 1240 class MapFn extends FunctionApply { | 979 class MapFn extends FunctionApply { |
| 1241 final List<String> keys; | 980 final List<String> keys; |
| 1242 | 981 |
| 1243 MapFn(this.keys); | 982 MapFn(this.keys); |
| 1244 | 983 |
| 1245 Map apply(List values) { | 984 apply(List values) { |
| 1246 // TODO(misko): figure out why do we need to make a copy instead of reusing
instance? | 985 // TODO(misko): figure out why do we need to make a copy instead of reusing
instance? |
| 1247 assert(values.length == keys.length); | 986 assert(values.length == keys.length); |
| 1248 return new Map.fromIterables(keys, values); | 987 return new Map.fromIterables(keys, values); |
| 1249 } | 988 } |
| 1250 } | 989 } |
| 1251 | 990 |
| 1252 class _FilterWrapper extends FunctionApply { | 991 class _FilterWrapper extends FunctionApply { |
| 1253 final Function filterFn; | 992 final Function filterFn; |
| 1254 final List args; | 993 final List args; |
| 1255 final List<Watch> argsWatches; | 994 final List<Watch> argsWatches; |
| 1256 _FilterWrapper(this.filterFn, length): | 995 _FilterWrapper(this.filterFn, length): |
| 1257 args = new List(length), | 996 args = new List(length), |
| 1258 argsWatches = new List(length); | 997 argsWatches = new List(length); |
| 1259 | 998 |
| 1260 apply(List values) { | 999 apply(List values) { |
| 1261 for (var i=0; i < values.length; i++) { | 1000 for (var i=0; i < values.length; i++) { |
| 1262 var value = values[i]; | 1001 var value = values[i]; |
| 1263 var lastValue = args[i]; | 1002 var lastValue = args[i]; |
| 1264 if (!identical(value, lastValue)) { | 1003 if (!identical(value, lastValue)) { |
| 1265 if (value is CollectionChangeRecord) { | 1004 if (value is CollectionChangeRecord) { |
| 1266 args[i] = (value as CollectionChangeRecord).iterable; | 1005 args[i] = (value as CollectionChangeRecord).iterable; |
| 1267 } else if (value is MapChangeRecord) { | |
| 1268 args[i] = (value as MapChangeRecord).map; | |
| 1269 } else { | 1006 } else { |
| 1270 args[i] = value; | 1007 args[i] = value; |
| 1271 } | 1008 } |
| 1272 } | 1009 } |
| 1273 } | 1010 } |
| 1274 var value = Function.apply(filterFn, args); | 1011 var value = Function.apply(filterFn, args); |
| 1275 if (value is Iterable) { | 1012 if (value is Iterable) { |
| 1276 // Since formatters are pure we can guarantee that this well never change. | 1013 // Since filters are pure we can guarantee that this well never change. |
| 1277 // By wrapping in UnmodifiableListView we can hint to the dirty checker | 1014 // By wrapping in UnmodifiableListView we can hint to the dirty checker |
| 1278 // and short circuit the iterator. | 1015 // and short circuit the iterator. |
| 1279 value = new UnmodifiableListView(value); | 1016 value = new UnmodifiableListView(value); |
| 1280 } | 1017 } |
| 1281 return value; | 1018 return value; |
| 1282 } | 1019 } |
| 1283 } | 1020 } |
| OLD | NEW |