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( | 10 @MirrorsUsed( |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
58 } | 58 } |
59 | 59 |
60 /** | 60 /** |
61 * Returns an [ExpressionObserver] that evaluates [expr] in the context of | 61 * Returns an [ExpressionObserver] that evaluates [expr] in the context of |
62 * scope] and listens for any changes on [Observable] values that are | 62 * scope] and listens for any changes on [Observable] values that are |
63 * returned from sub-expressions. When a value changes the expression is | 63 * returned from sub-expressions. When a value changes the expression is |
64 * reevaluated and the new result is sent to the [onUpdate] stream of the | 64 * reevaluated and the new result is sent to the [onUpdate] stream of the |
65 * [ExpressionObsserver]. | 65 * [ExpressionObsserver]. |
66 */ | 66 */ |
67 ExpressionObserver observe(Expression expr, Scope scope) { | 67 ExpressionObserver observe(Expression expr, Scope scope) { |
68 var observer = new ObserverBuilder(scope).visit(expr); | 68 var observer = new ObserverBuilder(/*scope*/).visit(expr); |
Jennifer Messerly
2014/03/17 23:10:08
did you mean to remove the scope argument to "obse
justinfagnani
2014/04/24 00:26:13
yep
| |
69 return observer; | 69 return observer; |
70 } | 70 } |
71 | 71 |
72 /** | 72 /** |
73 * Causes [expr] to be reevaluated a returns it's value. | 73 * Causes [expr] to be reevaluated a returns it's value. |
74 */ | 74 */ |
75 Object update(ExpressionObserver expr, Scope scope) { | 75 Object update(ExpressionObserver expr, Scope scope) { |
76 new Updater(scope).visit(expr); | 76 new Updater(scope).visit(expr); |
77 return expr.currentValue; | 77 return expr.currentValue; |
78 } | 78 } |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
157 */ | 157 */ |
158 class Scope { | 158 class Scope { |
159 final Scope parent; | 159 final Scope parent; |
160 final Object model; | 160 final Object model; |
161 // TODO(justinfagnani): disallow adding/removing names | 161 // TODO(justinfagnani): disallow adding/removing names |
162 final ObservableMap<String, Object> _variables; | 162 final ObservableMap<String, Object> _variables; |
163 | 163 |
164 Scope({this.model, Map<String, Object> variables, this.parent}) | 164 Scope({this.model, Map<String, Object> variables, this.parent}) |
165 : _variables = new ObservableMap.from(variables == null ? {} : variables); | 165 : _variables = new ObservableMap.from(variables == null ? {} : variables); |
166 | 166 |
167 get variables => _variables; | |
Jennifer Messerly
2014/03/17 23:10:08
another option is just to make _variables public.
justinfagnani
2014/04/24 00:26:13
Done.
| |
168 | |
167 Object operator[](String name) { | 169 Object operator[](String name) { |
168 if (name == 'this') { | 170 if (name == 'this') { |
169 return model; | 171 return model; |
170 } else if (_variables.containsKey(name)) { | 172 } else if (_variables.containsKey(name)) { |
171 return _convert(_variables[name]); | 173 return _convert(_variables[name]); |
172 } else { | 174 } else { |
173 var symbol = smoke.nameToSymbol(name); | 175 var symbol = smoke.nameToSymbol(name); |
174 if (model != null && smoke.hasGetter(model.runtimeType, symbol)) { | 176 if (model != null && smoke.hasGetter(model.runtimeType, symbol)) { |
175 return _convert(smoke.read(model, symbol)); | 177 return _convert(smoke.read(model, symbol)); |
176 } | 178 } |
(...skipping 12 matching lines...) Expand all Loading... | |
189 // seem neccessary | 191 // seem neccessary |
190 return null; | 192 return null; |
191 } else if (_variables.containsKey(name)) { | 193 } else if (_variables.containsKey(name)) { |
192 return _variables; | 194 return _variables; |
193 } else if (smoke.hasGetter(model.runtimeType, smoke.nameToSymbol(name))) { | 195 } else if (smoke.hasGetter(model.runtimeType, smoke.nameToSymbol(name))) { |
194 return model; | 196 return model; |
195 } | 197 } |
196 if (parent != null) { | 198 if (parent != null) { |
197 return parent.ownerOf(name); | 199 return parent.ownerOf(name); |
198 } | 200 } |
201 return null; | |
199 } | 202 } |
200 | 203 |
201 bool contains(String name) { | 204 String toString() => 'Scope(parent: $parent, model: $model, ' |
202 if (_variables.containsKey(name) || | 205 'variables: $variables)'; |
203 smoke.hasGetter(model.runtimeType, smoke.nameToSymbol(name))) { | |
204 return true; | |
205 } | |
206 if (parent != null) { | |
207 return parent.contains(name); | |
208 } | |
209 return false; | |
210 } | |
211 } | 206 } |
212 | 207 |
213 Object _convert(v) { | 208 Object _convert(v) { |
214 if (v is Stream) return new StreamBinding(v); | 209 if (v is Stream) return new StreamBinding(v); |
215 return v; | 210 return v; |
216 } | 211 } |
217 | 212 |
218 abstract class ExpressionObserver<E extends Expression> implements Expression { | 213 abstract class ExpressionObserver<E extends Expression> implements Expression { |
219 final E _expr; | 214 final E _expr; |
220 ExpressionObserver _parent; | 215 ExpressionObserver _parent; |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
263 } | 258 } |
264 | 259 |
265 class Updater extends RecursiveVisitor { | 260 class Updater extends RecursiveVisitor { |
266 final Scope scope; | 261 final Scope scope; |
267 | 262 |
268 Updater(this.scope); | 263 Updater(this.scope); |
269 | 264 |
270 visitExpression(ExpressionObserver e) { | 265 visitExpression(ExpressionObserver e) { |
271 e._observe(scope); | 266 e._observe(scope); |
272 } | 267 } |
273 | |
274 visitInExpression(InObserver c) { | |
275 visit(c.right); | |
276 visitExpression(c); | |
277 } | |
278 } | 268 } |
279 | 269 |
280 class ObserverBuilder extends Visitor { | 270 class ObserverBuilder extends Visitor { |
281 final Scope scope; | 271 // final Scope scope; |
282 final Queue parents = new Queue(); | 272 final Queue parents = new Queue(); |
283 | 273 |
284 ObserverBuilder(this.scope); | 274 ObserverBuilder(/*this.scope*/); |
285 | 275 |
286 visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e); | 276 visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e); |
287 | 277 |
288 visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); | 278 visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); |
289 | 279 |
290 visitGetter(Getter g) { | 280 visitGetter(Getter g) { |
291 var receiver = visit(g.receiver); | 281 var receiver = visit(g.receiver); |
292 var getter = new GetterObserver(g, receiver); | 282 var getter = new GetterObserver(g, receiver); |
293 receiver._parent = getter; | 283 receiver._parent = getter; |
294 return getter; | 284 return getter; |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
362 var trueExpr = visit(o.trueExpr); | 352 var trueExpr = visit(o.trueExpr); |
363 var falseExpr = visit(o.falseExpr); | 353 var falseExpr = visit(o.falseExpr); |
364 var ternary = new TernaryObserver(o, condition, trueExpr, falseExpr); | 354 var ternary = new TernaryObserver(o, condition, trueExpr, falseExpr); |
365 condition._parent = ternary; | 355 condition._parent = ternary; |
366 trueExpr._parent = ternary; | 356 trueExpr._parent = ternary; |
367 falseExpr._parent = ternary; | 357 falseExpr._parent = ternary; |
368 return ternary; | 358 return ternary; |
369 } | 359 } |
370 | 360 |
371 visitInExpression(InExpression i) { | 361 visitInExpression(InExpression i) { |
372 // don't visit the left. It's an identifier, but we don't want to evaluate | 362 throw new UnsupportedError("can't eval an 'in' expression"); |
373 // it, we just want to add it to the comprehension object | |
374 var left = visit(i.left); | |
375 var right = visit(i.right); | |
376 var inexpr = new InObserver(i, left, right); | |
377 right._parent = inexpr; | |
378 return inexpr; | |
379 } | 363 } |
364 | |
365 visitAsExpression(AsExpression i) { | |
366 throw new UnsupportedError("can't eval an 'as' expression"); | |
367 } | |
368 | |
380 } | 369 } |
381 | 370 |
382 class EmptyObserver extends ExpressionObserver<EmptyExpression> | 371 class EmptyObserver extends ExpressionObserver<EmptyExpression> |
383 implements EmptyExpression { | 372 implements EmptyExpression { |
384 | 373 |
385 EmptyObserver(EmptyExpression value) : super(value); | 374 EmptyObserver(EmptyExpression value) : super(value); |
386 | 375 |
387 _updateSelf(Scope scope) { | 376 _updateSelf(Scope scope) { |
388 _value = scope.model; | 377 _value = scope.model; |
389 // TODO(justin): listen for scope.model changes? | 378 // TODO(justin): listen for scope.model changes? |
(...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
647 _invalidate(scope); | 636 _invalidate(scope); |
648 } | 637 } |
649 }); | 638 }); |
650 } | 639 } |
651 } | 640 } |
652 } | 641 } |
653 | 642 |
654 accept(Visitor v) => v.visitInvoke(this); | 643 accept(Visitor v) => v.visitInvoke(this); |
655 } | 644 } |
656 | 645 |
657 class InObserver extends ExpressionObserver<InExpression> | |
658 implements InExpression { | |
659 IdentifierObserver left; | |
660 ExpressionObserver right; | |
661 | |
662 InObserver(Expression expr, this.left, this.right) : super(expr); | |
663 | |
664 _updateSelf(Scope scope) { | |
665 Identifier identifier = left; | |
666 var iterable = right._value; | |
667 | |
668 if (iterable is! Iterable && iterable != null) { | |
669 throw new EvalException("right side of 'in' is not an iterator"); | |
670 } | |
671 | |
672 if (iterable is ObservableList) { | |
673 _subscription = iterable.listChanges.listen((_) => _invalidate(scope)); | |
674 } | |
675 | |
676 // TODO: make Comprehension observable and update it | |
677 _value = new Comprehension(identifier.value, iterable); | |
678 } | |
679 | |
680 accept(Visitor v) => v.visitInExpression(this); | |
681 } | |
682 | |
683 _toBool(v) => (v == null) ? false : v; | 646 _toBool(v) => (v == null) ? false : v; |
684 | 647 |
685 /** | |
686 * A comprehension declaration ("a in b"). [identifier] is the loop variable | |
687 * that's added to the scope during iteration. [iterable] is the set of | |
688 * objects to iterate over. | |
689 */ | |
690 class Comprehension { | |
691 final String identifier; | |
692 final Iterable iterable; | |
693 | |
694 Comprehension(this.identifier, Iterable iterable) | |
695 : iterable = (iterable != null) ? iterable : const []; | |
696 } | |
697 | |
698 class EvalException implements Exception { | 648 class EvalException implements Exception { |
699 final String message; | 649 final String message; |
700 EvalException(this.message); | 650 EvalException(this.message); |
701 String toString() => "EvalException: $message"; | 651 String toString() => "EvalException: $message"; |
702 } | 652 } |
OLD | NEW |