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

Unified Diff: pkg/polymer_expressions/lib/eval.dart

Issue 22950008: move fancy_syntax into Dart SVN (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 4 months 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
« no previous file with comments | « pkg/polymer_expressions/lib/async.dart ('k') | pkg/polymer_expressions/lib/expression.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/polymer_expressions/lib/eval.dart
diff --git a/pkg/polymer_expressions/lib/eval.dart b/pkg/polymer_expressions/lib/eval.dart
new file mode 100644
index 0000000000000000000000000000000000000000..dbf1f8897412323ff29320faccb6b20f8b36b927
--- /dev/null
+++ b/pkg/polymer_expressions/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_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 && 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";
+}
« no previous file with comments | « pkg/polymer_expressions/lib/async.dart ('k') | pkg/polymer_expressions/lib/expression.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698