Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Precompute Scope.watch ASTs #1088

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/change_detection/ast.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ part of angular.watch_group;
abstract class AST {
static final String _CONTEXT = '#';
final String expression;
var parsedExp; // The parsed version of expression.
AST(expression)
: expression = expression.startsWith('#.')
? expression.substring(2)
Expand Down
246 changes: 246 additions & 0 deletions lib/change_detection/ast_parser.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
library angular.change_detection.ast_parser;

import 'dart:collection';

import 'package:angular/core/parser/syntax.dart' as syntax;
import 'package:angular/core/parser/parser.dart';
import 'package:angular/core/formatter.dart';
import 'package:angular/core/annotation_src.dart';
import 'package:angular/change_detection/watch_group.dart';
import 'package:angular/change_detection/change_detection.dart';
import 'package:angular/core/parser/utils.dart';

class _FunctionChain {
final Function fn;
_FunctionChain _next;

_FunctionChain(fn()): fn = fn {
assert(fn != null);
}
}

@Injectable()
class ASTParser {
final Parser _parser;
final ClosureMap _closureMap;

ASTParser(this._parser, this._closureMap);

AST call(String input, {FormatterMap formatters,
bool collection: false }) {
var visitor = new _ExpressionVisitor(_closureMap, formatters);
var exp = _parser(input);
AST ast = collection ? visitor.visitCollection(exp) : visitor.visit(exp);
ast.parsedExp = exp;
return ast;
}
}

class _ExpressionVisitor implements syntax.Visitor {
static final ContextReferenceAST contextRef = new ContextReferenceAST();
final ClosureMap _closureMap;
final FormatterMap _formatters;

_ExpressionVisitor(this._closureMap, this._formatters);

AST visit(syntax.Expression exp) => exp.accept(this);

AST visitCollection(syntax.Expression exp) => new CollectionAST(visit(exp));
AST _mapToAst(syntax.Expression expression) => visit(expression);

List<AST> _toAst(List<syntax.Expression> expressions) =>
expressions.map(_mapToAst).toList();

Map<Symbol, AST> _toAstMap(Map<String, syntax.Expression> expressions) {
if (expressions.isEmpty) return const {};
Map<Symbol, AST> result = new Map<Symbol, AST>();
expressions.forEach((String name, syntax.Expression expression) {
result[_closureMap.lookupSymbol(name)] = _mapToAst(expression);
});
return result;
}

AST visitCallScope(syntax.CallScope exp) {
List<AST> positionals = _toAst(exp.arguments.positionals);
Map<Symbol, AST> named = _toAstMap(exp.arguments.named);
return new MethodAST(contextRef, exp.name, positionals, named);
}
AST visitCallMember(syntax.CallMember exp) {
List<AST> positionals = _toAst(exp.arguments.positionals);
Map<Symbol, AST> named = _toAstMap(exp.arguments.named);
return new MethodAST(visit(exp.object), exp.name, positionals, named);
}
AST visitAccessScope(syntax.AccessScope exp) =>
new FieldReadAST(contextRef, exp.name);

AST visitAccessMember(syntax.AccessMember exp) =>
new FieldReadAST(visit(exp.object), exp.name);

AST visitBinary(syntax.Binary exp) =>
new PureFunctionAST(exp.operation,
_operationToFunction(exp.operation),
[visit(exp.left), visit(exp.right)]);

AST visitPrefix(syntax.Prefix exp) =>
new PureFunctionAST(exp.operation,
_operationToFunction(exp.operation),
[visit(exp.expression)]);

AST visitConditional(syntax.Conditional exp) =>
new PureFunctionAST('?:', _operation_ternary,
[visit(exp.condition), visit(exp.yes),
visit(exp.no)]);

AST visitAccessKeyed(syntax.AccessKeyed exp) =>
new ClosureAST('[]', _operation_bracket,
[visit(exp.object), visit(exp.key)]);

AST visitLiteralPrimitive(syntax.LiteralPrimitive exp) =>
new ConstantAST(exp.value);

AST visitLiteralString(syntax.LiteralString exp) =>
new ConstantAST(exp.value);

AST visitLiteralArray(syntax.LiteralArray exp) {
List<AST> items = _toAst(exp.elements);
return new PureFunctionAST('[${items.join(', ')}]', new ArrayFn(), items);
}

AST visitLiteralObject(syntax.LiteralObject exp) {
List<String> keys = exp.keys;
List<AST> values = _toAst(exp.values);
assert(keys.length == values.length);
var kv = <String>[];
for (var i = 0; i < keys.length; i++) {
kv.add('${keys[i]}: ${values[i]}');
}
return new PureFunctionAST('{${kv.join(', ')}}', new MapFn(keys), values);
}

AST visitFormatter(syntax.Formatter exp) {
if (_formatters == null) {
throw new Exception("No formatters have been registered");
}
Function formatterFunction = _formatters(exp.name);
List<AST> args = [visitCollection(exp.expression)];
args.addAll(_toAst(exp.arguments).map((ast) => new CollectionAST(ast)));
return new PureFunctionAST('|${exp.name}',
new _FormatterWrapper(formatterFunction, args.length), args);
}

// TODO(misko): this is a corner case. Choosing not to implement for now.
void visitCallFunction(syntax.CallFunction exp) {
_notSupported("function's returing functions");
}
void visitAssign(syntax.Assign exp) {
_notSupported('assignement');
}
void visitLiteral(syntax.Literal exp) {
_notSupported('literal');
}
void visitExpression(syntax.Expression exp) {
_notSupported('?');
}
void visitChain(syntax.Chain exp) {
_notSupported(';');
}

void _notSupported(String name) {
throw new StateError("Can not watch expression containing '$name'.");
}
}

Function _operationToFunction(String operation) {
switch(operation) {
case '!' : return _operation_negate;
case '+' : return _operation_add;
case '-' : return _operation_subtract;
case '*' : return _operation_multiply;
case '/' : return _operation_divide;
case '~/' : return _operation_divide_int;
case '%' : return _operation_remainder;
case '==' : return _operation_equals;
case '!=' : return _operation_not_equals;
case '<' : return _operation_less_then;
case '>' : return _operation_greater_then;
case '<=' : return _operation_less_or_equals_then;
case '>=' : return _operation_greater_or_equals_then;
case '^' : return _operation_power;
case '&' : return _operation_bitwise_and;
case '&&' : return _operation_logical_and;
case '||' : return _operation_logical_or;
default: throw new StateError(operation);
}
}

_operation_negate(value) => !toBool(value);
_operation_add(left, right) => autoConvertAdd(left, right);
_operation_subtract(left, right) => (left != null && right != null) ? left - right : (left != null ? left : (right != null ? 0 - right : 0));
_operation_multiply(left, right) => (left == null || right == null) ? null : left * right;
_operation_divide(left, right) => (left == null || right == null) ? null : left / right;
_operation_divide_int(left, right) => (left == null || right == null) ? null : left ~/ right;
_operation_remainder(left, right) => (left == null || right == null) ? null : left % right;
_operation_equals(left, right) => left == right;
_operation_not_equals(left, right) => left != right;
_operation_less_then(left, right) => (left == null || right == null) ? null : left < right;
_operation_greater_then(left, right) => (left == null || right == null) ? null : left > right;
_operation_less_or_equals_then(left, right) => (left == null || right == null) ? null : left <= right;
_operation_greater_or_equals_then(left, right) => (left == null || right == null) ? null : left >= right;
_operation_power(left, right) => (left == null || right == null) ? null : left ^ right;
_operation_bitwise_and(left, right) => (left == null || right == null) ? null : left & right;
// TODO(misko): these should short circuit the evaluation.
_operation_logical_and(left, right) => toBool(left) && toBool(right);
_operation_logical_or(left, right) => toBool(left) || toBool(right);

_operation_ternary(condition, yes, no) => toBool(condition) ? yes : no;
_operation_bracket(obj, key) => obj == null ? null : obj[key];

class ArrayFn extends FunctionApply {
// TODO(misko): figure out why do we need to make a copy?
apply(List args) => new List.from(args);
}

class MapFn extends FunctionApply {
final List<String> keys;

MapFn(this.keys);

Map apply(List values) {
// TODO(misko): figure out why do we need to make a copy instead of reusing instance?
assert(values.length == keys.length);
return new Map.fromIterables(keys, values);
}
}

class _FormatterWrapper extends FunctionApply {
final Function formatterFn;
final List args;
final List<Watch> argsWatches;
_FormatterWrapper(this.formatterFn, length):
args = new List(length),
argsWatches = new List(length);

apply(List values) {
for (var i=0; i < values.length; i++) {
var value = values[i];
var lastValue = args[i];
if (!identical(value, lastValue)) {
if (value is CollectionChangeRecord) {
args[i] = (value as CollectionChangeRecord).iterable;
} else if (value is MapChangeRecord) {
args[i] = (value as MapChangeRecord).map;
} else {
args[i] = value;
}
}
}
var value = Function.apply(formatterFn, args);
if (value is Iterable) {
// Since formatters are pure we can guarantee that this well never change.
// By wrapping in UnmodifiableListView we can hint to the dirty checker
// and short circuit the iterator.
value = new UnmodifiableListView(value);
}
return value;
}
}
5 changes: 4 additions & 1 deletion lib/core/formatter.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
part of angular.core_internal;
library angular.core_internal.formatter_map;

import 'package:di/di.dart';
import 'package:angular/core/annotation_src.dart';
import 'package:angular/core/registry.dart';

/**
* Registry of formatters at runtime.
Expand Down
6 changes: 4 additions & 2 deletions lib/core/module_internal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ import 'package:angular/core/annotation_src.dart';

import 'package:angular/change_detection/watch_group.dart';
export 'package:angular/change_detection/watch_group.dart';
import 'package:angular/change_detection/ast_parser.dart';
import 'package:angular/change_detection/change_detection.dart';
import 'package:angular/change_detection/dirty_checking_change_detector.dart';
import 'package:angular/core/formatter.dart';
export 'package:angular/core/formatter.dart';
import 'package:angular/core/parser/utils.dart';
import 'package:angular/core/parser/syntax.dart' as syntax;
import 'package:angular/core/registry.dart';
import 'package:angular/core/static_keys.dart';

part "cache.dart";
part "exception_handler.dart";
part 'formatter.dart';
part "interpolate.dart";
part "scope.dart";
part "zone.dart";
Expand Down Expand Up @@ -53,5 +54,6 @@ class CoreModule extends Module {
bind(DynamicParser);
bind(DynamicParserBackend);
bind(Lexer);
bind(ASTParser);
}
}
Loading