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

[WIP]feat(AstParser): Made the AST parser private to the scope #753

Closed
wants to merge 6 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
6 changes: 3 additions & 3 deletions lib/core/filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ class NgFilter {
@NgInjectableService()
class FilterMap extends AnnotationMap<NgFilter> {
Injector _injector;
FilterMap(Injector injector, MetadataExtractor extractMetadata) :
this._injector = injector,
super(injector, extractMetadata);
FilterMap(Injector injector, MetadataExtractor extractMetadata)
: this._injector = injector,
super(injector, extractMetadata);

call(String name) {
var filter = new NgFilter(name: name);
Expand Down
86 changes: 32 additions & 54 deletions lib/core/interpolate.dart
Original file line number Diff line number Diff line change
@@ -1,34 +1,12 @@
part of angular.core;

class Interpolation implements Function {
final String template;
final List<String> separators;
final List<String> expressions;
Function setter = (_) => _;

Interpolation(this.template, this.separators, this.expressions);

String call(List parts, [_]) {
if (parts == null) return separators.join('');
var sb = new StringBuffer();
for (var i = 0; i < parts.length; i++) {
sb.write(separators[i]);
var value = parts[i];
sb.write(value == null ? '' : '$value');
}
sb.write(separators.last);
return setter(sb.toString());
}
}

/**
* Compiles a string with markup into an interpolation function. This service
* is used by the HTML [Compiler] service for data binding.
*
* Compiles a string with markup into an expression. This service is used by the
* HTML [Compiler] service for data binding.
*
* var $interpolate = ...; // injected
* var exp = $interpolate('Hello {{name}}!');
* expect(exp({name:'Angular'}).toEqual('Hello Angular!');
* expect(exp).toEqual('"Hello "+(name)+"!"');
*/
@NgInjectableService()
class Interpolate implements Function {
Expand All @@ -37,49 +15,49 @@ class Interpolate implements Function {
Interpolate(this._parse);

/**
* Compiles markup text into interpolation function.
* Compiles markup text into expression.
*
* - `text`: The markup text to interpolate in form `foo {{expr}} bar`.
* - `template`: The markup text to interpolate in form `foo {{expr}} bar`.
* - `mustHaveExpression`: if set to true then the interpolation string must
* have embedded expression in order to return an interpolation function.
* Strings with no embedded expression will return null for the
* interpolation function.
* have embedded expression in order to return an expression. Strings with
* no embedded expression will return null.
* - `startSymbol`: The symbol to start interpolation. '{{' by default.
* - `endSymbol`: The symbol to end interpolation. '}}' by default.
*/
Interpolation call(String template, [bool mustHaveExpression = false,

String call(String template, [bool mustHaveExpression = false,
String startSymbol = '{{', String endSymbol = '}}']) {
int startSymbolLength = startSymbol.length;
int endSymbolLength = endSymbol.length;
int startIndex;
int endIndex;
int index = 0;

int startLen = startSymbol.length;
int endLen = endSymbol.length;
int length = template.length;

int startIdx;
int endIdx;
int index = 0;

bool hasInterpolation = false;
bool shouldAddSeparator = true;

String exp;
final separators = <String>[];
final expressions = <String>[];
final expParts = <String>[];

while (index < length) {
if (((startIndex = template.indexOf(startSymbol, index)) != -1) &&
((endIndex = template.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
separators.add(template.substring(index, startIndex));
exp = template.substring(startIndex + startSymbolLength, endIndex);
expressions.add(exp);
index = endIndex + endSymbolLength;
startIdx = template.indexOf(startSymbol, index);
endIdx = template.indexOf(endSymbol, startIdx + startLen);
if (startIdx != -1 && endIdx != -1) {
if (index < startIdx) {
expParts.add('"${template.substring(index, startIdx)}"');
}
expParts.add('(${template.substring(startIdx + startLen, endIdx)})');
index = endIdx + endLen;
hasInterpolation = true;
} else {
// we did not find anything, so we have to add the remainder to the
// chunks array
separators.add(template.substring(index));
shouldAddSeparator = false;
// we did not find any interpolation, so add the remainder
expParts.add('"${template.substring(index)}"');
break;
}
}
if (shouldAddSeparator) separators.add('');
return (!mustHaveExpression || hasInterpolation)
? new Interpolation(template, separators, expressions)
: null;

return !mustHaveExpression || hasInterpolation ? expParts.join('+') : null;
}
}
}
1 change: 0 additions & 1 deletion lib/core/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ class NgCoreModule extends Module {
value(ScopeStats, new ScopeStats());
value(GetterCache, new GetterCache({}));
value(Object, {}); // RootScope context
type(AstParser);
type(NgZone);

type(Parser, implementedBy: DynamicParser);
Expand Down
64 changes: 31 additions & 33 deletions lib/core/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -196,32 +196,30 @@ class Scope {
* On the opposite, [readOnly] should be set to [:false:] if the [reactionFn]
* could change the model so that the watch is observed in the [digest] cycle.
*/
Watch watch(expression, ReactionFn reactionFn,
{context, FilterMap filters, bool readOnly: false}) {
Watch watch(String expression, ReactionFn reactionFn, {Object context,
FilterMap filters, bool readOnly: false, bool collection: false}) {
assert(isAttached);
assert(expression != null);
AST ast;
assert(expression is String && expression != null);

Watch watch;
ReactionFn fn = reactionFn;
if (expression is AST) {
ast = expression;
} else if (expression is String) {
if (expression.startsWith('::')) {
expression = expression.substring(2);
fn = (value, last) {
if (value != null) {
watch.remove();
return reactionFn(value, last);
}
};
} else if (expression.startsWith(':')) {
expression = expression.substring(1);
fn = (value, last) => value == null ? null : reactionFn(value, last);
}
ast = rootScope._astParser(expression, context: context, filters: filters);
} else {
throw 'expressions must be String or AST got $expression.';

if (expression.startsWith('::')) {
expression = expression.substring(2);
fn = (value, last) {
if (value != null) {
watch.remove();
return reactionFn(value, last);
}
};
} else if (expression.startsWith(':')) {
expression = expression.substring(1);
fn = (value, last) => value == null ? null : reactionFn(value, last);
}

AST ast = rootScope._astParser(expression, context: context,
filters: filters, collection: collection);

WatchGroup group = readOnly ? _readOnlyGroup : _readWriteGroup;
return watch = group.watch(ast, fn);
}
Expand Down Expand Up @@ -253,10 +251,9 @@ class Scope {
} catch (e, s) {
rootScope._exceptionHandler(e, s);
} finally {
rootScope
.._transitionState(RootScope.STATE_APPLY, null)
..digest()
..flush();
rootScope.._transitionState(RootScope.STATE_APPLY, null)
..digest()
..flush();
}
}

Expand Down Expand Up @@ -409,7 +406,7 @@ class RootScope extends Scope {
static final STATE_FLUSH = 'flush';

final ExceptionHandler _exceptionHandler;
final AstParser _astParser;
final _AstParser _astParser;
final Parser _parser;
final ScopeDigestTTL _ttl;
final NgZone _zone;
Expand All @@ -422,11 +419,12 @@ class RootScope extends Scope {

String _state;

RootScope(Object context, this._astParser, this._parser,
GetterCache cacheGetter, FilterMap filterMap,
this._exceptionHandler, this._ttl, this._zone,
RootScope(Object context, Parser parser, GetterCache cacheGetter,
FilterMap filterMap, this._exceptionHandler, this._ttl, this._zone,
this._scopeStats)
: super(context, null, null,
: _astParser = new _AstParser(parser),
_parser = parser,
super(context, null, null,
new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context),
new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context),
'')
Expand Down Expand Up @@ -827,12 +825,12 @@ class _FunctionChain {
}
}

class AstParser {
class _AstParser {
final Parser _parser;
int _id = 0;
ExpressionVisitor _visitor = new ExpressionVisitor();

AstParser(this._parser);
_AstParser(this._parser);

AST call(String exp, { FilterMap filters,
bool collection: false,
Expand Down
4 changes: 2 additions & 2 deletions lib/core_dom/element_binder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ class ElementBinder {
nodeModule.factory(NgTextMustacheDirective, (Injector injector) {
return new NgTextMustacheDirective(
node, ref.value, injector.get(Interpolate), injector.get(Scope),
injector.get(AstParser), injector.get(FilterMap));
injector.get(FilterMap));
});
} else if (ref.type == NgAttrMustacheDirective) {
if (nodesAttrsDirectives == null) {
Expand All @@ -146,7 +146,7 @@ class ElementBinder {
var interpolate = injector.get(Interpolate);
for (var ref in nodesAttrsDirectives) {
new NgAttrMustacheDirective(nodeAttrs, ref.value, interpolate,
scope, injector.get(AstParser), injector.get(FilterMap));
scope, injector.get(FilterMap));
}
});
}
Expand Down
60 changes: 25 additions & 35 deletions lib/core_dom/ng_mustache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ part of angular.core.dom;
// This Directive is special and does not go through injection.
@NgDirective(selector: r':contains(/{{.*}}/)')
class NgTextMustacheDirective {
NgTextMustacheDirective(dom.Node element,
String markup,
final dom.Node _element;

NgTextMustacheDirective(this._element,
String template,
Interpolate interpolate,
Scope scope,
AstParser parser,
FilterMap filters) {
Interpolation interpolation = interpolate(markup);
interpolation.setter = (text) => element.text = text;
String expression = interpolate(template);

scope.watch(expression, _updateMarkup, readOnly: true, filters: filters);
}

List items = interpolation.expressions
.map((exp) => parser(exp, filters: filters))
.toList();
AST ast = new PureFunctionAST('[[$markup]]', new ArrayFn(), items);
scope.watch(ast, interpolation.call, readOnly: true);
void _updateMarkup(text, previousText) {
if (text != previousText) _element.text = text.toString();
}
}

Expand All @@ -25,40 +25,30 @@ class NgTextMustacheDirective {
class NgAttrMustacheDirective {
bool _hasObservers;
Watch _watch;
NodeAttrs _attrs;
String _attrName;

// This Directive is special and does not go through injection.
NgAttrMustacheDirective(NodeAttrs attrs,
String markup,
NgAttrMustacheDirective(this._attrs,
String template,
Interpolate interpolate,
Scope scope,
AstParser parser,
FilterMap filters) {
var eqPos = template.indexOf('=');
_attrName = template.substring(0, eqPos);
String expression = interpolate(template.substring(eqPos + 1));

var eqPos = markup.indexOf('=');
var attrName = markup.substring(0, eqPos);
var attrValue = markup.substring(eqPos + 1);
var lastValue = markup;
Interpolation interpolation = interpolate(attrValue)..setter = (text) {
if (lastValue != text) lastValue = attrs[attrName] = text;
};

// TODO(misko): figure out how to remove call to setter. It slows down
// View instantiation
interpolation.setter('');

List items = interpolation.expressions
.map((exp) => parser(exp, filters: filters))
.toList();

AST ast = new PureFunctionAST('[[$markup]]', new ArrayFn(), items);

attrs.listenObserverChanges(attrName, (hasObservers) {
_attrs.listenObserverChanges(_attrName, (hasObservers) {
if (_hasObservers != hasObservers) {
hasObservers = hasObservers;
_hasObservers = hasObservers;
if (_watch != null) _watch.remove();
_watch = scope.watch(ast, interpolation.call, readOnly: !hasObservers);
_watch = scope.watch(expression, _updateMarkup, filters: filters,
readOnly: !_hasObservers);
}
});
}

void _updateMarkup(text, previousText) {
if (previousText != text) _attrs[_attrName] = text.toString();
}
}

Loading