| 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 local variables, then in global variables, |
| 155 * Scopes can be nested by giving them a [parent]. If a name in not found in a | 151 * and then finally 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 return variables == null ? scope |
| 160 : new _GlobalsScope(new Map<String, Object>.from(variables), scope); |
| 161 } |
| 162 |
| 163 /** Return the unique model in this scope. */ |
| 164 Object get model; |
| 165 |
| 166 /** |
| 167 * Lookup the value of [name] in the current scope. If [name] is 'this', then |
| 168 * we return the [model]. For any other name, this finds the first variable |
| 169 * matching [name] or, if none exists, the property [name] in the [model]. |
| 170 */ |
| 171 Object operator[](String name); |
| 172 |
| 173 /** |
| 174 * Returns whether [name] is defined in [model], that is, a lookup |
| 175 * would not find a variable with that name, but there is a non-null model |
| 176 * where we can look it up as a property. |
| 177 */ |
| 178 bool _isModelProperty(String name); |
| 179 |
| 180 /** Create a new scope extending this scope with an additional variable. */ |
| 181 Scope childScope(String name, Object value) => |
| 182 new _LocalVariableScope(name, value, this); |
| 183 } |
| 184 |
| 185 /** |
| 186 * A scope that looks up names in a model object. This kind of scope has no |
| 187 * parent scope because all our lookup operations stop when we reach the model |
| 188 * object. Any variables added in scope or global variables are added as child |
| 189 * scopes. |
| 190 */ |
| 191 class _ModelScope extends Scope { |
| 160 final Object model; | 192 final Object model; |
| 161 // TODO(justinfagnani): disallow adding/removing names | |
| 162 final ObservableMap<String, Object> _variables; | |
| 163 | 193 |
| 164 Scope({this.model, Map<String, Object> variables, this.parent}) | 194 _ModelScope(this.model) : super._(); |
| 165 : _variables = new ObservableMap.from(variables == null ? {} : variables); | |
| 166 | 195 |
| 167 Object operator[](String name) { | 196 Object operator[](String name) { |
| 168 if (name == 'this') { | 197 if (name == 'this') return model; |
| 169 return model; | 198 var symbol = smoke.nameToSymbol(name); |
| 170 } else if (_variables.containsKey(name)) { | 199 if (model == null || symbol == null) { |
| 171 return _convert(_variables[name]); | 200 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 } | 201 } |
| 178 if (parent != null) { | 202 return _convert(smoke.read(model, symbol)); |
| 179 return _convert(parent[name]); | 203 } |
| 180 } else { | 204 |
| 181 throw new EvalException("variable '$name' not found"); | 205 Object _isModelProperty(String name) => name != 'this'; |
| 206 } |
| 207 |
| 208 /** |
| 209 * A scope that holds a reference to a single variable. Polymer expressions |
| 210 * introduce variables to the scope one at a time. Each time a variable is |
| 211 * added, a new [_LocalVariableScope] is created. |
| 212 */ |
| 213 class _LocalVariableScope extends Scope { |
| 214 final Scope parent; |
| 215 final String varName; |
| 216 // TODO(sigmund,justinfagnani): make this @observable? |
| 217 final Object value; |
| 218 |
| 219 _LocalVariableScope(this.varName, this.value, this.parent) : super._() { |
| 220 if (varName == 'this') { |
| 221 throw new EvalException("'this' cannot be used as a variable name."); |
| 182 } | 222 } |
| 183 } | 223 } |
| 184 | 224 |
| 185 Object ownerOf(String name) { | 225 Object get model => parent != null ? parent.model : null; |
| 186 if (name == 'this') { | 226 |
| 187 // we could return the Scope if it were Observable, but since assigning | 227 Object operator[](String name) { |
| 188 // a model to a template destroys and recreates the instance, it doesn't | 228 if (varName == name) return _convert(value); |
| 189 // seem neccessary | 229 if (parent != null) return parent[name]; |
| 190 return null; | 230 throw new EvalException("variable '$name' not found"); |
| 191 } else if (_variables.containsKey(name)) { | 231 } |
| 192 return _variables; | 232 |
| 193 } else if (smoke.hasGetter(model.runtimeType, smoke.nameToSymbol(name))) { | 233 bool _isModelProperty(String name) { |
| 194 return model; | 234 if (varName == name) return false; |
| 195 } | 235 return parent == null ? false : parent._isModelProperty(name); |
| 196 if (parent != null) { | 236 } |
| 197 return parent.ownerOf(name); | 237 } |
| 238 |
| 239 /** A scope that holds a reference to a global variables. */ |
| 240 class _GlobalsScope extends Scope { |
| 241 final _ModelScope parent; |
| 242 final Map<String, Object> variables; |
| 243 |
| 244 _GlobalsScope(this.variables, this.parent) : super._() { |
| 245 if (variables.containsKey('this')) { |
| 246 throw new EvalException("'this' cannot be used as a variable name."); |
| 198 } | 247 } |
| 199 } | 248 } |
| 200 | 249 |
| 201 bool contains(String name) { | 250 Object get model => parent != null ? parent.model : null; |
| 202 if (_variables.containsKey(name) || | 251 |
| 203 smoke.hasGetter(model.runtimeType, smoke.nameToSymbol(name))) { | 252 Object operator[](String name) { |
| 204 return true; | 253 if (variables.containsKey(name)) return _convert(variables[name]); |
| 205 } | 254 if (parent != null) return parent[name]; |
| 206 if (parent != null) { | 255 throw new EvalException("variable '$name' not found"); |
| 207 return parent.contains(name); | 256 } |
| 208 } | 257 |
| 209 return false; | 258 bool _isModelProperty(String name) { |
| 259 if (variables.containsKey(name)) return false; |
| 260 return parent == null ? false : parent._isModelProperty(name); |
| 210 } | 261 } |
| 211 } | 262 } |
| 212 | 263 |
| 213 Object _convert(v) { | 264 Object _convert(v) => v is Stream ? new StreamBinding(v) : v; |
| 214 if (v is Stream) return new StreamBinding(v); | |
| 215 return v; | |
| 216 } | |
| 217 | 265 |
| 218 abstract class ExpressionObserver<E extends Expression> implements Expression { | 266 abstract class ExpressionObserver<E extends Expression> implements Expression { |
| 219 final E _expr; | 267 final E _expr; |
| 220 ExpressionObserver _parent; | 268 ExpressionObserver _parent; |
| 221 | 269 |
| 222 StreamSubscription _subscription; | 270 StreamSubscription _subscription; |
| 223 Object _value; | 271 Object _value; |
| 224 | 272 |
| 225 StreamController _controller = new StreamController.broadcast(); | 273 StreamController _controller = new StreamController.broadcast(); |
| 226 Stream get onUpdate => _controller.stream; | 274 Stream get onUpdate => _controller.stream; |
| (...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 449 class IdentifierObserver extends ExpressionObserver<Identifier> | 497 class IdentifierObserver extends ExpressionObserver<Identifier> |
| 450 implements Identifier { | 498 implements Identifier { |
| 451 | 499 |
| 452 IdentifierObserver(Identifier value) : super(value); | 500 IdentifierObserver(Identifier value) : super(value); |
| 453 | 501 |
| 454 String get value => _expr.value; | 502 String get value => _expr.value; |
| 455 | 503 |
| 456 _updateSelf(Scope scope) { | 504 _updateSelf(Scope scope) { |
| 457 _value = scope[value]; | 505 _value = scope[value]; |
| 458 | 506 |
| 459 var owner = scope.ownerOf(value); | 507 if (!scope._isModelProperty(value)) return; |
| 460 if (owner is Observable) { | 508 var model = scope.model; |
| 461 var symbol = smoke.nameToSymbol(value); | 509 if (model is! Observable) return; |
| 462 _subscription = (owner as Observable).changes.listen((changes) { | 510 var symbol = smoke.nameToSymbol(value); |
| 463 if (changes.any( | 511 _subscription = (model as Observable).changes.listen((changes) { |
| 464 (c) => c is PropertyChangeRecord && c.name == symbol)) { | 512 if (changes.any((c) => c is PropertyChangeRecord && c.name == symbol)) { |
| 465 _invalidate(scope); | 513 _invalidate(scope); |
| 466 } | 514 } |
| 467 }); | 515 }); |
| 468 } | |
| 469 } | 516 } |
| 470 | 517 |
| 471 accept(Visitor v) => v.visitIdentifier(this); | 518 accept(Visitor v) => v.visitIdentifier(this); |
| 472 } | 519 } |
| 473 | 520 |
| 474 class ParenthesizedObserver extends ExpressionObserver<ParenthesizedExpression> | 521 class ParenthesizedObserver extends ExpressionObserver<ParenthesizedExpression> |
| 475 implements ParenthesizedExpression { | 522 implements ParenthesizedExpression { |
| 476 final ExpressionObserver child; | 523 final ExpressionObserver child; |
| 477 | 524 |
| 478 ParenthesizedObserver(ParenthesizedExpression expr, this.child) : super(expr); | 525 ParenthesizedObserver(ParenthesizedExpression expr, this.child) : super(expr); |
| (...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 693 | 740 |
| 694 Comprehension(this.identifier, Iterable iterable) | 741 Comprehension(this.identifier, Iterable iterable) |
| 695 : iterable = (iterable != null) ? iterable : const []; | 742 : iterable = (iterable != null) ? iterable : const []; |
| 696 } | 743 } |
| 697 | 744 |
| 698 class EvalException implements Exception { | 745 class EvalException implements Exception { |
| 699 final String message; | 746 final String message; |
| 700 EvalException(this.message); | 747 EvalException(this.message); |
| 701 String toString() => "EvalException: $message"; | 748 String toString() => "EvalException: $message"; |
| 702 } | 749 } |
| OLD | NEW |