| Index: pkg/polymer_expressions/lib/eval.dart
 | 
| ===================================================================
 | 
| --- pkg/polymer_expressions/lib/eval.dart	(revision 37373)
 | 
| +++ pkg/polymer_expressions/lib/eval.dart	(working copy)
 | 
| @@ -16,20 +16,22 @@
 | 
|  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) => a > b,
 | 
| -  '>=': (a, b) => a >= b,
 | 
| -  '<':  (a, b) => a < b,
 | 
| -  '<=': (a, b) => a <= b,
 | 
| -  '||': (a, b) => a || b,
 | 
| -  '&&': (a, b) => a && b,
 | 
| -  '|':  (a, f) {
 | 
| +  '+':   (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.");
 | 
| @@ -47,11 +49,7 @@
 | 
|  /**
 | 
|   * Evaluation [expr] in the context of [scope].
 | 
|   */
 | 
| -Object eval(Expression expr, Scope scope) {
 | 
| -  var observer = observe(expr, scope);
 | 
| -  new Updater(scope).visit(observer);
 | 
| -  return observer._value;
 | 
| -}
 | 
| +Object eval(Expression expr, Scope scope) => new EvalVisitor(scope).visit(expr);
 | 
|  
 | 
|  /**
 | 
|   * Returns an [ExpressionObserver] that evaluates [expr] in the context of
 | 
| @@ -152,9 +150,6 @@
 | 
|   * and then finally looks up the name as a property in the model.
 | 
|   */
 | 
|  abstract class Scope implements Indexable<String, Object> {
 | 
| -  static int __seq = 1;
 | 
| -  final int _seq = __seq++;
 | 
| -
 | 
|    Scope._();
 | 
|  
 | 
|    /** Create a scope containing a [model] and all of [variables]. */
 | 
| @@ -188,9 +183,6 @@
 | 
|    /** Create a new scope extending this scope with an additional variable. */
 | 
|    Scope childScope(String name, Object value) =>
 | 
|        new _LocalVariableScope(name, value, this);
 | 
| -
 | 
| -  String toString() => 'Scope(seq: $_seq model: $model)';
 | 
| -
 | 
|  }
 | 
|  
 | 
|  /**
 | 
| @@ -307,13 +299,16 @@
 | 
|      }
 | 
|    }
 | 
|  
 | 
| -  _observe(Scope scope) {
 | 
| -    // unobserve last value
 | 
| +  _unobserve() {
 | 
|      if (_subscription != null) {
 | 
|        _subscription.cancel();
 | 
|        _subscription = null;
 | 
|      }
 | 
| +  }
 | 
|  
 | 
| +  _observe(Scope scope) {
 | 
| +    _unobserve();
 | 
| +
 | 
|      var _oldValue = _value;
 | 
|  
 | 
|      // evaluate
 | 
| @@ -337,6 +332,110 @@
 | 
|    }
 | 
|  }
 | 
|  
 | 
| +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();
 | 
|  
 | 
| @@ -470,7 +569,7 @@
 | 
|    ListLiteralObserver(ListLiteral value, this.items) : super(value);
 | 
|  
 | 
|    _updateSelf(Scope scope) {
 | 
| -    _value = items.map((i) => i._value).toList(growable: false);
 | 
| +    _value = items.map((i) => i._value).toList();
 | 
|    }
 | 
|  
 | 
|    accept(Visitor v) => v.visitListLiteral(this);
 | 
| 
 |