| Index: pkg/polymer_expression/lib/eval.dart
|
| diff --git a/pkg/polymer_expression/lib/eval.dart b/pkg/polymer_expression/lib/eval.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cabdd263a45fe7c8e03af33ad5303a4d97572485
|
| --- /dev/null
|
| +++ b/pkg/polymer_expression/lib/eval.dart
|
| @@ -0,0 +1,613 @@
|
| +// 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_expression.eval;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:collection';
|
| +import 'dart:mirrors';
|
| +
|
| +import 'package:observe/observe.dart';
|
| +
|
| +import 'async.dart';
|
| +import 'expression.dart';
|
| +import 'filter.dart';
|
| +import 'visitor.dart';
|
| +import 'src/mirrors.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, 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) => observe(expr, scope)._value;
|
| +
|
| +
|
| +ExpressionObserver observe(Expression expr, Scope scope) {
|
| + var observer = new ObserverBuilder(scope).visit(expr);
|
| + new Updater(scope).visit(observer);
|
| + return observer;
|
| +}
|
| +
|
| +/**
|
| + * 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.
|
| + */
|
| +void assign(Expression expr, Object value, Scope scope) {
|
| +
|
| + notAssignable() =>
|
| + throw new EvalException("Expression is not assignable: $expr");
|
| +
|
| + Expression expression;
|
| + var property;
|
| + bool isIndex = false;
|
| + var filters = <Expression>[]; // reversed order for assignment
|
| +
|
| + while (expr is BinaryOperator && expr.operator == '|') {
|
| + filters.add(expr.right);
|
| + expr = expr.left;
|
| + }
|
| +
|
| + if (expr is Identifier) {
|
| + expression = empty();
|
| + property = expr.value;
|
| + } else if (expr is Invoke) {
|
| + expression = expr.receiver;
|
| + if (expr.method == '[]') {
|
| + if (expr.arguments[0] is! Literal) notAssignable();
|
| + Literal l = expr.arguments[0];
|
| + property = l.value;
|
| + isIndex = true;
|
| + } else if (expr.method != null) {
|
| + if (expr.arguments != null) notAssignable();
|
| + property = expr.method;
|
| + } else {
|
| + notAssignable();
|
| + }
|
| + } else {
|
| + notAssignable();
|
| + }
|
| +
|
| + // transform the values backwards through the filters
|
| + for (var filterExpr in filters) {
|
| + var filter = eval(filterExpr, scope);
|
| + if (filter is! Transformer) {
|
| + throw new EvalException("filter must implement Transformer: $filterExpr");
|
| + }
|
| + value = filter.reverse(value);
|
| + }
|
| + // make the assignment
|
| + var o = eval(expression, scope);
|
| + if (o == null) throw new EvalException("Can't assign to null: $expression");
|
| + if (isIndex) {
|
| + o[property] = value;
|
| + } else {
|
| + reflect(o).setField(new Symbol(property), value);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A mapping of names to objects. Scopes contain a set of named [variables] and
|
| + * a single [model] object (which can be thought of as the "this" reference).
|
| + * Names are currently looked up in [variables] first, then the [model].
|
| + *
|
| + * Scopes can be nested by giving them a [parent]. If a name in not found in a
|
| + * Scope, it will look for it in it's parent.
|
| + */
|
| +class Scope extends Object {
|
| + final Scope parent;
|
| + final Object model;
|
| + // TODO(justinfagnani): disallow adding/removing names
|
| + final ObservableMap<String, Object> _variables;
|
| + InstanceMirror __modelMirror;
|
| +
|
| + Scope({this.model, Map<String, Object> variables: const {}, this.parent})
|
| + : _variables = new ObservableMap.from(variables);
|
| +
|
| + InstanceMirror get _modelMirror {
|
| + if (__modelMirror != null) return __modelMirror;
|
| + __modelMirror = reflect(model);
|
| + return __modelMirror;
|
| + }
|
| +
|
| + Object operator[](String name) {
|
| + if (name == 'this') {
|
| + return model;
|
| + } else if (_variables.containsKey(name)) {
|
| + return _convert(_variables[name]);
|
| + } else if (model != null) {
|
| + var symbol = new Symbol(name);
|
| + var classMirror = _modelMirror.type;
|
| + var memberMirror = getMemberMirror(classMirror, symbol);
|
| + if (memberMirror is VariableMirror ||
|
| + (memberMirror is MethodMirror && memberMirror.isGetter)) {
|
| + return _convert(_modelMirror.getField(symbol).reflectee);
|
| + } else if (memberMirror is MethodMirror) {
|
| + return new Method(_modelMirror, symbol);
|
| + }
|
| + }
|
| + if (parent != null) {
|
| + return _convert(parent[name]);
|
| + } else {
|
| + throw new EvalException("variable not found: $name in $hashCode");
|
| + }
|
| + }
|
| +
|
| + Object ownerOf(String name) {
|
| + if (name == 'this') {
|
| + // we could return the Scope if it were Observable, but since assigning
|
| + // a model to a template destroys and recreates the instance, it doesn't
|
| + // seem neccessary
|
| + return null;
|
| + } else if (_variables.containsKey(name)) {
|
| + return _variables;
|
| + } else {
|
| + var symbol = new Symbol(name);
|
| + var classMirror = _modelMirror.type;
|
| + if (getMemberMirror(classMirror, symbol) != null) {
|
| + return model;
|
| + }
|
| + }
|
| + if (parent != null) {
|
| + return parent.ownerOf(name);
|
| + }
|
| + }
|
| +
|
| + bool contains(String name) {
|
| + if (_variables.containsKey(name)) {
|
| + return true;
|
| + } else {
|
| + var symbol = new Symbol(name);
|
| + var classMirror = _modelMirror.type;
|
| + if (getMemberMirror(classMirror, symbol) != null) {
|
| + return true;
|
| + }
|
| + }
|
| + if (parent != null) {
|
| + return parent.contains(name);
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + String toString() => 'Scope($hashCode $parent)';
|
| +}
|
| +
|
| +Object _convert(v) {
|
| + if (v is Stream) return new StreamBinding(v);
|
| + return 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);
|
| +
|
| + Object get currentValue => _value;
|
| +
|
| + update(Scope scope) => _updateSelf(scope);
|
| +
|
| + _updateSelf(Scope scope) {}
|
| +
|
| + _invalidate(Scope scope) {
|
| + _observe(scope);
|
| + if (_parent != null) {
|
| + _parent._invalidate(scope);
|
| + }
|
| + }
|
| +
|
| + _observe(Scope scope) {
|
| + // unobserve last value
|
| + if (_subscription != null) {
|
| + _subscription.cancel();
|
| + _subscription = null;
|
| + }
|
| +
|
| + var _oldValue = _value;
|
| +
|
| + // evaluate
|
| + _updateSelf(scope);
|
| +
|
| + if (!identical(_value, _oldValue)) {
|
| + _controller.add(_value);
|
| + }
|
| + }
|
| +
|
| + String toString() => _expr.toString();
|
| +}
|
| +
|
| +class Updater extends RecursiveVisitor<ExpressionObserver> {
|
| + final Scope scope;
|
| +
|
| + Updater(this.scope);
|
| +
|
| + visitExpression(ExpressionObserver e) {
|
| + e._observe(scope);
|
| + }
|
| +
|
| + visitInExpression(InObserver c) {
|
| + visit(c.right);
|
| + visitExpression(c);
|
| + }
|
| +}
|
| +
|
| +class ObserverBuilder extends Visitor {
|
| + final Scope scope;
|
| + final Queue parents = new Queue();
|
| +
|
| + ObserverBuilder(this.scope);
|
| +
|
| + visitEmptyExpression(EmptyExpression e) => new EmptyObserver(e);
|
| +
|
| + visitParenthesizedExpression(ParenthesizedExpression e) => visit(e.child);
|
| +
|
| + 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);
|
| +
|
| + 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;
|
| + }
|
| +
|
| + visitInExpression(InExpression i) {
|
| + // don't visit the left. It's an identifier, but we don't want to evaluate
|
| + // it, we just want to add it to the comprehension object
|
| + var left = visit(i.left);
|
| + var right = visit(i.right);
|
| + var inexpr = new InObserver(i, left, right);
|
| + right._parent = inexpr;
|
| + return inexpr;
|
| + }
|
| +}
|
| +
|
| +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 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);
|
| +
|
| + dynamic get value => _expr.value;
|
| +
|
| + _updateSelf(Scope scope) {
|
| + _value = scope[_expr.value];
|
| +
|
| + var owner = scope.ownerOf(_expr.value);
|
| + if (owner is Observable) {
|
| + _subscription = (owner as Observable).changes.listen(
|
| + (List<ChangeRecord> changes) {
|
| + var symbol = new Symbol(_expr.value);
|
| + if (changes.any((c) => c.changes(symbol))) {
|
| + _invalidate(scope);
|
| + }
|
| + });
|
| + }
|
| + }
|
| +
|
| + accept(Visitor v) => v.visitIdentifier(this);
|
| +}
|
| +
|
| +class ParenthesizedObserver extends ExpressionObserver<ParenthesizedExpression>
|
| + implements ParenthesizedExpression {
|
| + final ExpressionObserver child;
|
| +
|
| + ParenthesizedObserver(ExpressionObserver 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 {
|
| + _value = (left._value == null || right._value == null)
|
| + ? null : f(left._value, right._value);
|
| + }
|
| + }
|
| +
|
| + accept(Visitor v) => v.visitBinaryOperator(this);
|
| +
|
| +}
|
| +
|
| +class InvokeObserver extends ExpressionObserver<Invoke> implements Invoke {
|
| + final ExpressionObserver receiver;
|
| + List<ExpressionObserver> arguments;
|
| +
|
| + InvokeObserver(Expression expr, this.receiver, [this.arguments])
|
| + : super(expr);
|
| +
|
| + bool get isGetter => _expr.isGetter;
|
| +
|
| + String get method => _expr.method;
|
| +
|
| + _updateSelf(Scope scope) {
|
| + var args = (arguments == null)
|
| + ? []
|
| + : arguments.map((a) => a._value)
|
| + .toList(growable: false);
|
| + var receiverValue = receiver._value;
|
| + if (receiverValue == null) {
|
| + _value = null;
|
| + } else if (_expr.method == null) {
|
| + if (_expr.isGetter) {
|
| + // getter, but not a top-level identifier
|
| + // TODO(justin): listen to the receiver's owner
|
| + _value = receiverValue;
|
| + } else {
|
| + // 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 = call(receiverValue, args);
|
| + }
|
| + } else {
|
| + // special case [] because we don't need mirrors
|
| + if (_expr.method == '[]') {
|
| + assert(args.length == 1);
|
| + var key = args[0];
|
| + _value = receiverValue[key];
|
| +
|
| + if (receiverValue is Observable) {
|
| + _subscription = (receiverValue as Observable).changes.listen(
|
| + (List<ChangeRecord> changes) {
|
| + if (changes.any((c) =>
|
| + c is MapChangeRecord && c.changes(key))) {
|
| + _invalidate(scope);
|
| + }
|
| + });
|
| + }
|
| + } else {
|
| + var mirror = reflect(receiverValue);
|
| + var symbol = new Symbol(_expr.method);
|
| + _value = (_expr.isGetter)
|
| + ? mirror.getField(symbol).reflectee
|
| + : mirror.invoke(symbol, args, null).reflectee;
|
| +
|
| + if (receiverValue is Observable) {
|
| + _subscription = (receiverValue as Observable).changes.listen(
|
| + (List<ChangeRecord> changes) {
|
| + if (changes.any((c) => c.changes(symbol))) {
|
| + _invalidate(scope);
|
| + }
|
| + });
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + accept(Visitor v) => v.visitInvoke(this);
|
| +}
|
| +
|
| +class InObserver extends ExpressionObserver<InExpression>
|
| + implements InExpression {
|
| + IdentifierObserver left;
|
| + ExpressionObserver right;
|
| +
|
| + InObserver(Expression expr, this.left, this.right) : super(expr);
|
| +
|
| + _updateSelf(Scope scope) {
|
| + Identifier identifier = left;
|
| + var iterable = right._value;
|
| +
|
| + if (iterable is! Iterable && iterable != null) {
|
| + throw new EvalException("right side of 'in' is not an iterator");
|
| + }
|
| +
|
| + if (iterable is ObservableList) {
|
| + _subscription = (iterable as ObservableList).changes.listen(
|
| + (List<ChangeRecord> changes) {
|
| + if (changes.any((c) => c is ListChangeRecord)) {
|
| + _invalidate(scope);
|
| + }
|
| + });
|
| + }
|
| +
|
| + // TODO: make Comprehension observable and update it
|
| + _value = new Comprehension(identifier.value, iterable);
|
| + }
|
| +
|
| + accept(Visitor v) => v.visitInExpression(this);
|
| +}
|
| +
|
| +_toBool(v) => (v == null) ? false : v;
|
| +
|
| +call(dynamic receiver, List args) {
|
| + if (receiver is Method) {
|
| + return
|
| + _convert(receiver.mirror.invoke(receiver.symbol, args, null).reflectee);
|
| + } else {
|
| + return _convert(Function.apply(receiver, args, null));
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A comprehension declaration ("a in b").
|
| + */
|
| +class Comprehension {
|
| + final String identifier;
|
| + final Iterable iterable;
|
| + Comprehension(this.identifier, this.iterable);
|
| +}
|
| +
|
| +/**
|
| + * A method on a model object in a [Scope].
|
| + */
|
| +class Method { //implements _FunctionWrapper {
|
| + final InstanceMirror mirror;
|
| + final Symbol symbol;
|
| +
|
| + Method(this.mirror, this.symbol);
|
| +
|
| + dynamic call(List args) => mirror.invoke(symbol, args, null).reflectee;
|
| +}
|
| +
|
| +class EvalException implements Exception {
|
| + final String message;
|
| + EvalException(this.message);
|
| + String toString() => "EvalException: $message";
|
| +}
|
|
|