| 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 |