Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library polymer_expressions.eval; | 5 library polymer_expressions.eval; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 @MirrorsUsed( | |
| 11 metaTargets: const [Reflectable, ObservableProperty], | |
| 12 override: 'smoke.mirrors') | |
| 13 import 'dart:mirrors' show MirrorsUsed; | |
| 14 | |
| 15 import 'package:observe/observe.dart'; | 10 import 'package:observe/observe.dart'; |
| 16 import 'package:smoke/smoke.dart' as smoke; | 11 import 'package:smoke/smoke.dart' as smoke; |
| 17 | 12 |
| 18 import 'async.dart'; | 13 import 'async.dart'; |
| 19 import 'expression.dart'; | 14 import 'expression.dart'; |
| 20 import 'filter.dart'; | 15 import 'filter.dart'; |
| 21 import 'visitor.dart'; | 16 import 'visitor.dart'; |
| 22 | 17 |
| 23 final _BINARY_OPERATORS = { | 18 final _BINARY_OPERATORS = { |
| 24 '+': (a, b) => a + b, | 19 '+': (a, b) => a + b, |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 140 // make the assignment | 135 // make the assignment |
| 141 var o = eval(expression, scope); | 136 var o = eval(expression, scope); |
| 142 if (o == null) throw new EvalException("Can't assign to null: $expression"); | 137 if (o == null) throw new EvalException("Can't assign to null: $expression"); |
| 143 if (isIndex) { | 138 if (isIndex) { |
| 144 o[property] = value; | 139 o[property] = value; |
| 145 } else { | 140 } else { |
| 146 smoke.write(o, smoke.nameToSymbol(property), value); | 141 smoke.write(o, smoke.nameToSymbol(property), value); |
| 147 } | 142 } |
| 148 } | 143 } |
| 149 | 144 |
| 145 | |
| 150 /** | 146 /** |
| 151 * A mapping of names to objects. Scopes contain a set of named [variables] and | 147 * A scope in polymer expressions that can map names to objects. Scopes contain |
| 152 * a single [model] object (which can be thought of as the "this" reference). | 148 * a set of named variables and a unique model object. The scope structure |
| 153 * Names are currently looked up in [variables] first, then the [model]. | 149 * is then used to lookup names using the `[]` operator. The lookup first |
| 154 * | 150 * searches for the name in the variable set (even if these are global |
| 155 * Scopes can be nested by giving them a [parent]. If a name in not found in a | 151 * variables), otherwise it looks up the name as a property in the model. |
| 156 * Scope, it will look for it in it's parent. | |
| 157 */ | 152 */ |
| 158 class Scope { | 153 abstract class Scope { |
| 159 final Scope parent; | 154 Scope._(); |
| 155 | |
| 156 /** Create a scope containing a [model] and all of [variables]. */ | |
| 157 factory Scope({Object model, Map<String, Object> variables}) { | |
| 158 var scope = new _ModelScope(model); | |
| 159 if (variables == null) return scope; | |
| 160 variables.forEach((name, value) { | |
| 161 scope = new _VariableScope(name, value, scope); | |
|
Jennifer Messerly
2014/03/21 20:08:54
hmmm. Is the Map used for globals? We might want s
Siggi Cherem (dart-lang)
2014/03/21 21:09:47
good point. Added _GlobalsScope for this.
| |
| 162 }); | |
| 163 return scope; | |
| 164 } | |
| 165 | |
| 166 /** Return the unique model in this scope. */ | |
| 167 Object get model; | |
| 168 | |
| 169 /** | |
| 170 * Lookup the value of [name] in the current scope. If [name] is 'this', then | |
| 171 * we return the [model]. For any other name, this finds the first variable | |
| 172 * matching [name] or, if none exists, the property [name] in the [model]. | |
| 173 */ | |
| 174 Object operator[](String name); | |
| 175 | |
| 176 /** | |
| 177 * Return the object that defines the value of [name]. The result may be a | |
| 178 * [_VariableScope] if it is a variable on this scope, [model] if it is a | |
| 179 * member of the model object, a [_ModelScope] if name is 'this', or null if | |
| 180 * the name can't be found and [model] is null. | |
|
Siggi Cherem (dart-lang)
2014/03/21 01:43:46
Justin - a general question here:
I was wondering
Siggi Cherem (dart-lang)
2014/03/21 21:09:47
BWT - after adding the GlobalsScope and with the d
| |
| 181 */ | |
| 182 Object _ownerOf(String name); | |
| 183 | |
| 184 /** Create a new scope extending this scope with an additional variable. */ | |
| 185 Scope childScope(String name, Object value) => | |
| 186 new _VariableScope(name, value, this); | |
| 187 } | |
| 188 | |
| 189 /** | |
| 190 * A scope that looks up names in a model object. This kind of scope has no | |
| 191 * parent scope because all our lookup operations stop when we reach the model | |
| 192 * object. Any variables added in scope or global variables are added as child | |
| 193 * scopes. | |
| 194 */ | |
| 195 class _ModelScope extends Scope { | |
| 160 final Object model; | 196 final Object model; |
| 161 // TODO(justinfagnani): disallow adding/removing names | |
| 162 final ObservableMap<String, Object> _variables; | |
| 163 | 197 |
| 164 Scope({this.model, Map<String, Object> variables, this.parent}) | 198 _ModelScope(this.model) : super._(); |
| 165 : _variables = new ObservableMap.from(variables == null ? {} : variables); | |
| 166 | 199 |
| 167 Object operator[](String name) { | 200 Object operator[](String name) { |
| 168 if (name == 'this') { | 201 if (name == 'this') return model; |
| 169 return model; | 202 var symbol = smoke.nameToSymbol(name); |
| 170 } else if (_variables.containsKey(name)) { | 203 if (model == null || symbol == null) { |
| 171 return _convert(_variables[name]); | 204 throw new EvalException("variable '$name' not found"); |
| 172 } else { | |
| 173 var symbol = smoke.nameToSymbol(name); | |
| 174 if (model != null && smoke.hasGetter(model.runtimeType, symbol)) { | |
| 175 return _convert(smoke.read(model, symbol)); | |
| 176 } | |
| 177 } | 205 } |
| 178 if (parent != null) { | 206 return _convert(smoke.read(model, symbol)); |
| 179 return _convert(parent[name]); | 207 } |
| 180 } else { | 208 |
| 181 throw new EvalException("variable '$name' not found"); | 209 Object _ownerOf(String name) => name == 'this' ? this : model; |
| 210 } | |
| 211 | |
| 212 /** | |
| 213 * A scope that holds a reference to a single variable. Polymer expressions | |
| 214 * introduce variables to the scope one at a time. Each time a variable is | |
| 215 * added, a new [_VariableScope] is created. | |
| 216 */ | |
| 217 class _VariableScope extends Scope { | |
| 218 final Scope parent; | |
| 219 final String varName; | |
| 220 // TODO(sigmund,justinfagnani): make this @observable? | |
| 221 final Object value; | |
|
Siggi Cherem (dart-lang)
2014/03/21 01:43:46
I had this with @observable first (matching what y
Jennifer Messerly
2014/03/21 20:08:54
Not sure I understand the TODO. Shouldn't values a
Siggi Cherem (dart-lang)
2014/03/21 21:09:47
I was thinking if we were to make those constructs
Jennifer Messerly
2014/03/22 00:37:56
It doesn't currently reuse nodes if the model chan
| |
| 222 | |
| 223 _VariableScope(this.varName, this.value, this.parent) : super._() { | |
| 224 if (varName == 'this') { | |
| 225 throw new EvalException("'this' cannot be used as a variable name."); | |
| 182 } | 226 } |
| 183 } | 227 } |
| 184 | 228 |
| 185 Object ownerOf(String name) { | 229 Object get model => parent != null ? parent.model : null; |
| 186 if (name == 'this') { | 230 |
| 187 // we could return the Scope if it were Observable, but since assigning | 231 Object operator[](String name) { |
| 188 // a model to a template destroys and recreates the instance, it doesn't | 232 if (varName == name) return _convert(value); |
| 189 // seem neccessary | 233 if (parent != null) return parent[name]; |
|
Siggi Cherem (dart-lang)
2014/03/21 01:43:46
(see comment re _ownerOf)
| |
| 190 return null; | 234 throw new EvalException("variable '$name' not found"); |
| 191 } else if (_variables.containsKey(name)) { | |
| 192 return _variables; | |
| 193 } else if (smoke.hasGetter(model.runtimeType, smoke.nameToSymbol(name))) { | |
| 194 return model; | |
| 195 } | |
| 196 if (parent != null) { | |
| 197 return parent.ownerOf(name); | |
| 198 } | |
| 199 } | 235 } |
| 200 | 236 |
| 201 bool contains(String name) { | 237 Object _ownerOf(String name) { |
| 202 if (_variables.containsKey(name) || | 238 if (varName == name) return this; |
| 203 smoke.hasGetter(model.runtimeType, smoke.nameToSymbol(name))) { | 239 if (parent != null) return parent._ownerOf(name); |
| 204 return true; | 240 return null; |
| 205 } | |
| 206 if (parent != null) { | |
| 207 return parent.contains(name); | |
| 208 } | |
| 209 return false; | |
| 210 } | 241 } |
| 211 } | 242 } |
| 212 | 243 |
| 213 Object _convert(v) { | 244 Object _convert(v) => v is Stream ? new StreamBinding(v) : v; |
| 214 if (v is Stream) return new StreamBinding(v); | |
| 215 return v; | |
| 216 } | |
| 217 | 245 |
| 218 abstract class ExpressionObserver<E extends Expression> implements Expression { | 246 abstract class ExpressionObserver<E extends Expression> implements Expression { |
| 219 final E _expr; | 247 final E _expr; |
| 220 ExpressionObserver _parent; | 248 ExpressionObserver _parent; |
| 221 | 249 |
| 222 StreamSubscription _subscription; | 250 StreamSubscription _subscription; |
| 223 Object _value; | 251 Object _value; |
| 224 | 252 |
| 225 StreamController _controller = new StreamController.broadcast(); | 253 StreamController _controller = new StreamController.broadcast(); |
| 226 Stream get onUpdate => _controller.stream; | 254 Stream get onUpdate => _controller.stream; |
| (...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 449 class IdentifierObserver extends ExpressionObserver<Identifier> | 477 class IdentifierObserver extends ExpressionObserver<Identifier> |
| 450 implements Identifier { | 478 implements Identifier { |
| 451 | 479 |
| 452 IdentifierObserver(Identifier value) : super(value); | 480 IdentifierObserver(Identifier value) : super(value); |
| 453 | 481 |
| 454 String get value => _expr.value; | 482 String get value => _expr.value; |
| 455 | 483 |
| 456 _updateSelf(Scope scope) { | 484 _updateSelf(Scope scope) { |
| 457 _value = scope[value]; | 485 _value = scope[value]; |
| 458 | 486 |
| 459 var owner = scope.ownerOf(value); | 487 var owner = scope._ownerOf(value); |
| 460 if (owner is Observable) { | 488 if (owner is Observable) { |
| 461 var symbol = smoke.nameToSymbol(value); | 489 var symbol = smoke.nameToSymbol(value); |
| 462 _subscription = (owner as Observable).changes.listen((changes) { | 490 _subscription = (owner as Observable).changes.listen((changes) { |
| 463 if (changes.any( | 491 if (changes.any((c) => c is PropertyChangeRecord && c.name == symbol)) { |
| 464 (c) => c is PropertyChangeRecord && c.name == symbol)) { | |
| 465 _invalidate(scope); | 492 _invalidate(scope); |
| 466 } | 493 } |
| 467 }); | 494 }); |
| 468 } | 495 } |
| 469 } | 496 } |
| 470 | 497 |
| 471 accept(Visitor v) => v.visitIdentifier(this); | 498 accept(Visitor v) => v.visitIdentifier(this); |
| 472 } | 499 } |
| 473 | 500 |
| 474 class ParenthesizedObserver extends ExpressionObserver<ParenthesizedExpression> | 501 class ParenthesizedObserver extends ExpressionObserver<ParenthesizedExpression> |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 693 | 720 |
| 694 Comprehension(this.identifier, Iterable iterable) | 721 Comprehension(this.identifier, Iterable iterable) |
| 695 : iterable = (iterable != null) ? iterable : const []; | 722 : iterable = (iterable != null) ? iterable : const []; |
| 696 } | 723 } |
| 697 | 724 |
| 698 class EvalException implements Exception { | 725 class EvalException implements Exception { |
| 699 final String message; | 726 final String message; |
| 700 EvalException(this.message); | 727 EvalException(this.message); |
| 701 String toString() => "EvalException: $message"; | 728 String toString() => "EvalException: $message"; |
| 702 } | 729 } |
| OLD | NEW |