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 |