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 import 'package:observe/observe.dart'; | 10 import 'package:observe/observe.dart'; |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 53 } | 53 } |
| 54 | 54 |
| 55 /** | 55 /** |
| 56 * Returns an [ExpressionObserver] that evaluates [expr] in the context of | 56 * Returns an [ExpressionObserver] that evaluates [expr] in the context of |
| 57 * scope] and listens for any changes on [Observable] values that are | 57 * scope] and listens for any changes on [Observable] values that are |
| 58 * returned from sub-expressions. When a value changes the expression is | 58 * returned from sub-expressions. When a value changes the expression is |
| 59 * reevaluated and the new result is sent to the [onUpdate] stream of the | 59 * reevaluated and the new result is sent to the [onUpdate] stream of the |
| 60 * [ExpressionObsserver]. | 60 * [ExpressionObsserver]. |
| 61 */ | 61 */ |
| 62 ExpressionObserver observe(Expression expr, Scope scope) { | 62 ExpressionObserver observe(Expression expr, Scope scope) { |
| 63 var observer = new ObserverBuilder(scope).visit(expr); | 63 var observer = new ObserverBuilder().visit(expr); |
| 64 return observer; | 64 return observer; |
| 65 } | 65 } |
| 66 | 66 |
| 67 /** | 67 /** |
| 68 * Causes [expr] to be reevaluated a returns it's value. | 68 * Causes [expr] to be reevaluated a returns it's value. |
| 69 */ | 69 */ |
| 70 Object update(ExpressionObserver expr, Scope scope) { | 70 Object update(ExpressionObserver expr, Scope scope) { |
| 71 new Updater(scope).visit(expr); | 71 new Updater(scope).visit(expr); |
| 72 return expr.currentValue; | 72 return expr.currentValue; |
| 73 } | 73 } |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 144 | 144 |
| 145 | 145 |
| 146 /** | 146 /** |
| 147 * A scope in polymer expressions that can map names to objects. Scopes contain | 147 * A scope in polymer expressions that can map names to objects. Scopes contain |
| 148 * a set of named variables and a unique model object. The scope structure | 148 * a set of named variables and a unique model object. The scope structure |
| 149 * is then used to lookup names using the `[]` operator. The lookup first | 149 * is then used to lookup names using the `[]` operator. The lookup first |
| 150 * searches for the name in local variables, then in global variables, | 150 * searches for the name in local variables, then in global variables, |
| 151 * and then finally looks up the name as a property in the model. | 151 * and then finally looks up the name as a property in the model. |
| 152 */ | 152 */ |
| 153 abstract class Scope implements Indexable<String, Object> { | 153 abstract class Scope implements Indexable<String, Object> { |
| 154 static int __seq = 1; | |
| 155 final int _seq = __seq++; | |
| 156 | |
| 154 Scope._(); | 157 Scope._(); |
| 155 | 158 |
| 156 /** Create a scope containing a [model] and all of [variables]. */ | 159 /** Create a scope containing a [model] and all of [variables]. */ |
| 157 factory Scope({Object model, Map<String, Object> variables}) { | 160 factory Scope({Object model, Map<String, Object> variables}) { |
| 158 var scope = new _ModelScope(model); | 161 var scope = new _ModelScope(model); |
| 159 return variables == null ? scope | 162 return variables == null ? scope |
| 160 : new _GlobalsScope(new Map<String, Object>.from(variables), scope); | 163 : new _GlobalsScope(new Map<String, Object>.from(variables), scope); |
| 161 } | 164 } |
| 162 | 165 |
| 163 /** Return the unique model in this scope. */ | 166 /** Return the unique model in this scope. */ |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 177 /** | 180 /** |
| 178 * Returns whether [name] is defined in [model], that is, a lookup | 181 * Returns whether [name] is defined in [model], that is, a lookup |
| 179 * would not find a variable with that name, but there is a non-null model | 182 * would not find a variable with that name, but there is a non-null model |
| 180 * where we can look it up as a property. | 183 * where we can look it up as a property. |
| 181 */ | 184 */ |
| 182 bool _isModelProperty(String name); | 185 bool _isModelProperty(String name); |
| 183 | 186 |
| 184 /** Create a new scope extending this scope with an additional variable. */ | 187 /** Create a new scope extending this scope with an additional variable. */ |
| 185 Scope childScope(String name, Object value) => | 188 Scope childScope(String name, Object value) => |
| 186 new _LocalVariableScope(name, value, this); | 189 new _LocalVariableScope(name, value, this); |
| 190 | |
| 191 String toString() => 'Scope(seq: $_seq model: $model)'; | |
| 192 | |
| 187 } | 193 } |
| 188 | 194 |
| 189 /** | 195 /** |
| 190 * A scope that looks up names in a model object. This kind of scope has no | 196 * 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 | 197 * 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 | 198 * object. Any variables added in scope or global variables are added as child |
| 193 * scopes. | 199 * scopes. |
| 194 */ | 200 */ |
| 195 class _ModelScope extends Scope { | 201 class _ModelScope extends Scope { |
| 196 final Object model; | 202 final Object model; |
| 197 | 203 |
| 198 _ModelScope(this.model) : super._(); | 204 _ModelScope(this.model) : super._(); |
| 199 | 205 |
| 200 Object operator[](String name) { | 206 Object operator[](String name) { |
| 201 if (name == 'this') return model; | 207 if (name == 'this') return model; |
| 202 var symbol = smoke.nameToSymbol(name); | 208 var symbol = smoke.nameToSymbol(name); |
| 203 if (model == null || symbol == null) { | 209 if (model == null || symbol == null) { |
| 204 throw new EvalException("variable '$name' not found"); | 210 throw new EvalException("variable '$name' not found"); |
| 205 } | 211 } |
| 206 return _convert(smoke.read(model, symbol)); | 212 return _convert(smoke.read(model, symbol)); |
| 207 } | 213 } |
| 208 | 214 |
| 209 Object _isModelProperty(String name) => name != 'this'; | 215 Object _isModelProperty(String name) => name != 'this'; |
| 216 | |
| 217 String toString() => 'ModelScope(model: $model)'; | |
| 210 } | 218 } |
| 211 | 219 |
| 212 /** | 220 /** |
| 213 * A scope that holds a reference to a single variable. Polymer expressions | 221 * 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 | 222 * introduce variables to the scope one at a time. Each time a variable is |
| 215 * added, a new [_LocalVariableScope] is created. | 223 * added, a new [_LocalVariableScope] is created. |
| 216 */ | 224 */ |
| 217 class _LocalVariableScope extends Scope { | 225 class _LocalVariableScope extends Scope { |
| 218 final Scope parent; | 226 final Scope parent; |
| 219 final String varName; | 227 final String varName; |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 231 Object operator[](String name) { | 239 Object operator[](String name) { |
| 232 if (varName == name) return _convert(value); | 240 if (varName == name) return _convert(value); |
| 233 if (parent != null) return parent[name]; | 241 if (parent != null) return parent[name]; |
| 234 throw new EvalException("variable '$name' not found"); | 242 throw new EvalException("variable '$name' not found"); |
| 235 } | 243 } |
| 236 | 244 |
| 237 bool _isModelProperty(String name) { | 245 bool _isModelProperty(String name) { |
| 238 if (varName == name) return false; | 246 if (varName == name) return false; |
| 239 return parent == null ? false : parent._isModelProperty(name); | 247 return parent == null ? false : parent._isModelProperty(name); |
| 240 } | 248 } |
| 249 | |
| 250 String toString() => 'LocalVariableScope(varName: $varName, value: $value, par ent: $parent)'; | |
|
Jennifer Messerly
2014/04/24 00:51:34
long line
justinfagnani
2014/05/28 00:29:37
Done.
| |
| 241 } | 251 } |
| 242 | 252 |
| 243 /** A scope that holds a reference to a global variables. */ | 253 /** A scope that holds a reference to a global variables. */ |
| 244 class _GlobalsScope extends Scope { | 254 class _GlobalsScope extends Scope { |
| 245 final _ModelScope parent; | 255 final _ModelScope parent; |
| 246 final Map<String, Object> variables; | 256 final Map<String, Object> variables; |
| 247 | 257 |
| 248 _GlobalsScope(this.variables, this.parent) : super._() { | 258 _GlobalsScope(this.variables, this.parent) : super._() { |
| 249 if (variables.containsKey('this')) { | 259 if (variables.containsKey('this')) { |
| 250 throw new EvalException("'this' cannot be used as a variable name."); | 260 throw new EvalException("'this' cannot be used as a variable name."); |
| 251 } | 261 } |
| 252 } | 262 } |
| 253 | 263 |
| 254 Object get model => parent != null ? parent.model : null; | 264 Object get model => parent != null ? parent.model : null; |
| 255 | 265 |
| 256 Object operator[](String name) { | 266 Object operator[](String name) { |
| 257 if (variables.containsKey(name)) return _convert(variables[name]); | 267 if (variables.containsKey(name)) return _convert(variables[name]); |
| 258 if (parent != null) return parent[name]; | 268 if (parent != null) return parent[name]; |
| 259 throw new EvalException("variable '$name' not found"); | 269 throw new EvalException("variable '$name' not found"); |
| 260 } | 270 } |
| 261 | 271 |
| 262 bool _isModelProperty(String name) { | 272 bool _isModelProperty(String name) { |
| 263 if (variables.containsKey(name)) return false; | 273 if (variables.containsKey(name)) return false; |
| 264 return parent == null ? false : parent._isModelProperty(name); | 274 return parent == null ? false : parent._isModelProperty(name); |
| 265 } | 275 } |
| 276 | |
| 277 String toString() => 'GlobalsScope(variables: $variables, parent: $parent)'; | |
| 266 } | 278 } |
| 267 | 279 |
| 268 Object _convert(v) => v is Stream ? new StreamBinding(v) : v; | 280 Object _convert(v) => v is Stream ? new StreamBinding(v) : v; |
| 269 | 281 |
| 270 abstract class ExpressionObserver<E extends Expression> implements Expression { | 282 abstract class ExpressionObserver<E extends Expression> implements Expression { |
| 271 final E _expr; | 283 final E _expr; |
| 272 ExpressionObserver _parent; | 284 ExpressionObserver _parent; |
| 273 | 285 |
| 274 StreamSubscription _subscription; | 286 StreamSubscription _subscription; |
| 275 Object _value; | 287 Object _value; |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 315 } | 327 } |
| 316 | 328 |
| 317 class Updater extends RecursiveVisitor { | 329 class Updater extends RecursiveVisitor { |
| 318 final Scope scope; | 330 final Scope scope; |
| 319 | 331 |
| 320 Updater(this.scope); | 332 Updater(this.scope); |
| 321 | 333 |
| 322 visitExpression(ExpressionObserver e) { | 334 visitExpression(ExpressionObserver e) { |
| 323 e._observe(scope); | 335 e._observe(scope); |
| 324 } | 336 } |
| 325 | |
| 326 visitInExpression(InObserver c) { | |
| 327 visit(c.right); | |
| 328 visitExpression(c); | |
| 329 } | |
| 330 } | 337 } |
| 331 | 338 |
| 332 class ObserverBuilder extends Visitor { | 339 class ObserverBuilder extends Visitor { |
| 333 final Scope scope; | |
| 334 final Queue parents = new Queue(); | 340 final Queue parents = new Queue(); |
| 335 | 341 |
| 336 ObserverBuilder(this.scope); | 342 ObserverBuilder(); |
| 337 | 343 |
| 338 visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e); | 344 visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e); |
| 339 | 345 |
| 340 visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); | 346 visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); |
| 341 | 347 |
| 342 visitGetter(Getter g) { | 348 visitGetter(Getter g) { |
| 343 var receiver = visit(g.receiver); | 349 var receiver = visit(g.receiver); |
| 344 var getter = new GetterObserver(g, receiver); | 350 var getter = new GetterObserver(g, receiver); |
| 345 receiver._parent = getter; | 351 receiver._parent = getter; |
| 346 return getter; | 352 return getter; |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 414 var trueExpr = visit(o.trueExpr); | 420 var trueExpr = visit(o.trueExpr); |
| 415 var falseExpr = visit(o.falseExpr); | 421 var falseExpr = visit(o.falseExpr); |
| 416 var ternary = new TernaryObserver(o, condition, trueExpr, falseExpr); | 422 var ternary = new TernaryObserver(o, condition, trueExpr, falseExpr); |
| 417 condition._parent = ternary; | 423 condition._parent = ternary; |
| 418 trueExpr._parent = ternary; | 424 trueExpr._parent = ternary; |
| 419 falseExpr._parent = ternary; | 425 falseExpr._parent = ternary; |
| 420 return ternary; | 426 return ternary; |
| 421 } | 427 } |
| 422 | 428 |
| 423 visitInExpression(InExpression i) { | 429 visitInExpression(InExpression i) { |
| 424 // don't visit the left. It's an identifier, but we don't want to evaluate | 430 throw new UnsupportedError("can't eval an 'in' expression"); |
| 425 // it, we just want to add it to the comprehension object | 431 } |
| 426 var left = visit(i.left); | 432 |
| 427 var right = visit(i.right); | 433 visitAsExpression(AsExpression i) { |
| 428 var inexpr = new InObserver(i, left, right); | 434 throw new UnsupportedError("can't eval an 'as' expression"); |
| 429 right._parent = inexpr; | |
| 430 return inexpr; | |
| 431 } | 435 } |
| 432 } | 436 } |
| 433 | 437 |
| 434 class EmptyObserver extends ExpressionObserver<EmptyExpression> | 438 class EmptyObserver extends ExpressionObserver<EmptyExpression> |
| 435 implements EmptyExpression { | 439 implements EmptyExpression { |
| 436 | 440 |
| 437 EmptyObserver(EmptyExpression value) : super(value); | 441 EmptyObserver(EmptyExpression value) : super(value); |
| 438 | 442 |
| 439 _updateSelf(Scope scope) { | 443 _updateSelf(Scope scope) { |
| 440 _value = scope.model; | 444 _value = scope.model; |
| (...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 698 _invalidate(scope); | 702 _invalidate(scope); |
| 699 } | 703 } |
| 700 }); | 704 }); |
| 701 } | 705 } |
| 702 } | 706 } |
| 703 } | 707 } |
| 704 | 708 |
| 705 accept(Visitor v) => v.visitInvoke(this); | 709 accept(Visitor v) => v.visitInvoke(this); |
| 706 } | 710 } |
| 707 | 711 |
| 708 class InObserver extends ExpressionObserver<InExpression> | |
| 709 implements InExpression { | |
| 710 IdentifierObserver left; | |
| 711 ExpressionObserver right; | |
| 712 | |
| 713 InObserver(Expression expr, this.left, this.right) : super(expr); | |
| 714 | |
| 715 _updateSelf(Scope scope) { | |
| 716 Identifier identifier = left; | |
| 717 var iterable = right._value; | |
| 718 | |
| 719 if (iterable is! Iterable && iterable != null) { | |
| 720 throw new EvalException("right side of 'in' is not an iterator"); | |
| 721 } | |
| 722 | |
| 723 if (iterable is ObservableList) { | |
| 724 _subscription = iterable.listChanges.listen((_) => _invalidate(scope)); | |
| 725 } | |
| 726 | |
| 727 // TODO: make Comprehension observable and update it | |
| 728 _value = new Comprehension(identifier.value, iterable); | |
| 729 } | |
| 730 | |
| 731 accept(Visitor v) => v.visitInExpression(this); | |
| 732 } | |
| 733 | 712 |
| 734 _toBool(v) => (v == null) ? false : v; | 713 _toBool(v) => (v == null) ? false : v; |
| 735 | 714 |
| 736 /** | |
| 737 * A comprehension declaration ("a in b"). [identifier] is the loop variable | |
| 738 * that's added to the scope during iteration. [iterable] is the set of | |
| 739 * objects to iterate over. | |
| 740 */ | |
| 741 class Comprehension { | |
| 742 final String identifier; | |
| 743 final Iterable iterable; | |
| 744 | |
| 745 Comprehension(this.identifier, Iterable iterable) | |
| 746 : iterable = (iterable != null) ? iterable : const []; | |
| 747 } | |
| 748 | |
| 749 class EvalException implements Exception { | 715 class EvalException implements Exception { |
| 750 final String message; | 716 final String message; |
| 751 EvalException(this.message); | 717 EvalException(this.message); |
| 752 String toString() => "EvalException: $message"; | 718 String toString() => "EvalException: $message"; |
| 753 } | 719 } |
| OLD | NEW |