OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library polymer_expressions.eval; | |
6 | |
7 import 'dart:async'; | |
8 import 'dart:collection'; | |
9 | |
10 import 'package:observe/observe.dart'; | |
11 import 'package:smoke/smoke.dart' as smoke; | |
12 | |
13 import 'async.dart'; | |
14 import 'expression.dart'; | |
15 import 'filter.dart'; | |
16 import 'visitor.dart'; | |
17 | |
18 final _BINARY_OPERATORS = { | |
19 '+': (a, b) => a + b, | |
20 '-': (a, b) => a - b, | |
21 '*': (a, b) => a * b, | |
22 '/': (a, b) => a / b, | |
23 '%': (a, b) => a % b, | |
24 '==': (a, b) => a == b, | |
25 '!=': (a, b) => a != b, | |
26 '===': (a, b) => identical(a, b), | |
27 '!==': (a, b) => !identical(a, b), | |
28 '>': (a, b) => a > b, | |
29 '>=': (a, b) => a >= b, | |
30 '<': (a, b) => a < b, | |
31 '<=': (a, b) => a <= b, | |
32 '||': (a, b) => a || b, | |
33 '&&': (a, b) => a && b, | |
34 '|': (a, f) { | |
35 if (f is Transformer) return f.forward(a); | |
36 if (f is Filter) return f(a); | |
37 throw new EvalException("Filters must be a one-argument function."); | |
38 } | |
39 }; | |
40 | |
41 final _UNARY_OPERATORS = { | |
42 '+': (a) => a, | |
43 '-': (a) => -a, | |
44 '!': (a) => !a, | |
45 }; | |
46 | |
47 final _BOOLEAN_OPERATORS = ['!', '||', '&&']; | |
48 | |
49 /** | |
50 * Evaluation [expr] in the context of [scope]. | |
51 */ | |
52 Object eval(Expression expr, Scope scope) => new EvalVisitor(scope).visit(expr); | |
53 | |
54 /** | |
55 * Returns an [ExpressionObserver] that evaluates [expr] in the context of | |
56 * scope] and listens for any changes on [Observable] values that are | |
57 * returned from sub-expressions. When a value changes the expression is | |
58 * reevaluated and the new result is sent to the [onUpdate] stream of the | |
59 * [ExpressionObsserver]. | |
60 */ | |
61 ExpressionObserver observe(Expression expr, Scope scope) { | |
62 var observer = new ObserverBuilder().visit(expr); | |
63 return observer; | |
64 } | |
65 | |
66 /** | |
67 * Causes [expr] to be reevaluated a returns it's value. | |
68 */ | |
69 Object update(ExpressionObserver expr, Scope scope, {skipChanges: false}) { | |
70 new Updater(scope, skipChanges).visit(expr); | |
71 return expr.currentValue; | |
72 } | |
73 | |
74 /** | |
75 * Assign [value] to the variable or field referenced by [expr] in the context | |
76 * of [scope]. | |
77 * | |
78 * [expr] must be an /assignable/ expression, it must not contain | |
79 * operators or function invocations, and any index operations must use a | |
80 * literal index. | |
81 */ | |
82 Object assign(Expression expr, Object value, Scope scope, | |
83 {bool checkAssignability: true}) { | |
84 | |
85 Expression expression; | |
86 var property; | |
87 bool isIndex = false; | |
88 var filters = <Expression>[]; // reversed order for assignment | |
89 | |
90 while (expr is BinaryOperator) { | |
91 BinaryOperator op = expr; | |
92 if (op.operator != '|') { | |
93 break; | |
94 } | |
95 filters.add(op.right); | |
96 expr = op.left; | |
97 } | |
98 | |
99 if (expr is Identifier) { | |
100 expression = empty(); | |
101 property = expr.value; | |
102 } else if (expr is Index) { | |
103 expression = expr.receiver; | |
104 property = expr.argument; | |
105 isIndex = true; | |
106 } else if (expr is Getter) { | |
107 expression = expr.receiver; | |
108 property = expr.name; | |
109 } else { | |
110 if (checkAssignability) { | |
111 throw new EvalException("Expression is not assignable: $expr"); | |
112 } | |
113 return null; | |
114 } | |
115 | |
116 // transform the values backwards through the filters | |
117 for (var filterExpr in filters) { | |
118 var filter = eval(filterExpr, scope); | |
119 if (filter is! Transformer) { | |
120 if (checkAssignability) { | |
121 throw new EvalException("filter must implement Transformer to be " | |
122 "assignable: $filterExpr"); | |
123 } else { | |
124 return null; | |
125 } | |
126 } | |
127 value = filter.reverse(value); | |
128 } | |
129 // evaluate the receiver | |
130 var o = eval(expression, scope); | |
131 | |
132 // can't assign to a property on a null LHS object. Silently fail. | |
133 if (o == null) return null; | |
134 | |
135 if (isIndex) { | |
136 var index = eval(property, scope); | |
137 o[index] = value; | |
138 } else { | |
139 smoke.write(o, smoke.nameToSymbol(property), value); | |
140 } | |
141 return value; | |
142 } | |
143 | |
144 | |
145 /** | |
146 * A scope in polymer expressions that can map names to objects. Scopes contain | |
147 * a set of named variables and a unique model object. The scope structure | |
148 * is then used to lookup names using the `[]` operator. The lookup first | |
149 * searches for the name in local variables, then in global variables, | |
150 * and then finally looks up the name as a property in the model. | |
151 */ | |
152 abstract class Scope implements Indexable<String, Object> { | |
153 Scope._(); | |
154 | |
155 /** Create a scope containing a [model] and all of [variables]. */ | |
156 factory Scope({Object model, Map<String, Object> variables}) { | |
157 var scope = new _ModelScope(model); | |
158 return variables == null ? scope | |
159 : new _GlobalsScope(new Map<String, Object>.from(variables), scope); | |
160 } | |
161 | |
162 /** Return the unique model in this scope. */ | |
163 Object get model; | |
164 | |
165 /** | |
166 * Lookup the value of [name] in the current scope. If [name] is 'this', then | |
167 * we return the [model]. For any other name, this finds the first variable | |
168 * matching [name] or, if none exists, the property [name] in the [model]. | |
169 */ | |
170 Object operator [](String name); | |
171 | |
172 operator []=(String name, Object value) { | |
173 throw new UnsupportedError('[]= is not supported in Scope.'); | |
174 } | |
175 | |
176 /** | |
177 * Returns whether [name] is defined in [model], that is, a lookup | |
178 * would not find a variable with that name, but there is a non-null model | |
179 * where we can look it up as a property. | |
180 */ | |
181 bool _isModelProperty(String name); | |
182 | |
183 /** Create a new scope extending this scope with an additional variable. */ | |
184 Scope childScope(String name, Object value) => | |
185 new _LocalVariableScope(name, value, this); | |
186 } | |
187 | |
188 /** | |
189 * A scope that looks up names in a model object. This kind of scope has no | |
190 * parent scope because all our lookup operations stop when we reach the model | |
191 * object. Any variables added in scope or global variables are added as child | |
192 * scopes. | |
193 */ | |
194 class _ModelScope extends Scope { | |
195 final Object model; | |
196 | |
197 _ModelScope(this.model) : super._(); | |
198 | |
199 Object operator[](String name) { | |
200 if (name == 'this') return model; | |
201 var symbol = smoke.nameToSymbol(name); | |
202 if (model == null || symbol == null) { | |
203 throw new EvalException("variable '$name' not found"); | |
204 } | |
205 return _convert(smoke.read(model, symbol)); | |
206 } | |
207 | |
208 Object _isModelProperty(String name) => name != 'this'; | |
209 | |
210 String toString() => "[model: $model]"; | |
211 } | |
212 | |
213 /** | |
214 * A scope that holds a reference to a single variable. Polymer expressions | |
215 * introduce variables to the scope one at a time. Each time a variable is | |
216 * added, a new [_LocalVariableScope] is created. | |
217 */ | |
218 class _LocalVariableScope extends Scope { | |
219 final Scope parent; | |
220 final String varName; | |
221 // TODO(sigmund,justinfagnani): make this @observable? | |
222 final Object value; | |
223 | |
224 _LocalVariableScope(this.varName, this.value, this.parent) : super._() { | |
225 if (varName == 'this') { | |
226 throw new EvalException("'this' cannot be used as a variable name."); | |
227 } | |
228 } | |
229 | |
230 Object get model => parent != null ? parent.model : null; | |
231 | |
232 Object operator[](String name) { | |
233 if (varName == name) return _convert(value); | |
234 if (parent != null) return parent[name]; | |
235 throw new EvalException("variable '$name' not found"); | |
236 } | |
237 | |
238 bool _isModelProperty(String name) { | |
239 if (varName == name) return false; | |
240 return parent == null ? false : parent._isModelProperty(name); | |
241 } | |
242 | |
243 String toString() => "$parent > [local: $varName]"; | |
244 } | |
245 | |
246 /** A scope that holds a reference to a global variables. */ | |
247 class _GlobalsScope extends Scope { | |
248 final _ModelScope parent; | |
249 final Map<String, Object> variables; | |
250 | |
251 _GlobalsScope(this.variables, this.parent) : super._() { | |
252 if (variables.containsKey('this')) { | |
253 throw new EvalException("'this' cannot be used as a variable name."); | |
254 } | |
255 } | |
256 | |
257 Object get model => parent != null ? parent.model : null; | |
258 | |
259 Object operator[](String name) { | |
260 if (variables.containsKey(name)) return _convert(variables[name]); | |
261 if (parent != null) return parent[name]; | |
262 throw new EvalException("variable '$name' not found"); | |
263 } | |
264 | |
265 bool _isModelProperty(String name) { | |
266 if (variables.containsKey(name)) return false; | |
267 return parent == null ? false : parent._isModelProperty(name); | |
268 } | |
269 | |
270 String toString() => "$parent > [global: ${variables.keys}]"; | |
271 } | |
272 | |
273 Object _convert(v) => v is Stream ? new StreamBinding(v) : v; | |
274 | |
275 abstract class ExpressionObserver<E extends Expression> implements Expression { | |
276 final E _expr; | |
277 ExpressionObserver _parent; | |
278 | |
279 StreamSubscription _subscription; | |
280 Object _value; | |
281 | |
282 StreamController _controller = new StreamController.broadcast(); | |
283 Stream get onUpdate => _controller.stream; | |
284 | |
285 ExpressionObserver(this._expr); | |
286 | |
287 Expression get expression => _expr; | |
288 | |
289 Object get currentValue => _value; | |
290 | |
291 update(Scope scope) => _updateSelf(scope); | |
292 | |
293 _updateSelf(Scope scope) {} | |
294 | |
295 _invalidate(Scope scope) { | |
296 _observe(scope, false); | |
297 if (_parent != null) { | |
298 _parent._invalidate(scope); | |
299 } | |
300 } | |
301 | |
302 _unobserve() { | |
303 if (_subscription != null) { | |
304 _subscription.cancel(); | |
305 _subscription = null; | |
306 } | |
307 } | |
308 | |
309 _observe(Scope scope, skipChanges) { | |
310 _unobserve(); | |
311 | |
312 var _oldValue = _value; | |
313 | |
314 // evaluate | |
315 _updateSelf(scope); | |
316 | |
317 if (!skipChanges && !identical(_value, _oldValue)) { | |
318 _controller.add(_value); | |
319 } | |
320 } | |
321 | |
322 String toString() => _expr.toString(); | |
323 } | |
324 | |
325 class Updater extends RecursiveVisitor { | |
326 final Scope scope; | |
327 final bool skipChanges; | |
328 | |
329 Updater(this.scope, [this.skipChanges = false]); | |
330 | |
331 visitExpression(ExpressionObserver e) { | |
332 e._observe(scope, skipChanges); | |
333 } | |
334 } | |
335 | |
336 class Closer extends RecursiveVisitor { | |
337 static final _instance = new Closer._(); | |
338 factory Closer() => _instance; | |
339 Closer._(); | |
340 | |
341 visitExpression(ExpressionObserver e) { | |
342 e._unobserve(); | |
343 } | |
344 } | |
345 | |
346 class EvalVisitor extends Visitor { | |
347 final Scope scope; | |
348 | |
349 EvalVisitor(this.scope); | |
350 | |
351 visitEmptyExpression(EmptyExpression e) => scope.model; | |
352 | |
353 visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); | |
354 | |
355 visitGetter(Getter g) { | |
356 var receiver = visit(g.receiver); | |
357 if (receiver == null) return null; | |
358 var symbol = smoke.nameToSymbol(g.name); | |
359 return smoke.read(receiver, symbol); | |
360 } | |
361 | |
362 visitIndex(Index i) { | |
363 var receiver = visit(i.receiver); | |
364 if (receiver == null) return null; | |
365 var key = visit(i.argument); | |
366 return receiver[key]; | |
367 } | |
368 | |
369 visitInvoke(Invoke i) { | |
370 var receiver = visit(i.receiver); | |
371 if (receiver == null) return null; | |
372 var args = (i.arguments == null) | |
373 ? null | |
374 : i.arguments.map(visit).toList(growable: false); | |
375 | |
376 if (i.method == null) { | |
377 assert(receiver is Function); | |
378 return Function.apply(receiver, args); | |
379 } | |
380 | |
381 var symbol = smoke.nameToSymbol(i.method); | |
382 return smoke.invoke(receiver, symbol, args); | |
383 } | |
384 | |
385 visitLiteral(Literal l) => l.value; | |
386 | |
387 visitListLiteral(ListLiteral l) => l.items.map(visit).toList(); | |
388 | |
389 visitMapLiteral(MapLiteral l) { | |
390 var map = {}; | |
391 for (var entry in l.entries) { | |
392 var key = visit(entry.key); | |
393 var value = visit(entry.entryValue); | |
394 map[key] = value; | |
395 } | |
396 return map; | |
397 } | |
398 | |
399 visitMapLiteralEntry(MapLiteralEntry e) => | |
400 throw new UnsupportedError("should never be called"); | |
401 | |
402 visitIdentifier(Identifier i) => scope[i.value]; | |
403 | |
404 visitBinaryOperator(BinaryOperator o) { | |
405 var operator = o.operator; | |
406 var left = visit(o.left); | |
407 var right = visit(o.right); | |
408 | |
409 var f = _BINARY_OPERATORS[operator]; | |
410 if (operator == '&&' || operator == '||') { | |
411 // TODO: short-circuit | |
412 return f(_toBool(left), _toBool(right)); | |
413 } else if (operator == '==' || operator == '!=') { | |
414 return f(left, right); | |
415 } else if (left == null || right == null) { | |
416 return null; | |
417 } | |
418 return f(left, right); | |
419 } | |
420 | |
421 visitUnaryOperator(UnaryOperator o) { | |
422 var expr = visit(o.child); | |
423 var f = _UNARY_OPERATORS[o.operator]; | |
424 if (o.operator == '!') { | |
425 return f(_toBool(expr)); | |
426 } | |
427 return (expr == null) ? null : f(expr); | |
428 } | |
429 | |
430 visitTernaryOperator(TernaryOperator o) => | |
431 visit(o.condition) == true ? visit(o.trueExpr) : visit(o.falseExpr); | |
432 | |
433 visitInExpression(InExpression i) => | |
434 throw new UnsupportedError("can't eval an 'in' expression"); | |
435 | |
436 visitAsExpression(AsExpression i) => | |
437 throw new UnsupportedError("can't eval an 'as' expression"); | |
438 } | |
439 | |
440 class ObserverBuilder extends Visitor { | |
441 final Queue parents = new Queue(); | |
442 | |
443 ObserverBuilder(); | |
444 | |
445 visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e); | |
446 | |
447 visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); | |
448 | |
449 visitGetter(Getter g) { | |
450 var receiver = visit(g.receiver); | |
451 var getter = new GetterObserver(g, receiver); | |
452 receiver._parent = getter; | |
453 return getter; | |
454 } | |
455 | |
456 visitIndex(Index i) { | |
457 var receiver = visit(i.receiver); | |
458 var arg = visit(i.argument); | |
459 var index = new IndexObserver(i, receiver, arg); | |
460 receiver._parent = index; | |
461 arg._parent = index; | |
462 return index; | |
463 } | |
464 | |
465 visitInvoke(Invoke i) { | |
466 var receiver = visit(i.receiver); | |
467 var args = (i.arguments == null) | |
468 ? null | |
469 : i.arguments.map(visit).toList(growable: false); | |
470 var invoke = new InvokeObserver(i, receiver, args); | |
471 receiver._parent = invoke; | |
472 if (args != null) args.forEach((a) => a._parent = invoke); | |
473 return invoke; | |
474 } | |
475 | |
476 visitLiteral(Literal l) => new LiteralObserver(l); | |
477 | |
478 visitListLiteral(ListLiteral l) { | |
479 var items = l.items.map(visit).toList(growable: false); | |
480 var list = new ListLiteralObserver(l, items); | |
481 items.forEach((e) => e._parent = list); | |
482 return list; | |
483 } | |
484 | |
485 visitMapLiteral(MapLiteral l) { | |
486 var entries = l.entries.map(visit).toList(growable: false); | |
487 var map = new MapLiteralObserver(l, entries); | |
488 entries.forEach((e) => e._parent = map); | |
489 return map; | |
490 } | |
491 | |
492 visitMapLiteralEntry(MapLiteralEntry e) { | |
493 var key = visit(e.key); | |
494 var value = visit(e.entryValue); | |
495 var entry = new MapLiteralEntryObserver(e, key, value); | |
496 key._parent = entry; | |
497 value._parent = entry; | |
498 return entry; | |
499 } | |
500 | |
501 visitIdentifier(Identifier i) => new IdentifierObserver(i); | |
502 | |
503 visitBinaryOperator(BinaryOperator o) { | |
504 var left = visit(o.left); | |
505 var right = visit(o.right); | |
506 var binary = new BinaryObserver(o, left, right); | |
507 left._parent = binary; | |
508 right._parent = binary; | |
509 return binary; | |
510 } | |
511 | |
512 visitUnaryOperator(UnaryOperator o) { | |
513 var expr = visit(o.child); | |
514 var unary = new UnaryObserver(o, expr); | |
515 expr._parent = unary; | |
516 return unary; | |
517 } | |
518 | |
519 visitTernaryOperator(TernaryOperator o) { | |
520 var condition = visit(o.condition); | |
521 var trueExpr = visit(o.trueExpr); | |
522 var falseExpr = visit(o.falseExpr); | |
523 var ternary = new TernaryObserver(o, condition, trueExpr, falseExpr); | |
524 condition._parent = ternary; | |
525 trueExpr._parent = ternary; | |
526 falseExpr._parent = ternary; | |
527 return ternary; | |
528 } | |
529 | |
530 visitInExpression(InExpression i) { | |
531 throw new UnsupportedError("can't eval an 'in' expression"); | |
532 } | |
533 | |
534 visitAsExpression(AsExpression i) { | |
535 throw new UnsupportedError("can't eval an 'as' expression"); | |
536 } | |
537 } | |
538 | |
539 class EmptyObserver extends ExpressionObserver<EmptyExpression> | |
540 implements EmptyExpression { | |
541 | |
542 EmptyObserver(EmptyExpression value) : super(value); | |
543 | |
544 _updateSelf(Scope scope) { | |
545 _value = scope.model; | |
546 // TODO(justin): listen for scope.model changes? | |
547 } | |
548 | |
549 accept(Visitor v) => v.visitEmptyExpression(this); | |
550 } | |
551 | |
552 class LiteralObserver extends ExpressionObserver<Literal> implements Literal { | |
553 | |
554 LiteralObserver(Literal value) : super(value); | |
555 | |
556 dynamic get value => _expr.value; | |
557 | |
558 _updateSelf(Scope scope) { | |
559 _value = _expr.value; | |
560 } | |
561 | |
562 accept(Visitor v) => v.visitLiteral(this); | |
563 } | |
564 | |
565 class ListLiteralObserver extends ExpressionObserver<ListLiteral> | |
566 implements ListLiteral { | |
567 | |
568 final List<ExpressionObserver> items; | |
569 | |
570 ListLiteralObserver(ListLiteral value, this.items) : super(value); | |
571 | |
572 _updateSelf(Scope scope) { | |
573 _value = items.map((i) => i._value).toList(); | |
574 } | |
575 | |
576 accept(Visitor v) => v.visitListLiteral(this); | |
577 } | |
578 | |
579 class MapLiteralObserver extends ExpressionObserver<MapLiteral> | |
580 implements MapLiteral { | |
581 | |
582 final List<MapLiteralEntryObserver> entries; | |
583 | |
584 MapLiteralObserver(MapLiteral value, this.entries) : super(value); | |
585 | |
586 _updateSelf(Scope scope) { | |
587 _value = entries.fold(new Map(), | |
588 (m, e) => m..[e.key._value] = e.entryValue._value); | |
589 } | |
590 | |
591 accept(Visitor v) => v.visitMapLiteral(this); | |
592 } | |
593 | |
594 class MapLiteralEntryObserver extends ExpressionObserver<MapLiteralEntry> | |
595 implements MapLiteralEntry { | |
596 | |
597 final LiteralObserver key; | |
598 final ExpressionObserver entryValue; | |
599 | |
600 MapLiteralEntryObserver(MapLiteralEntry value, this.key, this.entryValue) | |
601 : super(value); | |
602 | |
603 accept(Visitor v) => v.visitMapLiteralEntry(this); | |
604 } | |
605 | |
606 class IdentifierObserver extends ExpressionObserver<Identifier> | |
607 implements Identifier { | |
608 | |
609 IdentifierObserver(Identifier value) : super(value); | |
610 | |
611 String get value => _expr.value; | |
612 | |
613 _updateSelf(Scope scope) { | |
614 _value = scope[value]; | |
615 if (!scope._isModelProperty(value)) return; | |
616 var model = scope.model; | |
617 if (model is! Observable) return; | |
618 var symbol = smoke.nameToSymbol(value); | |
619 _subscription = (model as Observable).changes.listen((changes) { | |
620 if (changes.any((c) => c is PropertyChangeRecord && c.name == symbol)) { | |
621 _invalidate(scope); | |
622 } | |
623 }); | |
624 } | |
625 | |
626 accept(Visitor v) => v.visitIdentifier(this); | |
627 } | |
628 | |
629 class ParenthesizedObserver extends ExpressionObserver<ParenthesizedExpression> | |
630 implements ParenthesizedExpression { | |
631 final ExpressionObserver child; | |
632 | |
633 ParenthesizedObserver(ParenthesizedExpression expr, this.child) : super(expr); | |
634 | |
635 | |
636 _updateSelf(Scope scope) { | |
637 _value = child._value; | |
638 } | |
639 | |
640 accept(Visitor v) => v.visitParenthesizedExpression(this); | |
641 } | |
642 | |
643 class UnaryObserver extends ExpressionObserver<UnaryOperator> | |
644 implements UnaryOperator { | |
645 final ExpressionObserver child; | |
646 | |
647 UnaryObserver(UnaryOperator expr, this.child) : super(expr); | |
648 | |
649 String get operator => _expr.operator; | |
650 | |
651 _updateSelf(Scope scope) { | |
652 var f = _UNARY_OPERATORS[_expr.operator]; | |
653 if (operator == '!') { | |
654 _value = f(_toBool(child._value)); | |
655 } else { | |
656 _value = (child._value == null) ? null : f(child._value); | |
657 } | |
658 } | |
659 | |
660 accept(Visitor v) => v.visitUnaryOperator(this); | |
661 } | |
662 | |
663 class BinaryObserver extends ExpressionObserver<BinaryOperator> | |
664 implements BinaryOperator { | |
665 | |
666 final ExpressionObserver left; | |
667 final ExpressionObserver right; | |
668 | |
669 BinaryObserver(BinaryOperator expr, this.left, this.right) | |
670 : super(expr); | |
671 | |
672 String get operator => _expr.operator; | |
673 | |
674 _updateSelf(Scope scope) { | |
675 var f = _BINARY_OPERATORS[operator]; | |
676 if (operator == '&&' || operator == '||') { | |
677 _value = f(_toBool(left._value), _toBool(right._value)); | |
678 } else if (operator == '==' || operator == '!=') { | |
679 _value = f(left._value, right._value); | |
680 } else if (left._value == null || right._value == null) { | |
681 _value = null; | |
682 } else { | |
683 if (operator == '|' && left._value is ObservableList) { | |
684 _subscription = (left._value as ObservableList).listChanges | |
685 .listen((_) => _invalidate(scope)); | |
686 } | |
687 _value = f(left._value, right._value); | |
688 } | |
689 } | |
690 | |
691 accept(Visitor v) => v.visitBinaryOperator(this); | |
692 | |
693 } | |
694 | |
695 class TernaryObserver extends ExpressionObserver<TernaryOperator> | |
696 implements TernaryOperator { | |
697 | |
698 final ExpressionObserver condition; | |
699 final ExpressionObserver trueExpr; | |
700 final ExpressionObserver falseExpr; | |
701 | |
702 TernaryObserver(TernaryOperator expr, this.condition, this.trueExpr, | |
703 this.falseExpr) : super(expr); | |
704 | |
705 _updateSelf(Scope scope) { | |
706 _value = _toBool(condition._value) ? trueExpr._value : falseExpr._value; | |
707 } | |
708 | |
709 accept(Visitor v) => v.visitTernaryOperator(this); | |
710 | |
711 } | |
712 | |
713 class GetterObserver extends ExpressionObserver<Getter> implements Getter { | |
714 final ExpressionObserver receiver; | |
715 | |
716 GetterObserver(Expression expr, this.receiver) : super(expr); | |
717 | |
718 String get name => _expr.name; | |
719 | |
720 _updateSelf(Scope scope) { | |
721 var receiverValue = receiver._value; | |
722 if (receiverValue == null) { | |
723 _value = null; | |
724 return; | |
725 } | |
726 var symbol = smoke.nameToSymbol(_expr.name); | |
727 _value = smoke.read(receiverValue, symbol); | |
728 | |
729 if (receiverValue is Observable) { | |
730 _subscription = (receiverValue as Observable).changes.listen((changes) { | |
731 if (changes.any((c) => c is PropertyChangeRecord && c.name == symbol)) { | |
732 _invalidate(scope); | |
733 } | |
734 }); | |
735 } | |
736 } | |
737 | |
738 accept(Visitor v) => v.visitGetter(this); | |
739 } | |
740 | |
741 class IndexObserver extends ExpressionObserver<Index> implements Index { | |
742 final ExpressionObserver receiver; | |
743 final ExpressionObserver argument; | |
744 | |
745 IndexObserver(Expression expr, this.receiver, this.argument) : super(expr); | |
746 | |
747 _updateSelf(Scope scope) { | |
748 var receiverValue = receiver._value; | |
749 if (receiverValue == null) { | |
750 _value = null; | |
751 return; | |
752 } | |
753 var key = argument._value; | |
754 _value = receiverValue[key]; | |
755 | |
756 if (receiverValue is ObservableList) { | |
757 _subscription = (receiverValue as ObservableList).listChanges | |
758 .listen((changes) { | |
759 if (changes.any((c) => c.indexChanged(key))) _invalidate(scope); | |
760 }); | |
761 } else if (receiverValue is Observable) { | |
762 _subscription = (receiverValue as Observable).changes.listen((changes) { | |
763 if (changes.any((c) => c is MapChangeRecord && c.key == key)) { | |
764 _invalidate(scope); | |
765 } | |
766 }); | |
767 } | |
768 } | |
769 | |
770 accept(Visitor v) => v.visitIndex(this); | |
771 } | |
772 | |
773 class InvokeObserver extends ExpressionObserver<Invoke> implements Invoke { | |
774 final ExpressionObserver receiver; | |
775 final List<ExpressionObserver> arguments; | |
776 | |
777 InvokeObserver(Expression expr, this.receiver, this.arguments) | |
778 : super(expr) { | |
779 assert(arguments != null); | |
780 } | |
781 | |
782 String get method => _expr.method; | |
783 | |
784 _updateSelf(Scope scope) { | |
785 var args = arguments.map((a) => a._value).toList(); | |
786 var receiverValue = receiver._value; | |
787 if (receiverValue == null) { | |
788 _value = null; | |
789 return; | |
790 } | |
791 if (_expr.method == null) { | |
792 // top-level function or model method | |
793 // TODO(justin): listen to model changes to see if the method has | |
794 // changed? listen to the scope to see if the top-level method has | |
795 // changed? | |
796 assert(receiverValue is Function); | |
797 _value = _convert(Function.apply(receiverValue, args)); | |
798 } else { | |
799 var symbol = smoke.nameToSymbol(_expr.method); | |
800 _value = smoke.invoke(receiverValue, symbol, args); | |
801 | |
802 if (receiverValue is Observable) { | |
803 _subscription = (receiverValue as Observable).changes.listen( | |
804 (List<ChangeRecord> changes) { | |
805 if (changes.any( | |
806 (c) => c is PropertyChangeRecord && c.name == symbol)) { | |
807 _invalidate(scope); | |
808 } | |
809 }); | |
810 } | |
811 } | |
812 } | |
813 | |
814 accept(Visitor v) => v.visitInvoke(this); | |
815 } | |
816 | |
817 _toBool(v) => (v == null) ? false : v; | |
818 | |
819 class EvalException implements Exception { | |
820 final String message; | |
821 EvalException(this.message); | |
822 String toString() => "EvalException: $message"; | |
823 } | |
OLD | NEW |