| Index: dart/pkg/polymer_expressions/lib/eval.dart | 
| =================================================================== | 
| --- dart/pkg/polymer_expressions/lib/eval.dart	(revision 37358) | 
| +++ dart/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); | 
|  |