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 |