Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Unified Diff: observatory_pub_packages/polymer_expressions/eval.dart

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: observatory_pub_packages/polymer_expressions/eval.dart
===================================================================
--- observatory_pub_packages/polymer_expressions/eval.dart (revision 0)
+++ observatory_pub_packages/polymer_expressions/eval.dart (working copy)
@@ -0,0 +1,823 @@
+// 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";
+}
« no previous file with comments | « observatory_pub_packages/polymer_expressions/async.dart ('k') | observatory_pub_packages/polymer_expressions/expression.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698