chore: move core modules into core directory

BREAKING CHANGE:
    This change moves the http module into angular2/, so its import
    path is now angular2/http instead of http/http.

    Many other modules have also been moved around inside of angular2,
    but the public API paths have not changed as of this commit.
This commit is contained in:
Jeff Cross
2015-08-20 14:28:09 -07:00
parent 56e88058f1
commit 38a5a2a955
260 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,287 @@
import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectionUtil} from './change_detection_util';
import {ChangeDetectorRef} from './change_detector_ref';
import {DirectiveIndex} from './directive_record';
import {ChangeDetector, ChangeDispatcher} from './interfaces';
import {Pipes} from './pipes';
import {
ChangeDetectionError,
ExpressionChangedAfterItHasBeenCheckedException,
DehydratedException
} from './exceptions';
import {BindingTarget} from './binding_record';
import {Locals} from './parser/locals';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
import {isObservable} from './observable_facade';
import {ON_PUSH_OBSERVE} from './constants';
var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);
class _Context {
constructor(public element: any, public componentElement: any, public context: any,
public locals: any, public injector: any, public expression: any) {}
}
export class AbstractChangeDetector<T> implements ChangeDetector {
lightDomChildren: List<any> = [];
shadowDomChildren: List<any> = [];
parent: ChangeDetector;
ref: ChangeDetectorRef;
// The names of the below fields must be kept in sync with codegen_name_util.ts or
// change detection will fail.
alreadyChecked: any = false;
context: T;
locals: Locals = null;
mode: string = null;
pipes: Pipes = null;
propertyBindingIndex: number;
// This is an experimental feature. Works only in Dart.
subscriptions: any[];
streams: any[];
constructor(public id: string, public dispatcher: ChangeDispatcher,
public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[],
public directiveIndices: DirectiveIndex[], public strategy: string) {
this.ref = new ChangeDetectorRef(this);
}
addChild(cd: ChangeDetector): void {
this.lightDomChildren.push(cd);
cd.parent = this;
}
removeChild(cd: ChangeDetector): void { ListWrapper.remove(this.lightDomChildren, cd); }
addShadowDomChild(cd: ChangeDetector): void {
this.shadowDomChildren.push(cd);
cd.parent = this;
}
removeShadowDomChild(cd: ChangeDetector): void { ListWrapper.remove(this.shadowDomChildren, cd); }
remove(): void { this.parent.removeChild(this); }
handleEvent(eventName: string, elIndex: number, locals: Locals): boolean {
var res = this.handleEventInternal(eventName, elIndex, locals);
this.markPathToRootAsCheckOnce();
return res;
}
handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean { return false; }
detectChanges(): void { this.runDetectChanges(false); }
checkNoChanges(): void { throw new BaseException("Not implemented"); }
runDetectChanges(throwOnChange: boolean): void {
if (StringWrapper.equals(this.mode, DETACHED) || StringWrapper.equals(this.mode, CHECKED))
return;
var s = _scope_check(this.id, throwOnChange);
this.detectChangesInRecords(throwOnChange);
this._detectChangesInLightDomChildren(throwOnChange);
if (throwOnChange === false) this.callOnAllChangesDone();
this._detectChangesInShadowDomChildren(throwOnChange);
if (StringWrapper.equals(this.mode, CHECK_ONCE)) this.mode = CHECKED;
wtfLeave(s);
}
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `detectChangesInRecordsInternal` which does the work of detecting changes
// and which this method will call.
// This method expects that `detectChangesInRecordsInternal` will set the property
// `this.propertyBindingIndex` to the propertyBindingIndex of the first proto record. This is to
// facilitate error reporting.
detectChangesInRecords(throwOnChange: boolean): void {
if (!this.hydrated()) {
this.throwDehydratedError();
}
try {
this.detectChangesInRecordsInternal(throwOnChange);
} catch (e) {
this._throwError(e, e.stack);
}
}
// Subclasses should override this method to perform any work necessary to detect and report
// changes. For example, changes should be reported via `ChangeDetectionUtil.addChange`, lifecycle
// methods should be called, etc.
// This implementation should also set `this.propertyBindingIndex` to the propertyBindingIndex of
// the
// first proto record to facilitate error reporting. See {@link #detectChangesInRecords}.
detectChangesInRecordsInternal(throwOnChange: boolean): void {}
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `hydrateDirectives`.
hydrate(context: T, locals: Locals, directives: any, pipes: any): void {
this.mode = ChangeDetectionUtil.changeDetectionMode(this.strategy);
this.context = context;
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
this.observeComponent(context);
}
this.locals = locals;
this.pipes = pipes;
this.hydrateDirectives(directives);
this.alreadyChecked = false;
}
// Subclasses should override this method to hydrate any directives.
hydrateDirectives(directives: any): void {}
// This method is not intended to be overridden. Subclasses should instead provide an
// implementation of `dehydrateDirectives`.
dehydrate(): void {
this.dehydrateDirectives(true);
// This is an experimental feature. Works only in Dart.
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
this._unsubsribeFromObservables();
}
this.context = null;
this.locals = null;
this.pipes = null;
}
// Subclasses should override this method to dehydrate any directives. This method should reverse
// any work done in `hydrateDirectives`.
dehydrateDirectives(destroyPipes: boolean): void {}
hydrated(): boolean { return this.context !== null; }
callOnAllChangesDone(): void { this.dispatcher.notifyOnAllChangesDone(); }
_detectChangesInLightDomChildren(throwOnChange: boolean): void {
var c = this.lightDomChildren;
for (var i = 0; i < c.length; ++i) {
c[i].runDetectChanges(throwOnChange);
}
}
_detectChangesInShadowDomChildren(throwOnChange: boolean): void {
var c = this.shadowDomChildren;
for (var i = 0; i < c.length; ++i) {
c[i].runDetectChanges(throwOnChange);
}
}
markAsCheckOnce(): void { this.mode = CHECK_ONCE; }
markPathToRootAsCheckOnce(): void {
var c: ChangeDetector = this;
while (isPresent(c) && !StringWrapper.equals(c.mode, DETACHED)) {
if (StringWrapper.equals(c.mode, CHECKED)) c.mode = CHECK_ONCE;
c = c.parent;
}
}
// This is an experimental feature. Works only in Dart.
private _unsubsribeFromObservables(): void {
if (isPresent(this.subscriptions)) {
for (var i = 0; i < this.subscriptions.length; ++i) {
var s = this.subscriptions[i];
if (isPresent(this.subscriptions[i])) {
s.cancel();
this.subscriptions[i] = null;
}
}
}
}
// This is an experimental feature. Works only in Dart.
protected observeValue(value: any, index: number): any {
if (isObservable(value)) {
this._createArrayToStoreObservables();
if (isBlank(this.subscriptions[index])) {
this.streams[index] = value.changes;
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
} else if (this.streams[index] !== value.changes) {
this.subscriptions[index].cancel();
this.streams[index] = value.changes;
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
}
}
return value;
}
// This is an experimental feature. Works only in Dart.
protected observeDirective(value: any, index: number): any {
if (isObservable(value)) {
this._createArrayToStoreObservables();
var arrayIndex = this.numberOfPropertyProtoRecords + index + 2; // +1 is component
this.streams[arrayIndex] = value.changes;
this.subscriptions[arrayIndex] = value.changes.listen((_) => this.ref.requestCheck());
}
return value;
}
// This is an experimental feature. Works only in Dart.
protected observeComponent(value: any): any {
if (isObservable(value)) {
this._createArrayToStoreObservables();
var index = this.numberOfPropertyProtoRecords + 1;
this.streams[index] = value.changes;
this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck());
}
return value;
}
private _createArrayToStoreObservables(): void {
if (isBlank(this.subscriptions)) {
this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords +
this.directiveIndices.length + 2);
this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords +
this.directiveIndices.length + 2);
}
}
protected getDirectiveFor(directives: any, index: number): any {
return directives.getDirectiveFor(this.directiveIndices[index]);
}
protected getDetectorFor(directives: any, index: number): ChangeDetector {
return directives.getDetectorFor(this.directiveIndices[index]);
}
protected notifyDispatcher(value: any): void {
this.dispatcher.notifyOnBinding(this._currentBinding(), value);
}
protected logBindingUpdate(value: any): void {
this.dispatcher.logBindingUpdate(this._currentBinding(), value);
}
protected addChange(changes: StringMap<string, any>, oldValue: any,
newValue: any): StringMap<string, any> {
if (isBlank(changes)) {
changes = {};
}
changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue);
return changes;
}
private _throwError(exception: any, stack: any): void {
var c = this.dispatcher.getDebugContext(this._currentBinding().elementIndex, null);
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
c.injector, this._currentBinding().debug) :
null;
throw new ChangeDetectionError(this._currentBinding().debug, exception, stack, context);
}
protected throwOnChangeError(oldValue: any, newValue: any): void {
throw new ExpressionChangedAfterItHasBeenCheckedException(this._currentBinding().debug,
oldValue, newValue, null);
}
protected throwDehydratedError(): void { throw new DehydratedException(); }
private _currentBinding(): BindingTarget {
return this.bindingTargets[this.propertyBindingIndex];
}
}

View File

@ -0,0 +1,148 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {SetterFn} from 'angular2/src/reflection/types';
import {AST} from './parser/ast';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
const DIRECTIVE_LIFECYCLE = "directiveLifecycle";
const BINDING = "native";
const DIRECTIVE = "directive";
const ELEMENT_PROPERTY = "elementProperty";
const ELEMENT_ATTRIBUTE = "elementAttribute";
const ELEMENT_CLASS = "elementClass";
const ELEMENT_STYLE = "elementStyle";
const TEXT_NODE = "textNode";
const EVENT = "event";
const HOST_EVENT = "hostEvent";
export class BindingTarget {
constructor(public mode: string, public elementIndex: number, public name: string,
public unit: string, public debug: string) {}
isDirective(): boolean { return this.mode === DIRECTIVE; }
isElementProperty(): boolean { return this.mode === ELEMENT_PROPERTY; }
isElementAttribute(): boolean { return this.mode === ELEMENT_ATTRIBUTE; }
isElementClass(): boolean { return this.mode === ELEMENT_CLASS; }
isElementStyle(): boolean { return this.mode === ELEMENT_STYLE; }
isTextNode(): boolean { return this.mode === TEXT_NODE; }
}
export class BindingRecord {
constructor(public mode: string, public target: BindingTarget, public implicitReceiver: any,
public ast: AST, public setter: SetterFn, public lifecycleEvent: string,
public directiveRecord: DirectiveRecord) {}
isDirectiveLifecycle(): boolean { return this.mode === DIRECTIVE_LIFECYCLE; }
callOnChange(): boolean {
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
}
isDefaultChangeDetection(): boolean {
return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection();
}
static createDirectiveOnCheck(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onCheck", directiveRecord);
}
static createDirectiveOnInit(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onInit", directiveRecord);
}
static createDirectiveOnChange(directiveRecord: DirectiveRecord): BindingRecord {
return new BindingRecord(DIRECTIVE_LIFECYCLE, null, 0, null, null, "onChange", directiveRecord);
}
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
directiveRecord: DirectiveRecord): BindingRecord {
var elementIndex = directiveRecord.directiveIndex.elementIndex;
var t = new BindingTarget(DIRECTIVE, elementIndex, propertyName, null, ast.toString());
return new BindingRecord(DIRECTIVE, t, 0, ast, setter, null, directiveRecord);
}
static createForElementProperty(ast: AST, elementIndex: number,
propertyName: string): BindingRecord {
var t = new BindingTarget(ELEMENT_PROPERTY, elementIndex, propertyName, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForElementAttribute(ast: AST, elementIndex: number,
attributeName: string): BindingRecord {
var t = new BindingTarget(ELEMENT_ATTRIBUTE, elementIndex, attributeName, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForElementClass(ast: AST, elementIndex: number, className: string): BindingRecord {
var t = new BindingTarget(ELEMENT_CLASS, elementIndex, className, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForElementStyle(ast: AST, elementIndex: number, styleName: string,
unit: string): BindingRecord {
var t = new BindingTarget(ELEMENT_STYLE, elementIndex, styleName, unit, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST,
propertyName: string): BindingRecord {
var t = new BindingTarget(ELEMENT_PROPERTY, directiveIndex.elementIndex, propertyName, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}
static createForHostAttribute(directiveIndex: DirectiveIndex, ast: AST,
attributeName: string): BindingRecord {
var t = new BindingTarget(ELEMENT_ATTRIBUTE, directiveIndex.elementIndex, attributeName, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}
static createForHostClass(directiveIndex: DirectiveIndex, ast: AST,
className: string): BindingRecord {
var t = new BindingTarget(ELEMENT_CLASS, directiveIndex.elementIndex, className, null,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}
static createForHostStyle(directiveIndex: DirectiveIndex, ast: AST, styleName: string,
unit: string): BindingRecord {
var t = new BindingTarget(ELEMENT_STYLE, directiveIndex.elementIndex, styleName, unit,
ast.toString());
return new BindingRecord(BINDING, t, directiveIndex, ast, null, null, null);
}
static createForTextNode(ast: AST, elementIndex: number): BindingRecord {
var t = new BindingTarget(TEXT_NODE, elementIndex, null, null, ast.toString());
return new BindingRecord(BINDING, t, 0, ast, null, null, null);
}
static createForEvent(ast: AST, eventName: string, elementIndex: number): BindingRecord {
var t = new BindingTarget(EVENT, elementIndex, eventName, null, ast.toString());
return new BindingRecord(EVENT, t, 0, ast, null, null, null);
}
static createForHostEvent(ast: AST, eventName: string,
directiveRecord: DirectiveRecord): BindingRecord {
var directiveIndex = directiveRecord.directiveIndex;
var t =
new BindingTarget(HOST_EVENT, directiveIndex.elementIndex, eventName, null, ast.toString());
return new BindingRecord(HOST_EVENT, t, directiveIndex, ast, null, null, directiveRecord);
}
}

View File

@ -0,0 +1,171 @@
import {JitProtoChangeDetector} from './jit_proto_change_detector';
import {PregenProtoChangeDetector} from './pregen_proto_change_detector';
import {DynamicProtoChangeDetector} from './proto_change_detector';
import {IterableDiffers, IterableDifferFactory} from './differs/iterable_differs';
import {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
import {KeyValueDiffers, KeyValueDifferFactory} from './differs/keyvalue_differs';
import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
import {
ChangeDetection,
ProtoChangeDetector,
ChangeDetectorDefinition,
ChangeDetectorGenConfig
} from './interfaces';
import {Injector, Inject, Injectable, OpaqueToken, Optional, Binding} from 'angular2/di';
import {List, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {
CONST,
CONST_EXPR,
isPresent,
BaseException,
assertionsEnabled
} from 'angular2/src/facade/lang';
export {
ASTWithSource,
AST,
AstTransformer,
PropertyRead,
LiteralArray,
ImplicitReceiver
} from './parser/ast';
export {Lexer} from './parser/lexer';
export {Parser} from './parser/parser';
export {Locals} from './parser/locals';
export {
DehydratedException,
ExpressionChangedAfterItHasBeenCheckedException,
ChangeDetectionError
} from './exceptions';
export {
ProtoChangeDetector,
ChangeDetector,
ChangeDispatcher,
ChangeDetection,
ChangeDetectorDefinition,
DebugContext,
ChangeDetectorGenConfig
} from './interfaces';
export {CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED, ON_PUSH, DEFAULT} from './constants';
export {DynamicProtoChangeDetector} from './proto_change_detector';
export {BindingRecord, BindingTarget} from './binding_record';
export {DirectiveIndex, DirectiveRecord} from './directive_record';
export {DynamicChangeDetector} from './dynamic_change_detector';
export {ChangeDetectorRef} from './change_detector_ref';
export {IterableDiffers, IterableDiffer, IterableDifferFactory} from './differs/iterable_differs';
export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs';
export {PipeTransform, PipeOnDestroy} from './pipe_transform';
export {WrappedValue} from './change_detection_util';
/**
* Structural diffing for `Object`s and `Map`s.
*/
export const keyValDiff: KeyValueDifferFactory[] =
CONST_EXPR([CONST_EXPR(new DefaultKeyValueDifferFactory())]);
/**
* Structural diffing for `Iterable` types such as `Array`s.
*/
export const iterableDiff: IterableDifferFactory[] =
CONST_EXPR([CONST_EXPR(new DefaultIterableDifferFactory())]);
export const defaultIterableDiffers = CONST_EXPR(new IterableDiffers(iterableDiff));
export const defaultKeyValueDiffers = CONST_EXPR(new KeyValueDiffers(keyValDiff));
/**
* Map from {@link ChangeDetectorDefinition#id} to a factory method which takes a
* {@link Pipes} and a {@link ChangeDetectorDefinition} and generates a
* {@link ProtoChangeDetector} associated with the definition.
*/
// TODO(kegluneq): Use PregenProtoChangeDetectorFactory rather than Function once possible in
// dart2js. See https://github.com/dart-lang/sdk/issues/23630 for details.
export var preGeneratedProtoDetectors: StringMap<string, Function> = {};
/**
* Implements change detection using a map of pregenerated proto detectors.
*/
@Injectable()
export class PreGeneratedChangeDetection extends ChangeDetection {
_dynamicChangeDetection: ChangeDetection;
_protoChangeDetectorFactories: StringMap<string, Function>;
_genConfig: ChangeDetectorGenConfig;
constructor(config?: ChangeDetectorGenConfig,
protoChangeDetectorsForTest?: StringMap<string, Function>) {
super();
this._dynamicChangeDetection = new DynamicChangeDetection();
this._protoChangeDetectorFactories = isPresent(protoChangeDetectorsForTest) ?
protoChangeDetectorsForTest :
preGeneratedProtoDetectors;
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); }
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
if (StringMapWrapper.contains(this._protoChangeDetectorFactories, id)) {
return StringMapWrapper.get(this._protoChangeDetectorFactories, id)(definition);
}
return this._dynamicChangeDetection.getProtoChangeDetector(id, definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
}
/**
* Implements change detection that does not require `eval()`.
*
* This is slower than {@link JitChangeDetection}.
*/
@Injectable()
export class DynamicChangeDetection extends ChangeDetection {
_genConfig: ChangeDetectorGenConfig;
constructor(config?: ChangeDetectorGenConfig) {
super();
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return new DynamicProtoChangeDetector(definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
}
/**
* Implements faster change detection by generating source code.
*
* This requires `eval()`. For change detection that does not require `eval()`, see
* {@link DynamicChangeDetection} and {@link PreGeneratedChangeDetection}.
*/
@Injectable()
export class JitChangeDetection extends ChangeDetection {
_genConfig: ChangeDetectorGenConfig;
constructor(config?: ChangeDetectorGenConfig) {
super();
this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false);
}
static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); }
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return new JitProtoChangeDetector(definition);
}
get genConfig(): ChangeDetectorGenConfig { return this._genConfig; }
get generateDetectors(): boolean { return true; }
}

View File

@ -0,0 +1,17 @@
library change_detection.change_detection_jit_generator;
/// Placeholder JIT generator for Dart.
/// Dart does not support `eval`, so JIT generation is not an option. Instead,
/// the Dart transformer pre-generates these Change Detector classes and
/// registers them with the system. See `PreGeneratedChangeDetection`,
/// `PregenProtoChangeDetector`, and
/// `src/transform/template_compiler/change_detector_codegen.dart` for details.
class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, strategy, records, directiveMementos) {}
generate() {
throw "Jit Change Detection is not supported in Dart";
}
static bool isSupported() => false;
}

View File

@ -0,0 +1,384 @@
import {BaseException, Type, isBlank, isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {AbstractChangeDetector} from './abstract_change_detector';
import {ChangeDetectionUtil} from './change_detection_util';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
import {ProtoRecord, RecordType} from './proto_record';
import {CodegenNameUtil, sanitizeName} from './codegen_name_util';
import {CodegenLogicUtil} from './codegen_logic_util';
import {codify} from './codegen_facade';
import {EventBinding} from './event_binding';
import {BindingTarget} from './binding_record';
import {ChangeDetectorGenConfig} from './interfaces';
/**
* The code generator takes a list of proto records and creates a function/class
* that "emulates" what the developer would write by hand to implement the same
* kind of behaviour.
*
* This code should be kept in sync with the Dart transformer's
* `angular2.transform.template_compiler.change_detector_codegen` library. If you make updates
* here, please make equivalent changes there.
*/
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
var UTIL = "ChangeDetectionUtil";
var IS_CHANGED_LOCAL = "isChanged";
var CHANGES_LOCAL = "changes";
export class ChangeDetectorJITGenerator {
_logic: CodegenLogicUtil;
_names: CodegenNameUtil;
_typeName: string;
constructor(private id: string, private changeDetectionStrategy: string,
private records: List<ProtoRecord>, private propertyBindingTargets: BindingTarget[],
private eventBindings: EventBinding[], private directiveRecords: List<any>,
private genConfig: ChangeDetectorGenConfig) {
this._names =
new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL);
this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy);
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
}
generate(): Function {
var classDefinition = `
var ${this._typeName} = function ${this._typeName}(dispatcher) {
${ABSTRACT_CHANGE_DETECTOR}.call(
this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length},
${this._typeName}.gen_propertyBindingTargets, ${this._typeName}.gen_directiveIndices,
${codify(this.changeDetectionStrategy)});
this.dehydrateDirectives(false);
}
${this._typeName}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
${this._typeName}.prototype.detectChangesInRecordsInternal = function(throwOnChange) {
${this._names.genInitLocals()}
var ${IS_CHANGED_LOCAL} = false;
var ${CHANGES_LOCAL} = null;
${this.records.map((r) => this._genRecord(r)).join("\n")}
${this._names.getAlreadyCheckedName()} = true;
}
${this._maybeGenHandleEventInternal()}
${this._genCheckNoChanges()}
${this._maybeGenCallOnAllChangesDone()}
${this._maybeGenHydrateDirectives()}
${this._maybeGenDehydrateDirectives()}
${this._genPropertyBindingTargets()};
${this._genDirectiveIndices()};
return function(dispatcher) {
return new ${this._typeName}(dispatcher);
}
`;
return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, classDefinition)(AbstractChangeDetector,
ChangeDetectionUtil);
}
_genPropertyBindingTargets(): string {
var targets = this._logic.genPropertyBindingTargets(this.propertyBindingTargets,
this.genConfig.genDebugInfo);
return `${this._typeName}.gen_propertyBindingTargets = ${targets};`;
}
_genDirectiveIndices(): string {
var indices = this._logic.genDirectiveIndices(this.directiveRecords);
return `${this._typeName}.gen_directiveIndices = ${indices};`;
}
_maybeGenHandleEventInternal(): string {
if (this.eventBindings.length > 0) {
var handlers = this.eventBindings.map(eb => this._genEventBinding(eb)).join("\n");
return `
${this._typeName}.prototype.handleEventInternal = function(eventName, elIndex, locals) {
var ${this._names.getPreventDefaultAccesor()} = false;
${this._names.genInitEventLocals()}
${handlers}
return ${this._names.getPreventDefaultAccesor()};
}
`;
} else {
return '';
}
}
_genEventBinding(eb: EventBinding): string {
var recs = eb.records.map(r => this._genEventBindingEval(eb, r)).join("\n");
return `
if (eventName === "${eb.eventName}" && elIndex === ${eb.elIndex}) {
${recs}
}`;
}
_genEventBindingEval(eb: EventBinding, r: ProtoRecord): string {
if (r.lastInBinding) {
var evalRecord = this._logic.genEventBindingEvalValue(eb, r);
var markPath = this._genMarkPathToRootAsCheckOnce(r);
var prevDefault = this._genUpdatePreventDefault(eb, r);
return `${evalRecord}\n${markPath}\n${prevDefault}`;
} else {
return this._logic.genEventBindingEvalValue(eb, r);
}
}
_genMarkPathToRootAsCheckOnce(r: ProtoRecord): string {
var br = r.bindingRecord;
if (br.isDefaultChangeDetection()) {
return "";
} else {
return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`;
}
}
_genUpdatePreventDefault(eb: EventBinding, r: ProtoRecord): string {
var local = this._names.getEventLocalName(eb, r.selfIndex);
return `if (${local} === false) { ${this._names.getPreventDefaultAccesor()} = true};`;
}
_maybeGenDehydrateDirectives(): string {
var destroyPipesCode = this._names.genPipeOnDestroy();
if (destroyPipesCode) {
destroyPipesCode = `if (destroyPipes) { ${destroyPipesCode} }`;
}
var dehydrateFieldsCode = this._names.genDehydrateFields();
if (!destroyPipesCode && !dehydrateFieldsCode) return '';
return `${this._typeName}.prototype.dehydrateDirectives = function(destroyPipes) {
${destroyPipesCode}
${dehydrateFieldsCode}
}`;
}
_maybeGenHydrateDirectives(): string {
var hydrateDirectivesCode = this._logic.genHydrateDirectives(this.directiveRecords);
var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords);
if (!hydrateDirectivesCode && !hydrateDetectorsCode) return '';
return `${this._typeName}.prototype.hydrateDirectives = function(directives) {
${hydrateDirectivesCode}
${hydrateDetectorsCode}
}`;
}
_maybeGenCallOnAllChangesDone(): string {
var notifications = [];
var dirs = this.directiveRecords;
// NOTE(kegluneq): Order is important!
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callOnAllChangesDone) {
notifications.push(
`${this._names.getDirectiveName(dir.directiveIndex)}.onAllChangesDone();`);
}
}
if (notifications.length > 0) {
var directiveNotifications = notifications.join("\n");
return `
${this._typeName}.prototype.callOnAllChangesDone = function() {
${ABSTRACT_CHANGE_DETECTOR}.prototype.callOnAllChangesDone.call(this);
${directiveNotifications}
}
`;
} else {
return '';
}
}
_genRecord(r: ProtoRecord): string {
var rec;
if (r.isLifeCycleRecord()) {
rec = this._genDirectiveLifecycle(r);
} else if (r.isPipeRecord()) {
rec = this._genPipeCheck(r);
} else {
rec = this._genReferenceCheck(r);
}
return `
${this._maybeFirstInBinding(r)}
${rec}
${this._maybeGenLastInDirective(r)}
`;
}
_genDirectiveLifecycle(r: ProtoRecord): string {
if (r.name === "onCheck") {
return this._genOnCheck(r);
} else if (r.name === "onInit") {
return this._genOnInit(r);
} else if (r.name === "onChange") {
return this._genOnChange(r);
} else {
throw new BaseException(`Unknown lifecycle event '${r.name}'`);
}
}
_genPipeCheck(r: ProtoRecord): string {
var context = this._names.getLocalName(r.contextIndex);
var argString = r.args.map((arg) => this._names.getLocalName(arg)).join(", ");
var oldValue = this._names.getFieldName(r.selfIndex);
var newValue = this._names.getLocalName(r.selfIndex);
var pipe = this._names.getPipeName(r.selfIndex);
var pipeType = r.name;
var read = `
if (${pipe} === ${UTIL}.uninitialized) {
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}');
}
${newValue} = ${pipe}.transform(${context}, [${argString}]);
`;
var check = `
if (${oldValue} !== ${newValue}) {
${newValue} = ${UTIL}.unwrapValue(${newValue})
${this._genChangeMarker(r)}
${this._genUpdateDirectiveOrElement(r)}
${this._genAddToChanges(r)}
${oldValue} = ${newValue};
}
`;
return r.shouldBeChecked() ? `${read}${check}` : read;
}
_genReferenceCheck(r: ProtoRecord): string {
var oldValue = this._names.getFieldName(r.selfIndex);
var newValue = this._names.getLocalName(r.selfIndex);
var read = `
${this._logic.genPropertyBindingEvalValue(r)}
`;
var check = `
if (${newValue} !== ${oldValue}) {
${this._genChangeMarker(r)}
${this._genUpdateDirectiveOrElement(r)}
${this._genAddToChanges(r)}
${oldValue} = ${newValue};
}
`;
var genCode = r.shouldBeChecked() ? `${read}${check}` : read;
if (r.isPureFunction()) {
var condition = r.args.map((a) => this._names.getChangeName(a)).join(" || ");
if (r.isUsedByOtherRecord()) {
return `if (${condition}) { ${genCode} } else { ${newValue} = ${oldValue}; }`;
} else {
return `if (${condition}) { ${genCode} }`;
}
} else {
return genCode;
}
}
_genChangeMarker(r: ProtoRecord): string {
return r.argumentToPureFunction ? `${this._names.getChangeName(r.selfIndex)} = true` : ``;
}
_genUpdateDirectiveOrElement(r: ProtoRecord): string {
if (!r.lastInBinding) return "";
var newValue = this._names.getLocalName(r.selfIndex);
var oldValue = this._names.getFieldName(r.selfIndex);
var notifyDebug = this.genConfig.logBindingUpdate ? `this.logBindingUpdate(${newValue});` : "";
var br = r.bindingRecord;
if (br.target.isDirective()) {
var directiveProperty =
`${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.target.name}`;
return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
${directiveProperty} = ${newValue};
${notifyDebug}
${IS_CHANGED_LOCAL} = true;
`;
} else {
return `
${this._genThrowOnChangeCheck(oldValue, newValue)}
this.notifyDispatcher(${newValue});
${notifyDebug}
`;
}
}
_genThrowOnChangeCheck(oldValue: string, newValue: string): string {
if (this.genConfig.genCheckNoChanges) {
return `
if(throwOnChange) {
this.throwOnChangeError(${oldValue}, ${newValue});
}
`;
} else {
return '';
}
}
_genCheckNoChanges(): string {
if (this.genConfig.genCheckNoChanges) {
return `${this._typeName}.prototype.checkNoChanges = function() { this.runDetectChanges(true); }`;
} else {
return '';
}
}
_genAddToChanges(r: ProtoRecord): string {
var newValue = this._names.getLocalName(r.selfIndex);
var oldValue = this._names.getFieldName(r.selfIndex);
if (!r.bindingRecord.callOnChange()) return "";
return `${CHANGES_LOCAL} = this.addChange(${CHANGES_LOCAL}, ${oldValue}, ${newValue});`;
}
_maybeFirstInBinding(r: ProtoRecord): string {
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
var firstInBindng = isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
return firstInBindng && !r.bindingRecord.isDirectiveLifecycle() ?
`${this._names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};` :
'';
}
_maybeGenLastInDirective(r: ProtoRecord): string {
if (!r.lastInDirective) return "";
return `
${CHANGES_LOCAL} = null;
${this._genNotifyOnPushDetectors(r)}
${IS_CHANGED_LOCAL} = false;
`;
}
_genOnCheck(r: ProtoRecord): string {
var br = r.bindingRecord;
return `if (!throwOnChange) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onCheck();`;
}
_genOnInit(r: ProtoRecord): string {
var br = r.bindingRecord;
return `if (!throwOnChange && !${this._names.getAlreadyCheckedName()}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();`;
}
_genOnChange(r: ProtoRecord): string {
var br = r.bindingRecord;
return `if (!throwOnChange && ${CHANGES_LOCAL}) ${this._names.getDirectiveName(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});`;
}
_genNotifyOnPushDetectors(r: ProtoRecord): string {
var br = r.bindingRecord;
if (!r.lastInDirective || br.isDefaultChangeDetection()) return "";
var retVal = `
if(${IS_CHANGED_LOCAL}) {
${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
}
`;
return retVal;
}
}

View File

@ -0,0 +1,215 @@
import {
CONST_EXPR,
isPresent,
isBlank,
BaseException,
Type,
StringWrapper
} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {ProtoRecord} from './proto_record';
import {
CHECK_ALWAYS,
CHECK_ONCE,
CHECKED,
DETACHED,
isDefaultChangeDetectionStrategy
} from './constants';
import {implementsOnDestroy} from './pipe_lifecycle_reflector';
import {BindingTarget} from './binding_record';
import {DirectiveIndex} from './directive_record';
/**
* Indicates that the result of a {@link PipeMetadata} transformation has changed even though the
* reference
* has not changed.
*
* The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored.
*
* Example:
*
* ```
* if (this._latestValue === this._latestReturnedValue) {
* return this._latestReturnedValue;
* } else {
* this._latestReturnedValue = this._latestValue;
* return WrappedValue.wrap(this._latestValue); // this will force update
* }
* ```
*/
export class WrappedValue {
constructor(public wrapped: any) {}
static wrap(value: any): WrappedValue {
var w = _wrappedValues[_wrappedIndex++ % 5];
w.wrapped = value;
return w;
}
}
var _wrappedValues = [
new WrappedValue(null),
new WrappedValue(null),
new WrappedValue(null),
new WrappedValue(null),
new WrappedValue(null)
];
var _wrappedIndex = 0;
export class SimpleChange {
constructor(public previousValue: any, public currentValue: any) {}
isFirstChange(): boolean { return this.previousValue === ChangeDetectionUtil.uninitialized; }
}
var _simpleChangesIndex = 0;
var _simpleChanges = [
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null)
];
function _simpleChange(previousValue, currentValue): SimpleChange {
var index = _simpleChangesIndex++ % 20;
var s = _simpleChanges[index];
s.previousValue = previousValue;
s.currentValue = currentValue;
return s;
}
/* tslint:disable:requireParameterType */
export class ChangeDetectionUtil {
static uninitialized: Object = CONST_EXPR<Object>(new Object());
static arrayFn0(): any[] { return []; }
static arrayFn1(a1): any[] { return [a1]; }
static arrayFn2(a1, a2): any[] { return [a1, a2]; }
static arrayFn3(a1, a2, a3): any[] { return [a1, a2, a3]; }
static arrayFn4(a1, a2, a3, a4): any[] { return [a1, a2, a3, a4]; }
static arrayFn5(a1, a2, a3, a4, a5): any[] { return [a1, a2, a3, a4, a5]; }
static arrayFn6(a1, a2, a3, a4, a5, a6): any[] { return [a1, a2, a3, a4, a5, a6]; }
static arrayFn7(a1, a2, a3, a4, a5, a6, a7): any[] { return [a1, a2, a3, a4, a5, a6, a7]; }
static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8): any[] {
return [a1, a2, a3, a4, a5, a6, a7, a8];
}
static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9): any[] {
return [a1, a2, a3, a4, a5, a6, a7, a8, a9];
}
static operation_negate(value): any { return !value; }
static operation_add(left, right): any { return left + right; }
static operation_subtract(left, right): any { return left - right; }
static operation_multiply(left, right): any { return left * right; }
static operation_divide(left, right): any { return left / right; }
static operation_remainder(left, right): any { return left % right; }
static operation_equals(left, right): any { return left == right; }
static operation_not_equals(left, right): any { return left != right; }
static operation_identical(left, right): any { return left === right; }
static operation_not_identical(left, right): any { return left !== right; }
static operation_less_then(left, right): any { return left < right; }
static operation_greater_then(left, right): any { return left > right; }
static operation_less_or_equals_then(left, right): any { return left <= right; }
static operation_greater_or_equals_then(left, right): any { return left >= right; }
static operation_logical_and(left, right): any { return left && right; }
static operation_logical_or(left, right): any { return left || right; }
static cond(cond, trueVal, falseVal): any { return cond ? trueVal : falseVal; }
static mapFn(keys: List<any>): any {
function buildMap(values): StringMap<any, any> {
var res = StringMapWrapper.create();
for (var i = 0; i < keys.length; ++i) {
StringMapWrapper.set(res, keys[i], values[i]);
}
return res;
}
switch (keys.length) {
case 0:
return () => [];
case 1:
return (a1) => buildMap([a1]);
case 2:
return (a1, a2) => buildMap([a1, a2]);
case 3:
return (a1, a2, a3) => buildMap([a1, a2, a3]);
case 4:
return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
case 5:
return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
case 6:
return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
case 7:
return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
case 8:
return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
case 9:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
default:
throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
static keyedAccess(obj, args): any { return obj[args[0]]; }
static unwrapValue(value: any): any {
if (value instanceof WrappedValue) {
return value.wrapped;
} else {
return value;
}
}
static changeDetectionMode(strategy: string): string {
return isDefaultChangeDetectionStrategy(strategy) ? CHECK_ALWAYS : CHECK_ONCE;
}
static simpleChange(previousValue: any, currentValue: any): SimpleChange {
return _simpleChange(previousValue, currentValue);
}
static isValueBlank(value: any): boolean { return isBlank(value); }
static s(value: any): string { return isPresent(value) ? `${value}` : ''; }
static protoByIndex(protos: ProtoRecord[], selfIndex: number): ProtoRecord {
return selfIndex < 1 ?
null :
protos[selfIndex - 1]; // self index is shifted by one because of context
}
static callPipeOnDestroy(pipe: any): void {
if (implementsOnDestroy(pipe)) {
pipe.onDestroy();
}
}
static bindingTarget(mode: string, elementIndex: number, name: string, unit: string,
debug: string): BindingTarget {
return new BindingTarget(mode, elementIndex, name, unit, debug);
}
static directiveIndex(elementIndex: number, directiveIndex: number): DirectiveIndex {
return new DirectiveIndex(elementIndex, directiveIndex);
}
}

View File

@ -0,0 +1,39 @@
import {ChangeDetector} from './interfaces';
import {CHECK_ONCE, DETACHED, CHECK_ALWAYS} from './constants';
/**
* Controls change detection.
*
* {@link ChangeDetectorRef} allows requesting checks for detectors that rely on observables. It
* also allows detaching and attaching change detector subtrees.
*/
export class ChangeDetectorRef {
/**
* @private
*/
constructor(private _cd: ChangeDetector) {}
/**
* Request to check all ON_PUSH ancestors.
*/
requestCheck(): void { this._cd.markPathToRootAsCheckOnce(); }
/**
* Detaches the change detector from the change detector tree.
*
* The detached change detector will not be checked until it is reattached.
*/
detach(): void { this._cd.mode = DETACHED; }
/**
* Reattach the change detector to the change detector tree.
*
* This also requests a check of this change detector. This reattached change detector will be
*checked during the
* next change detection run.
*/
reattach(): void {
this._cd.mode = CHECK_ALWAYS;
this.requestCheck();
}
}

View File

@ -0,0 +1,81 @@
import {isPresent, isBlank, looseIdentical} from 'angular2/src/facade/lang';
import {List, ListWrapper, Map} from 'angular2/src/facade/collection';
import {RecordType, ProtoRecord} from './proto_record';
/**
* Removes "duplicate" records. It assuming that record evaluation does not
* have side-effects.
*
* Records that are not last in bindings are removed and all the indices
* of the records that depend on them are updated.
*
* Records that are last in bindings CANNOT be removed, and instead are
* replaced with very cheap SELF records.
*/
export function coalesce(records: ProtoRecord[]): ProtoRecord[] {
var res: List<ProtoRecord> = [];
var indexMap: Map<number, number> = new Map<number, number>();
for (var i = 0; i < records.length; ++i) {
var r = records[i];
var record = _replaceIndices(r, res.length + 1, indexMap);
var matchingRecord = _findMatching(record, res);
if (isPresent(matchingRecord) && record.lastInBinding) {
res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1));
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
matchingRecord.referencedBySelf = true;
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
if (record.argumentToPureFunction) {
matchingRecord.argumentToPureFunction = true;
}
indexMap.set(r.selfIndex, matchingRecord.selfIndex);
} else {
res.push(record);
indexMap.set(r.selfIndex, record.selfIndex);
}
}
return res;
}
function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
return new ProtoRecord(RecordType.SELF, "self", null, [], r.fixedArgs, contextIndex,
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
r.lastInDirective, false, false, r.propertyBindingIndex);
}
function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
return ListWrapper.find(
rs, (rr) => rr.mode !== RecordType.DIRECTIVE_LIFECYCLE && _sameDirIndex(rr, r) &&
rr.mode === r.mode && looseIdentical(rr.funcOrValue, r.funcOrValue) &&
rr.contextIndex === r.contextIndex && looseIdentical(rr.name, r.name) &&
ListWrapper.equals(rr.args, r.args));
}
function _sameDirIndex(a: ProtoRecord, b: ProtoRecord): boolean {
var di1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.directiveIndex;
var ei1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.elementIndex;
var di2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.directiveIndex;
var ei2 = isBlank(b.directiveIndex) ? null : b.directiveIndex.elementIndex;
return di1 === di2 && ei1 === ei2;
}
function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map<any, any>) {
var args = ListWrapper.map(r.args, (a) => _map(indexMap, a));
var contextIndex = _map(indexMap, r.contextIndex);
return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex,
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
r.lastInDirective, r.argumentToPureFunction, r.referencedBySelf,
r.propertyBindingIndex);
}
function _map(indexMap: Map<any, any>, value: number) {
var r = indexMap.get(value);
return isPresent(r) ? r : value;
}

View File

@ -0,0 +1,19 @@
library angular2.src.change_detection.codegen_facade;
import 'dart:convert' show JSON;
/// Converts `funcOrValue` to a string which can be used in generated code.
String codify(funcOrValue) => JSON.encode(funcOrValue).replaceAll(r'$', r'\$');
/// Combine the strings of generated code into a single interpolated string.
/// Each element of `vals` is expected to be a string literal or a codegen'd
/// call to a method returning a string.
/// The return format interpolates each value as an expression which reads
/// poorly, but the resulting code is easily flattened by dart2js.
String combineGeneratedStrings(List<String> vals) {
return '"${vals.map((v) => '\${$v}').join('')}"';
}
String rawString(String str) {
return "r'$str'";
}

View File

@ -0,0 +1,21 @@
import {List} from 'angular2/src/facade/collection';
/**
* Converts `funcOrValue` to a string which can be used in generated code.
*/
export function codify(obj: any): string {
return JSON.stringify(obj);
}
export function rawString(str: string): string {
return `'${str}'`;
}
/**
* Combine the strings of generated code into a single interpolated string.
* Each element of `vals` is expected to be a string literal or a codegen'd
* call to a method returning a string.
*/
export function combineGeneratedStrings(vals: string[]): string {
return vals.join(' + ');
}

View File

@ -0,0 +1,180 @@
import {ListWrapper} from 'angular2/src/facade/collection';
import {BaseException, Json, StringWrapper, isPresent, isBlank} from 'angular2/src/facade/lang';
import {CodegenNameUtil} from './codegen_name_util';
import {codify, combineGeneratedStrings, rawString} from './codegen_facade';
import {ProtoRecord, RecordType} from './proto_record';
import {BindingTarget} from './binding_record';
import {DirectiveRecord} from './directive_record';
import {ON_PUSH_OBSERVE} from './constants';
/**
* Class responsible for providing change detection logic for chagne detector classes.
*/
export class CodegenLogicUtil {
constructor(private _names: CodegenNameUtil, private _utilName: string,
private _changeDetection: string) {}
/**
* Generates a statement which updates the local variable representing `protoRec` with the current
* value of the record. Used by property bindings.
*/
genPropertyBindingEvalValue(protoRec: ProtoRecord): string {
return this.genEvalValue(protoRec, idx => this._names.getLocalName(idx),
this._names.getLocalsAccessorName());
}
/**
* Generates a statement which updates the local variable representing `protoRec` with the current
* value of the record. Used by event bindings.
*/
genEventBindingEvalValue(eventRecord: any, protoRec: ProtoRecord): string {
return this.genEvalValue(protoRec, idx => this._names.getEventLocalName(eventRecord, idx),
"locals");
}
private genEvalValue(protoRec: ProtoRecord, getLocalName: Function,
localsAccessor: string): string {
var context = (protoRec.contextIndex == -1) ?
this._names.getDirectiveName(protoRec.directiveIndex) :
getLocalName(protoRec.contextIndex);
var argString = ListWrapper.map(protoRec.args, (arg) => getLocalName(arg)).join(", ");
var rhs: string;
switch (protoRec.mode) {
case RecordType.SELF:
rhs = context;
break;
case RecordType.CONST:
rhs = codify(protoRec.funcOrValue);
break;
case RecordType.PROPERTY_READ:
rhs = this._observe(`${context}.${protoRec.name}`, protoRec);
break;
case RecordType.SAFE_PROPERTY:
var read = this._observe(`${context}.${protoRec.name}`, protoRec);
rhs =
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(read, protoRec)}`;
break;
case RecordType.PROPERTY_WRITE:
rhs = `${context}.${protoRec.name} = ${getLocalName(protoRec.args[0])}`;
break;
case RecordType.LOCAL:
rhs = this._observe(`${localsAccessor}.get(${rawString(protoRec.name)})`, protoRec);
break;
case RecordType.INVOKE_METHOD:
rhs = this._observe(`${context}.${protoRec.name}(${argString})`, protoRec);
break;
case RecordType.SAFE_INVOKE_METHOD:
var invoke = `${context}.${protoRec.name}(${argString})`;
rhs =
`${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(invoke, protoRec)}`;
break;
case RecordType.INVOKE_CLOSURE:
rhs = `${context}(${argString})`;
break;
case RecordType.PRIMITIVE_OP:
rhs = `${this._utilName}.${protoRec.name}(${argString})`;
break;
case RecordType.COLLECTION_LITERAL:
rhs = `${this._utilName}.${protoRec.name}(${argString})`;
break;
case RecordType.INTERPOLATE:
rhs = this._genInterpolation(protoRec);
break;
case RecordType.KEYED_READ:
rhs = this._observe(`${context}[${getLocalName(protoRec.args[0])}]`, protoRec);
break;
case RecordType.KEYED_WRITE:
rhs = `${context}[${getLocalName(protoRec.args[0])}] = ${getLocalName(protoRec.args[1])}`;
break;
case RecordType.CHAIN:
rhs = 'null';
break;
default:
throw new BaseException(`Unknown operation ${protoRec.mode}`);
}
return `${getLocalName(protoRec.selfIndex)} = ${rhs};`;
}
_observe(exp: string, rec: ProtoRecord): string {
// This is an experimental feature. Works only in Dart.
if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) {
return `this.observeValue(${exp}, ${rec.selfIndex})`;
} else {
return exp;
}
}
genPropertyBindingTargets(propertyBindingTargets: BindingTarget[],
genDebugInfo: boolean): string {
var bs = propertyBindingTargets.map(b => {
if (isBlank(b)) return "null";
var debug = genDebugInfo ? codify(b.debug) : "null";
return `${this._utilName}.bindingTarget(${codify(b.mode)}, ${b.elementIndex}, ${codify(b.name)}, ${codify(b.unit)}, ${debug})`;
});
return `[${bs.join(", ")}]`;
}
genDirectiveIndices(directiveRecords: DirectiveRecord[]): string {
var bs = directiveRecords.map(
b =>
`${this._utilName}.directiveIndex(${b.directiveIndex.elementIndex}, ${b.directiveIndex.directiveIndex})`);
return `[${bs.join(", ")}]`;
}
_genInterpolation(protoRec: ProtoRecord): string {
var iVals = [];
for (var i = 0; i < protoRec.args.length; ++i) {
iVals.push(codify(protoRec.fixedArgs[i]));
iVals.push(`${this._utilName}.s(${this._names.getLocalName(protoRec.args[i])})`);
}
iVals.push(codify(protoRec.fixedArgs[protoRec.args.length]));
return combineGeneratedStrings(iVals);
}
genHydrateDirectives(directiveRecords: DirectiveRecord[]): string {
var res = [];
for (var i = 0; i < directiveRecords.length; ++i) {
var r = directiveRecords[i];
res.push(`${this._names.getDirectiveName(r.directiveIndex)} = ${this._genReadDirective(i)};`);
}
return res.join("\n");
}
private _genReadDirective(index: number) {
// This is an experimental feature. Works only in Dart.
if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) {
return `this.observeDirective(this.getDirectiveFor(directives, ${index}), ${index})`;
} else {
return `this.getDirectiveFor(directives, ${index})`;
}
}
genHydrateDetectors(directiveRecords: DirectiveRecord[]): string {
var res = [];
for (var i = 0; i < directiveRecords.length; ++i) {
var r = directiveRecords[i];
if (!r.isDefaultChangeDetection()) {
res.push(
`${this._names.getDetectorName(r.directiveIndex)} = this.getDetectorFor(directives, ${i});`);
}
}
return res.join("\n");
}
}

View File

@ -0,0 +1,198 @@
import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, Map} from 'angular2/src/facade/collection';
import {DirectiveIndex} from './directive_record';
import {ProtoRecord} from './proto_record';
import {EventBinding} from './event_binding';
// The names of these fields must be kept in sync with abstract_change_detector.ts or change
// detection will fail.
const _ALREADY_CHECKED_ACCESSOR = "alreadyChecked";
const _CONTEXT_ACCESSOR = "context";
const _PROP_BINDING_INDEX = "propertyBindingIndex";
const _DIRECTIVES_ACCESSOR = "directiveIndices";
const _DISPATCHER_ACCESSOR = "dispatcher";
const _LOCALS_ACCESSOR = "locals";
const _MODE_ACCESSOR = "mode";
const _PIPES_ACCESSOR = "pipes";
const _PROTOS_ACCESSOR = "protos";
// `context` is always first.
export const CONTEXT_INDEX = 0;
const _FIELD_PREFIX = 'this.';
var _whiteSpaceRegExp = RegExpWrapper.create("\\W", "g");
/**
* Returns `s` with all non-identifier characters removed.
*/
export function sanitizeName(s: string): string {
return StringWrapper.replaceAll(s, _whiteSpaceRegExp, '');
}
/**
* Class responsible for providing field and local variable names for change detector classes.
* Also provides some convenience functions, for example, declaring variables, destroying pipes,
* and dehydrating the detector.
*/
export class CodegenNameUtil {
/**
* Record names sanitized for use as fields.
* See [sanitizeName] for details.
*/
_sanitizedNames: List<string>;
_sanitizedEventNames: Map<EventBinding, List<string>>;
constructor(private records: List<ProtoRecord>, private eventBindings: EventBinding[],
private directiveRecords: List<any>, private utilName: string) {
this._sanitizedNames = ListWrapper.createFixedSize(this.records.length + 1);
this._sanitizedNames[CONTEXT_INDEX] = _CONTEXT_ACCESSOR;
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
this._sanitizedNames[i + 1] = sanitizeName(`${this.records[i].name}${i}`);
}
this._sanitizedEventNames = new Map();
for (var ebIndex = 0; ebIndex < eventBindings.length; ++ebIndex) {
var eb = eventBindings[ebIndex];
var names = [_CONTEXT_ACCESSOR];
for (var i = 0, iLen = eb.records.length; i < iLen; ++i) {
names.push(sanitizeName(`${eb.records[i].name}${i}_${ebIndex}`));
}
this._sanitizedEventNames.set(eb, names);
}
}
_addFieldPrefix(name: string): string { return `${_FIELD_PREFIX}${name}`; }
getDispatcherName(): string { return this._addFieldPrefix(_DISPATCHER_ACCESSOR); }
getPipesAccessorName(): string { return this._addFieldPrefix(_PIPES_ACCESSOR); }
getProtosName(): string { return this._addFieldPrefix(_PROTOS_ACCESSOR); }
getDirectivesAccessorName(): string { return this._addFieldPrefix(_DIRECTIVES_ACCESSOR); }
getLocalsAccessorName(): string { return this._addFieldPrefix(_LOCALS_ACCESSOR); }
getAlreadyCheckedName(): string { return this._addFieldPrefix(_ALREADY_CHECKED_ACCESSOR); }
getModeName(): string { return this._addFieldPrefix(_MODE_ACCESSOR); }
getPropertyBindingIndex(): string { return this._addFieldPrefix(_PROP_BINDING_INDEX); }
getLocalName(idx: number): string { return `l_${this._sanitizedNames[idx]}`; }
getEventLocalName(eb: EventBinding, idx: number): string {
return `l_${MapWrapper.get(this._sanitizedEventNames, eb)[idx]}`;
}
getChangeName(idx: number): string { return `c_${this._sanitizedNames[idx]}`; }
/**
* Generate a statement initializing local variables used when detecting changes.
*/
genInitLocals(): string {
var declarations = [];
var assignments = [];
for (var i = 0, iLen = this.getFieldCount(); i < iLen; ++i) {
if (i == CONTEXT_INDEX) {
declarations.push(`${this.getLocalName(i)} = ${this.getFieldName(i)}`);
} else {
var rec = this.records[i - 1];
if (rec.argumentToPureFunction) {
var changeName = this.getChangeName(i);
declarations.push(`${this.getLocalName(i)},${changeName}`);
assignments.push(changeName);
} else {
declarations.push(`${this.getLocalName(i)}`);
}
}
}
var assignmentsCode =
ListWrapper.isEmpty(assignments) ? '' : `${ListWrapper.join(assignments, '=')} = false;`;
return `var ${ListWrapper.join(declarations, ',')};${assignmentsCode}`;
}
/**
* Generate a statement initializing local variables for event handlers.
*/
genInitEventLocals(): string {
var res = [`${this.getLocalName(CONTEXT_INDEX)} = ${this.getFieldName(CONTEXT_INDEX)}`];
MapWrapper.forEach(this._sanitizedEventNames, (names, eb) => {
for (var i = 0; i < names.length; ++i) {
if (i !== CONTEXT_INDEX) {
res.push(`${this.getEventLocalName(eb, i)}`);
}
}
});
return res.length > 1 ? `var ${res.join(',')};` : '';
}
getPreventDefaultAccesor(): string { return "preventDefault"; }
getFieldCount(): number { return this._sanitizedNames.length; }
getFieldName(idx: number): string { return this._addFieldPrefix(this._sanitizedNames[idx]); }
getAllFieldNames(): List<string> {
var fieldList = [];
for (var k = 0, kLen = this.getFieldCount(); k < kLen; ++k) {
if (k === 0 || this.records[k - 1].shouldBeChecked()) {
fieldList.push(this.getFieldName(k));
}
}
for (var i = 0, iLen = this.records.length; i < iLen; ++i) {
var rec = this.records[i];
if (rec.isPipeRecord()) {
fieldList.push(this.getPipeName(rec.selfIndex));
}
}
for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) {
var dRec = this.directiveRecords[j];
fieldList.push(this.getDirectiveName(dRec.directiveIndex));
if (!dRec.isDefaultChangeDetection()) {
fieldList.push(this.getDetectorName(dRec.directiveIndex));
}
}
return fieldList;
}
/**
* Generates statements which clear all fields so that the change detector is dehydrated.
*/
genDehydrateFields(): string {
var fields = this.getAllFieldNames();
ListWrapper.removeAt(fields, CONTEXT_INDEX);
if (ListWrapper.isEmpty(fields)) return '';
// At least one assignment.
fields.push(`${this.utilName}.uninitialized;`);
return ListWrapper.join(fields, ' = ');
}
/**
* Generates statements destroying all pipe variables.
*/
genPipeOnDestroy(): string {
return ListWrapper.join(
ListWrapper.map(
ListWrapper.filter(this.records, (r) => { return r.isPipeRecord(); }),
(r) => {
return `${this.utilName}.callPipeOnDestroy(${this.getPipeName(r.selfIndex)});`;
}),
'\n');
}
getPipeName(idx: number): string {
return this._addFieldPrefix(`${this._sanitizedNames[idx]}_pipe`);
}
getDirectiveName(d: DirectiveIndex): string {
return this._addFieldPrefix(`directive_${d.name}`);
}
getDetectorName(d: DirectiveIndex): string { return this._addFieldPrefix(`detector_${d.name}`); }
}

View File

@ -0,0 +1,46 @@
// TODO:vsavkin Use enums after switching to TypeScript
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
/**
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
* will become CHECKED.
*/
export const CHECK_ONCE: string = "CHECK_ONCE";
/**
* CHECKED means that the change detector should be skipped until its mode changes to
* CHECK_ONCE or CHECK_ALWAYS.
*/
export const CHECKED: string = "CHECKED";
/**
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
* will remain CHECK_ALWAYS.
*/
export const CHECK_ALWAYS: string = "ALWAYS_CHECK";
/**
* DETACHED means that the change detector sub tree is not a part of the main tree and
* should be skipped.
*/
export const DETACHED: string = "DETACHED";
/**
* ON_PUSH means that the change detector's mode will be set to CHECK_ONCE during hydration.
*/
export const ON_PUSH: string = "ON_PUSH";
/**
* DEFAULT means that the change detector's mode will be set to CHECK_ALWAYS during hydration.
*/
export const DEFAULT: string = "DEFAULT";
export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: string): boolean {
return isBlank(changeDetectionStrategy) || StringWrapper.equals(changeDetectionStrategy, DEFAULT);
}
/**
* This is an experimental feature. Works only in Dart.
*/
export const ON_PUSH_OBSERVE = "ON_PUSH_OBSERVE";

View File

@ -0,0 +1,620 @@
import {CONST, BaseException} from 'angular2/src/facade/lang';
import {
isListLikeIterable,
iterateListLike,
ListWrapper,
MapWrapper
} from 'angular2/src/facade/collection';
import {
isBlank,
isPresent,
stringify,
getMapKey,
looseIdentical,
isArray
} from 'angular2/src/facade/lang';
import {ChangeDetectorRef} from '../change_detector_ref';
import {IterableDiffer, IterableDifferFactory} from '../differs/iterable_differs';
@CONST()
export class DefaultIterableDifferFactory implements IterableDifferFactory {
supports(obj: Object): boolean { return isListLikeIterable(obj); }
create(cdRef: ChangeDetectorRef): any { return new DefaultIterableDiffer(); }
}
export class DefaultIterableDiffer implements IterableDiffer {
private _collection = null;
private _length: number = null;
// Keeps track of the used records at any point in time (during & across `_check()` calls)
private _linkedRecords: _DuplicateMap = null;
// Keeps track of the removed records at any point in time during `_check()` calls.
private _unlinkedRecords: _DuplicateMap = null;
private _previousItHead: CollectionChangeRecord = null;
private _itHead: CollectionChangeRecord = null;
private _itTail: CollectionChangeRecord = null;
private _additionsHead: CollectionChangeRecord = null;
private _additionsTail: CollectionChangeRecord = null;
private _movesHead: CollectionChangeRecord = null;
private _movesTail: CollectionChangeRecord = null;
private _removalsHead: CollectionChangeRecord = null;
private _removalsTail: CollectionChangeRecord = null;
get collection() { return this._collection; }
get length(): number { return this._length; }
forEachItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._itHead; record !== null; record = record._next) {
fn(record);
}
}
forEachPreviousItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
fn(record);
}
}
forEachAddedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record);
}
}
forEachMovedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._movesHead; record !== null; record = record._nextMoved) {
fn(record);
}
}
forEachRemovedItem(fn: Function) {
var record: CollectionChangeRecord;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record);
}
}
diff(collection: any): DefaultIterableDiffer {
if (isBlank(collection)) collection = [];
if (!isListLikeIterable(collection)) {
throw new BaseException(`Error trying to diff '${collection}'`);
}
if (this.check(collection)) {
return this;
} else {
return null;
}
}
onDestroy() {}
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
check(collection: any): boolean {
this._reset();
var record: CollectionChangeRecord = this._itHead;
var mayBeDirty: boolean = false;
var index: number;
var item;
if (isArray(collection)) {
var list = collection;
this._length = collection.length;
for (index = 0; index < this._length; index++) {
item = list[index];
if (record === null || !looseIdentical(record.item, item)) {
record = this._mismatch(record, item, index);
mayBeDirty = true;
} else if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, index);
}
record = record._next;
}
} else {
index = 0;
iterateListLike(collection, (item) => {
if (record === null || !looseIdentical(record.item, item)) {
record = this._mismatch(record, item, index);
mayBeDirty = true;
} else if (mayBeDirty) {
// TODO(misko): can we limit this to duplicates only?
record = this._verifyReinsertion(record, item, index);
}
record = record._next;
index++;
});
this._length = index;
}
this._truncate(record);
this._collection = collection;
return this.isDirty;
}
// CollectionChanges is considered dirty if it has any additions, moves or removals.
get isDirty(): boolean {
return this._additionsHead !== null || this._movesHead !== null || this._removalsHead !== null;
}
/**
* Reset the state of the change objects to show no changes. This means set previousKey to
* currentKey, and clear all of the queues (additions, moves, removals).
* Set the previousIndexes of moved and added items to their currentIndexes
* Reset the list of additions, moves and removals
*/
_reset() {
if (this.isDirty) {
var record: CollectionChangeRecord;
var nextRecord: CollectionChangeRecord;
for (record = this._previousItHead = this._itHead; record !== null; record = record._next) {
record._nextPrevious = record._next;
}
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
record.previousIndex = record.currentIndex;
}
this._additionsHead = this._additionsTail = null;
for (record = this._movesHead; record !== null; record = nextRecord) {
record.previousIndex = record.currentIndex;
nextRecord = record._nextMoved;
}
this._movesHead = this._movesTail = null;
this._removalsHead = this._removalsTail = null;
// todo(vicb) when assert gets supported
// assert(!this.isDirty);
}
}
/**
* This is the core function which handles differences between collections.
*
* - `record` is the record which we saw at this position last time. If null then it is a new
* item.
* - `item` is the current item in the collection
* - `index` is the position of the item in the collection
*/
_mismatch(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord {
// The previous record after which we will append the current one.
var previousRecord: CollectionChangeRecord;
if (record === null) {
previousRecord = this._itTail;
} else {
previousRecord = record._prev;
// Remove the record from the collection since we know it does not match the item.
this._remove(record);
}
// Attempt to see if we have seen the item before.
record = this._linkedRecords === null ? null : this._linkedRecords.get(item, index);
if (record !== null) {
// We have seen this before, we need to move it forward in the collection.
this._moveAfter(record, previousRecord, index);
} else {
// Never seen it, check evicted list.
record = this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
if (record !== null) {
// It is an item which we have evicted earlier: reinsert it back into the list.
this._reinsertAfter(record, previousRecord, index);
} else {
// It is a new item: add it.
record = this._addAfter(new CollectionChangeRecord(item), previousRecord, index);
}
}
return record;
}
/**
* This check is only needed if an array contains duplicates. (Short circuit of nothing dirty)
*
* Use case: `[a, a]` => `[b, a, a]`
*
* If we did not have this check then the insertion of `b` would:
* 1) evict first `a`
* 2) insert `b` at `0` index.
* 3) leave `a` at index `1` as is. <-- this is wrong!
* 3) reinsert `a` at index 2. <-- this is wrong!
*
* The correct behavior is:
* 1) evict first `a`
* 2) insert `b` at `0` index.
* 3) reinsert `a` at index 1.
* 3) move `a` at from `1` to `2`.
*
*
* Double check that we have not evicted a duplicate item. We need to check if the item type may
* have already been removed:
* The insertion of b will evict the first 'a'. If we don't reinsert it now it will be reinserted
* at the end. Which will show up as the two 'a's switching position. This is incorrect, since a
* better way to think of it is as insert of 'b' rather then switch 'a' with 'b' and then add 'a'
* at the end.
*/
_verifyReinsertion(record: CollectionChangeRecord, item, index: number): CollectionChangeRecord {
var reinsertRecord: CollectionChangeRecord =
this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
if (reinsertRecord !== null) {
record = this._reinsertAfter(reinsertRecord, record._prev, index);
} else if (record.currentIndex != index) {
record.currentIndex = index;
this._addToMoves(record, index);
}
return record;
}
/**
* Get rid of any excess {@link CollectionChangeRecord}s from the previous collection
*
* - `record` The first excess {@link CollectionChangeRecord}.
*/
_truncate(record: CollectionChangeRecord) {
// Anything after that needs to be removed;
while (record !== null) {
var nextRecord: CollectionChangeRecord = record._next;
this._addToRemovals(this._unlink(record));
record = nextRecord;
}
if (this._unlinkedRecords !== null) {
this._unlinkedRecords.clear();
}
if (this._additionsTail !== null) {
this._additionsTail._nextAdded = null;
}
if (this._movesTail !== null) {
this._movesTail._nextMoved = null;
}
if (this._itTail !== null) {
this._itTail._next = null;
}
if (this._removalsTail !== null) {
this._removalsTail._nextRemoved = null;
}
}
_reinsertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
if (this._unlinkedRecords !== null) {
this._unlinkedRecords.remove(record);
}
var prev = record._prevRemoved;
var next = record._nextRemoved;
if (prev === null) {
this._removalsHead = next;
} else {
prev._nextRemoved = next;
}
if (next === null) {
this._removalsTail = prev;
} else {
next._prevRemoved = prev;
}
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
_moveAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
this._unlink(record);
this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index);
return record;
}
_addAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
this._insertAfter(record, prevRecord, index);
if (this._additionsTail === null) {
// todo(vicb)
// assert(this._additionsHead === null);
this._additionsTail = this._additionsHead = record;
} else {
// todo(vicb)
// assert(_additionsTail._nextAdded === null);
// assert(record._nextAdded === null);
this._additionsTail = this._additionsTail._nextAdded = record;
}
return record;
}
_insertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index: number): CollectionChangeRecord {
// todo(vicb)
// assert(record != prevRecord);
// assert(record._next === null);
// assert(record._prev === null);
var next: CollectionChangeRecord = prevRecord === null ? this._itHead : prevRecord._next;
// todo(vicb)
// assert(next != record);
// assert(prevRecord != record);
record._next = next;
record._prev = prevRecord;
if (next === null) {
this._itTail = record;
} else {
next._prev = record;
}
if (prevRecord === null) {
this._itHead = record;
} else {
prevRecord._next = record;
}
if (this._linkedRecords === null) {
this._linkedRecords = new _DuplicateMap();
}
this._linkedRecords.put(record);
record.currentIndex = index;
return record;
}
_remove(record: CollectionChangeRecord): CollectionChangeRecord {
return this._addToRemovals(this._unlink(record));
}
_unlink(record: CollectionChangeRecord): CollectionChangeRecord {
if (this._linkedRecords !== null) {
this._linkedRecords.remove(record);
}
var prev = record._prev;
var next = record._next;
// todo(vicb)
// assert((record._prev = null) === null);
// assert((record._next = null) === null);
if (prev === null) {
this._itHead = next;
} else {
prev._next = next;
}
if (next === null) {
this._itTail = prev;
} else {
next._prev = prev;
}
return record;
}
_addToMoves(record: CollectionChangeRecord, toIndex: number): CollectionChangeRecord {
// todo(vicb)
// assert(record._nextMoved === null);
if (record.previousIndex === toIndex) {
return record;
}
if (this._movesTail === null) {
// todo(vicb)
// assert(_movesHead === null);
this._movesTail = this._movesHead = record;
} else {
// todo(vicb)
// assert(_movesTail._nextMoved === null);
this._movesTail = this._movesTail._nextMoved = record;
}
return record;
}
_addToRemovals(record: CollectionChangeRecord): CollectionChangeRecord {
if (this._unlinkedRecords === null) {
this._unlinkedRecords = new _DuplicateMap();
}
this._unlinkedRecords.put(record);
record.currentIndex = null;
record._nextRemoved = null;
if (this._removalsTail === null) {
// todo(vicb)
// assert(_removalsHead === null);
this._removalsTail = this._removalsHead = record;
record._prevRemoved = null;
} else {
// todo(vicb)
// assert(_removalsTail._nextRemoved === null);
// assert(record._nextRemoved === null);
record._prevRemoved = this._removalsTail;
this._removalsTail = this._removalsTail._nextRemoved = record;
}
return record;
}
toString(): string {
var record: CollectionChangeRecord;
var list = [];
for (record = this._itHead; record !== null; record = record._next) {
list.push(record);
}
var previous = [];
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
previous.push(record);
}
var additions = [];
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
additions.push(record);
}
var moves = [];
for (record = this._movesHead; record !== null; record = record._nextMoved) {
moves.push(record);
}
var removals = [];
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
removals.push(record);
}
return "collection: " + list.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
"additions: " + additions.join(', ') + "\n" + "moves: " + moves.join(', ') + "\n" +
"removals: " + removals.join(', ') + "\n";
}
}
export class CollectionChangeRecord {
currentIndex: number = null;
previousIndex: number = null;
_nextPrevious: CollectionChangeRecord = null;
_prev: CollectionChangeRecord = null;
_next: CollectionChangeRecord = null;
_prevDup: CollectionChangeRecord = null;
_nextDup: CollectionChangeRecord = null;
_prevRemoved: CollectionChangeRecord = null;
_nextRemoved: CollectionChangeRecord = null;
_nextAdded: CollectionChangeRecord = null;
_nextMoved: CollectionChangeRecord = null;
constructor(public item: any) {}
toString(): string {
return this.previousIndex === this.currentIndex ?
stringify(this.item) :
stringify(this.item) + '[' + stringify(this.previousIndex) + '->' +
stringify(this.currentIndex) + ']';
}
}
// A linked list of CollectionChangeRecords with the same CollectionChangeRecord.item
class _DuplicateItemRecordList {
_head: CollectionChangeRecord = null;
_tail: CollectionChangeRecord = null;
/**
* Append the record to the list of duplicates.
*
* Note: by design all records in the list of duplicates hold the same value in record.item.
*/
add(record: CollectionChangeRecord): void {
if (this._head === null) {
this._head = this._tail = record;
record._nextDup = null;
record._prevDup = null;
} else {
// todo(vicb)
// assert(record.item == _head.item ||
// record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN);
this._tail._nextDup = record;
record._prevDup = this._tail;
record._nextDup = null;
this._tail = record;
}
}
// Returns a CollectionChangeRecord having CollectionChangeRecord.item == item and
// CollectionChangeRecord.currentIndex >= afterIndex
get(item: any, afterIndex: number): CollectionChangeRecord {
var record: CollectionChangeRecord;
for (record = this._head; record !== null; record = record._nextDup) {
if ((afterIndex === null || afterIndex < record.currentIndex) &&
looseIdentical(record.item, item)) {
return record;
}
}
return null;
}
/**
* Remove one {@link CollectionChangeRecord} from the list of duplicates.
*
* Returns whether the list of duplicates is empty.
*/
remove(record: CollectionChangeRecord): boolean {
// todo(vicb)
// assert(() {
// // verify that the record being removed is in the list.
// for (CollectionChangeRecord cursor = _head; cursor != null; cursor = cursor._nextDup) {
// if (identical(cursor, record)) return true;
// }
// return false;
//});
var prev: CollectionChangeRecord = record._prevDup;
var next: CollectionChangeRecord = record._nextDup;
if (prev === null) {
this._head = next;
} else {
prev._nextDup = next;
}
if (next === null) {
this._tail = prev;
} else {
next._prevDup = prev;
}
return this._head === null;
}
}
class _DuplicateMap {
map: Map<any, _DuplicateItemRecordList> = new Map();
put(record: CollectionChangeRecord) {
// todo(vicb) handle corner cases
var key = getMapKey(record.item);
var duplicates = this.map.get(key);
if (!isPresent(duplicates)) {
duplicates = new _DuplicateItemRecordList();
this.map.set(key, duplicates);
}
duplicates.add(record);
}
/**
* Retrieve the `value` using key. Because the CollectionChangeRecord value maybe one which we
* have already iterated over, we use the afterIndex to pretend it is not there.
*
* Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we
* have any more `a`s needs to return the last `a` not the first or second.
*/
get(value: any, afterIndex: number = null): CollectionChangeRecord {
var key = getMapKey(value);
var recordList = this.map.get(key);
return isBlank(recordList) ? null : recordList.get(value, afterIndex);
}
/**
* Removes a {@link CollectionChangeRecord} from the list of duplicates.
*
* The list of duplicates also is removed from the map if it gets empty.
*/
remove(record: CollectionChangeRecord): CollectionChangeRecord {
var key = getMapKey(record.item);
// todo(vicb)
// assert(this.map.containsKey(key));
var recordList: _DuplicateItemRecordList = this.map.get(key);
// Remove the list of duplicates when it gets empty
if (recordList.remove(record)) {
MapWrapper.delete(this.map, key);
}
return record;
}
get isEmpty(): boolean { return MapWrapper.size(this.map) === 0; }
clear() { this.map.clear(); }
toString(): string { return '_DuplicateMap(' + stringify(this.map) + ')'; }
}

View File

@ -0,0 +1,353 @@
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {
stringify,
looseIdentical,
isJsObject,
CONST,
isBlank,
BaseException
} from 'angular2/src/facade/lang';
import {ChangeDetectorRef} from '../change_detector_ref';
import {KeyValueDiffer, KeyValueDifferFactory} from '../differs/keyvalue_differs';
@CONST()
export class DefaultKeyValueDifferFactory implements KeyValueDifferFactory {
supports(obj: any): boolean { return obj instanceof Map || isJsObject(obj); }
create(cdRef: ChangeDetectorRef): KeyValueDiffer { return new DefaultKeyValueDiffer(); }
}
export class DefaultKeyValueDiffer implements KeyValueDiffer {
private _records: Map<any, any> = new Map();
private _mapHead: KVChangeRecord = null;
private _previousMapHead: KVChangeRecord = null;
private _changesHead: KVChangeRecord = null;
private _changesTail: KVChangeRecord = null;
private _additionsHead: KVChangeRecord = null;
private _additionsTail: KVChangeRecord = null;
private _removalsHead: KVChangeRecord = null;
private _removalsTail: KVChangeRecord = null;
get isDirty(): boolean {
return this._additionsHead !== null || this._changesHead !== null ||
this._removalsHead !== null;
}
forEachItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._mapHead; record !== null; record = record._next) {
fn(record);
}
}
forEachPreviousItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
fn(record);
}
}
forEachChangedItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._changesHead; record !== null; record = record._nextChanged) {
fn(record);
}
}
forEachAddedItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record);
}
}
forEachRemovedItem(fn: Function) {
var record: KVChangeRecord;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record);
}
}
diff(map: Map<any, any>): any {
if (isBlank(map)) map = MapWrapper.createFromPairs([]);
if (!(map instanceof Map || isJsObject(map))) {
throw new BaseException(`Error trying to diff '${map}'`);
}
if (this.check(map)) {
return this;
} else {
return null;
}
}
onDestroy() {}
check(map: Map<any, any>): boolean {
this._reset();
var records = this._records;
var oldSeqRecord: KVChangeRecord = this._mapHead;
var lastOldSeqRecord: KVChangeRecord = null;
var lastNewSeqRecord: KVChangeRecord = null;
var seqChanged: boolean = false;
this._forEach(map, (value, key) => {
var newSeqRecord;
if (oldSeqRecord !== null && key === oldSeqRecord.key) {
newSeqRecord = oldSeqRecord;
if (!looseIdentical(value, oldSeqRecord.currentValue)) {
oldSeqRecord.previousValue = oldSeqRecord.currentValue;
oldSeqRecord.currentValue = value;
this._addToChanges(oldSeqRecord);
}
} else {
seqChanged = true;
if (oldSeqRecord !== null) {
oldSeqRecord._next = null;
this._removeFromSeq(lastOldSeqRecord, oldSeqRecord);
this._addToRemovals(oldSeqRecord);
}
if (records.has(key)) {
newSeqRecord = records.get(key);
} else {
newSeqRecord = new KVChangeRecord(key);
records.set(key, newSeqRecord);
newSeqRecord.currentValue = value;
this._addToAdditions(newSeqRecord);
}
}
if (seqChanged) {
if (this._isInRemovals(newSeqRecord)) {
this._removeFromRemovals(newSeqRecord);
}
if (lastNewSeqRecord == null) {
this._mapHead = newSeqRecord;
} else {
lastNewSeqRecord._next = newSeqRecord;
}
}
lastOldSeqRecord = oldSeqRecord;
lastNewSeqRecord = newSeqRecord;
oldSeqRecord = oldSeqRecord === null ? null : oldSeqRecord._next;
});
this._truncate(lastOldSeqRecord, oldSeqRecord);
return this.isDirty;
}
_reset() {
if (this.isDirty) {
var record: KVChangeRecord;
// Record the state of the mapping
for (record = this._previousMapHead = this._mapHead; record !== null; record = record._next) {
record._nextPrevious = record._next;
}
for (record = this._changesHead; record !== null; record = record._nextChanged) {
record.previousValue = record.currentValue;
}
for (record = this._additionsHead; record != null; record = record._nextAdded) {
record.previousValue = record.currentValue;
}
// todo(vicb) once assert is supported
// assert(() {
// var r = _changesHead;
// while (r != null) {
// var nextRecord = r._nextChanged;
// r._nextChanged = null;
// r = nextRecord;
// }
//
// r = _additionsHead;
// while (r != null) {
// var nextRecord = r._nextAdded;
// r._nextAdded = null;
// r = nextRecord;
// }
//
// r = _removalsHead;
// while (r != null) {
// var nextRecord = r._nextRemoved;
// r._nextRemoved = null;
// r = nextRecord;
// }
//
// return true;
//});
this._changesHead = this._changesTail = null;
this._additionsHead = this._additionsTail = null;
this._removalsHead = this._removalsTail = null;
}
}
_truncate(lastRecord: KVChangeRecord, record: KVChangeRecord) {
while (record !== null) {
if (lastRecord === null) {
this._mapHead = null;
} else {
lastRecord._next = null;
}
var nextRecord = record._next;
// todo(vicb) assert
// assert((() {
// record._next = null;
// return true;
//}));
this._addToRemovals(record);
lastRecord = record;
record = nextRecord;
}
for (var rec: KVChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
rec.previousValue = rec.currentValue;
rec.currentValue = null;
MapWrapper.delete(this._records, rec.key);
}
}
_isInRemovals(record: KVChangeRecord) {
return record === this._removalsHead || record._nextRemoved !== null ||
record._prevRemoved !== null;
}
_addToRemovals(record: KVChangeRecord) {
// todo(vicb) assert
// assert(record._next == null);
// assert(record._nextAdded == null);
// assert(record._nextChanged == null);
// assert(record._nextRemoved == null);
// assert(record._prevRemoved == null);
if (this._removalsHead === null) {
this._removalsHead = this._removalsTail = record;
} else {
this._removalsTail._nextRemoved = record;
record._prevRemoved = this._removalsTail;
this._removalsTail = record;
}
}
_removeFromSeq(prev: KVChangeRecord, record: KVChangeRecord) {
var next = record._next;
if (prev === null) {
this._mapHead = next;
} else {
prev._next = next;
}
// todo(vicb) assert
// assert((() {
// record._next = null;
// return true;
//})());
}
_removeFromRemovals(record: KVChangeRecord) {
// todo(vicb) assert
// assert(record._next == null);
// assert(record._nextAdded == null);
// assert(record._nextChanged == null);
var prev = record._prevRemoved;
var next = record._nextRemoved;
if (prev === null) {
this._removalsHead = next;
} else {
prev._nextRemoved = next;
}
if (next === null) {
this._removalsTail = prev;
} else {
next._prevRemoved = prev;
}
record._prevRemoved = record._nextRemoved = null;
}
_addToAdditions(record: KVChangeRecord) {
// todo(vicb): assert
// assert(record._next == null);
// assert(record._nextAdded == null);
// assert(record._nextChanged == null);
// assert(record._nextRemoved == null);
// assert(record._prevRemoved == null);
if (this._additionsHead === null) {
this._additionsHead = this._additionsTail = record;
} else {
this._additionsTail._nextAdded = record;
this._additionsTail = record;
}
}
_addToChanges(record: KVChangeRecord) {
// todo(vicb) assert
// assert(record._nextAdded == null);
// assert(record._nextChanged == null);
// assert(record._nextRemoved == null);
// assert(record._prevRemoved == null);
if (this._changesHead === null) {
this._changesHead = this._changesTail = record;
} else {
this._changesTail._nextChanged = record;
this._changesTail = record;
}
}
toString(): string {
var items = [];
var previous = [];
var changes = [];
var additions = [];
var removals = [];
var record: KVChangeRecord;
for (record = this._mapHead; record !== null; record = record._next) {
items.push(stringify(record));
}
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
previous.push(stringify(record));
}
for (record = this._changesHead; record !== null; record = record._nextChanged) {
changes.push(stringify(record));
}
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
additions.push(stringify(record));
}
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
removals.push(stringify(record));
}
return "map: " + items.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
"additions: " + additions.join(', ') + "\n" + "changes: " + changes.join(', ') + "\n" +
"removals: " + removals.join(', ') + "\n";
}
_forEach(obj, fn: Function) {
if (obj instanceof Map) {
MapWrapper.forEach(obj, fn);
} else {
StringMapWrapper.forEach(obj, fn);
}
}
}
export class KVChangeRecord {
previousValue: any = null;
currentValue: any = null;
_nextPrevious: KVChangeRecord = null;
_next: KVChangeRecord = null;
_nextAdded: KVChangeRecord = null;
_nextRemoved: KVChangeRecord = null;
_prevRemoved: KVChangeRecord = null;
_nextChanged: KVChangeRecord = null;
constructor(public key: any) {}
toString(): string {
return looseIdentical(this.previousValue, this.currentValue) ?
stringify(this.key) :
(stringify(this.key) + '[' + stringify(this.previousValue) + '->' +
stringify(this.currentValue) + ']');
}
}

View File

@ -0,0 +1,80 @@
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectorRef} from '../change_detector_ref';
import {Binding, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/di';
export interface IterableDiffer {
diff(object: Object): any;
onDestroy();
}
/**
* Provides a factory for {@link IterableDiffer}.
*/
export interface IterableDifferFactory {
supports(objects: Object): boolean;
create(cdRef: ChangeDetectorRef): IterableDiffer;
}
/**
* A repository of different iterable diffing strategies used by NgFor, NgClass, and others.
*/
@Injectable()
@CONST()
export class IterableDiffers {
constructor(public factories: IterableDifferFactory[]) {}
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
if (isPresent(parent)) {
var copied = ListWrapper.clone(parent.factories);
factories = factories.concat(copied);
return new IterableDiffers(factories);
} else {
return new IterableDiffers(factories);
}
}
/**
* Takes an array of {@link IterableDifferFactory} and returns a binding used to extend the
* inherited {@link IterableDiffers} instance with the provided factories and return a new
* {@link IterableDiffers} instance.
*
* The following example shows how to extend an existing list of factories,
* which will only be applied to the injector for this component and its children.
* This step is all that's required to make a new {@link IterableDiffer} available.
*
* # Example
*
* ```
* @Component({
* viewBindings: [
* IterableDiffers.extend([new ImmutableListDiffer()])
* ]
* })
* ```
*/
static extend(factories: IterableDifferFactory[]): Binding {
return new Binding(IterableDiffers, {
toFactory: (parent: IterableDiffers) => {
if (isBlank(parent)) {
// Typically would occur when calling IterableDiffers.extend inside of dependencies passed
// to
// bootstrap(), which would override default pipes instead of extending them.
throw new BaseException('Cannot extend IterableDiffers without a parent injector');
}
return IterableDiffers.create(factories, parent);
},
// Dependency technically isn't optional, but we can provide a better error message this way.
deps: [[IterableDiffers, new SkipSelfMetadata(), new OptionalMetadata()]]
});
}
find(iterable: Object): IterableDifferFactory {
var factory = ListWrapper.find(this.factories, f => f.supports(iterable));
if (isPresent(factory)) {
return factory;
} else {
throw new BaseException(`Cannot find a differ supporting object '${iterable}'`);
}
}
}

View File

@ -0,0 +1,80 @@
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectorRef} from '../change_detector_ref';
import {Binding, SkipSelfMetadata, OptionalMetadata, Injectable} from 'angular2/di';
export interface KeyValueDiffer {
diff(object: Object);
onDestroy();
}
/**
* Provides a factory for {@link KeyValueDiffer}.
*/
export interface KeyValueDifferFactory {
supports(objects: Object): boolean;
create(cdRef: ChangeDetectorRef): KeyValueDiffer;
}
/**
* A repository of different Map diffing strategies used by NgClass, NgStyle, and others.
*/
@Injectable()
@CONST()
export class KeyValueDiffers {
constructor(public factories: KeyValueDifferFactory[]) {}
static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
if (isPresent(parent)) {
var copied = ListWrapper.clone(parent.factories);
factories = factories.concat(copied);
return new KeyValueDiffers(factories);
} else {
return new KeyValueDiffers(factories);
}
}
/**
* Takes an array of {@link KeyValueDifferFactory} and returns a binding used to extend the
* inherited {@link KeyValueDiffers} instance with the provided factories and return a new
* {@link KeyValueDiffers} instance.
*
* The following example shows how to extend an existing list of factories,
* which will only be applied to the injector for this component and its children.
* This step is all that's required to make a new {@link KeyValueDiffer} available.
*
* # Example
*
* ```
* @Component({
* viewBindings: [
* KeyValueDiffers.extend([new ImmutableMapDiffer()])
* ]
* })
* ```
*/
static extend(factories: KeyValueDifferFactory[]): Binding {
return new Binding(KeyValueDiffers, {
toFactory: (parent: KeyValueDiffers) => {
if (isBlank(parent)) {
// Typically would occur when calling KeyValueDiffers.extend inside of dependencies passed
// to
// bootstrap(), which would override default pipes instead of extending them.
throw new BaseException('Cannot extend KeyValueDiffers without a parent injector');
}
return KeyValueDiffers.create(factories, parent);
},
// Dependency technically isn't optional, but we can provide a better error message this way.
deps: [[KeyValueDiffers, new SkipSelfMetadata(), new OptionalMetadata()]]
});
}
find(kv: Object): KeyValueDifferFactory {
var factory = ListWrapper.find(this.factories, f => f.supports(kv));
if (isPresent(factory)) {
return factory;
} else {
throw new BaseException(`Cannot find a differ supporting object '${kv}'`);
}
}
}

View File

@ -0,0 +1,38 @@
import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang';
import {isDefaultChangeDetectionStrategy} from './constants';
export class DirectiveIndex {
constructor(public elementIndex: number, public directiveIndex: number) {}
get name() { return `${this.elementIndex}_${this.directiveIndex}`; }
}
export class DirectiveRecord {
directiveIndex: DirectiveIndex;
callOnAllChangesDone: boolean;
callOnChange: boolean;
callOnCheck: boolean;
callOnInit: boolean;
changeDetection: string;
constructor({directiveIndex, callOnAllChangesDone, callOnChange, callOnCheck, callOnInit,
changeDetection}: {
directiveIndex?: DirectiveIndex,
callOnAllChangesDone?: boolean,
callOnChange?: boolean,
callOnCheck?: boolean,
callOnInit?: boolean,
changeDetection?: string
} = {}) {
this.directiveIndex = directiveIndex;
this.callOnAllChangesDone = normalizeBool(callOnAllChangesDone);
this.callOnChange = normalizeBool(callOnChange);
this.callOnCheck = normalizeBool(callOnCheck);
this.callOnInit = normalizeBool(callOnInit);
this.changeDetection = changeDetection;
}
isDefaultChangeDetection(): boolean {
return isDefaultChangeDetectionStrategy(this.changeDetection);
}
}

View File

@ -0,0 +1,417 @@
import {
isPresent,
isBlank,
BaseException,
FunctionWrapper,
StringWrapper
} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {AbstractChangeDetector} from './abstract_change_detector';
import {EventBinding} from './event_binding';
import {BindingRecord, BindingTarget} from './binding_record';
import {DirectiveRecord, DirectiveIndex} from './directive_record';
import {Locals} from './parser/locals';
import {ChangeDetectorGenConfig} from './interfaces';
import {ChangeDetectionUtil, SimpleChange} from './change_detection_util';
import {ON_PUSH_OBSERVE} from './constants';
import {ProtoRecord, RecordType} from './proto_record';
export class DynamicChangeDetector extends AbstractChangeDetector<any> {
values: List<any>;
changes: List<any>;
localPipes: List<any>;
prevContexts: List<any>;
directives: any = null;
constructor(id: string, dispatcher: any, numberOfPropertyProtoRecords: number,
propertyBindingTargets: BindingTarget[], directiveIndices: DirectiveIndex[],
strategy: string, private records: ProtoRecord[],
private eventBindings: EventBinding[], private directiveRecords: DirectiveRecord[],
private genConfig: ChangeDetectorGenConfig) {
super(id, dispatcher, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices,
strategy);
var len = records.length + 1;
this.values = ListWrapper.createFixedSize(len);
this.localPipes = ListWrapper.createFixedSize(len);
this.prevContexts = ListWrapper.createFixedSize(len);
this.changes = ListWrapper.createFixedSize(len);
this.dehydrateDirectives(false);
}
handleEventInternal(eventName: string, elIndex: number, locals: Locals): boolean {
var preventDefault = false;
this._matchingEventBindings(eventName, elIndex)
.forEach(rec => {
var res = this._processEventBinding(rec, locals);
if (res === false) {
preventDefault = true;
}
});
return preventDefault;
}
_processEventBinding(eb: EventBinding, locals: Locals): any {
var values = ListWrapper.createFixedSize(eb.records.length);
values[0] = this.values[0];
for (var i = 0; i < eb.records.length; ++i) {
var proto = eb.records[i];
var res = this._calculateCurrValue(proto, values, locals);
if (proto.lastInBinding) {
this._markPathAsCheckOnce(proto);
return res;
} else {
this._writeSelf(proto, res, values);
}
}
throw new BaseException("Cannot be reached");
}
_markPathAsCheckOnce(proto: ProtoRecord): void {
if (!proto.bindingRecord.isDefaultChangeDetection()) {
var dir = proto.bindingRecord.directiveRecord;
this._getDetectorFor(dir.directiveIndex).markPathToRootAsCheckOnce();
}
}
_matchingEventBindings(eventName: string, elIndex: number): EventBinding[] {
return ListWrapper.filter(this.eventBindings,
eb => eb.eventName == eventName && eb.elIndex === elIndex);
}
hydrateDirectives(directives: any): void {
this.values[0] = this.context;
this.directives = directives;
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
for (var i = 0; i < this.directiveIndices.length; ++i) {
var index = this.directiveIndices[i];
super.observeDirective(directives.getDirectiveFor(index), i);
}
}
}
dehydrateDirectives(destroyPipes: boolean) {
if (destroyPipes) {
this._destroyPipes();
}
this.values[0] = null;
this.directives = null;
ListWrapper.fill(this.values, ChangeDetectionUtil.uninitialized, 1);
ListWrapper.fill(this.changes, false);
ListWrapper.fill(this.localPipes, null);
ListWrapper.fill(this.prevContexts, ChangeDetectionUtil.uninitialized);
}
_destroyPipes() {
for (var i = 0; i < this.localPipes.length; ++i) {
if (isPresent(this.localPipes[i])) {
ChangeDetectionUtil.callPipeOnDestroy(this.localPipes[i]);
}
}
}
checkNoChanges(): void { this.runDetectChanges(true); }
detectChangesInRecordsInternal(throwOnChange: boolean) {
var protos = this.records;
var changes = null;
var isChanged = false;
for (var i = 0; i < protos.length; ++i) {
var proto: ProtoRecord = protos[i];
var bindingRecord = proto.bindingRecord;
var directiveRecord = bindingRecord.directiveRecord;
if (this._firstInBinding(proto)) {
this.propertyBindingIndex = proto.propertyBindingIndex;
}
if (proto.isLifeCycleRecord()) {
if (proto.name === "onCheck" && !throwOnChange) {
this._getDirectiveFor(directiveRecord.directiveIndex).onCheck();
} else if (proto.name === "onInit" && !throwOnChange && !this.alreadyChecked) {
this._getDirectiveFor(directiveRecord.directiveIndex).onInit();
} else if (proto.name === "onChange" && isPresent(changes) && !throwOnChange) {
this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
}
} else {
var change = this._check(proto, throwOnChange, this.values, this.locals);
if (isPresent(change)) {
this._updateDirectiveOrElement(change, bindingRecord);
isChanged = true;
changes = this._addChange(bindingRecord, change, changes);
}
}
if (proto.lastInDirective) {
changes = null;
if (isChanged && !bindingRecord.isDefaultChangeDetection()) {
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
}
isChanged = false;
}
}
this.alreadyChecked = true;
}
_firstInBinding(r: ProtoRecord): boolean {
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
return isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
}
callOnAllChangesDone() {
super.callOnAllChangesDone();
var dirs = this.directiveRecords;
for (var i = dirs.length - 1; i >= 0; --i) {
var dir = dirs[i];
if (dir.callOnAllChangesDone) {
this._getDirectiveFor(dir.directiveIndex).onAllChangesDone();
}
}
}
_updateDirectiveOrElement(change, bindingRecord) {
if (isBlank(bindingRecord.directiveRecord)) {
super.notifyDispatcher(change.currentValue);
} else {
var directiveIndex = bindingRecord.directiveRecord.directiveIndex;
bindingRecord.setter(this._getDirectiveFor(directiveIndex), change.currentValue);
}
if (this.genConfig.logBindingUpdate) {
super.logBindingUpdate(change.currentValue);
}
}
_addChange(bindingRecord: BindingRecord, change, changes) {
if (bindingRecord.callOnChange()) {
return super.addChange(changes, change.previousValue, change.currentValue);
} else {
return changes;
}
}
_getDirectiveFor(directiveIndex) { return this.directives.getDirectiveFor(directiveIndex); }
_getDetectorFor(directiveIndex) { return this.directives.getDetectorFor(directiveIndex); }
_check(proto: ProtoRecord, throwOnChange: boolean, values: any[], locals: Locals): SimpleChange {
if (proto.isPipeRecord()) {
return this._pipeCheck(proto, throwOnChange, values);
} else {
return this._referenceCheck(proto, throwOnChange, values, locals);
}
}
_referenceCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[], locals: Locals) {
if (this._pureFuncAndArgsDidNotChange(proto)) {
this._setChanged(proto, false);
return null;
}
var currValue = this._calculateCurrValue(proto, values, locals);
if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) {
super.observeValue(currValue, proto.selfIndex);
}
if (proto.shouldBeChecked()) {
var prevValue = this._readSelf(proto, values);
if (!isSame(prevValue, currValue)) {
if (proto.lastInBinding) {
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return change;
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
} else {
this._setChanged(proto, false);
return null;
}
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
}
_calculateCurrValue(proto: ProtoRecord, values: any[], locals: Locals) {
switch (proto.mode) {
case RecordType.SELF:
return this._readContext(proto, values);
case RecordType.CONST:
return proto.funcOrValue;
case RecordType.PROPERTY_READ:
var context = this._readContext(proto, values);
return proto.funcOrValue(context);
case RecordType.SAFE_PROPERTY:
var context = this._readContext(proto, values);
return isBlank(context) ? null : proto.funcOrValue(context);
case RecordType.PROPERTY_WRITE:
var context = this._readContext(proto, values);
var value = this._readArgs(proto, values)[0];
proto.funcOrValue(context, value);
return value;
case RecordType.KEYED_WRITE:
var context = this._readContext(proto, values);
var key = this._readArgs(proto, values)[0];
var value = this._readArgs(proto, values)[1];
context[key] = value;
return value;
case RecordType.LOCAL:
return locals.get(proto.name);
case RecordType.INVOKE_METHOD:
var context = this._readContext(proto, values);
var args = this._readArgs(proto, values);
return proto.funcOrValue(context, args);
case RecordType.SAFE_INVOKE_METHOD:
var context = this._readContext(proto, values);
if (isBlank(context)) {
return null;
}
var args = this._readArgs(proto, values);
return proto.funcOrValue(context, args);
case RecordType.KEYED_READ:
var arg = this._readArgs(proto, values)[0];
return this._readContext(proto, values)[arg];
case RecordType.CHAIN:
var args = this._readArgs(proto, values);
return args[args.length - 1];
case RecordType.INVOKE_CLOSURE:
return FunctionWrapper.apply(this._readContext(proto, values),
this._readArgs(proto, values));
case RecordType.INTERPOLATE:
case RecordType.PRIMITIVE_OP:
case RecordType.COLLECTION_LITERAL:
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto, values));
default:
throw new BaseException(`Unknown operation ${proto.mode}`);
}
}
_pipeCheck(proto: ProtoRecord, throwOnChange: boolean, values: any[]) {
var context = this._readContext(proto, values);
var args = this._readArgs(proto, values);
var pipe = this._pipeFor(proto, context);
var currValue = pipe.transform(context, args);
if (proto.shouldBeChecked()) {
var prevValue = this._readSelf(proto, values);
if (!isSame(prevValue, currValue)) {
currValue = ChangeDetectionUtil.unwrapValue(currValue);
if (proto.lastInBinding) {
var change = ChangeDetectionUtil.simpleChange(prevValue, currValue);
if (throwOnChange) this.throwOnChangeError(prevValue, currValue);
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return change;
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
} else {
this._setChanged(proto, false);
return null;
}
} else {
this._writeSelf(proto, currValue, values);
this._setChanged(proto, true);
return null;
}
}
_pipeFor(proto: ProtoRecord, context) {
var storedPipe = this._readPipe(proto);
if (isPresent(storedPipe)) return storedPipe;
var pipe = this.pipes.get(proto.name);
this._writePipe(proto, pipe);
return pipe;
}
_readContext(proto: ProtoRecord, values: any[]) {
if (proto.contextIndex == -1) {
return this._getDirectiveFor(proto.directiveIndex);
} else {
return values[proto.contextIndex];
}
return values[proto.contextIndex];
}
_readSelf(proto: ProtoRecord, values: any[]) { return values[proto.selfIndex]; }
_writeSelf(proto: ProtoRecord, value, values: any[]) { values[proto.selfIndex] = value; }
_readPipe(proto: ProtoRecord) { return this.localPipes[proto.selfIndex]; }
_writePipe(proto: ProtoRecord, value) { this.localPipes[proto.selfIndex] = value; }
_setChanged(proto: ProtoRecord, value: boolean) {
if (proto.argumentToPureFunction) this.changes[proto.selfIndex] = value;
}
_pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean {
return proto.isPureFunction() && !this._argsChanged(proto);
}
_argsChanged(proto: ProtoRecord): boolean {
var args = proto.args;
for (var i = 0; i < args.length; ++i) {
if (this.changes[args[i]]) {
return true;
}
}
return false;
}
_readArgs(proto: ProtoRecord, values: any[]) {
var res = ListWrapper.createFixedSize(proto.args.length);
var args = proto.args;
for (var i = 0; i < args.length; ++i) {
res[i] = values[args[i]];
}
return res;
}
}
function isSame(a, b): boolean {
if (a === b) return true;
if (a instanceof String && b instanceof String && a == b) return true;
if ((a !== a) && (b !== b)) return true;
return false;
}

View File

@ -0,0 +1,7 @@
import {DirectiveIndex} from './directive_record';
import {ProtoRecord} from './proto_record';
export class EventBinding {
constructor(public eventName: string, public elIndex: number, public dirIndex: DirectiveIndex,
public records: ProtoRecord[]) {}
}

View File

@ -0,0 +1,43 @@
import {BaseException} from "angular2/src/facade/lang";
/**
* An error thrown if application changes model breaking the top-down data flow.
*
* Angular expects that the data flows from top (root) component to child (leaf) components.
* This is known as directed acyclic graph. This allows Angular to only execute change detection
* once and prevents loops in change detection data flow.
*
* This exception is only thrown in dev mode.
*/
export class ExpressionChangedAfterItHasBeenCheckedException extends BaseException {
constructor(exp: string, oldValue: any, currValue: any, context: any) {
super(`Expression '${exp}' has changed after it was checked. ` +
`Previous value: '${oldValue}'. Current value: '${currValue}'`);
}
}
/**
* Thrown when an expression evaluation raises an exception.
*
* This error wraps the original exception, this is done to attach expression location information.
*/
export class ChangeDetectionError extends BaseException {
/**
* Location of the expression.
*/
location: string;
constructor(exp: string, originalException: any, originalStack: any, context: any) {
super(`${originalException} in [${exp}]`, originalException, originalStack, context);
this.location = exp;
}
}
/**
* Thrown when change detector executes on dehydrated view.
*
* This is angular internal error.
*/
export class DehydratedException extends BaseException {
constructor() { super('Attempt to detect changes on a dehydrated detector.'); }
}

View File

@ -0,0 +1,87 @@
import {List} from 'angular2/src/facade/collection';
import {CONST} from 'angular2/src/facade/lang';
import {Locals} from './parser/locals';
import {BindingTarget, BindingRecord} from './binding_record';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
import {ChangeDetectorRef} from './change_detector_ref';
/**
* Interface used by Angular to control the change detection strategy for an application.
*
* Angular implements the following change detection strategies by default:
*
* - {@link DynamicChangeDetection}: slower, but does not require `eval()`.
* - {@link JitChangeDetection}: faster, but requires `eval()`.
*
* In JavaScript, you should always use `JitChangeDetection`, unless you are in an environment that
*has
* [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP), such as a Chrome Extension.
*
* In Dart, use `DynamicChangeDetection` during development. The Angular transformer generates an
*analog to the
* `JitChangeDetection` strategy at compile time.
*
*
* See: {@link DynamicChangeDetection}, {@link JitChangeDetection},
* {@link PreGeneratedChangeDetection}
*
* # Example
* ```javascript
* bootstrap(MyApp, [bind(ChangeDetection).toValue(new DynamicChangeDetection())]);
* ```
*/
@CONST()
export class ChangeDetection {
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
return null;
}
get generateDetectors(): boolean { return null; }
get genConfig(): ChangeDetectorGenConfig { return null; }
}
export class DebugContext {
constructor(public element: any, public componentElement: any, public directive: any,
public context: any, public locals: any, public injector: any) {}
}
export interface ChangeDispatcher {
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext;
notifyOnBinding(bindingTarget: BindingTarget, value: any): void;
logBindingUpdate(bindingTarget: BindingTarget, value: any): void;
notifyOnAllChangesDone(): void;
}
export interface ChangeDetector {
parent: ChangeDetector;
mode: string;
ref: ChangeDetectorRef;
addChild(cd: ChangeDetector): void;
addShadowDomChild(cd: ChangeDetector): void;
removeChild(cd: ChangeDetector): void;
removeShadowDomChild(cd: ChangeDetector): void;
remove(): void;
hydrate(context: any, locals: Locals, directives: any, pipes: any): void;
dehydrate(): void;
markPathToRootAsCheckOnce(): void;
handleEvent(eventName: string, elIndex: number, locals: Locals);
detectChanges(): void;
checkNoChanges(): void;
}
export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher): ChangeDetector; }
export class ChangeDetectorGenConfig {
constructor(public genCheckNoChanges: boolean, public genDebugInfo: boolean,
public logBindingUpdate: boolean) {}
}
export class ChangeDetectorDefinition {
constructor(public id: string, public strategy: string, public variableNames: List<string>,
public bindingRecords: BindingRecord[], public eventRecords: BindingRecord[],
public directiveRecords: DirectiveRecord[],
public genConfig: ChangeDetectorGenConfig) {}
}

View File

@ -0,0 +1,13 @@
library change_detection.jit_proto_change_detector;
import 'interfaces.dart' show ChangeDetector, ProtoChangeDetector;
class JitProtoChangeDetector implements ProtoChangeDetector {
JitProtoChangeDetector(definition) : super();
static bool isSupported() => false;
ChangeDetector instantiate(dispatcher) {
throw "Jit Change Detection not supported in Dart";
}
}

View File

@ -0,0 +1,31 @@
import {ListWrapper} from 'angular2/src/facade/collection';
import {isPresent} from 'angular2/src/facade/lang';
import {ProtoChangeDetector, ChangeDetector, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {coalesce} from './coalesce';
import {createPropertyRecords, createEventRecords} from './proto_change_detector';
export class JitProtoChangeDetector implements ProtoChangeDetector {
_factory: Function;
constructor(private definition: ChangeDetectorDefinition) {
this._factory = this._createFactory(definition);
}
static isSupported(): boolean { return true; }
instantiate(dispatcher: any): ChangeDetector { return this._factory(dispatcher); }
_createFactory(definition: ChangeDetectorDefinition) {
var propertyBindingRecords = createPropertyRecords(definition);
var eventBindingRecords = createEventRecords(definition);
var propertyBindingTargets = this.definition.bindingRecords.map(b => b.target);
return new ChangeDetectorJITGenerator(
definition.id, definition.strategy, propertyBindingRecords, propertyBindingTargets,
eventBindingRecords, this.definition.directiveRecords, this.definition.genConfig)
.generate();
}
}

View File

@ -0,0 +1,3 @@
import 'package:observe/observe.dart';
bool isObservable(value) => value is Observable;

View File

@ -0,0 +1,3 @@
export function isObservable(value: any): boolean {
return false;
}

View File

@ -0,0 +1,311 @@
import {isBlank, isPresent, FunctionWrapper, BaseException} from "angular2/src/facade/lang";
import {List, Map, ListWrapper, StringMapWrapper} from "angular2/src/facade/collection";
export class AST {
visit(visitor: AstVisitor): any { return null; }
toString(): string { return "AST"; }
}
export class EmptyExpr extends AST {
visit(visitor: AstVisitor) {
// do nothing
}
}
export class ImplicitReceiver extends AST {
visit(visitor: AstVisitor): any { return visitor.visitImplicitReceiver(this); }
}
/**
* Multiple expressions separated by a semicolon.
*/
export class Chain extends AST {
constructor(public expressions: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitChain(this); }
}
export class Conditional extends AST {
constructor(public condition: AST, public trueExp: AST, public falseExp: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitConditional(this); }
}
export class If extends AST {
constructor(public condition: AST, public trueExp: AST, public falseExp?: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitIf(this); }
}
export class PropertyRead extends AST {
constructor(public receiver: AST, public name: string, public getter: Function) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitPropertyRead(this); }
}
export class PropertyWrite extends AST {
constructor(public receiver: AST, public name: string, public setter: Function,
public value: AST) {
super();
}
visit(visitor: AstVisitor): any { return visitor.visitPropertyWrite(this); }
}
export class SafePropertyRead extends AST {
constructor(public receiver: AST, public name: string, public getter: Function) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitSafePropertyRead(this); }
}
export class KeyedRead extends AST {
constructor(public obj: AST, public key: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitKeyedRead(this); }
}
export class KeyedWrite extends AST {
constructor(public obj: AST, public key: AST, public value: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitKeyedWrite(this); }
}
export class BindingPipe extends AST {
constructor(public exp: AST, public name: string, public args: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitPipe(this); }
}
export class LiteralPrimitive extends AST {
constructor(public value) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitLiteralPrimitive(this); }
}
export class LiteralArray extends AST {
constructor(public expressions: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitLiteralArray(this); }
}
export class LiteralMap extends AST {
constructor(public keys: List<any>, public values: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitLiteralMap(this); }
}
export class Interpolation extends AST {
constructor(public strings: List<any>, public expressions: List<any>) { super(); }
visit(visitor: AstVisitor) { visitor.visitInterpolation(this); }
}
export class Binary extends AST {
constructor(public operation: string, public left: AST, public right: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitBinary(this); }
}
export class PrefixNot extends AST {
constructor(public expression: AST) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitPrefixNot(this); }
}
export class MethodCall extends AST {
constructor(public receiver: AST, public name: string, public fn: Function,
public args: List<any>) {
super();
}
visit(visitor: AstVisitor): any { return visitor.visitMethodCall(this); }
}
export class SafeMethodCall extends AST {
constructor(public receiver: AST, public name: string, public fn: Function,
public args: List<any>) {
super();
}
visit(visitor: AstVisitor): any { return visitor.visitSafeMethodCall(this); }
}
export class FunctionCall extends AST {
constructor(public target: AST, public args: List<any>) { super(); }
visit(visitor: AstVisitor): any { return visitor.visitFunctionCall(this); }
}
export class ASTWithSource extends AST {
constructor(public ast: AST, public source: string, public location: string) { super(); }
visit(visitor: AstVisitor): any { return this.ast.visit(visitor); }
toString(): string { return `${this.source} in ${this.location}`; }
}
export class TemplateBinding {
constructor(public key: string, public keyIsVar: boolean, public name: string,
public expression: ASTWithSource) {}
}
export interface AstVisitor {
visitBinary(ast: Binary): any;
visitChain(ast: Chain): any;
visitConditional(ast: Conditional): any;
visitFunctionCall(ast: FunctionCall): any;
visitIf(ast: If): any;
visitImplicitReceiver(ast: ImplicitReceiver): any;
visitInterpolation(ast: Interpolation): any;
visitKeyedRead(ast: KeyedRead): any;
visitKeyedWrite(ast: KeyedWrite): any;
visitLiteralArray(ast: LiteralArray): any;
visitLiteralMap(ast: LiteralMap): any;
visitLiteralPrimitive(ast: LiteralPrimitive): any;
visitMethodCall(ast: MethodCall): any;
visitPipe(ast: BindingPipe): any;
visitPrefixNot(ast: PrefixNot): any;
visitPropertyRead(ast: PropertyRead): any;
visitPropertyWrite(ast: PropertyWrite): any;
visitSafeMethodCall(ast: SafeMethodCall): any;
visitSafePropertyRead(ast: SafePropertyRead): any;
}
export class RecursiveAstVisitor implements AstVisitor {
visitBinary(ast: Binary): any {
ast.left.visit(this);
ast.right.visit(this);
return null;
}
visitChain(ast: Chain): any { return this.visitAll(ast.expressions); }
visitConditional(ast: Conditional): any {
ast.condition.visit(this);
ast.trueExp.visit(this);
ast.falseExp.visit(this);
return null;
}
visitIf(ast: If): any {
ast.condition.visit(this);
ast.trueExp.visit(this);
ast.falseExp.visit(this);
return null;
}
visitPipe(ast: BindingPipe): any {
ast.exp.visit(this);
this.visitAll(ast.args);
return null;
}
visitFunctionCall(ast: FunctionCall): any {
ast.target.visit(this);
this.visitAll(ast.args);
return null;
}
visitImplicitReceiver(ast: ImplicitReceiver): any { return null; }
visitInterpolation(ast: Interpolation): any { return this.visitAll(ast.expressions); }
visitKeyedRead(ast: KeyedRead): any {
ast.obj.visit(this);
ast.key.visit(this);
return null;
}
visitKeyedWrite(ast: KeyedWrite): any {
ast.obj.visit(this);
ast.key.visit(this);
ast.value.visit(this);
return null;
}
visitLiteralArray(ast: LiteralArray): any { return this.visitAll(ast.expressions); }
visitLiteralMap(ast: LiteralMap): any { return this.visitAll(ast.values); }
visitLiteralPrimitive(ast: LiteralPrimitive): any { return null; }
visitMethodCall(ast: MethodCall): any {
ast.receiver.visit(this);
return this.visitAll(ast.args);
}
visitPrefixNot(ast: PrefixNot): any {
ast.expression.visit(this);
return null;
}
visitPropertyRead(ast: PropertyRead): any {
ast.receiver.visit(this);
return null;
}
visitPropertyWrite(ast: PropertyWrite): any {
ast.receiver.visit(this);
ast.value.visit(this);
return null;
}
visitSafePropertyRead(ast: SafePropertyRead): any {
ast.receiver.visit(this);
return null;
}
visitSafeMethodCall(ast: SafeMethodCall): any {
ast.receiver.visit(this);
return this.visitAll(ast.args);
}
visitAll(asts: List<AST>): any {
ListWrapper.forEach(asts, (ast) => { ast.visit(this); });
return null;
}
}
export class AstTransformer implements AstVisitor {
visitImplicitReceiver(ast: ImplicitReceiver): ImplicitReceiver { return ast; }
visitInterpolation(ast: Interpolation): Interpolation {
return new Interpolation(ast.strings, this.visitAll(ast.expressions));
}
visitLiteralPrimitive(ast: LiteralPrimitive): LiteralPrimitive {
return new LiteralPrimitive(ast.value);
}
visitPropertyRead(ast: PropertyRead): PropertyRead {
return new PropertyRead(ast.receiver.visit(this), ast.name, ast.getter);
}
visitPropertyWrite(ast: PropertyWrite): PropertyWrite {
return new PropertyWrite(ast.receiver.visit(this), ast.name, ast.setter, ast.value);
}
visitSafePropertyRead(ast: SafePropertyRead): SafePropertyRead {
return new SafePropertyRead(ast.receiver.visit(this), ast.name, ast.getter);
}
visitMethodCall(ast: MethodCall): MethodCall {
return new MethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
}
visitSafeMethodCall(ast: SafeMethodCall): SafeMethodCall {
return new SafeMethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
}
visitFunctionCall(ast: FunctionCall): FunctionCall {
return new FunctionCall(ast.target.visit(this), this.visitAll(ast.args));
}
visitLiteralArray(ast: LiteralArray): LiteralArray {
return new LiteralArray(this.visitAll(ast.expressions));
}
visitLiteralMap(ast: LiteralMap): LiteralMap {
return new LiteralMap(ast.keys, this.visitAll(ast.values));
}
visitBinary(ast: Binary): Binary {
return new Binary(ast.operation, ast.left.visit(this), ast.right.visit(this));
}
visitPrefixNot(ast: PrefixNot): PrefixNot { return new PrefixNot(ast.expression.visit(this)); }
visitConditional(ast: Conditional): Conditional {
return new Conditional(ast.condition.visit(this), ast.trueExp.visit(this),
ast.falseExp.visit(this));
}
visitPipe(ast: BindingPipe): BindingPipe {
return new BindingPipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args));
}
visitKeyedRead(ast: KeyedRead): KeyedRead {
return new KeyedRead(ast.obj.visit(this), ast.key.visit(this));
}
visitKeyedWrite(ast: KeyedWrite): KeyedWrite {
return new KeyedWrite(ast.obj.visit(this), ast.key.visit(this), ast.value.visit(this));
}
visitAll(asts: List<any>): List<any> {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
visitChain(ast: Chain): Chain { return new Chain(this.visitAll(ast.expressions)); }
visitIf(ast: If): If {
let falseExp = isPresent(ast.falseExp) ? ast.falseExp.visit(this) : null;
return new If(ast.condition.visit(this), ast.trueExp.visit(this), falseExp);
}
}

View File

@ -0,0 +1,468 @@
import {Injectable} from 'angular2/src/di/decorators';
import {List, ListWrapper, SetWrapper} from "angular2/src/facade/collection";
import {
NumberWrapper,
StringJoiner,
StringWrapper,
BaseException,
isPresent
} from "angular2/src/facade/lang";
export enum TokenType {
CHARACTER,
IDENTIFIER,
KEYWORD,
STRING,
OPERATOR,
NUMBER
}
@Injectable()
export class Lexer {
tokenize(text: string): List<any> {
var scanner = new _Scanner(text);
var tokens = [];
var token = scanner.scanToken();
while (token != null) {
tokens.push(token);
token = scanner.scanToken();
}
return tokens;
}
}
export class Token {
constructor(public index: number, public type: TokenType, public numValue: number,
public strValue: string) {}
isCharacter(code: number): boolean {
return (this.type == TokenType.CHARACTER && this.numValue == code);
}
isNumber(): boolean { return (this.type == TokenType.NUMBER); }
isString(): boolean { return (this.type == TokenType.STRING); }
isOperator(operater: string): boolean {
return (this.type == TokenType.OPERATOR && this.strValue == operater);
}
isIdentifier(): boolean { return (this.type == TokenType.IDENTIFIER); }
isKeyword(): boolean { return (this.type == TokenType.KEYWORD); }
isKeywordVar(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "var"); }
isKeywordNull(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "null"); }
isKeywordUndefined(): boolean {
return (this.type == TokenType.KEYWORD && this.strValue == "undefined");
}
isKeywordTrue(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "true"); }
isKeywordIf(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "if"); }
isKeywordElse(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "else"); }
isKeywordFalse(): boolean { return (this.type == TokenType.KEYWORD && this.strValue == "false"); }
toNumber(): number {
// -1 instead of NULL ok?
return (this.type == TokenType.NUMBER) ? this.numValue : -1;
}
toString(): string {
switch (this.type) {
case TokenType.CHARACTER:
case TokenType.STRING:
case TokenType.IDENTIFIER:
case TokenType.KEYWORD:
return this.strValue;
case TokenType.NUMBER:
return this.numValue.toString();
default:
return null;
}
}
}
function newCharacterToken(index: number, code: number): Token {
return new Token(index, TokenType.CHARACTER, code, StringWrapper.fromCharCode(code));
}
function newIdentifierToken(index: number, text: string): Token {
return new Token(index, TokenType.IDENTIFIER, 0, text);
}
function newKeywordToken(index: number, text: string): Token {
return new Token(index, TokenType.KEYWORD, 0, text);
}
function newOperatorToken(index: number, text: string): Token {
return new Token(index, TokenType.OPERATOR, 0, text);
}
function newStringToken(index: number, text: string): Token {
return new Token(index, TokenType.STRING, 0, text);
}
function newNumberToken(index: number, n: number): Token {
return new Token(index, TokenType.NUMBER, n, "");
}
export var EOF: Token = new Token(-1, TokenType.CHARACTER, 0, "");
export const $EOF = 0;
export const $TAB = 9;
export const $LF = 10;
export const $VTAB = 11;
export const $FF = 12;
export const $CR = 13;
export const $SPACE = 32;
export const $BANG = 33;
export const $DQ = 34;
export const $HASH = 35;
export const $$ = 36;
export const $PERCENT = 37;
export const $AMPERSAND = 38;
export const $SQ = 39;
export const $LPAREN = 40;
export const $RPAREN = 41;
export const $STAR = 42;
export const $PLUS = 43;
export const $COMMA = 44;
export const $MINUS = 45;
export const $PERIOD = 46;
export const $SLASH = 47;
export const $COLON = 58;
export const $SEMICOLON = 59;
export const $LT = 60;
export const $EQ = 61;
export const $GT = 62;
export const $QUESTION = 63;
const $0 = 48;
const $9 = 57;
const $A = 65, $E = 69, $Z = 90;
export const $LBRACKET = 91;
export const $BACKSLASH = 92;
export const $RBRACKET = 93;
const $CARET = 94;
const $_ = 95;
const $a = 97, $e = 101, $f = 102, $n = 110, $r = 114, $t = 116, $u = 117, $v = 118, $z = 122;
export const $LBRACE = 123;
export const $BAR = 124;
export const $RBRACE = 125;
const $NBSP = 160;
export class ScannerError extends BaseException {
constructor(public message) { super(); }
toString(): string { return this.message; }
}
class _Scanner {
length: number;
peek: number = 0;
index: number = -1;
constructor(public input: string) {
this.length = input.length;
this.advance();
}
advance() {
this.peek =
++this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
}
scanToken(): Token {
var input = this.input, length = this.length, peek = this.peek, index = this.index;
// Skip whitespace.
while (peek <= $SPACE) {
if (++index >= length) {
peek = $EOF;
break;
} else {
peek = StringWrapper.charCodeAt(input, index);
}
}
this.peek = peek;
this.index = index;
if (index >= length) {
return null;
}
// Handle identifiers and numbers.
if (isIdentifierStart(peek)) return this.scanIdentifier();
if (isDigit(peek)) return this.scanNumber(index);
var start: number = index;
switch (peek) {
case $PERIOD:
this.advance();
return isDigit(this.peek) ? this.scanNumber(start) : newCharacterToken(start, $PERIOD);
case $LPAREN:
case $RPAREN:
case $LBRACE:
case $RBRACE:
case $LBRACKET:
case $RBRACKET:
case $COMMA:
case $COLON:
case $SEMICOLON:
return this.scanCharacter(start, peek);
case $SQ:
case $DQ:
return this.scanString();
case $HASH:
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $QUESTION:
return this.scanComplexOperator(start, '?', $PERIOD, '.');
case $LT:
case $GT:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=');
case $BANG:
case $EQ:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=', $EQ,
'=');
case $AMPERSAND:
return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
case $BAR:
return this.scanComplexOperator(start, '|', $BAR, '|');
case $NBSP:
while (isWhitespace(this.peek)) this.advance();
return this.scanToken();
}
this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
return null;
}
scanCharacter(start: number, code: number): Token {
assert(this.peek == code);
this.advance();
return newCharacterToken(start, code);
}
scanOperator(start: number, str: string): Token {
assert(this.peek == StringWrapper.charCodeAt(str, 0));
assert(SetWrapper.has(OPERATORS, str));
this.advance();
return newOperatorToken(start, str);
}
/**
* Tokenize a 2/3 char long operator
*
* @param start start index in the expression
* @param one first symbol (always part of the operator)
* @param twoCode code point for the second symbol
* @param two second symbol (part of the operator when the second code point matches)
* @param threeCode code point for the third symbol
* @param three third symbol (part of the operator when provided and matches source expression)
* @returns {Token}
*/
scanComplexOperator(start: number, one: string, twoCode: number, two: string, threeCode?: number,
three?: string): Token {
assert(this.peek == StringWrapper.charCodeAt(one, 0));
this.advance();
var str: string = one;
if (this.peek == twoCode) {
this.advance();
str += two;
}
if (isPresent(threeCode) && this.peek == threeCode) {
this.advance();
str += three;
}
assert(SetWrapper.has(OPERATORS, str));
return newOperatorToken(start, str);
}
scanIdentifier(): Token {
assert(isIdentifierStart(this.peek));
var start: number = this.index;
this.advance();
while (isIdentifierPart(this.peek)) this.advance();
var str: string = this.input.substring(start, this.index);
if (SetWrapper.has(KEYWORDS, str)) {
return newKeywordToken(start, str);
} else {
return newIdentifierToken(start, str);
}
}
scanNumber(start: number): Token {
assert(isDigit(this.peek));
var simple: boolean = (this.index === start);
this.advance(); // Skip initial digit.
while (true) {
if (isDigit(this.peek)) {
// Do nothing.
} else if (this.peek == $PERIOD) {
simple = false;
} else if (isExponentStart(this.peek)) {
this.advance();
if (isExponentSign(this.peek)) this.advance();
if (!isDigit(this.peek)) this.error('Invalid exponent', -1);
simple = false;
} else {
break;
}
this.advance();
}
var str: string = this.input.substring(start, this.index);
// TODO
var value: number =
simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str);
return newNumberToken(start, value);
}
scanString(): Token {
assert(this.peek == $SQ || this.peek == $DQ);
var start: number = this.index;
var quote: number = this.peek;
this.advance(); // Skip initial quote.
var buffer: StringJoiner;
var marker: number = this.index;
var input: string = this.input;
while (this.peek != quote) {
if (this.peek == $BACKSLASH) {
if (buffer == null) buffer = new StringJoiner();
buffer.add(input.substring(marker, this.index));
this.advance();
var unescapedCode: number;
if (this.peek == $u) {
// 4 character hex code for unicode character.
var hex: string = input.substring(this.index + 1, this.index + 5);
try {
unescapedCode = NumberWrapper.parseInt(hex, 16);
} catch (e) {
this.error(`Invalid unicode escape [\\u${hex}]`, 0);
}
for (var i: number = 0; i < 5; i++) {
this.advance();
}
} else {
unescapedCode = unescape(this.peek);
this.advance();
}
buffer.add(StringWrapper.fromCharCode(unescapedCode));
marker = this.index;
} else if (this.peek == $EOF) {
this.error('Unterminated quote', 0);
} else {
this.advance();
}
}
var last: string = input.substring(marker, this.index);
this.advance(); // Skip terminating quote.
// Compute the unescaped string value.
var unescaped: string = last;
if (buffer != null) {
buffer.add(last);
unescaped = buffer.toString();
}
return newStringToken(start, unescaped);
}
error(message: string, offset: number) {
var position: number = this.index + offset;
throw new ScannerError(
`Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
}
}
function isWhitespace(code: number): boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}
function isIdentifierStart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || (code == $_) || (code == $$);
}
function isIdentifierPart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || ($0 <= code && code <= $9) ||
(code == $_) || (code == $$);
}
function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
}
function isExponentStart(code: number): boolean {
return code == $e || code == $E;
}
function isExponentSign(code: number): boolean {
return code == $MINUS || code == $PLUS;
}
function unescape(code: number): number {
switch (code) {
case $n:
return $LF;
case $f:
return $FF;
case $r:
return $CR;
case $t:
return $TAB;
case $v:
return $VTAB;
default:
return code;
}
}
var OPERATORS = SetWrapper.createFromList([
'+',
'-',
'*',
'/',
'%',
'^',
'=',
'==',
'!=',
'===',
'!==',
'<',
'>',
'<=',
'>=',
'&&',
'||',
'&',
'|',
'!',
'?',
'#',
'?.'
]);
var KEYWORDS =
SetWrapper.createFromList(['var', 'null', 'undefined', 'true', 'false', 'if', 'else']);

View File

@ -0,0 +1,44 @@
import {isPresent, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
export class Locals {
constructor(public parent: Locals, public current: Map<any, any>) {}
contains(name: string): boolean {
if (this.current.has(name)) {
return true;
}
if (isPresent(this.parent)) {
return this.parent.contains(name);
}
return false;
}
get(name: string): any {
if (this.current.has(name)) {
return this.current.get(name);
}
if (isPresent(this.parent)) {
return this.parent.get(name);
}
throw new BaseException(`Cannot find '${name}'`);
}
set(name: string, value: any): void {
// TODO(rado): consider removing this check if we can guarantee this is not
// exposed to the public API.
// TODO: vsavkin maybe it should check only the local map
if (this.current.has(name)) {
this.current.set(name, value);
} else {
throw new BaseException(
`Setting of new keys post-construction is not supported. Key: ${name}.`);
}
}
clearValues(): void { MapWrapper.clearValues(this.current); }
}

View File

@ -0,0 +1,691 @@
import {Injectable} from 'angular2/src/di/decorators';
import {isBlank, isPresent, BaseException, StringWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, List} from 'angular2/src/facade/collection';
import {
Lexer,
EOF,
Token,
$PERIOD,
$COLON,
$SEMICOLON,
$LBRACKET,
$RBRACKET,
$COMMA,
$LBRACE,
$RBRACE,
$LPAREN,
$RPAREN
} from './lexer';
import {reflector, Reflector} from 'angular2/src/reflection/reflection';
import {
AST,
EmptyExpr,
ImplicitReceiver,
PropertyRead,
PropertyWrite,
SafePropertyRead,
LiteralPrimitive,
Binary,
PrefixNot,
Conditional,
If,
BindingPipe,
Chain,
KeyedRead,
KeyedWrite,
LiteralArray,
LiteralMap,
Interpolation,
MethodCall,
SafeMethodCall,
FunctionCall,
TemplateBinding,
ASTWithSource,
AstVisitor
} from './ast';
var _implicitReceiver = new ImplicitReceiver();
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
var INTERPOLATION_REGEXP = /\{\{(.*?)\}\}/g;
class ParseException extends BaseException {
constructor(message: string, input: string, errLocation: string, ctxLocation?: any) {
super(`Parser Error: ${message} ${errLocation} [${input}] in ${ctxLocation}`, null, null,
ctxLocation);
}
}
@Injectable()
export class Parser {
_reflector: Reflector;
constructor(public _lexer: Lexer, providedReflector: Reflector = null) {
this._reflector = isPresent(providedReflector) ? providedReflector : reflector;
}
parseAction(input: string, location: any): ASTWithSource {
this._checkNoInterpolation(input, location);
var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, true).parseChain();
return new ASTWithSource(ast, input, location);
}
parseBinding(input: string, location: any): ASTWithSource {
this._checkNoInterpolation(input, location);
var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
return new ASTWithSource(ast, input, location);
}
parseSimpleBinding(input: string, location: string): ASTWithSource {
this._checkNoInterpolation(input, location);
var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseSimpleBinding();
return new ASTWithSource(ast, input, location);
}
parseTemplateBindings(input: string, location: any): List<TemplateBinding> {
var tokens = this._lexer.tokenize(input);
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
}
parseInterpolation(input: string, location: any): ASTWithSource {
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
if (parts.length <= 1) {
return null;
}
var strings = [];
var expressions = [];
for (var i = 0; i < parts.length; i++) {
var part: string = parts[i];
if (i % 2 === 0) {
// fixed string
strings.push(part);
} else if (part.trim().length > 0) {
var tokens = this._lexer.tokenize(part);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
expressions.push(ast);
} else {
throw new ParseException('Blank expressions are not allowed in interpolated strings', input,
`at column ${this._findInterpolationErrorColumn(parts, i)} in`,
location);
}
}
return new ASTWithSource(new Interpolation(strings, expressions), input, location);
}
wrapLiteralPrimitive(input: string, location: any): ASTWithSource {
return new ASTWithSource(new LiteralPrimitive(input), input, location);
}
private _checkNoInterpolation(input: string, location: any): void {
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
if (parts.length > 1) {
throw new ParseException('Got interpolation ({{}}) where expression was expected', input,
`at column ${this._findInterpolationErrorColumn(parts, 1)} in`,
location);
}
}
private _findInterpolationErrorColumn(parts: string[], partInErrIdx: number): number {
var errLocation = '';
for (var j = 0; j < partInErrIdx; j++) {
errLocation += j % 2 === 0 ? parts[j] : `{{${parts[j]}}}`;
}
return errLocation.length;
}
}
export class _ParseAST {
index: number = 0;
constructor(public input: string, public location: any, public tokens: List<any>,
public reflector: Reflector, public parseAction: boolean) {}
peek(offset: number): Token {
var i = this.index + offset;
return i < this.tokens.length ? this.tokens[i] : EOF;
}
get next(): Token { return this.peek(0); }
get inputIndex(): number {
return (this.index < this.tokens.length) ? this.next.index : this.input.length;
}
advance() { this.index++; }
optionalCharacter(code: number): boolean {
if (this.next.isCharacter(code)) {
this.advance();
return true;
} else {
return false;
}
}
optionalKeywordVar(): boolean {
if (this.peekKeywordVar()) {
this.advance();
return true;
} else {
return false;
}
}
peekKeywordVar(): boolean { return this.next.isKeywordVar() || this.next.isOperator('#'); }
expectCharacter(code: number) {
if (this.optionalCharacter(code)) return;
this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`);
}
optionalOperator(op: string): boolean {
if (this.next.isOperator(op)) {
this.advance();
return true;
} else {
return false;
}
}
expectOperator(operator: string) {
if (this.optionalOperator(operator)) return;
this.error(`Missing expected operator ${operator}`);
}
expectIdentifierOrKeyword(): string {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword()) {
this.error(`Unexpected token ${n}, expected identifier or keyword`);
}
this.advance();
return n.toString();
}
expectIdentifierOrKeywordOrString(): string {
var n = this.next;
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
this.error(`Unexpected token ${n}, expected identifier, keyword, or string`);
}
this.advance();
return n.toString();
}
parseChain(): AST {
var exprs = [];
while (this.index < this.tokens.length) {
var expr = this.parsePipe();
exprs.push(expr);
if (this.optionalCharacter($SEMICOLON)) {
if (!this.parseAction) {
this.error("Binding expression cannot contain chained expression");
}
while (this.optionalCharacter($SEMICOLON)) {
} // read all semicolons
} else if (this.index < this.tokens.length) {
this.error(`Unexpected token '${this.next}'`);
}
}
if (exprs.length == 0) return new EmptyExpr();
if (exprs.length == 1) return exprs[0];
return new Chain(exprs);
}
parseSimpleBinding(): AST {
var ast = this.parseChain();
if (!SimpleExpressionChecker.check(ast)) {
this.error(`Simple binding expression can only contain field access and constants'`);
}
return ast;
}
parsePipe(): AST {
var result = this.parseExpression();
if (this.optionalOperator("|")) {
if (this.parseAction) {
this.error("Cannot have a pipe in an action expression");
}
do {
var name = this.expectIdentifierOrKeyword();
var args = [];
while (this.optionalCharacter($COLON)) {
args.push(this.parsePipe());
}
result = new BindingPipe(result, name, args);
} while (this.optionalOperator("|"));
}
return result;
}
parseExpression(): AST { return this.parseConditional(); }
parseConditional(): AST {
var start = this.inputIndex;
var result = this.parseLogicalOr();
if (this.optionalOperator('?')) {
var yes = this.parsePipe();
if (!this.optionalCharacter($COLON)) {
var end = this.inputIndex;
var expression = this.input.substring(start, end);
this.error(`Conditional expression ${expression} requires all 3 expressions`);
}
var no = this.parsePipe();
return new Conditional(result, yes, no);
} else {
return result;
}
}
parseLogicalOr(): AST {
// '||'
var result = this.parseLogicalAnd();
while (this.optionalOperator('||')) {
result = new Binary('||', result, this.parseLogicalAnd());
}
return result;
}
parseLogicalAnd(): AST {
// '&&'
var result = this.parseEquality();
while (this.optionalOperator('&&')) {
result = new Binary('&&', result, this.parseEquality());
}
return result;
}
parseEquality(): AST {
// '==','!=','===','!=='
var result = this.parseRelational();
while (true) {
if (this.optionalOperator('==')) {
result = new Binary('==', result, this.parseRelational());
} else if (this.optionalOperator('===')) {
result = new Binary('===', result, this.parseRelational());
} else if (this.optionalOperator('!=')) {
result = new Binary('!=', result, this.parseRelational());
} else if (this.optionalOperator('!==')) {
result = new Binary('!==', result, this.parseRelational());
} else {
return result;
}
}
}
parseRelational(): AST {
// '<', '>', '<=', '>='
var result = this.parseAdditive();
while (true) {
if (this.optionalOperator('<')) {
result = new Binary('<', result, this.parseAdditive());
} else if (this.optionalOperator('>')) {
result = new Binary('>', result, this.parseAdditive());
} else if (this.optionalOperator('<=')) {
result = new Binary('<=', result, this.parseAdditive());
} else if (this.optionalOperator('>=')) {
result = new Binary('>=', result, this.parseAdditive());
} else {
return result;
}
}
}
parseAdditive(): AST {
// '+', '-'
var result = this.parseMultiplicative();
while (true) {
if (this.optionalOperator('+')) {
result = new Binary('+', result, this.parseMultiplicative());
} else if (this.optionalOperator('-')) {
result = new Binary('-', result, this.parseMultiplicative());
} else {
return result;
}
}
}
parseMultiplicative(): AST {
// '*', '%', '/'
var result = this.parsePrefix();
while (true) {
if (this.optionalOperator('*')) {
result = new Binary('*', result, this.parsePrefix());
} else if (this.optionalOperator('%')) {
result = new Binary('%', result, this.parsePrefix());
} else if (this.optionalOperator('/')) {
result = new Binary('/', result, this.parsePrefix());
} else {
return result;
}
}
}
parsePrefix(): AST {
if (this.optionalOperator('+')) {
return this.parsePrefix();
} else if (this.optionalOperator('-')) {
return new Binary('-', new LiteralPrimitive(0), this.parsePrefix());
} else if (this.optionalOperator('!')) {
return new PrefixNot(this.parsePrefix());
} else {
return this.parseCallChain();
}
}
parseCallChain(): AST {
var result = this.parsePrimary();
while (true) {
if (this.optionalCharacter($PERIOD)) {
result = this.parseAccessMemberOrMethodCall(result, false);
} else if (this.optionalOperator('?.')) {
result = this.parseAccessMemberOrMethodCall(result, true);
} else if (this.optionalCharacter($LBRACKET)) {
var key = this.parsePipe();
this.expectCharacter($RBRACKET);
if (this.optionalOperator("=")) {
var value = this.parseConditional();
result = new KeyedWrite(result, key, value);
} else {
result = new KeyedRead(result, key);
}
} else if (this.optionalCharacter($LPAREN)) {
var args = this.parseCallArguments();
this.expectCharacter($RPAREN);
result = new FunctionCall(result, args);
} else {
return result;
}
}
}
parsePrimary(): AST {
if (this.optionalCharacter($LPAREN)) {
let result = this.parsePipe();
this.expectCharacter($RPAREN);
return result;
} else if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) {
this.advance();
return new LiteralPrimitive(null);
} else if (this.next.isKeywordTrue()) {
this.advance();
return new LiteralPrimitive(true);
} else if (this.next.isKeywordFalse()) {
this.advance();
return new LiteralPrimitive(false);
} else if (this.parseAction && this.next.isKeywordIf()) {
this.advance();
this.expectCharacter($LPAREN);
let condition = this.parseExpression();
this.expectCharacter($RPAREN);
let ifExp = this.parseExpressionOrBlock();
let elseExp;
if (this.next.isKeywordElse()) {
this.advance();
elseExp = this.parseExpressionOrBlock();
}
return new If(condition, ifExp, elseExp);
} else if (this.optionalCharacter($LBRACKET)) {
var elements = this.parseExpressionList($RBRACKET);
this.expectCharacter($RBRACKET);
return new LiteralArray(elements);
} else if (this.next.isCharacter($LBRACE)) {
return this.parseLiteralMap();
} else if (this.next.isIdentifier()) {
return this.parseAccessMemberOrMethodCall(_implicitReceiver, false);
} else if (this.next.isNumber()) {
var value = this.next.toNumber();
this.advance();
return new LiteralPrimitive(value);
} else if (this.next.isString()) {
var literalValue = this.next.toString();
this.advance();
return new LiteralPrimitive(literalValue);
} else if (this.index >= this.tokens.length) {
this.error(`Unexpected end of expression: ${this.input}`);
} else {
this.error(`Unexpected token ${this.next}`);
}
// error() throws, so we don't reach here.
throw new BaseException("Fell through all cases in parsePrimary");
}
parseExpressionList(terminator: number): List<any> {
var result = [];
if (!this.next.isCharacter(terminator)) {
do {
result.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
}
return result;
}
parseLiteralMap(): LiteralMap {
var keys = [];
var values = [];
this.expectCharacter($LBRACE);
if (!this.optionalCharacter($RBRACE)) {
do {
var key = this.expectIdentifierOrKeywordOrString();
keys.push(key);
this.expectCharacter($COLON);
values.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
this.expectCharacter($RBRACE);
}
return new LiteralMap(keys, values);
}
parseAccessMemberOrMethodCall(receiver: AST, isSafe: boolean = false): AST {
let id = this.expectIdentifierOrKeyword();
if (this.optionalCharacter($LPAREN)) {
let args = this.parseCallArguments();
this.expectCharacter($RPAREN);
let fn = this.reflector.method(id);
return isSafe ? new SafeMethodCall(receiver, id, fn, args) :
new MethodCall(receiver, id, fn, args);
} else {
if (isSafe) {
if (this.optionalOperator("=")) {
this.error("The '?.' operator cannot be used in the assignment");
} else {
return new SafePropertyRead(receiver, id, this.reflector.getter(id));
}
} else {
if (this.optionalOperator("=")) {
if (!this.parseAction) {
this.error("Bindings cannot contain assignments");
}
let value = this.parseConditional();
return new PropertyWrite(receiver, id, this.reflector.setter(id), value);
} else {
return new PropertyRead(receiver, id, this.reflector.getter(id));
}
}
}
return null;
}
parseCallArguments(): BindingPipe[] {
if (this.next.isCharacter($RPAREN)) return [];
var positionals = [];
do {
positionals.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
return positionals;
}
parseExpressionOrBlock(): AST {
if (this.optionalCharacter($LBRACE)) {
let block = this.parseBlockContent();
this.expectCharacter($RBRACE);
return block;
}
return this.parseExpression();
}
parseBlockContent(): AST {
if (!this.parseAction) {
this.error("Binding expression cannot contain chained expression");
}
var exprs = [];
while (this.index < this.tokens.length && !this.next.isCharacter($RBRACE)) {
var expr = this.parseExpression();
exprs.push(expr);
if (this.optionalCharacter($SEMICOLON)) {
while (this.optionalCharacter($SEMICOLON)) {
} // read all semicolons
}
}
if (exprs.length == 0) return new EmptyExpr();
if (exprs.length == 1) return exprs[0];
return new Chain(exprs);
}
/**
* An identifier, a keyword, a string with an optional `-` inbetween.
*/
expectTemplateBindingKey(): string {
var result = '';
var operatorFound = false;
do {
result += this.expectIdentifierOrKeywordOrString();
operatorFound = this.optionalOperator('-');
if (operatorFound) {
result += '-';
}
} while (operatorFound);
return result.toString();
}
parseTemplateBindings(): any[] {
var bindings = [];
var prefix = null;
while (this.index < this.tokens.length) {
var keyIsVar: boolean = this.optionalKeywordVar();
var key = this.expectTemplateBindingKey();
if (!keyIsVar) {
if (prefix == null) {
prefix = key;
} else {
key = prefix + '-' + key;
}
}
this.optionalCharacter($COLON);
var name = null;
var expression = null;
if (keyIsVar) {
if (this.optionalOperator("=")) {
name = this.expectTemplateBindingKey();
} else {
name = '\$implicit';
}
} else if (this.next !== EOF && !this.peekKeywordVar()) {
var start = this.inputIndex;
var ast = this.parsePipe();
var source = this.input.substring(start, this.inputIndex);
expression = new ASTWithSource(ast, source, this.location);
}
bindings.push(new TemplateBinding(key, keyIsVar, name, expression));
if (!this.optionalCharacter($SEMICOLON)) {
this.optionalCharacter($COMMA);
}
}
return bindings;
}
error(message: string, index: number = null) {
if (isBlank(index)) index = this.index;
var location = (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` :
`at the end of the expression`;
throw new ParseException(message, this.input, location, this.location);
}
}
class SimpleExpressionChecker implements AstVisitor {
static check(ast: AST): boolean {
var s = new SimpleExpressionChecker();
ast.visit(s);
return s.simple;
}
simple = true;
visitImplicitReceiver(ast: ImplicitReceiver) {}
visitInterpolation(ast: Interpolation) { this.simple = false; }
visitLiteralPrimitive(ast: LiteralPrimitive) {}
visitPropertyRead(ast: PropertyRead) {}
visitPropertyWrite(ast: PropertyWrite) { this.simple = false; }
visitSafePropertyRead(ast: SafePropertyRead) { this.simple = false; }
visitMethodCall(ast: MethodCall) { this.simple = false; }
visitSafeMethodCall(ast: SafeMethodCall) { this.simple = false; }
visitFunctionCall(ast: FunctionCall) { this.simple = false; }
visitLiteralArray(ast: LiteralArray) { this.visitAll(ast.expressions); }
visitLiteralMap(ast: LiteralMap) { this.visitAll(ast.values); }
visitBinary(ast: Binary) { this.simple = false; }
visitPrefixNot(ast: PrefixNot) { this.simple = false; }
visitConditional(ast: Conditional) { this.simple = false; }
visitPipe(ast: BindingPipe) { this.simple = false; }
visitKeyedRead(ast: KeyedRead) { this.simple = false; }
visitKeyedWrite(ast: KeyedWrite) { this.simple = false; }
visitAll(asts: List<any>): List<any> {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
visitChain(ast: Chain) { this.simple = false; }
visitIf(ast: If) { this.simple = false; }
}

View File

@ -0,0 +1,5 @@
library angular2.core.compiler.pipe_lifecycle_reflector;
import 'package:angular2/src/change_detection/pipe_transform.dart';
bool implementsOnDestroy(Object pipe) => pipe is PipeOnDestroy;

View File

@ -0,0 +1,3 @@
export function implementsOnDestroy(pipe: any): boolean {
return pipe.constructor.prototype.onDestroy;
}

View File

@ -0,0 +1,39 @@
import {ABSTRACT, BaseException, CONST, Type} from 'angular2/src/facade/lang';
/**
* An interface which all pipes must implement.
*
* #Example
*
* ```
* class DoublePipe implements PipeTransform {
* transform(value, args = []) {
* return `${value}${value}`;
* }
* }
* ```
*/
export interface PipeTransform { transform(value: any, args: List<any>): any; }
/**
* An interface that stateful pipes should implement.
*
* #Example
*
* ```
* class StatefulPipe implements PipeTransform, PipeOnDestroy {
* connection;
*
* onDestroy() {
* this.connection.release();
* }
*
* transform(value, args = []) {
* this.connection = createConnection();
* // ...
* return someValue;
* }
* }
* ```
*/
export interface PipeOnDestroy { onDestroy(): void; }

View File

@ -0,0 +1,3 @@
import {PipeTransform} from './pipe_transform';
export interface Pipes { get(name: string): PipeTransform; }

View File

@ -0,0 +1,56 @@
library angular2.src.change_detection.pregen_proto_change_detector;
import 'package:angular2/src/change_detection/interfaces.dart';
import 'package:angular2/src/facade/lang.dart' show looseIdentical;
export 'dart:core' show List;
export 'package:angular2/src/change_detection/abstract_change_detector.dart'
show AbstractChangeDetector;
export 'package:angular2/src/change_detection/change_detection.dart'
show preGeneratedProtoDetectors;
export 'package:angular2/src/change_detection/directive_record.dart'
show DirectiveIndex, DirectiveRecord;
export 'package:angular2/src/change_detection/interfaces.dart'
show ChangeDetector, ChangeDetectorDefinition, ProtoChangeDetector;
export 'package:angular2/src/change_detection/pipes.dart' show Pipes;
export 'package:angular2/src/change_detection/proto_record.dart'
show ProtoRecord;
export 'package:angular2/src/change_detection/change_detection_util.dart'
show ChangeDetectionUtil;
export 'package:angular2/src/facade/lang.dart' show looseIdentical;
typedef ProtoChangeDetector PregenProtoChangeDetectorFactory(
ChangeDetectorDefinition definition);
typedef ChangeDetector InstantiateMethod(dynamic dispatcher);
/// Implementation of [ProtoChangeDetector] for use by pre-generated change
/// detectors in Angular 2 Dart.
/// Classes generated by the `TemplateCompiler` use this. The `export`s above
/// allow the generated code to `import` a single library and get all
/// dependencies.
class PregenProtoChangeDetector extends ProtoChangeDetector {
/// The [ChangeDetectorDefinition#id]. Strictly informational.
final String id;
/// Closure used to generate an actual [ChangeDetector].
final InstantiateMethod _instantiateMethod;
/// Internal ctor.
PregenProtoChangeDetector._(this.id, this._instantiateMethod);
static bool isSupported() => true;
factory PregenProtoChangeDetector(
InstantiateMethod instantiateMethod, ChangeDetectorDefinition def) {
return new PregenProtoChangeDetector._(def.id, instantiateMethod);
}
@override
instantiate(dynamic dispatcher) => _instantiateMethod(dispatcher);
}
/// Provided as an optimization to cut down on '!' characters in generated
/// change detectors. See https://github.com/angular/angular/issues/3248 for
/// for details.
bool looseNotIdentical(a, b) => !looseIdentical(a, b);

View File

@ -0,0 +1,14 @@
import {BaseException} from 'angular2/src/facade/lang';
import {ProtoChangeDetector, ChangeDetector} from './interfaces';
import {coalesce} from './coalesce';
export {Function as PregenProtoChangeDetectorFactory};
export class PregenProtoChangeDetector implements ProtoChangeDetector {
static isSupported(): boolean { return false; }
instantiate(dispatcher: any): ChangeDetector {
throw new BaseException('Pregen change detection not supported in Js');
}
}

View File

@ -0,0 +1,435 @@
import {BaseException, Type, isBlank, isPresent, isString} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {
PropertyRead,
PropertyWrite,
KeyedWrite,
AST,
ASTWithSource,
AstVisitor,
Binary,
Chain,
Conditional,
If,
BindingPipe,
FunctionCall,
ImplicitReceiver,
Interpolation,
KeyedRead,
LiteralArray,
LiteralMap,
LiteralPrimitive,
MethodCall,
PrefixNot,
SafePropertyRead,
SafeMethodCall
} from './parser/ast';
import {ChangeDetector, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector';
import {BindingRecord, BindingTarget} from './binding_record';
import {DirectiveRecord, DirectiveIndex} from './directive_record';
import {EventBinding} from './event_binding';
import {coalesce} from './coalesce';
import {ProtoRecord, RecordType} from './proto_record';
export class DynamicProtoChangeDetector implements ProtoChangeDetector {
_propertyBindingRecords: ProtoRecord[];
_propertyBindingTargets: BindingTarget[];
_eventBindingRecords: EventBinding[];
_directiveIndices: DirectiveIndex[];
constructor(private definition: ChangeDetectorDefinition) {
this._propertyBindingRecords = createPropertyRecords(definition);
this._eventBindingRecords = createEventRecords(definition);
this._propertyBindingTargets = this.definition.bindingRecords.map(b => b.target);
this._directiveIndices = this.definition.directiveRecords.map(d => d.directiveIndex);
}
instantiate(dispatcher: any): ChangeDetector {
return new DynamicChangeDetector(
this.definition.id, dispatcher, this._propertyBindingRecords.length,
this._propertyBindingTargets, this._directiveIndices, this.definition.strategy,
this._propertyBindingRecords, this._eventBindingRecords, this.definition.directiveRecords,
this.definition.genConfig);
}
}
export function createPropertyRecords(definition: ChangeDetectorDefinition): ProtoRecord[] {
var recordBuilder = new ProtoRecordBuilder();
ListWrapper.forEachWithIndex(definition.bindingRecords,
(b, index) => recordBuilder.add(b, definition.variableNames, index));
return coalesce(recordBuilder.records);
}
export function createEventRecords(definition: ChangeDetectorDefinition): EventBinding[] {
// TODO: vsavkin: remove $event when the compiler handles render-side variables properly
var varNames = ListWrapper.concat(['$event'], definition.variableNames);
return definition.eventRecords.map(er => {
var records = _ConvertAstIntoProtoRecords.create(er, varNames);
var dirIndex = er.implicitReceiver instanceof DirectiveIndex ? er.implicitReceiver : null;
return new EventBinding(er.target.name, er.target.elementIndex, dirIndex, records);
});
}
export class ProtoRecordBuilder {
records: List<ProtoRecord>;
constructor() { this.records = []; }
add(b: BindingRecord, variableNames: string[], bindingIndex: number) {
var oldLast = ListWrapper.last(this.records);
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
oldLast.lastInDirective = false;
}
var numberOfRecordsBefore = this.records.length;
this._appendRecords(b, variableNames, bindingIndex);
var newLast = ListWrapper.last(this.records);
if (isPresent(newLast) && newLast !== oldLast) {
newLast.lastInBinding = true;
newLast.lastInDirective = true;
this._setArgumentToPureFunction(numberOfRecordsBefore);
}
}
_setArgumentToPureFunction(startIndex: number): void {
for (var i = startIndex; i < this.records.length; ++i) {
var rec = this.records[i];
if (rec.isPureFunction()) {
rec.args.forEach(recordIndex => this.records[recordIndex - 1].argumentToPureFunction =
true);
}
}
}
_appendRecords(b: BindingRecord, variableNames: string[], bindingIndex: number) {
if (b.isDirectiveLifecycle()) {
this.records.push(new ProtoRecord(RecordType.DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [],
[], -1, null, this.records.length + 1, b, false, false,
false, false, null));
} else {
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames, bindingIndex);
}
}
}
class _ConvertAstIntoProtoRecords implements AstVisitor {
constructor(private _records: List<ProtoRecord>, private _bindingRecord: BindingRecord,
private _variableNames: string[], private _bindingIndex: number) {}
static append(records: List<ProtoRecord>, b: BindingRecord, variableNames: string[],
bindingIndex: number) {
var c = new _ConvertAstIntoProtoRecords(records, b, variableNames, bindingIndex);
b.ast.visit(c);
}
static create(b: BindingRecord, variableNames: List<any>): ProtoRecord[] {
var rec = [];
_ConvertAstIntoProtoRecords.append(rec, b, variableNames, null);
rec[rec.length - 1].lastInBinding = true;
return rec;
}
visitImplicitReceiver(ast: ImplicitReceiver): any { return this._bindingRecord.implicitReceiver; }
visitInterpolation(ast: Interpolation): number {
var args = this._visitAll(ast.expressions);
return this._addRecord(RecordType.INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
args, ast.strings, 0);
}
visitLiteralPrimitive(ast: LiteralPrimitive): number {
return this._addRecord(RecordType.CONST, "literal", ast.value, [], null, 0);
}
visitPropertyRead(ast: PropertyRead): number {
var receiver = ast.receiver.visit(this);
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name) &&
ast.receiver instanceof ImplicitReceiver) {
return this._addRecord(RecordType.LOCAL, ast.name, ast.name, [], null, receiver);
} else {
return this._addRecord(RecordType.PROPERTY_READ, ast.name, ast.getter, [], null, receiver);
}
}
visitPropertyWrite(ast: PropertyWrite): number {
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name) &&
ast.receiver instanceof ImplicitReceiver) {
throw new BaseException(`Cannot reassign a variable binding ${ast.name}`);
} else {
var receiver = ast.receiver.visit(this);
var value = ast.value.visit(this);
return this._addRecord(RecordType.PROPERTY_WRITE, ast.name, ast.setter, [value], null,
receiver);
}
}
visitKeyedWrite(ast: KeyedWrite): number {
var obj = ast.obj.visit(this);
var key = ast.key.visit(this);
var value = ast.value.visit(this);
return this._addRecord(RecordType.KEYED_WRITE, null, null, [key, value], null, obj);
}
visitSafePropertyRead(ast: SafePropertyRead): number {
var receiver = ast.receiver.visit(this);
return this._addRecord(RecordType.SAFE_PROPERTY, ast.name, ast.getter, [], null, receiver);
}
visitMethodCall(ast: MethodCall): number {
var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args);
if (isPresent(this._variableNames) && ListWrapper.contains(this._variableNames, ast.name)) {
var target = this._addRecord(RecordType.LOCAL, ast.name, ast.name, [], null, receiver);
return this._addRecord(RecordType.INVOKE_CLOSURE, "closure", null, args, null, target);
} else {
return this._addRecord(RecordType.INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
}
}
visitSafeMethodCall(ast: SafeMethodCall): number {
var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RecordType.SAFE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
}
visitFunctionCall(ast: FunctionCall): number {
var target = ast.target.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RecordType.INVOKE_CLOSURE, "closure", null, args, null, target);
}
visitLiteralArray(ast: LiteralArray): number {
var primitiveName = `arrayFn${ast.expressions.length}`;
return this._addRecord(RecordType.COLLECTION_LITERAL, primitiveName,
_arrayFn(ast.expressions.length), this._visitAll(ast.expressions), null,
0);
}
visitLiteralMap(ast: LiteralMap): number {
return this._addRecord(RecordType.COLLECTION_LITERAL, _mapPrimitiveName(ast.keys),
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null,
0);
}
visitBinary(ast: Binary): number {
var left = ast.left.visit(this);
var right = ast.right.visit(this);
return this._addRecord(RecordType.PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
_operationToFunction(ast.operation), [left, right], null, 0);
}
visitPrefixNot(ast: PrefixNot): number {
var exp = ast.expression.visit(this);
return this._addRecord(RecordType.PRIMITIVE_OP, "operation_negate",
ChangeDetectionUtil.operation_negate, [exp], null, 0);
}
visitConditional(ast: Conditional): number {
var c = ast.condition.visit(this);
var t = ast.trueExp.visit(this);
var f = ast.falseExp.visit(this);
return this._addRecord(RecordType.PRIMITIVE_OP, "cond", ChangeDetectionUtil.cond, [c, t, f],
null, 0);
}
visitPipe(ast: BindingPipe): number {
var value = ast.exp.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RecordType.PIPE, ast.name, ast.name, args, null, value);
}
visitKeyedRead(ast: KeyedRead): number {
var obj = ast.obj.visit(this);
var key = ast.key.visit(this);
return this._addRecord(RecordType.KEYED_READ, "keyedAccess", ChangeDetectionUtil.keyedAccess,
[key], null, obj);
}
visitChain(ast: Chain): number {
var args = ast.expressions.map(e => e.visit(this));
return this._addRecord(RecordType.CHAIN, "chain", null, args, null, 0);
}
visitIf(ast: If) { throw new BaseException('Not supported'); }
_visitAll(asts: List<any>) {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
_addRecord(type, name, funcOrValue, args, fixedArgs, context) {
var selfIndex = this._records.length + 1;
if (context instanceof DirectiveIndex) {
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context,
selfIndex, this._bindingRecord, false, false, false, false,
this._bindingIndex));
} else {
this._records.push(new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null,
selfIndex, this._bindingRecord, false, false, false, false,
this._bindingIndex));
}
return selfIndex;
}
}
function _arrayFn(length: number): Function {
switch (length) {
case 0:
return ChangeDetectionUtil.arrayFn0;
case 1:
return ChangeDetectionUtil.arrayFn1;
case 2:
return ChangeDetectionUtil.arrayFn2;
case 3:
return ChangeDetectionUtil.arrayFn3;
case 4:
return ChangeDetectionUtil.arrayFn4;
case 5:
return ChangeDetectionUtil.arrayFn5;
case 6:
return ChangeDetectionUtil.arrayFn6;
case 7:
return ChangeDetectionUtil.arrayFn7;
case 8:
return ChangeDetectionUtil.arrayFn8;
case 9:
return ChangeDetectionUtil.arrayFn9;
default:
throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
function _mapPrimitiveName(keys: List<any>) {
var stringifiedKeys =
ListWrapper.join(ListWrapper.map(keys, (k) => isString(k) ? `"${k}"` : `${k}`), ", ");
return `mapFn([${stringifiedKeys}])`;
}
function _operationToPrimitiveName(operation: string): string {
switch (operation) {
case '+':
return "operation_add";
case '-':
return "operation_subtract";
case '*':
return "operation_multiply";
case '/':
return "operation_divide";
case '%':
return "operation_remainder";
case '==':
return "operation_equals";
case '!=':
return "operation_not_equals";
case '===':
return "operation_identical";
case '!==':
return "operation_not_identical";
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_logical_and";
case '||':
return "operation_logical_or";
default:
throw new BaseException(`Unsupported operation ${operation}`);
}
}
function _operationToFunction(operation: string): Function {
switch (operation) {
case '+':
return ChangeDetectionUtil.operation_add;
case '-':
return ChangeDetectionUtil.operation_subtract;
case '*':
return ChangeDetectionUtil.operation_multiply;
case '/':
return ChangeDetectionUtil.operation_divide;
case '%':
return ChangeDetectionUtil.operation_remainder;
case '==':
return ChangeDetectionUtil.operation_equals;
case '!=':
return ChangeDetectionUtil.operation_not_equals;
case '===':
return ChangeDetectionUtil.operation_identical;
case '!==':
return ChangeDetectionUtil.operation_not_identical;
case '<':
return ChangeDetectionUtil.operation_less_then;
case '>':
return ChangeDetectionUtil.operation_greater_then;
case '<=':
return ChangeDetectionUtil.operation_less_or_equals_then;
case '>=':
return ChangeDetectionUtil.operation_greater_or_equals_then;
case '&&':
return ChangeDetectionUtil.operation_logical_and;
case '||':
return ChangeDetectionUtil.operation_logical_or;
default:
throw new BaseException(`Unsupported operation ${operation}`);
}
}
function s(v): string {
return isPresent(v) ? `${v}` : '';
}
function _interpolationFn(strings: List<any>) {
var length = strings.length;
var c0 = length > 0 ? strings[0] : null;
var c1 = length > 1 ? strings[1] : null;
var c2 = length > 2 ? strings[2] : null;
var c3 = length > 3 ? strings[3] : null;
var c4 = length > 4 ? strings[4] : null;
var c5 = length > 5 ? strings[5] : null;
var c6 = length > 6 ? strings[6] : null;
var c7 = length > 7 ? strings[7] : null;
var c8 = length > 8 ? strings[8] : null;
var c9 = length > 9 ? strings[9] : null;
switch (length - 1) {
case 1:
return (a1) => c0 + s(a1) + c1;
case 2:
return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
case 3:
return (a1, a2, a3) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3;
case 4:
return (a1, a2, a3, a4) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4;
case 5:
return (a1, a2, a3, a4, a5) =>
c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5;
case 6:
return (a1, a2, a3, a4, a5, a6) =>
c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6;
case 7:
return (a1, a2, a3, a4, a5, a6, a7) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) +
c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7;
case 8:
return (a1, a2, a3, a4, a5, a6, a7, a8) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) +
c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) +
c8;
case 9:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 +
s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) +
c7 + s(a8) + c8 + s(a9) + c9;
default:
throw new BaseException(`Does not support more than 9 expressions`);
}
}

View File

@ -0,0 +1,46 @@
import {List} from 'angular2/src/facade/collection';
import {BindingRecord} from './binding_record';
import {DirectiveIndex} from './directive_record';
export enum RecordType {
SELF,
CONST,
PRIMITIVE_OP,
PROPERTY_READ,
PROPERTY_WRITE,
LOCAL,
INVOKE_METHOD,
INVOKE_CLOSURE,
KEYED_READ,
KEYED_WRITE,
PIPE,
INTERPOLATE,
SAFE_PROPERTY,
COLLECTION_LITERAL,
SAFE_INVOKE_METHOD,
DIRECTIVE_LIFECYCLE,
CHAIN
}
export class ProtoRecord {
constructor(public mode: RecordType, public name: string, public funcOrValue,
public args: List<any>, public fixedArgs: List<any>, public contextIndex: number,
public directiveIndex: DirectiveIndex, public selfIndex: number,
public bindingRecord: BindingRecord, public lastInBinding: boolean,
public lastInDirective: boolean, public argumentToPureFunction: boolean,
public referencedBySelf: boolean, public propertyBindingIndex: number) {}
isPureFunction(): boolean {
return this.mode === RecordType.INTERPOLATE || this.mode === RecordType.COLLECTION_LITERAL;
}
isUsedByOtherRecord(): boolean { return !this.lastInBinding || this.referencedBySelf; }
shouldBeChecked(): boolean {
return this.argumentToPureFunction || this.lastInBinding || this.isPureFunction();
}
isPipeRecord(): boolean { return this.mode === RecordType.PIPE; }
isLifeCycleRecord(): boolean { return this.mode === RecordType.DIRECTIVE_LIFECYCLE; }
}