Skip to content
This repository was archived by the owner on Apr 15, 2025. It is now read-only.

Commit 8944f0d

Browse files
vicbmhevery
authored andcommitted
feat(AstParser): Made the AST parser private to the scope
relates to dart-archive#648
1 parent 5c0e608 commit 8944f0d

15 files changed

+267
-351
lines changed

lib/core/interpolate.dart

Lines changed: 41 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,12 @@
11
part of angular.core;
22

3-
class Interpolation implements Function {
4-
final String template;
5-
final List<String> separators;
6-
final List<String> expressions;
7-
Function setter = (_) => _;
8-
9-
Interpolation(this.template, this.separators, this.expressions);
10-
11-
String call(List parts, [_]) {
12-
if (parts == null) return separators.join('');
13-
var sb = new StringBuffer();
14-
for (var i = 0; i < parts.length; i++) {
15-
sb.write(separators[i]);
16-
var value = parts[i];
17-
sb.write(value == null ? '' : '$value');
18-
}
19-
sb.write(separators.last);
20-
return setter(sb.toString());
21-
}
22-
}
23-
243
/**
25-
* Compiles a string with markup into an interpolation function. This service
26-
* is used by the HTML [Compiler] service for data binding.
27-
*
4+
* Compiles a string with markup into an expression. This service is used by the
5+
* HTML [Compiler] service for data binding.
286
*
297
* var $interpolate = ...; // injected
308
* var exp = $interpolate('Hello {{name}}!');
31-
* expect(exp({name:'Angular'}).toEqual('Hello Angular!');
9+
* expect(exp).toEqual('"Hello "+(name)+"!"');
3210
*/
3311
@NgInjectableService()
3412
class Interpolate implements Function {
@@ -37,49 +15,54 @@ class Interpolate implements Function {
3715
Interpolate(this._parse);
3816

3917
/**
40-
* Compiles markup text into interpolation function.
18+
* Compiles markup text into expression.
4119
*
42-
* - `template`: The markup text to interpolate in form `foo {{expr}} bar`.
43-
* - `mustHaveExpression`: if set to true then the interpolation string must
44-
* have embedded expression in order to return an interpolation function.
45-
* Strings with no embedded expression will return null for the
46-
* interpolation function.
47-
* - `startSymbol`: The symbol to start interpolation. '{{' by default.
48-
* - `endSymbol`: The symbol to end interpolation. '}}' by default.
20+
* - [template]: The markup text to interpolate in form `foo {{expr}} bar`.
21+
* - [mustHaveExpression]: if set to true then the interpolation string must
22+
* have embedded expression in order to return an expression. Strings with
23+
* no embedded expression will return null.
24+
* - [startSymbol]: The symbol to start interpolation. '{{' by default.
25+
* - [endSymbol]: The symbol to end interpolation. '}}' by default.
4926
*/
50-
Interpolation call(String template, [bool mustHaveExpression = false,
51-
String startSymbol = '{{', String endSymbol = '}}']) {
52-
int startSymbolLength = startSymbol.length;
53-
int endSymbolLength = endSymbol.length;
54-
int startIndex;
55-
int endIndex;
27+
28+
String call(String template, [bool mustHaveExpression = false,
29+
String startSymbol = '{{', String endSymbol = '}}']) {
30+
if (template == null || template.isEmpty) return "";
31+
32+
final startLen = startSymbol.length;
33+
final endLen = endSymbol.length;
34+
final length = template.length;
35+
36+
int startIdx;
37+
int endIdx;
5638
int index = 0;
57-
int length = template.length;
39+
5840
bool hasInterpolation = false;
59-
bool shouldAddSeparator = true;
41+
6042
String exp;
61-
final separators = <String>[];
62-
final expressions = <String>[];
43+
final expParts = <String>[];
6344

6445
while (index < length) {
65-
if (((startIndex = template.indexOf(startSymbol, index)) != -1) &&
66-
((endIndex = template.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
67-
separators.add(template.substring(index, startIndex));
68-
exp = template.substring(startIndex + startSymbolLength, endIndex);
69-
expressions.add(exp);
70-
index = endIndex + endSymbolLength;
46+
startIdx = template.indexOf(startSymbol, index);
47+
endIdx = template.indexOf(endSymbol, startIdx + startLen);
48+
if (startIdx != -1 && endIdx != -1) {
49+
if (index < startIdx) {
50+
// Empty strings could be stripped thanks to the stringify
51+
// filter
52+
expParts.add('"${template.substring(index, startIdx)}"');
53+
}
54+
expParts.add('(' + template.substring(startIdx + startLen, endIdx) +
55+
'|stringify)');
56+
57+
index = endIdx + endLen;
7158
hasInterpolation = true;
7259
} else {
73-
// we did not find anything, so we have to add the remainder to the
74-
// chunks array
75-
separators.add(template.substring(index));
76-
shouldAddSeparator = false;
60+
// we did not find any interpolation, so add the remainder
61+
expParts.add('"${template.substring(index)}"');
7762
break;
7863
}
7964
}
80-
if (shouldAddSeparator) separators.add('');
81-
return (!mustHaveExpression || hasInterpolation)
82-
? new Interpolation(template, separators, expressions)
83-
: null;
65+
66+
return !mustHaveExpression || hasInterpolation ? expParts.join('+') : null;
8467
}
85-
}
68+
}

lib/core/scope.dart

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -199,32 +199,27 @@ class Scope {
199199
* On the opposite, [readOnly] should be set to [:false:] if the [reactionFn]
200200
* could change the model so that the watch is observed in the [digest] cycle.
201201
*/
202-
Watch watch(expression, ReactionFn reactionFn,
203-
{context, FilterMap filters, bool readOnly: false}) {
202+
Watch watch(String expression, ReactionFn reactionFn, {context,
203+
FilterMap filters, bool readOnly: false, bool collection: false}) {
204204
assert(isAttached);
205-
assert(expression != null);
206-
AST ast;
205+
assert(expression is String);
207206
Watch watch;
208207
ReactionFn fn = reactionFn;
209-
if (expression is AST) {
210-
ast = expression;
211-
} else if (expression is String) {
212-
if (expression.startsWith('::')) {
213-
expression = expression.substring(2);
214-
fn = (value, last) {
215-
if (value != null) {
216-
watch.remove();
217-
return reactionFn(value, last);
218-
}
219-
};
220-
} else if (expression.startsWith(':')) {
221-
expression = expression.substring(1);
222-
fn = (value, last) => value == null ? null : reactionFn(value, last);
223-
}
224-
ast = rootScope._astParser(expression, context: context, filters: filters);
225-
} else {
226-
throw 'expressions must be String or AST got $expression.';
208+
if (expression.startsWith('::')) {
209+
expression = expression.substring(2);
210+
fn = (value, last) {
211+
if (value != null) {
212+
watch.remove();
213+
return reactionFn(value, last);
214+
}
215+
};
216+
} else if (expression.startsWith(':')) {
217+
expression = expression.substring(1);
218+
fn = (value, last) => value == null ? null : reactionFn(value, last);
227219
}
220+
221+
AST ast = rootScope._astParser(expression, context: context,
222+
filters: filters, collection: collection);
228223
WatchGroup group = readOnly ? _readOnlyGroup : _readWriteGroup;
229224
return watch = group.watch(ast, fn);
230225
}
@@ -256,10 +251,9 @@ class Scope {
256251
} catch (e, s) {
257252
rootScope._exceptionHandler(e, s);
258253
} finally {
259-
rootScope
260-
.._transitionState(RootScope.STATE_APPLY, null)
261-
..digest()
262-
..flush();
254+
rootScope.._transitionState(RootScope.STATE_APPLY, null)
255+
..digest()
256+
..flush();
263257
}
264258
}
265259

@@ -436,11 +430,12 @@ class RootScope extends Scope {
436430

437431
String _state;
438432

439-
RootScope(Object context, this._astParser, this._parser,
440-
GetterCache cacheGetter, FilterMap filterMap,
441-
this._exceptionHandler, this._ttl, this._zone,
433+
RootScope(Object context, Parser parser, GetterCache cacheGetter,
434+
FilterMap filterMap, this._exceptionHandler, this._ttl, this._zone,
442435
ScopeStats _scopeStats)
443436
: _scopeStats = _scopeStats,
437+
_parser = parser,
438+
_astParser = new AstParser(parser),
444439
super(context, null, null,
445440
new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context),
446441
new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context),
@@ -963,6 +958,9 @@ class ExpressionVisitor implements Visitor {
963958
}
964959

965960
void visitFilter(Filter exp) {
961+
if (filters == null) {
962+
throw new Exception("No filters have been registered");
963+
}
966964
Function filterFunction = filters(exp.name);
967965
List<AST> args = [visitCollection(exp.expression)];
968966
args.addAll(_toAst(exp.arguments).map((ast) => new CollectionAST(ast)));

lib/core_dom/element_binder.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ class ElementBinder {
136136
nodeModule.factory(NgTextMustacheDirective, (Injector injector) {
137137
return new NgTextMustacheDirective(
138138
node, ref.value, injector.get(Interpolate), injector.get(Scope),
139-
injector.get(AstParser), injector.get(FilterMap));
139+
injector.get(FilterMap));
140140
});
141141
} else if (ref.type == NgAttrMustacheDirective) {
142142
if (nodesAttrsDirectives == null) {
@@ -146,7 +146,7 @@ class ElementBinder {
146146
var interpolate = injector.get(Interpolate);
147147
for (var ref in nodesAttrsDirectives) {
148148
new NgAttrMustacheDirective(nodeAttrs, ref.value, interpolate,
149-
scope, injector.get(AstParser), injector.get(FilterMap));
149+
scope, injector.get(FilterMap));
150150
}
151151
});
152152
}

lib/core_dom/ng_mustache.dart

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ part of angular.core.dom;
33
// This Directive is special and does not go through injection.
44
@NgDirective(selector: r':contains(/{{.*}}/)')
55
class NgTextMustacheDirective {
6-
NgTextMustacheDirective(dom.Node element,
7-
String markup,
6+
final dom.Node _element;
7+
8+
NgTextMustacheDirective(this._element,
9+
String template,
810
Interpolate interpolate,
911
Scope scope,
10-
AstParser parser,
1112
FilterMap filters) {
12-
Interpolation interpolation = interpolate(markup);
13-
interpolation.setter = (text) => element.text = text;
13+
String expression = interpolate(template);
14+
15+
scope.watch(expression, _updateMarkup, readOnly: true, filters: filters);
16+
}
1417

15-
List items = interpolation.expressions
16-
.map((exp) => parser(exp, filters: filters))
17-
.toList();
18-
AST ast = new PureFunctionAST('[[$markup]]', new ArrayFn(), items);
19-
scope.watch(ast, interpolation.call, readOnly: true);
18+
void _updateMarkup(text, previousText) {
19+
_element.text = text;
2020
}
2121
}
2222

@@ -25,40 +25,35 @@ class NgTextMustacheDirective {
2525
class NgAttrMustacheDirective {
2626
bool _hasObservers;
2727
Watch _watch;
28+
NodeAttrs _attrs;
29+
String _attrName;
2830

2931
// This Directive is special and does not go through injection.
30-
NgAttrMustacheDirective(NodeAttrs attrs,
31-
String markup,
32+
NgAttrMustacheDirective(this._attrs,
33+
String template,
3234
Interpolate interpolate,
3335
Scope scope,
34-
AstParser parser,
3536
FilterMap filters) {
36-
37-
var eqPos = markup.indexOf('=');
38-
var attrName = markup.substring(0, eqPos);
39-
var attrValue = markup.substring(eqPos + 1);
40-
var lastValue = markup;
41-
Interpolation interpolation = interpolate(attrValue)..setter = (text) {
42-
if (lastValue != text) lastValue = attrs[attrName] = text;
43-
};
44-
45-
// TODO(misko): figure out how to remove call to setter. It slows down
46-
// View instantiation
47-
interpolation.setter('');
48-
49-
List items = interpolation.expressions
50-
.map((exp) => parser(exp, filters: filters))
51-
.toList();
52-
53-
AST ast = new PureFunctionAST('[[$markup]]', new ArrayFn(), items);
54-
55-
attrs.listenObserverChanges(attrName, (hasObservers) {
56-
if (_hasObservers != hasObservers) {
57-
_hasObservers = hasObservers;
58-
if (_watch != null) _watch.remove();
59-
_watch = scope.watch(ast, interpolation.call, readOnly: !hasObservers);
37+
var eqPos = template.indexOf('=');
38+
_attrName = template.substring(0, eqPos);
39+
String expression = interpolate(template.substring(eqPos + 1));
40+
41+
_updateMarkup('', template);
42+
43+
_attrs.listenObserverChanges(_attrName, (hasObservers) {
44+
if (_hasObservers != hasObservers) {
45+
_hasObservers = hasObservers;
46+
if (_watch != null) _watch.remove();
47+
_watch = scope.watch(expression, _updateMarkup, filters: filters,
48+
readOnly: !_hasObservers);
6049
}
6150
});
6251
}
52+
53+
void _updateMarkup(text, previousText) {
54+
if (text != previousText && !(previousText == null && text == '')) {
55+
_attrs[_attrName] = text;
56+
}
57+
}
6358
}
6459

lib/directive/ng_class.dart

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ part of angular.directive;
6868
exportExpressionAttrs: const ['ng-class'])
6969
class NgClassDirective extends _NgClassBase {
7070
NgClassDirective(dom.Element element, Scope scope, NodeAttrs attrs,
71-
AstParser parser, NgAnimate animate)
72-
: super(element, scope, null, attrs, parser, animate);
71+
NgAnimate animate)
72+
: super(element, scope, null, attrs, animate);
7373
}
7474

7575
/**
@@ -104,8 +104,8 @@ class NgClassDirective extends _NgClassBase {
104104
exportExpressionAttrs: const ['ng-class-odd'])
105105
class NgClassOddDirective extends _NgClassBase {
106106
NgClassOddDirective(dom.Element element, Scope scope, NodeAttrs attrs,
107-
AstParser parser, NgAnimate animate)
108-
: super(element, scope, 0, attrs, parser, animate);
107+
NgAnimate animate)
108+
: super(element, scope, 0, attrs, animate);
109109
}
110110

111111
/**
@@ -140,23 +140,22 @@ class NgClassOddDirective extends _NgClassBase {
140140
exportExpressionAttrs: const ['ng-class-even'])
141141
class NgClassEvenDirective extends _NgClassBase {
142142
NgClassEvenDirective(dom.Element element, Scope scope, NodeAttrs attrs,
143-
AstParser parser, NgAnimate animate)
144-
: super(element, scope, 1, attrs, parser, animate);
143+
NgAnimate animate)
144+
: super(element, scope, 1, attrs, animate);
145145
}
146146

147147
abstract class _NgClassBase {
148148
final dom.Element element;
149149
final Scope scope;
150150
final int mode;
151151
final NodeAttrs nodeAttrs;
152-
final AstParser _parser;
153152
final NgAnimate _animate;
154153
var previousSet = [];
155154
var currentSet = [];
156155
Watch _watch;
157156

158157
_NgClassBase(this.element, this.scope, this.mode, this.nodeAttrs,
159-
this._parser, this._animate)
158+
this._animate)
160159
{
161160
var prevClass;
162161

@@ -168,14 +167,17 @@ abstract class _NgClassBase {
168167
});
169168
}
170169

171-
set valueExpression(currentExpression) {
170+
set valueExpression(expression) {
172171
if (_watch != null) _watch.remove();
173-
_watch = scope.watch(_parser(currentExpression, collection: true), (current, _) {
172+
_watch = scope.watch(expression, (current, _) {
174173
currentSet = _flatten(current);
175174
_handleChange(scope.context[r'$index']);
176-
}, readOnly: true);
175+
},
176+
readOnly: true,
177+
collection: true);
178+
177179
if (mode != null) {
178-
scope.watch(_parser(r'$index'), (index, oldIndex) {
180+
scope.watch(r'$index', (index, oldIndex) {
179181
var mod = index % 2;
180182
if (oldIndex == null || mod != oldIndex % 2) {
181183
if (mod == mode) {

0 commit comments

Comments
 (0)