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 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
145 | 145 |
146 | 146 |
147 /** | 147 /** |
148 * A scope in polymer expressions that can map names to objects. Scopes contain | 148 * A scope in polymer expressions that can map names to objects. Scopes contain |
149 * a set of named variables and a unique model object. The scope structure | 149 * a set of named variables and a unique model object. The scope structure |
150 * is then used to lookup names using the `[]` operator. The lookup first | 150 * is then used to lookup names using the `[]` operator. The lookup first |
151 * searches for the name in local variables, then in global variables, | 151 * searches for the name in local variables, then in global variables, |
152 * and then finally looks up the name as a property in the model. | 152 * and then finally looks up the name as a property in the model. |
153 */ | 153 */ |
154 abstract class Scope implements Indexable<String, Object> { | 154 abstract class Scope implements Indexable<String, Object> { |
| 155 static int __seq = 1; |
| 156 final int _seq = __seq++; |
| 157 |
155 Scope._(); | 158 Scope._(); |
156 | 159 |
157 /** Create a scope containing a [model] and all of [variables]. */ | 160 /** Create a scope containing a [model] and all of [variables]. */ |
158 factory Scope({Object model, Map<String, Object> variables}) { | 161 factory Scope({Object model, Map<String, Object> variables}) { |
159 var scope = new _ModelScope(model); | 162 var scope = new _ModelScope(model); |
160 return variables == null ? scope | 163 return variables == null ? scope |
161 : new _GlobalsScope(new Map<String, Object>.from(variables), scope); | 164 : new _GlobalsScope(new Map<String, Object>.from(variables), scope); |
162 } | 165 } |
163 | 166 |
164 /** Return the unique model in this scope. */ | 167 /** Return the unique model in this scope. */ |
(...skipping 13 matching lines...) Expand all Loading... |
178 /** | 181 /** |
179 * Returns whether [name] is defined in [model], that is, a lookup | 182 * Returns whether [name] is defined in [model], that is, a lookup |
180 * would not find a variable with that name, but there is a non-null model | 183 * would not find a variable with that name, but there is a non-null model |
181 * where we can look it up as a property. | 184 * where we can look it up as a property. |
182 */ | 185 */ |
183 bool _isModelProperty(String name); | 186 bool _isModelProperty(String name); |
184 | 187 |
185 /** Create a new scope extending this scope with an additional variable. */ | 188 /** Create a new scope extending this scope with an additional variable. */ |
186 Scope childScope(String name, Object value) => | 189 Scope childScope(String name, Object value) => |
187 new _LocalVariableScope(name, value, this); | 190 new _LocalVariableScope(name, value, this); |
| 191 |
| 192 String toString() => 'Scope(seq: $_seq model: $model)'; |
| 193 |
188 } | 194 } |
189 | 195 |
190 /** | 196 /** |
191 * A scope that looks up names in a model object. This kind of scope has no | 197 * A scope that looks up names in a model object. This kind of scope has no |
192 * parent scope because all our lookup operations stop when we reach the model | 198 * parent scope because all our lookup operations stop when we reach the model |
193 * object. Any variables added in scope or global variables are added as child | 199 * object. Any variables added in scope or global variables are added as child |
194 * scopes. | 200 * scopes. |
195 */ | 201 */ |
196 class _ModelScope extends Scope { | 202 class _ModelScope extends Scope { |
197 final Object model; | 203 final Object model; |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
322 } | 328 } |
323 | 329 |
324 class Updater extends RecursiveVisitor { | 330 class Updater extends RecursiveVisitor { |
325 final Scope scope; | 331 final Scope scope; |
326 | 332 |
327 Updater(this.scope); | 333 Updater(this.scope); |
328 | 334 |
329 visitExpression(ExpressionObserver e) { | 335 visitExpression(ExpressionObserver e) { |
330 e._observe(scope); | 336 e._observe(scope); |
331 } | 337 } |
332 | |
333 visitInExpression(InObserver c) { | |
334 visit(c.right); | |
335 visitExpression(c); | |
336 } | |
337 } | 338 } |
338 | 339 |
339 class ObserverBuilder extends Visitor { | 340 class ObserverBuilder extends Visitor { |
340 final Scope scope; | |
341 final Queue parents = new Queue(); | 341 final Queue parents = new Queue(); |
342 | 342 |
343 ObserverBuilder(this.scope); | 343 ObserverBuilder(); |
344 | 344 |
345 visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e); | 345 visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e); |
346 | 346 |
347 visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); | 347 visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); |
348 | 348 |
349 visitGetter(Getter g) { | 349 visitGetter(Getter g) { |
350 var receiver = visit(g.receiver); | 350 var receiver = visit(g.receiver); |
351 var getter = new GetterObserver(g, receiver); | 351 var getter = new GetterObserver(g, receiver); |
352 receiver._parent = getter; | 352 receiver._parent = getter; |
353 return getter; | 353 return getter; |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
421 var trueExpr = visit(o.trueExpr); | 421 var trueExpr = visit(o.trueExpr); |
422 var falseExpr = visit(o.falseExpr); | 422 var falseExpr = visit(o.falseExpr); |
423 var ternary = new TernaryObserver(o, condition, trueExpr, falseExpr); | 423 var ternary = new TernaryObserver(o, condition, trueExpr, falseExpr); |
424 condition._parent = ternary; | 424 condition._parent = ternary; |
425 trueExpr._parent = ternary; | 425 trueExpr._parent = ternary; |
426 falseExpr._parent = ternary; | 426 falseExpr._parent = ternary; |
427 return ternary; | 427 return ternary; |
428 } | 428 } |
429 | 429 |
430 visitInExpression(InExpression i) { | 430 visitInExpression(InExpression i) { |
431 // don't visit the left. It's an identifier, but we don't want to evaluate | 431 throw new UnsupportedError("can't eval an 'in' expression"); |
432 // it, we just want to add it to the comprehension object | 432 } |
433 var left = visit(i.left); | 433 |
434 var right = visit(i.right); | 434 visitAsExpression(AsExpression i) { |
435 var inexpr = new InObserver(i, left, right); | 435 throw new UnsupportedError("can't eval an 'as' expression"); |
436 right._parent = inexpr; | |
437 return inexpr; | |
438 } | 436 } |
439 } | 437 } |
440 | 438 |
441 class EmptyObserver extends ExpressionObserver<EmptyExpression> | 439 class EmptyObserver extends ExpressionObserver<EmptyExpression> |
442 implements EmptyExpression { | 440 implements EmptyExpression { |
443 | 441 |
444 EmptyObserver(EmptyExpression value) : super(value); | 442 EmptyObserver(EmptyExpression value) : super(value); |
445 | 443 |
446 _updateSelf(Scope scope) { | 444 _updateSelf(Scope scope) { |
447 _value = scope.model; | 445 _value = scope.model; |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
507 | 505 |
508 class IdentifierObserver extends ExpressionObserver<Identifier> | 506 class IdentifierObserver extends ExpressionObserver<Identifier> |
509 implements Identifier { | 507 implements Identifier { |
510 | 508 |
511 IdentifierObserver(Identifier value) : super(value); | 509 IdentifierObserver(Identifier value) : super(value); |
512 | 510 |
513 String get value => _expr.value; | 511 String get value => _expr.value; |
514 | 512 |
515 _updateSelf(Scope scope) { | 513 _updateSelf(Scope scope) { |
516 _value = scope[value]; | 514 _value = scope[value]; |
517 | |
518 if (!scope._isModelProperty(value)) return; | 515 if (!scope._isModelProperty(value)) return; |
519 var model = scope.model; | 516 var model = scope.model; |
520 if (model is! Observable) return; | 517 if (model is! Observable) return; |
521 var symbol = smoke.nameToSymbol(value); | 518 var symbol = smoke.nameToSymbol(value); |
522 _subscription = (model as Observable).changes.listen((changes) { | 519 _subscription = (model as Observable).changes.listen((changes) { |
523 if (changes.any((c) => c is PropertyChangeRecord && c.name == symbol)) { | 520 if (changes.any((c) => c is PropertyChangeRecord && c.name == symbol)) { |
524 _invalidate(scope); | 521 _invalidate(scope); |
525 } | 522 } |
526 }); | 523 }); |
527 } | 524 } |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
710 _invalidate(scope); | 707 _invalidate(scope); |
711 } | 708 } |
712 }); | 709 }); |
713 } | 710 } |
714 } | 711 } |
715 } | 712 } |
716 | 713 |
717 accept(Visitor v) => v.visitInvoke(this); | 714 accept(Visitor v) => v.visitInvoke(this); |
718 } | 715 } |
719 | 716 |
720 class InObserver extends ExpressionObserver<InExpression> | |
721 implements InExpression { | |
722 IdentifierObserver left; | |
723 ExpressionObserver right; | |
724 | |
725 InObserver(Expression expr, this.left, this.right) : super(expr); | |
726 | |
727 _updateSelf(Scope scope) { | |
728 Identifier identifier = left; | |
729 var iterable = right._value; | |
730 | |
731 if (iterable is! Iterable && iterable != null) { | |
732 throw new EvalException("right side of 'in' is not an iterator"); | |
733 } | |
734 | |
735 if (iterable is ObservableList) { | |
736 _subscription = iterable.listChanges.listen((_) => _invalidate(scope)); | |
737 } | |
738 | |
739 var name = identifier.value; | |
740 _value = iterable == null ? const [] : | |
741 iterable.map((i) => scope.childScope(name, i)).toList(growable: false); | |
742 } | |
743 | |
744 accept(Visitor v) => v.visitInExpression(this); | |
745 } | |
746 | |
747 _toBool(v) => (v == null) ? false : v; | 717 _toBool(v) => (v == null) ? false : v; |
748 | 718 |
749 class EvalException implements Exception { | 719 class EvalException implements Exception { |
750 final String message; | 720 final String message; |
751 EvalException(this.message); | 721 EvalException(this.message); |
752 String toString() => "EvalException: $message"; | 722 String toString() => "EvalException: $message"; |
753 } | 723 } |
OLD | NEW |