blob: 7ed7b5a5afb9ec4364b662554ec9695cb4cf9444 [file] [log] [blame]
// 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 '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) {
BinaryOperator op = expr;
if (op.operator != '|') {
break;
}
filters.add(op.right);
expr = op.left;
}
if (expr is Identifier) {
expression = empty();
Identifier ident = expr;
property = ident.value;
} else if (expr is Invoke) {
Invoke invoke = expr;
expression = invoke.receiver;
if (invoke.method == '[]') {
if (invoke.arguments[0] is! Literal) notAssignable();
Literal l = invoke.arguments[0];
property = l.value;
isIndex = true;
} else if (invoke.method != null) {
if (invoke.arguments != null) notAssignable();
property = invoke.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(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 {
_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";
}