Index: polymer_expressions/lib/eval.dart |
diff --git a/polymer_expressions/lib/eval.dart b/polymer_expressions/lib/eval.dart |
deleted file mode 100644 |
index 8f7988b662808aac3ea97ceeb8aeeab37d122729..0000000000000000000000000000000000000000 |
--- a/polymer_expressions/lib/eval.dart |
+++ /dev/null |
@@ -1,823 +0,0 @@ |
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-library polymer_expressions.eval; |
- |
-import 'dart:async'; |
-import 'dart:collection'; |
- |
-import 'package:observe/observe.dart'; |
-import 'package:smoke/smoke.dart' as smoke; |
- |
-import 'async.dart'; |
-import 'expression.dart'; |
-import 'filter.dart'; |
-import 'visitor.dart'; |
- |
-final _BINARY_OPERATORS = { |
- '+': (a, b) => a + b, |
- '-': (a, b) => a - b, |
- '*': (a, b) => a * b, |
- '/': (a, b) => a / b, |
- '%': (a, b) => a % b, |
- '==': (a, b) => a == b, |
- '!=': (a, b) => a != b, |
- '===': (a, b) => identical(a, b), |
- '!==': (a, b) => !identical(a, b), |
- '>': (a, b) => a > b, |
- '>=': (a, b) => a >= b, |
- '<': (a, b) => a < b, |
- '<=': (a, b) => a <= b, |
- '||': (a, b) => a || b, |
- '&&': (a, b) => a && b, |
- '|': (a, f) { |
- if (f is Transformer) return f.forward(a); |
- if (f is Filter) return f(a); |
- throw new EvalException("Filters must be a one-argument function."); |
- } |
-}; |
- |
-final _UNARY_OPERATORS = { |
- '+': (a) => a, |
- '-': (a) => -a, |
- '!': (a) => !a, |
-}; |
- |
-final _BOOLEAN_OPERATORS = ['!', '||', '&&']; |
- |
-/** |
- * Evaluation [expr] in the context of [scope]. |
- */ |
-Object eval(Expression expr, Scope scope) => new EvalVisitor(scope).visit(expr); |
- |
-/** |
- * Returns an [ExpressionObserver] that evaluates [expr] in the context of |
- * scope] and listens for any changes on [Observable] values that are |
- * returned from sub-expressions. When a value changes the expression is |
- * reevaluated and the new result is sent to the [onUpdate] stream of the |
- * [ExpressionObsserver]. |
- */ |
-ExpressionObserver observe(Expression expr, Scope scope) { |
- var observer = new ObserverBuilder().visit(expr); |
- return observer; |
-} |
- |
-/** |
- * Causes [expr] to be reevaluated a returns it's value. |
- */ |
-Object update(ExpressionObserver expr, Scope scope, {skipChanges: false}) { |
- new Updater(scope, skipChanges).visit(expr); |
- return expr.currentValue; |
-} |
- |
-/** |
- * Assign [value] to the variable or field referenced by [expr] in the context |
- * of [scope]. |
- * |
- * [expr] must be an /assignable/ expression, it must not contain |
- * operators or function invocations, and any index operations must use a |
- * literal index. |
- */ |
-Object assign(Expression expr, Object value, Scope scope, |
- {bool checkAssignability: true}) { |
- |
- Expression expression; |
- var property; |
- bool isIndex = false; |
- var filters = <Expression>[]; // reversed order for assignment |
- |
- while (expr is BinaryOperator) { |
- BinaryOperator op = expr; |
- if (op.operator != '|') { |
- break; |
- } |
- filters.add(op.right); |
- expr = op.left; |
- } |
- |
- if (expr is Identifier) { |
- expression = empty(); |
- property = expr.value; |
- } else if (expr is Index) { |
- expression = expr.receiver; |
- property = expr.argument; |
- isIndex = true; |
- } else if (expr is Getter) { |
- expression = expr.receiver; |
- property = expr.name; |
- } else { |
- if (checkAssignability) { |
- throw new EvalException("Expression is not assignable: $expr"); |
- } |
- return null; |
- } |
- |
- // transform the values backwards through the filters |
- for (var filterExpr in filters) { |
- var filter = eval(filterExpr, scope); |
- if (filter is! Transformer) { |
- if (checkAssignability) { |
- throw new EvalException("filter must implement Transformer to be " |
- "assignable: $filterExpr"); |
- } else { |
- return null; |
- } |
- } |
- value = filter.reverse(value); |
- } |
- // evaluate the receiver |
- var o = eval(expression, scope); |
- |
- // can't assign to a property on a null LHS object. Silently fail. |
- if (o == null) return null; |
- |
- if (isIndex) { |
- var index = eval(property, scope); |
- o[index] = value; |
- } else { |
- smoke.write(o, smoke.nameToSymbol(property), value); |
- } |
- return value; |
-} |
- |
- |
-/** |
- * A scope in polymer expressions that can map names to objects. Scopes contain |
- * a set of named variables and a unique model object. The scope structure |
- * is then used to lookup names using the `[]` operator. The lookup first |
- * searches for the name in local variables, then in global variables, |
- * and then finally looks up the name as a property in the model. |
- */ |
-abstract class Scope implements Indexable<String, Object> { |
- Scope._(); |
- |
- /** Create a scope containing a [model] and all of [variables]. */ |
- factory Scope({Object model, Map<String, Object> variables}) { |
- var scope = new _ModelScope(model); |
- return variables == null ? scope |
- : new _GlobalsScope(new Map<String, Object>.from(variables), scope); |
- } |
- |
- /** Return the unique model in this scope. */ |
- Object get model; |
- |
- /** |
- * Lookup the value of [name] in the current scope. If [name] is 'this', then |
- * we return the [model]. For any other name, this finds the first variable |
- * matching [name] or, if none exists, the property [name] in the [model]. |
- */ |
- Object operator [](String name); |
- |
- operator []=(String name, Object value) { |
- throw new UnsupportedError('[]= is not supported in Scope.'); |
- } |
- |
- /** |
- * Returns whether [name] is defined in [model], that is, a lookup |
- * would not find a variable with that name, but there is a non-null model |
- * where we can look it up as a property. |
- */ |
- bool _isModelProperty(String name); |
- |
- /** Create a new scope extending this scope with an additional variable. */ |
- Scope childScope(String name, Object value) => |
- new _LocalVariableScope(name, value, this); |
-} |
- |
-/** |
- * A scope that looks up names in a model object. This kind of scope has no |
- * parent scope because all our lookup operations stop when we reach the model |
- * object. Any variables added in scope or global variables are added as child |
- * scopes. |
- */ |
-class _ModelScope extends Scope { |
- final Object model; |
- |
- _ModelScope(this.model) : super._(); |
- |
- Object operator[](String name) { |
- if (name == 'this') return model; |
- var symbol = smoke.nameToSymbol(name); |
- if (model == null || symbol == null) { |
- throw new EvalException("variable '$name' not found"); |
- } |
- return _convert(smoke.read(model, symbol)); |
- } |
- |
- Object _isModelProperty(String name) => name != 'this'; |
- |
- String toString() => "[model: $model]"; |
-} |
- |
-/** |
- * A scope that holds a reference to a single variable. Polymer expressions |
- * introduce variables to the scope one at a time. Each time a variable is |
- * added, a new [_LocalVariableScope] is created. |
- */ |
-class _LocalVariableScope extends Scope { |
- final Scope parent; |
- final String varName; |
- // TODO(sigmund,justinfagnani): make this @observable? |
- final Object value; |
- |
- _LocalVariableScope(this.varName, this.value, this.parent) : super._() { |
- if (varName == 'this') { |
- throw new EvalException("'this' cannot be used as a variable name."); |
- } |
- } |
- |
- Object get model => parent != null ? parent.model : null; |
- |
- Object operator[](String name) { |
- if (varName == name) return _convert(value); |
- if (parent != null) return parent[name]; |
- throw new EvalException("variable '$name' not found"); |
- } |
- |
- bool _isModelProperty(String name) { |
- if (varName == name) return false; |
- return parent == null ? false : parent._isModelProperty(name); |
- } |
- |
- String toString() => "$parent > [local: $varName]"; |
-} |
- |
-/** A scope that holds a reference to a global variables. */ |
-class _GlobalsScope extends Scope { |
- final _ModelScope parent; |
- final Map<String, Object> variables; |
- |
- _GlobalsScope(this.variables, this.parent) : super._() { |
- if (variables.containsKey('this')) { |
- throw new EvalException("'this' cannot be used as a variable name."); |
- } |
- } |
- |
- Object get model => parent != null ? parent.model : null; |
- |
- Object operator[](String name) { |
- if (variables.containsKey(name)) return _convert(variables[name]); |
- if (parent != null) return parent[name]; |
- throw new EvalException("variable '$name' not found"); |
- } |
- |
- bool _isModelProperty(String name) { |
- if (variables.containsKey(name)) return false; |
- return parent == null ? false : parent._isModelProperty(name); |
- } |
- |
- String toString() => "$parent > [global: ${variables.keys}]"; |
-} |
- |
-Object _convert(v) => v is Stream ? new StreamBinding(v) : v; |
- |
-abstract class ExpressionObserver<E extends Expression> implements Expression { |
- final E _expr; |
- ExpressionObserver _parent; |
- |
- StreamSubscription _subscription; |
- Object _value; |
- |
- StreamController _controller = new StreamController.broadcast(); |
- Stream get onUpdate => _controller.stream; |
- |
- ExpressionObserver(this._expr); |
- |
- Expression get expression => _expr; |
- |
- Object get currentValue => _value; |
- |
- update(Scope scope) => _updateSelf(scope); |
- |
- _updateSelf(Scope scope) {} |
- |
- _invalidate(Scope scope) { |
- _observe(scope, false); |
- if (_parent != null) { |
- _parent._invalidate(scope); |
- } |
- } |
- |
- _unobserve() { |
- if (_subscription != null) { |
- _subscription.cancel(); |
- _subscription = null; |
- } |
- } |
- |
- _observe(Scope scope, skipChanges) { |
- _unobserve(); |
- |
- var _oldValue = _value; |
- |
- // evaluate |
- _updateSelf(scope); |
- |
- if (!skipChanges && !identical(_value, _oldValue)) { |
- _controller.add(_value); |
- } |
- } |
- |
- String toString() => _expr.toString(); |
-} |
- |
-class Updater extends RecursiveVisitor { |
- final Scope scope; |
- final bool skipChanges; |
- |
- Updater(this.scope, [this.skipChanges = false]); |
- |
- visitExpression(ExpressionObserver e) { |
- e._observe(scope, skipChanges); |
- } |
-} |
- |
-class Closer extends RecursiveVisitor { |
- static final _instance = new Closer._(); |
- factory Closer() => _instance; |
- Closer._(); |
- |
- visitExpression(ExpressionObserver e) { |
- e._unobserve(); |
- } |
-} |
- |
-class EvalVisitor extends Visitor { |
- final Scope scope; |
- |
- EvalVisitor(this.scope); |
- |
- visitEmptyExpression(EmptyExpression e) => scope.model; |
- |
- visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); |
- |
- visitGetter(Getter g) { |
- var receiver = visit(g.receiver); |
- if (receiver == null) return null; |
- var symbol = smoke.nameToSymbol(g.name); |
- return smoke.read(receiver, symbol); |
- } |
- |
- visitIndex(Index i) { |
- var receiver = visit(i.receiver); |
- if (receiver == null) return null; |
- var key = visit(i.argument); |
- return receiver[key]; |
- } |
- |
- visitInvoke(Invoke i) { |
- var receiver = visit(i.receiver); |
- if (receiver == null) return null; |
- var args = (i.arguments == null) |
- ? null |
- : i.arguments.map(visit).toList(growable: false); |
- |
- if (i.method == null) { |
- assert(receiver is Function); |
- return Function.apply(receiver, args); |
- } |
- |
- var symbol = smoke.nameToSymbol(i.method); |
- return smoke.invoke(receiver, symbol, args); |
- } |
- |
- visitLiteral(Literal l) => l.value; |
- |
- visitListLiteral(ListLiteral l) => l.items.map(visit).toList(); |
- |
- visitMapLiteral(MapLiteral l) { |
- var map = {}; |
- for (var entry in l.entries) { |
- var key = visit(entry.key); |
- var value = visit(entry.entryValue); |
- map[key] = value; |
- } |
- return map; |
- } |
- |
- visitMapLiteralEntry(MapLiteralEntry e) => |
- throw new UnsupportedError("should never be called"); |
- |
- visitIdentifier(Identifier i) => scope[i.value]; |
- |
- visitBinaryOperator(BinaryOperator o) { |
- var operator = o.operator; |
- var left = visit(o.left); |
- var right = visit(o.right); |
- |
- var f = _BINARY_OPERATORS[operator]; |
- if (operator == '&&' || operator == '||') { |
- // TODO: short-circuit |
- return f(_toBool(left), _toBool(right)); |
- } else if (operator == '==' || operator == '!=') { |
- return f(left, right); |
- } else if (left == null || right == null) { |
- return null; |
- } |
- return f(left, right); |
- } |
- |
- visitUnaryOperator(UnaryOperator o) { |
- var expr = visit(o.child); |
- var f = _UNARY_OPERATORS[o.operator]; |
- if (o.operator == '!') { |
- return f(_toBool(expr)); |
- } |
- return (expr == null) ? null : f(expr); |
- } |
- |
- visitTernaryOperator(TernaryOperator o) => |
- visit(o.condition) == true ? visit(o.trueExpr) : visit(o.falseExpr); |
- |
- visitInExpression(InExpression i) => |
- throw new UnsupportedError("can't eval an 'in' expression"); |
- |
- visitAsExpression(AsExpression i) => |
- throw new UnsupportedError("can't eval an 'as' expression"); |
-} |
- |
-class ObserverBuilder extends Visitor { |
- final Queue parents = new Queue(); |
- |
- ObserverBuilder(); |
- |
- visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e); |
- |
- visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child); |
- |
- visitGetter(Getter g) { |
- var receiver = visit(g.receiver); |
- var getter = new GetterObserver(g, receiver); |
- receiver._parent = getter; |
- return getter; |
- } |
- |
- visitIndex(Index i) { |
- var receiver = visit(i.receiver); |
- var arg = visit(i.argument); |
- var index = new IndexObserver(i, receiver, arg); |
- receiver._parent = index; |
- arg._parent = index; |
- return index; |
- } |
- |
- visitInvoke(Invoke i) { |
- var receiver = visit(i.receiver); |
- var args = (i.arguments == null) |
- ? null |
- : i.arguments.map(visit).toList(growable: false); |
- var invoke = new InvokeObserver(i, receiver, args); |
- receiver._parent = invoke; |
- if (args != null) args.forEach((a) => a._parent = invoke); |
- return invoke; |
- } |
- |
- visitLiteral(Literal l) => new LiteralObserver(l); |
- |
- visitListLiteral(ListLiteral l) { |
- var items = l.items.map(visit).toList(growable: false); |
- var list = new ListLiteralObserver(l, items); |
- items.forEach((e) => e._parent = list); |
- return list; |
- } |
- |
- visitMapLiteral(MapLiteral l) { |
- var entries = l.entries.map(visit).toList(growable: false); |
- var map = new MapLiteralObserver(l, entries); |
- entries.forEach((e) => e._parent = map); |
- return map; |
- } |
- |
- visitMapLiteralEntry(MapLiteralEntry e) { |
- var key = visit(e.key); |
- var value = visit(e.entryValue); |
- var entry = new MapLiteralEntryObserver(e, key, value); |
- key._parent = entry; |
- value._parent = entry; |
- return entry; |
- } |
- |
- visitIdentifier(Identifier i) => new IdentifierObserver(i); |
- |
- visitBinaryOperator(BinaryOperator o) { |
- var left = visit(o.left); |
- var right = visit(o.right); |
- var binary = new BinaryObserver(o, left, right); |
- left._parent = binary; |
- right._parent = binary; |
- return binary; |
- } |
- |
- visitUnaryOperator(UnaryOperator o) { |
- var expr = visit(o.child); |
- var unary = new UnaryObserver(o, expr); |
- expr._parent = unary; |
- return unary; |
- } |
- |
- visitTernaryOperator(TernaryOperator o) { |
- var condition = visit(o.condition); |
- var trueExpr = visit(o.trueExpr); |
- var falseExpr = visit(o.falseExpr); |
- var ternary = new TernaryObserver(o, condition, trueExpr, falseExpr); |
- condition._parent = ternary; |
- trueExpr._parent = ternary; |
- falseExpr._parent = ternary; |
- return ternary; |
- } |
- |
- visitInExpression(InExpression i) { |
- throw new UnsupportedError("can't eval an 'in' expression"); |
- } |
- |
- visitAsExpression(AsExpression i) { |
- throw new UnsupportedError("can't eval an 'as' expression"); |
- } |
-} |
- |
-class EmptyObserver extends ExpressionObserver<EmptyExpression> |
- implements EmptyExpression { |
- |
- EmptyObserver(EmptyExpression value) : super(value); |
- |
- _updateSelf(Scope scope) { |
- _value = scope.model; |
- // TODO(justin): listen for scope.model changes? |
- } |
- |
- accept(Visitor v) => v.visitEmptyExpression(this); |
-} |
- |
-class LiteralObserver extends ExpressionObserver<Literal> implements Literal { |
- |
- LiteralObserver(Literal value) : super(value); |
- |
- dynamic get value => _expr.value; |
- |
- _updateSelf(Scope scope) { |
- _value = _expr.value; |
- } |
- |
- accept(Visitor v) => v.visitLiteral(this); |
-} |
- |
-class ListLiteralObserver extends ExpressionObserver<ListLiteral> |
- implements ListLiteral { |
- |
- final List<ExpressionObserver> items; |
- |
- ListLiteralObserver(ListLiteral value, this.items) : super(value); |
- |
- _updateSelf(Scope scope) { |
- _value = items.map((i) => i._value).toList(); |
- } |
- |
- accept(Visitor v) => v.visitListLiteral(this); |
-} |
- |
-class MapLiteralObserver extends ExpressionObserver<MapLiteral> |
- implements MapLiteral { |
- |
- final List<MapLiteralEntryObserver> entries; |
- |
- MapLiteralObserver(MapLiteral value, this.entries) : super(value); |
- |
- _updateSelf(Scope scope) { |
- _value = entries.fold(new Map(), |
- (m, e) => m..[e.key._value] = e.entryValue._value); |
- } |
- |
- accept(Visitor v) => v.visitMapLiteral(this); |
-} |
- |
-class MapLiteralEntryObserver extends ExpressionObserver<MapLiteralEntry> |
- implements MapLiteralEntry { |
- |
- final LiteralObserver key; |
- final ExpressionObserver entryValue; |
- |
- MapLiteralEntryObserver(MapLiteralEntry value, this.key, this.entryValue) |
- : super(value); |
- |
- accept(Visitor v) => v.visitMapLiteralEntry(this); |
-} |
- |
-class IdentifierObserver extends ExpressionObserver<Identifier> |
- implements Identifier { |
- |
- IdentifierObserver(Identifier value) : super(value); |
- |
- String get value => _expr.value; |
- |
- _updateSelf(Scope scope) { |
- _value = scope[value]; |
- if (!scope._isModelProperty(value)) return; |
- var model = scope.model; |
- if (model is! Observable) return; |
- var symbol = smoke.nameToSymbol(value); |
- _subscription = (model as Observable).changes.listen((changes) { |
- if (changes.any((c) => c is PropertyChangeRecord && c.name == symbol)) { |
- _invalidate(scope); |
- } |
- }); |
- } |
- |
- accept(Visitor v) => v.visitIdentifier(this); |
-} |
- |
-class ParenthesizedObserver extends ExpressionObserver<ParenthesizedExpression> |
- implements ParenthesizedExpression { |
- final ExpressionObserver child; |
- |
- ParenthesizedObserver(ParenthesizedExpression expr, this.child) : super(expr); |
- |
- |
- _updateSelf(Scope scope) { |
- _value = child._value; |
- } |
- |
- accept(Visitor v) => v.visitParenthesizedExpression(this); |
-} |
- |
-class UnaryObserver extends ExpressionObserver<UnaryOperator> |
- implements UnaryOperator { |
- final ExpressionObserver child; |
- |
- UnaryObserver(UnaryOperator expr, this.child) : super(expr); |
- |
- String get operator => _expr.operator; |
- |
- _updateSelf(Scope scope) { |
- var f = _UNARY_OPERATORS[_expr.operator]; |
- if (operator == '!') { |
- _value = f(_toBool(child._value)); |
- } else { |
- _value = (child._value == null) ? null : f(child._value); |
- } |
- } |
- |
- accept(Visitor v) => v.visitUnaryOperator(this); |
-} |
- |
-class BinaryObserver extends ExpressionObserver<BinaryOperator> |
- implements BinaryOperator { |
- |
- final ExpressionObserver left; |
- final ExpressionObserver right; |
- |
- BinaryObserver(BinaryOperator expr, this.left, this.right) |
- : super(expr); |
- |
- String get operator => _expr.operator; |
- |
- _updateSelf(Scope scope) { |
- var f = _BINARY_OPERATORS[operator]; |
- if (operator == '&&' || operator == '||') { |
- _value = f(_toBool(left._value), _toBool(right._value)); |
- } else if (operator == '==' || operator == '!=') { |
- _value = f(left._value, right._value); |
- } else if (left._value == null || right._value == null) { |
- _value = null; |
- } else { |
- if (operator == '|' && left._value is ObservableList) { |
- _subscription = (left._value as ObservableList).listChanges |
- .listen((_) => _invalidate(scope)); |
- } |
- _value = f(left._value, right._value); |
- } |
- } |
- |
- accept(Visitor v) => v.visitBinaryOperator(this); |
- |
-} |
- |
-class TernaryObserver extends ExpressionObserver<TernaryOperator> |
- implements TernaryOperator { |
- |
- final ExpressionObserver condition; |
- final ExpressionObserver trueExpr; |
- final ExpressionObserver falseExpr; |
- |
- TernaryObserver(TernaryOperator expr, this.condition, this.trueExpr, |
- this.falseExpr) : super(expr); |
- |
- _updateSelf(Scope scope) { |
- _value = _toBool(condition._value) ? trueExpr._value : falseExpr._value; |
- } |
- |
- accept(Visitor v) => v.visitTernaryOperator(this); |
- |
-} |
- |
-class GetterObserver extends ExpressionObserver<Getter> implements Getter { |
- final ExpressionObserver receiver; |
- |
- GetterObserver(Expression expr, this.receiver) : super(expr); |
- |
- String get name => _expr.name; |
- |
- _updateSelf(Scope scope) { |
- var receiverValue = receiver._value; |
- if (receiverValue == null) { |
- _value = null; |
- return; |
- } |
- var symbol = smoke.nameToSymbol(_expr.name); |
- _value = smoke.read(receiverValue, symbol); |
- |
- if (receiverValue is Observable) { |
- _subscription = (receiverValue as Observable).changes.listen((changes) { |
- if (changes.any((c) => c is PropertyChangeRecord && c.name == symbol)) { |
- _invalidate(scope); |
- } |
- }); |
- } |
- } |
- |
- accept(Visitor v) => v.visitGetter(this); |
-} |
- |
-class IndexObserver extends ExpressionObserver<Index> implements Index { |
- final ExpressionObserver receiver; |
- final ExpressionObserver argument; |
- |
- IndexObserver(Expression expr, this.receiver, this.argument) : super(expr); |
- |
- _updateSelf(Scope scope) { |
- var receiverValue = receiver._value; |
- if (receiverValue == null) { |
- _value = null; |
- return; |
- } |
- var key = argument._value; |
- _value = receiverValue[key]; |
- |
- if (receiverValue is ObservableList) { |
- _subscription = (receiverValue as ObservableList).listChanges |
- .listen((changes) { |
- if (changes.any((c) => c.indexChanged(key))) _invalidate(scope); |
- }); |
- } else if (receiverValue is Observable) { |
- _subscription = (receiverValue as Observable).changes.listen((changes) { |
- if (changes.any((c) => c is MapChangeRecord && c.key == key)) { |
- _invalidate(scope); |
- } |
- }); |
- } |
- } |
- |
- accept(Visitor v) => v.visitIndex(this); |
-} |
- |
-class InvokeObserver extends ExpressionObserver<Invoke> implements Invoke { |
- final ExpressionObserver receiver; |
- final List<ExpressionObserver> arguments; |
- |
- InvokeObserver(Expression expr, this.receiver, this.arguments) |
- : super(expr) { |
- assert(arguments != null); |
- } |
- |
- String get method => _expr.method; |
- |
- _updateSelf(Scope scope) { |
- var args = arguments.map((a) => a._value).toList(); |
- var receiverValue = receiver._value; |
- if (receiverValue == null) { |
- _value = null; |
- return; |
- } |
- if (_expr.method == null) { |
- // top-level function or model method |
- // TODO(justin): listen to model changes to see if the method has |
- // changed? listen to the scope to see if the top-level method has |
- // changed? |
- assert(receiverValue is Function); |
- _value = _convert(Function.apply(receiverValue, args)); |
- } else { |
- var symbol = smoke.nameToSymbol(_expr.method); |
- _value = smoke.invoke(receiverValue, symbol, args); |
- |
- if (receiverValue is Observable) { |
- _subscription = (receiverValue as Observable).changes.listen( |
- (List<ChangeRecord> changes) { |
- if (changes.any( |
- (c) => c is PropertyChangeRecord && c.name == symbol)) { |
- _invalidate(scope); |
- } |
- }); |
- } |
- } |
- } |
- |
- accept(Visitor v) => v.visitInvoke(this); |
-} |
- |
-_toBool(v) => (v == null) ? false : v; |
- |
-class EvalException implements Exception { |
- final String message; |
- EvalException(this.message); |
- String toString() => "EvalException: $message"; |
-} |