feat(ChangeDetector): Add support for short-circuiting
This commit is contained in:
@ -31,6 +31,7 @@ const CHANGES_LOCAL = "changes";
|
|||||||
export class ChangeDetectorJITGenerator {
|
export class ChangeDetectorJITGenerator {
|
||||||
private _logic: CodegenLogicUtil;
|
private _logic: CodegenLogicUtil;
|
||||||
private _names: CodegenNameUtil;
|
private _names: CodegenNameUtil;
|
||||||
|
private _endOfBlockIdxs: number[];
|
||||||
private id: string;
|
private id: string;
|
||||||
private changeDetectionStrategy: ChangeDetectionStrategy;
|
private changeDetectionStrategy: ChangeDetectionStrategy;
|
||||||
private records: ProtoRecord[];
|
private records: ProtoRecord[];
|
||||||
@ -91,7 +92,7 @@ export class ChangeDetectorJITGenerator {
|
|||||||
var ${IS_CHANGED_LOCAL} = false;
|
var ${IS_CHANGED_LOCAL} = false;
|
||||||
var ${CHANGES_LOCAL} = null;
|
var ${CHANGES_LOCAL} = null;
|
||||||
|
|
||||||
${this.records.map((r) => this._genRecord(r)).join("\n")}
|
${this._genAllRecords(this.records)}
|
||||||
}
|
}
|
||||||
|
|
||||||
${this._maybeGenHandleEventInternal()}
|
${this._maybeGenHandleEventInternal()}
|
||||||
@ -144,10 +145,28 @@ export class ChangeDetectorJITGenerator {
|
|||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_genEventBinding(eb: EventBinding): string {
|
_genEventBinding(eb: EventBinding): string {
|
||||||
var recs = eb.records.map(r => this._genEventBindingEval(eb, r)).join("\n");
|
let codes: String[] = [];
|
||||||
|
this._endOfBlockIdxs = [];
|
||||||
|
|
||||||
|
ListWrapper.forEachWithIndex(eb.records, (r, i) => {
|
||||||
|
let code;
|
||||||
|
|
||||||
|
if (r.isConditionalSkipRecord()) {
|
||||||
|
code = this._genConditionalSkip(r, this._names.getEventLocalName(eb, i));
|
||||||
|
} else if (r.isUnconditionalSkipRecord()) {
|
||||||
|
code = this._genUnconditionalSkip(r);
|
||||||
|
} else {
|
||||||
|
code = this._genEventBindingEval(eb, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
code += this._genEndOfSkipBlock(i);
|
||||||
|
|
||||||
|
codes.push(code);
|
||||||
|
});
|
||||||
|
|
||||||
return `
|
return `
|
||||||
if (eventName === "${eb.eventName}" && elIndex === ${eb.elIndex}) {
|
if (eventName === "${eb.eventName}" && elIndex === ${eb.elIndex}) {
|
||||||
${recs}
|
${codes.join("\n")}
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,20 +254,65 @@ export class ChangeDetectorJITGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_genRecord(r: ProtoRecord): string {
|
_genAllRecords(rs: ProtoRecord[]): string {
|
||||||
var rec;
|
var codes: String[] = [];
|
||||||
if (r.isLifeCycleRecord()) {
|
this._endOfBlockIdxs = [];
|
||||||
rec = this._genDirectiveLifecycle(r);
|
|
||||||
} else if (r.isPipeRecord()) {
|
for (let i = 0; i < rs.length; i++) {
|
||||||
rec = this._genPipeCheck(r);
|
let code;
|
||||||
} else {
|
let r = rs[i];
|
||||||
rec = this._genReferenceCheck(r);
|
|
||||||
|
if (r.isLifeCycleRecord()) {
|
||||||
|
code = this._genDirectiveLifecycle(r);
|
||||||
|
} else if (r.isPipeRecord()) {
|
||||||
|
code = this._genPipeCheck(r);
|
||||||
|
} else if (r.isConditionalSkipRecord()) {
|
||||||
|
code = this._genConditionalSkip(r, this._names.getLocalName(r.contextIndex));
|
||||||
|
} else if (r.isUnconditionalSkipRecord()) {
|
||||||
|
code = this._genUnconditionalSkip(r);
|
||||||
|
} else {
|
||||||
|
code = this._genReferenceCheck(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
code = `
|
||||||
|
${this._maybeFirstInBinding(r)}
|
||||||
|
${code}
|
||||||
|
${this._maybeGenLastInDirective(r)}
|
||||||
|
${this._genEndOfSkipBlock(i)}
|
||||||
|
`;
|
||||||
|
|
||||||
|
codes.push(code);
|
||||||
}
|
}
|
||||||
return `
|
|
||||||
${this._maybeFirstInBinding(r)}
|
return codes.join("\n");
|
||||||
${rec}
|
}
|
||||||
${this._maybeGenLastInDirective(r)}
|
|
||||||
`;
|
/** @internal */
|
||||||
|
_genConditionalSkip(r: ProtoRecord, condition: string): string {
|
||||||
|
let maybeNegate = r.mode === RecordType.SkipRecordsIf ? '!' : '';
|
||||||
|
this._endOfBlockIdxs.push(r.fixedArgs[0] - 1);
|
||||||
|
|
||||||
|
return `if (${maybeNegate}${condition}) {`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_genUnconditionalSkip(r: ProtoRecord): string {
|
||||||
|
this._endOfBlockIdxs.pop();
|
||||||
|
this._endOfBlockIdxs.push(r.fixedArgs[0] - 1);
|
||||||
|
return `} else {`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_genEndOfSkipBlock(protoIndex: number): string {
|
||||||
|
if (!ListWrapper.isEmpty(this._endOfBlockIdxs)) {
|
||||||
|
let endOfBlock = ListWrapper.last(this._endOfBlockIdxs);
|
||||||
|
if (protoIndex === endOfBlock) {
|
||||||
|
this._endOfBlockIdxs.pop();
|
||||||
|
return '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
@ -401,8 +465,8 @@ export class ChangeDetectorJITGenerator {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_maybeFirstInBinding(r: ProtoRecord): string {
|
_maybeFirstInBinding(r: ProtoRecord): string {
|
||||||
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
|
var prev = ChangeDetectionUtil.protoByIndex(this.records, r.selfIndex - 1);
|
||||||
var firstInBindng = isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
|
var firstInBinding = isBlank(prev) || prev.bindingRecord !== r.bindingRecord;
|
||||||
return firstInBindng && !r.bindingRecord.isDirectiveLifecycle() ?
|
return firstInBinding && !r.bindingRecord.isDirectiveLifecycle() ?
|
||||||
`${this._names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};` :
|
`${this._names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};` :
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
@ -126,8 +126,6 @@ export class ChangeDetectionUtil {
|
|||||||
static operation_greater_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_less_or_equals_then(left, right): any { return left <= right; }
|
||||||
static operation_greater_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 cond(cond, trueVal, falseVal): any { return cond ? trueVal : falseVal; }
|
||||||
|
|
||||||
static mapFn(keys: any[]): any {
|
static mapFn(keys: any[]): any {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {isPresent, isBlank, looseIdentical, StringWrapper} from 'angular2/src/core/facade/lang';
|
import {isPresent, isBlank, looseIdentical} from 'angular2/src/core/facade/lang';
|
||||||
import {ListWrapper, Map} from 'angular2/src/core/facade/collection';
|
import {ListWrapper, Map} from 'angular2/src/core/facade/collection';
|
||||||
import {RecordType, ProtoRecord} from './proto_record';
|
import {RecordType, ProtoRecord} from './proto_record';
|
||||||
|
|
||||||
@ -13,51 +13,158 @@ import {RecordType, ProtoRecord} from './proto_record';
|
|||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export function coalesce(records: ProtoRecord[]): ProtoRecord[] {
|
export function coalesce(srcRecords: ProtoRecord[]): ProtoRecord[] {
|
||||||
var res: ProtoRecord[] = [];
|
let dstRecords = [];
|
||||||
var indexMap: Map<number, number> = new Map<number, number>();
|
let excludedIdxs = [];
|
||||||
|
let indexMap: Map<number, number> = new Map<number, number>();
|
||||||
|
let skipDepth = 0;
|
||||||
|
let skipSources: ProtoRecord[] = ListWrapper.createFixedSize(srcRecords.length);
|
||||||
|
|
||||||
for (var i = 0; i < records.length; ++i) {
|
for (let protoIndex = 0; protoIndex < srcRecords.length; protoIndex++) {
|
||||||
var r = records[i];
|
let skipRecord = skipSources[protoIndex];
|
||||||
var record = _replaceIndices(r, res.length + 1, indexMap);
|
if (isPresent(skipRecord)) {
|
||||||
var matchingRecord = _findMatching(record, res);
|
skipDepth--;
|
||||||
|
skipRecord.fixedArgs[0] = dstRecords.length;
|
||||||
|
}
|
||||||
|
|
||||||
if (isPresent(matchingRecord) && record.lastInBinding) {
|
let src = srcRecords[protoIndex];
|
||||||
res.push(_selfRecord(record, matchingRecord.selfIndex, res.length + 1));
|
let dst = _cloneAndUpdateIndexes(src, dstRecords, indexMap);
|
||||||
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);
|
|
||||||
|
|
||||||
|
if (dst.isSkipRecord()) {
|
||||||
|
dstRecords.push(dst);
|
||||||
|
skipDepth++;
|
||||||
|
skipSources[dst.fixedArgs[0]] = dst;
|
||||||
} else {
|
} else {
|
||||||
res.push(record);
|
let record = _mayBeAddRecord(dst, dstRecords, excludedIdxs, skipDepth > 0);
|
||||||
indexMap.set(r.selfIndex, record.selfIndex);
|
indexMap.set(src.selfIndex, record.selfIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return _optimizeSkips(dstRecords);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
|
/**
|
||||||
|
* - Conditional skip of 1 record followed by an unconditional skip of N are replaced by a
|
||||||
|
* conditional skip of N with the negated condition,
|
||||||
|
* - Skips of 0 records are removed
|
||||||
|
*/
|
||||||
|
function _optimizeSkips(srcRecords: ProtoRecord[]): ProtoRecord[] {
|
||||||
|
let dstRecords = [];
|
||||||
|
let skipSources = ListWrapper.createFixedSize(srcRecords.length);
|
||||||
|
let indexMap: Map<number, number> = new Map<number, number>();
|
||||||
|
|
||||||
|
for (let protoIndex = 0; protoIndex < srcRecords.length; protoIndex++) {
|
||||||
|
let skipRecord = skipSources[protoIndex];
|
||||||
|
if (isPresent(skipRecord)) {
|
||||||
|
skipRecord.fixedArgs[0] = dstRecords.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = srcRecords[protoIndex];
|
||||||
|
|
||||||
|
if (src.isSkipRecord()) {
|
||||||
|
if (src.isConditionalSkipRecord() && src.fixedArgs[0] === protoIndex + 2 &&
|
||||||
|
protoIndex < srcRecords.length - 1 &&
|
||||||
|
srcRecords[protoIndex + 1].mode === RecordType.SkipRecords) {
|
||||||
|
src.mode = src.mode === RecordType.SkipRecordsIf ? RecordType.SkipRecordsIfNot :
|
||||||
|
RecordType.SkipRecordsIf;
|
||||||
|
src.fixedArgs[0] = srcRecords[protoIndex + 1].fixedArgs[0];
|
||||||
|
protoIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.fixedArgs[0] > protoIndex + 1) {
|
||||||
|
let dst = _cloneAndUpdateIndexes(src, dstRecords, indexMap);
|
||||||
|
dstRecords.push(dst);
|
||||||
|
skipSources[dst.fixedArgs[0]] = dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let dst = _cloneAndUpdateIndexes(src, dstRecords, indexMap);
|
||||||
|
dstRecords.push(dst);
|
||||||
|
indexMap.set(src.selfIndex, dst.selfIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dstRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new record or re-use one of the existing records.
|
||||||
|
*/
|
||||||
|
function _mayBeAddRecord(record: ProtoRecord, dstRecords: ProtoRecord[], excludedIdxs: number[],
|
||||||
|
excluded: boolean): ProtoRecord {
|
||||||
|
let match = _findFirstMatch(record, dstRecords, excludedIdxs);
|
||||||
|
|
||||||
|
if (isPresent(match)) {
|
||||||
|
if (record.lastInBinding) {
|
||||||
|
dstRecords.push(_createSelfRecord(record, match.selfIndex, dstRecords.length + 1));
|
||||||
|
match.referencedBySelf = true;
|
||||||
|
} else {
|
||||||
|
if (record.argumentToPureFunction) {
|
||||||
|
match.argumentToPureFunction = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excluded) {
|
||||||
|
excludedIdxs.push(record.selfIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
dstRecords.push(record);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first `ProtoRecord` that matches the record.
|
||||||
|
*/
|
||||||
|
function _findFirstMatch(record: ProtoRecord, dstRecords: ProtoRecord[],
|
||||||
|
excludedIdxs: number[]): ProtoRecord {
|
||||||
|
return ListWrapper.find(
|
||||||
|
dstRecords,
|
||||||
|
// TODO(vicb): optimize notReusableIndexes.indexOf (sorted array)
|
||||||
|
rr => excludedIdxs.indexOf(rr.selfIndex) == -1 && rr.mode !== RecordType.DirectiveLifecycle &&
|
||||||
|
_haveSameDirIndex(rr, record) && rr.mode === record.mode &&
|
||||||
|
looseIdentical(rr.funcOrValue, record.funcOrValue) &&
|
||||||
|
rr.contextIndex === record.contextIndex && looseIdentical(rr.name, record.name) &&
|
||||||
|
ListWrapper.equals(rr.args, record.args));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clone the `ProtoRecord` and changes the indexes for the ones in the destination array for:
|
||||||
|
* - the arguments,
|
||||||
|
* - the context,
|
||||||
|
* - self
|
||||||
|
*/
|
||||||
|
function _cloneAndUpdateIndexes(record: ProtoRecord, dstRecords: ProtoRecord[],
|
||||||
|
indexMap: Map<number, number>): ProtoRecord {
|
||||||
|
let args = record.args.map(src => _srcToDstSelfIndex(indexMap, src));
|
||||||
|
let contextIndex = _srcToDstSelfIndex(indexMap, record.contextIndex);
|
||||||
|
let selfIndex = dstRecords.length + 1;
|
||||||
|
|
||||||
|
return new ProtoRecord(record.mode, record.name, record.funcOrValue, args, record.fixedArgs,
|
||||||
|
contextIndex, record.directiveIndex, selfIndex, record.bindingRecord,
|
||||||
|
record.lastInBinding, record.lastInDirective,
|
||||||
|
record.argumentToPureFunction, record.referencedBySelf,
|
||||||
|
record.propertyBindingIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index in the destination array corresponding to the index in the src array.
|
||||||
|
* When the element is not present in the destination array, return the source index.
|
||||||
|
*/
|
||||||
|
function _srcToDstSelfIndex(indexMap: Map<number, number>, srcIdx: number): number {
|
||||||
|
var dstIdx = indexMap.get(srcIdx);
|
||||||
|
return isPresent(dstIdx) ? dstIdx : srcIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _createSelfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
|
||||||
return new ProtoRecord(RecordType.Self, "self", null, [], r.fixedArgs, contextIndex,
|
return new ProtoRecord(RecordType.Self, "self", null, [], r.fixedArgs, contextIndex,
|
||||||
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
|
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
|
||||||
r.lastInDirective, false, false, r.propertyBindingIndex);
|
r.lastInDirective, false, false, r.propertyBindingIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _findMatching(r: ProtoRecord, rs: ProtoRecord[]) {
|
function _haveSameDirIndex(a: ProtoRecord, b: ProtoRecord): boolean {
|
||||||
return ListWrapper.find(
|
|
||||||
rs, (rr) => rr.mode !== RecordType.DirectiveLifecycle && _sameDirIndex(rr, r) &&
|
|
||||||
rr.mode === r.mode && looseIdentical(rr.funcOrValue, r.funcOrValue) &&
|
|
||||||
rr.contextIndex === r.contextIndex && StringWrapper.equals(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 di1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.directiveIndex;
|
||||||
var ei1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.elementIndex;
|
var ei1 = isBlank(a.directiveIndex) ? null : a.directiveIndex.elementIndex;
|
||||||
|
|
||||||
@ -66,17 +173,3 @@ function _sameDirIndex(a: ProtoRecord, b: ProtoRecord): boolean {
|
|||||||
|
|
||||||
return di1 === di2 && ei1 === ei2;
|
return di1 === di2 && ei1 === ei2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map<any, any>) {
|
|
||||||
var args = r.args.map(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;
|
|
||||||
}
|
|
||||||
|
@ -54,20 +54,43 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
|||||||
var values = ListWrapper.createFixedSize(eb.records.length);
|
var values = ListWrapper.createFixedSize(eb.records.length);
|
||||||
values[0] = this.values[0];
|
values[0] = this.values[0];
|
||||||
|
|
||||||
for (var i = 0; i < eb.records.length; ++i) {
|
for (var protoIdx = 0; protoIdx < eb.records.length; ++protoIdx) {
|
||||||
var proto = eb.records[i];
|
var proto = eb.records[protoIdx];
|
||||||
var res = this._calculateCurrValue(proto, values, locals);
|
|
||||||
if (proto.lastInBinding) {
|
if (proto.isSkipRecord()) {
|
||||||
this._markPathAsCheckOnce(proto);
|
protoIdx += this._computeSkipLength(protoIdx, proto, values);
|
||||||
return res;
|
|
||||||
} else {
|
} else {
|
||||||
this._writeSelf(proto, res, values);
|
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");
|
throw new BaseException("Cannot be reached");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _computeSkipLength(protoIndex: number, proto: ProtoRecord, values: any[]): number {
|
||||||
|
if (proto.mode === RecordType.SkipRecords) {
|
||||||
|
return proto.fixedArgs[0] - protoIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proto.mode === RecordType.SkipRecordsIf) {
|
||||||
|
let condition = this._readContext(proto, values);
|
||||||
|
return condition ? proto.fixedArgs[0] - protoIndex - 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proto.mode === RecordType.SkipRecordsIfNot) {
|
||||||
|
let condition = this._readContext(proto, values);
|
||||||
|
return condition ? 0 : proto.fixedArgs[0] - protoIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BaseException("Cannot be reached");
|
||||||
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_markPathAsCheckOnce(proto: ProtoRecord): void {
|
_markPathAsCheckOnce(proto: ProtoRecord): void {
|
||||||
if (!proto.bindingRecord.isDefaultChangeDetection()) {
|
if (!proto.bindingRecord.isDefaultChangeDetection()) {
|
||||||
@ -122,8 +145,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
|||||||
|
|
||||||
var changes = null;
|
var changes = null;
|
||||||
var isChanged = false;
|
var isChanged = false;
|
||||||
for (var i = 0; i < protos.length; ++i) {
|
for (var protoIdx = 0; protoIdx < protos.length; ++protoIdx) {
|
||||||
var proto: ProtoRecord = protos[i];
|
var proto: ProtoRecord = protos[protoIdx];
|
||||||
var bindingRecord = proto.bindingRecord;
|
var bindingRecord = proto.bindingRecord;
|
||||||
var directiveRecord = bindingRecord.directiveRecord;
|
var directiveRecord = bindingRecord.directiveRecord;
|
||||||
|
|
||||||
@ -140,7 +163,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
|
|||||||
} else if (proto.name === "OnChanges" && isPresent(changes) && !throwOnChange) {
|
} else if (proto.name === "OnChanges" && isPresent(changes) && !throwOnChange) {
|
||||||
this._getDirectiveFor(directiveRecord.directiveIndex).onChanges(changes);
|
this._getDirectiveFor(directiveRecord.directiveIndex).onChanges(changes);
|
||||||
}
|
}
|
||||||
|
} else if (proto.isSkipRecord()) {
|
||||||
|
protoIdx += this._computeSkipLength(protoIdx, proto, this.values);
|
||||||
} else {
|
} else {
|
||||||
var change = this._check(proto, throwOnChange, this.values, this.locals);
|
var change = this._check(proto, throwOnChange, this.values, this.locals);
|
||||||
if (isPresent(change)) {
|
if (isPresent(change)) {
|
||||||
|
@ -226,9 +226,28 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
|
|||||||
|
|
||||||
visitBinary(ast: Binary): number {
|
visitBinary(ast: Binary): number {
|
||||||
var left = ast.left.visit(this);
|
var left = ast.left.visit(this);
|
||||||
var right = ast.right.visit(this);
|
switch (ast.operation) {
|
||||||
return this._addRecord(RecordType.PrimitiveOp, _operationToPrimitiveName(ast.operation),
|
case '&&':
|
||||||
_operationToFunction(ast.operation), [left, right], null, 0);
|
var branchEnd = [null];
|
||||||
|
this._addRecord(RecordType.SkipRecordsIfNot, "SkipRecordsIfNot", null, [], branchEnd, left);
|
||||||
|
var right = ast.right.visit(this);
|
||||||
|
branchEnd[0] = right;
|
||||||
|
return this._addRecord(RecordType.PrimitiveOp, "cond", ChangeDetectionUtil.cond,
|
||||||
|
[left, right, left], null, 0);
|
||||||
|
|
||||||
|
case '||':
|
||||||
|
var branchEnd = [null];
|
||||||
|
this._addRecord(RecordType.SkipRecordsIf, "SkipRecordsIf", null, [], branchEnd, left);
|
||||||
|
var right = ast.right.visit(this);
|
||||||
|
branchEnd[0] = right;
|
||||||
|
return this._addRecord(RecordType.PrimitiveOp, "cond", ChangeDetectionUtil.cond,
|
||||||
|
[left, left, right], null, 0);
|
||||||
|
|
||||||
|
default:
|
||||||
|
var right = ast.right.visit(this);
|
||||||
|
return this._addRecord(RecordType.PrimitiveOp, _operationToPrimitiveName(ast.operation),
|
||||||
|
_operationToFunction(ast.operation), [left, right], null, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
visitPrefixNot(ast: PrefixNot): number {
|
visitPrefixNot(ast: PrefixNot): number {
|
||||||
@ -238,11 +257,20 @@ class _ConvertAstIntoProtoRecords implements AstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
visitConditional(ast: Conditional): number {
|
visitConditional(ast: Conditional): number {
|
||||||
var c = ast.condition.visit(this);
|
var condition = ast.condition.visit(this);
|
||||||
var t = ast.trueExp.visit(this);
|
var startOfFalseBranch = [null];
|
||||||
var f = ast.falseExp.visit(this);
|
var endOfFalseBranch = [null];
|
||||||
return this._addRecord(RecordType.PrimitiveOp, "cond", ChangeDetectionUtil.cond, [c, t, f],
|
this._addRecord(RecordType.SkipRecordsIfNot, "SkipRecordsIfNot", null, [], startOfFalseBranch,
|
||||||
null, 0);
|
condition);
|
||||||
|
var whenTrue = ast.trueExp.visit(this);
|
||||||
|
var skip =
|
||||||
|
this._addRecord(RecordType.SkipRecords, "SkipRecords", null, [], endOfFalseBranch, 0);
|
||||||
|
var whenFalse = ast.falseExp.visit(this);
|
||||||
|
startOfFalseBranch[0] = skip;
|
||||||
|
endOfFalseBranch[0] = whenFalse;
|
||||||
|
|
||||||
|
return this._addRecord(RecordType.PrimitiveOp, "cond", ChangeDetectionUtil.cond,
|
||||||
|
[condition, whenTrue, whenFalse], null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitPipe(ast: BindingPipe): number {
|
visitPipe(ast: BindingPipe): number {
|
||||||
@ -350,10 +378,6 @@ function _operationToPrimitiveName(operation: string): string {
|
|||||||
return "operation_less_or_equals_then";
|
return "operation_less_or_equals_then";
|
||||||
case '>=':
|
case '>=':
|
||||||
return "operation_greater_or_equals_then";
|
return "operation_greater_or_equals_then";
|
||||||
case '&&':
|
|
||||||
return "operation_logical_and";
|
|
||||||
case '||':
|
|
||||||
return "operation_logical_or";
|
|
||||||
default:
|
default:
|
||||||
throw new BaseException(`Unsupported operation ${operation}`);
|
throw new BaseException(`Unsupported operation ${operation}`);
|
||||||
}
|
}
|
||||||
@ -387,10 +411,6 @@ function _operationToFunction(operation: string): Function {
|
|||||||
return ChangeDetectionUtil.operation_less_or_equals_then;
|
return ChangeDetectionUtil.operation_less_or_equals_then;
|
||||||
case '>=':
|
case '>=':
|
||||||
return ChangeDetectionUtil.operation_greater_or_equals_then;
|
return ChangeDetectionUtil.operation_greater_or_equals_then;
|
||||||
case '&&':
|
|
||||||
return ChangeDetectionUtil.operation_logical_and;
|
|
||||||
case '||':
|
|
||||||
return ChangeDetectionUtil.operation_logical_or;
|
|
||||||
default:
|
default:
|
||||||
throw new BaseException(`Unsupported operation ${operation}`);
|
throw new BaseException(`Unsupported operation ${operation}`);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,10 @@ export enum RecordType {
|
|||||||
CollectionLiteral,
|
CollectionLiteral,
|
||||||
SafeMethodInvoke,
|
SafeMethodInvoke,
|
||||||
DirectiveLifecycle,
|
DirectiveLifecycle,
|
||||||
Chain
|
Chain,
|
||||||
|
SkipRecordsIf, // Skip records when the condition is true
|
||||||
|
SkipRecordsIfNot, // Skip records when the condition is false
|
||||||
|
SkipRecords // Skip records unconditionally
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProtoRecord {
|
export class ProtoRecord {
|
||||||
@ -42,5 +45,15 @@ export class ProtoRecord {
|
|||||||
|
|
||||||
isPipeRecord(): boolean { return this.mode === RecordType.Pipe; }
|
isPipeRecord(): boolean { return this.mode === RecordType.Pipe; }
|
||||||
|
|
||||||
|
isConditionalSkipRecord(): boolean {
|
||||||
|
return this.mode === RecordType.SkipRecordsIfNot || this.mode === RecordType.SkipRecordsIf;
|
||||||
|
}
|
||||||
|
|
||||||
|
isUnconditionalSkipRecord(): boolean { return this.mode === RecordType.SkipRecords; }
|
||||||
|
|
||||||
|
isSkipRecord(): boolean {
|
||||||
|
return this.isConditionalSkipRecord() || this.isUnconditionalSkipRecord();
|
||||||
|
}
|
||||||
|
|
||||||
isLifeCycleRecord(): boolean { return this.mode === RecordType.DirectiveLifecycle; }
|
isLifeCycleRecord(): boolean { return this.mode === RecordType.DirectiveLifecycle; }
|
||||||
}
|
}
|
||||||
|
@ -417,7 +417,14 @@ var _availableDefinitions = [
|
|||||||
'a.sayHi("Jim")',
|
'a.sayHi("Jim")',
|
||||||
'passThrough([12])',
|
'passThrough([12])',
|
||||||
'invalidFn(1)',
|
'invalidFn(1)',
|
||||||
'age'
|
'age',
|
||||||
|
'true ? city : zipcode',
|
||||||
|
'false ? city : zipcode',
|
||||||
|
'getTrue() && getTrue()',
|
||||||
|
'getFalse() && getTrue()',
|
||||||
|
'getFalse() || getFalse()',
|
||||||
|
'getTrue() || getFalse()',
|
||||||
|
'name == "Victor" ? (true ? address.city : address.zipcode) : address.zipcode'
|
||||||
];
|
];
|
||||||
|
|
||||||
var _availableEventDefinitions = [
|
var _availableEventDefinitions = [
|
||||||
@ -427,7 +434,8 @@ var _availableEventDefinitions = [
|
|||||||
// '(event)="\$event=1"',
|
// '(event)="\$event=1"',
|
||||||
'(event)="a=a+1; a=a+1;"',
|
'(event)="a=a+1; a=a+1;"',
|
||||||
'(event)="false"',
|
'(event)="false"',
|
||||||
'(event)="true"'
|
'(event)="true"',
|
||||||
|
'(event)="true ? a = a + 1 : a = a + 1"',
|
||||||
];
|
];
|
||||||
|
|
||||||
var _availableHostEventDefinitions = ['(host-event)="onEvent(\$event)"'];
|
var _availableHostEventDefinitions = ['(host-event)="onEvent(\$event)"'];
|
||||||
|
@ -111,6 +111,54 @@ export function main() {
|
|||||||
return val.dispatcher.log;
|
return val.dispatcher.log;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('short-circuit', () => {
|
||||||
|
it('should support short-circuit for the ternary operator', () => {
|
||||||
|
var address = new Address('Sunnyvale', '94085');
|
||||||
|
expect(_bindSimpleValue('true ? city : zipcode', address))
|
||||||
|
.toEqual(['propName=Sunnyvale']);
|
||||||
|
expect(address.cityGetterCalls).toEqual(1);
|
||||||
|
expect(address.zipCodeGetterCalls).toEqual(0);
|
||||||
|
|
||||||
|
address = new Address('Sunnyvale', '94085');
|
||||||
|
expect(_bindSimpleValue('false ? city : zipcode', address)).toEqual(['propName=94085']);
|
||||||
|
expect(address.cityGetterCalls).toEqual(0);
|
||||||
|
expect(address.zipCodeGetterCalls).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support short-circuit for the && operator', () => {
|
||||||
|
var logical = new Logical();
|
||||||
|
expect(_bindSimpleValue('getTrue() && getTrue()', logical)).toEqual(['propName=true']);
|
||||||
|
expect(logical.trueCalls).toEqual(2);
|
||||||
|
|
||||||
|
logical = new Logical();
|
||||||
|
expect(_bindSimpleValue('getFalse() && getTrue()', logical)).toEqual(['propName=false']);
|
||||||
|
expect(logical.falseCalls).toEqual(1);
|
||||||
|
expect(logical.trueCalls).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support short-circuit for the || operator', () => {
|
||||||
|
var logical = new Logical();
|
||||||
|
expect(_bindSimpleValue('getFalse() || getFalse()', logical)).toEqual(['propName=false']);
|
||||||
|
expect(logical.falseCalls).toEqual(2);
|
||||||
|
|
||||||
|
logical = new Logical();
|
||||||
|
expect(_bindSimpleValue('getTrue() || getFalse()', logical)).toEqual(['propName=true']);
|
||||||
|
expect(logical.falseCalls).toEqual(0);
|
||||||
|
expect(logical.trueCalls).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support nested short-circuits', () => {
|
||||||
|
var address = new Address('Sunnyvale', '94085');
|
||||||
|
var person = new Person('Victor', address);
|
||||||
|
expect(_bindSimpleValue(
|
||||||
|
'name == "Victor" ? (true ? address.city : address.zipcode) : address.zipcode',
|
||||||
|
person))
|
||||||
|
.toEqual(['propName=Sunnyvale']);
|
||||||
|
expect(address.cityGetterCalls).toEqual(1);
|
||||||
|
expect(address.zipCodeGetterCalls).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should support literals',
|
it('should support literals',
|
||||||
() => { expect(_bindSimpleValue('10')).toEqual(['propName=10']); });
|
() => { expect(_bindSimpleValue('10')).toEqual(['propName=10']); });
|
||||||
|
|
||||||
@ -1299,6 +1347,13 @@ export function main() {
|
|||||||
res = val.changeDetector.handleEvent("event", 0, locals);
|
res = val.changeDetector.handleEvent("event", 0, locals);
|
||||||
expect(res).toBe(false);
|
expect(res).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support short-circuiting', () => {
|
||||||
|
d.a = 0;
|
||||||
|
var val = _createChangeDetector('(event)="true ? a = a + 1 : a = a + 1"', d, null);
|
||||||
|
val.changeDetector.handleEvent("event", 0, locals);
|
||||||
|
expect(d.a).toEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1417,11 +1472,43 @@ class Person {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Address {
|
class Address {
|
||||||
constructor(public city: string) {}
|
cityGetterCalls: number = 0;
|
||||||
|
zipCodeGetterCalls: number = 0;
|
||||||
|
|
||||||
|
constructor(public _city: string, public _zipcode = null) {}
|
||||||
|
|
||||||
|
get city() {
|
||||||
|
this.cityGetterCalls++;
|
||||||
|
return this._city;
|
||||||
|
}
|
||||||
|
|
||||||
|
get zipcode() {
|
||||||
|
this.zipCodeGetterCalls++;
|
||||||
|
return this._zipcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
set city(v) { this._city = v; }
|
||||||
|
|
||||||
|
set zipcode(v) { this._zipcode = v; }
|
||||||
|
|
||||||
toString(): string { return isBlank(this.city) ? '-' : this.city }
|
toString(): string { return isBlank(this.city) ? '-' : this.city }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Logical {
|
||||||
|
trueCalls: number = 0;
|
||||||
|
falseCalls: number = 0;
|
||||||
|
|
||||||
|
getTrue() {
|
||||||
|
this.trueCalls++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFalse() {
|
||||||
|
this.falseCalls++;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Uninitialized {
|
class Uninitialized {
|
||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
|
@ -16,28 +16,29 @@ import {DirectiveIndex} from 'angular2/src/core/change_detection/directive_recor
|
|||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
function r(funcOrValue, args, contextIndex, selfIndex,
|
function r(funcOrValue, args, contextIndex, selfIndex,
|
||||||
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction}: {
|
{lastInBinding, mode, name, directiveIndex, argumentToPureFunction, fixedArgs}: {
|
||||||
lastInBinding?: any,
|
lastInBinding?: any,
|
||||||
mode?: any,
|
mode?: any,
|
||||||
name?: any,
|
name?: any,
|
||||||
directiveIndex?: any,
|
directiveIndex?: any,
|
||||||
argumentToPureFunction?: boolean
|
argumentToPureFunction?: boolean,
|
||||||
|
fixedArgs?: any[]
|
||||||
} = {}) {
|
} = {}) {
|
||||||
if (isBlank(lastInBinding)) lastInBinding = false;
|
if (isBlank(lastInBinding)) lastInBinding = false;
|
||||||
if (isBlank(mode)) mode = RecordType.PropertyRead;
|
if (isBlank(mode)) mode = RecordType.PropertyRead;
|
||||||
if (isBlank(name)) name = "name";
|
if (isBlank(name)) name = "name";
|
||||||
if (isBlank(directiveIndex)) directiveIndex = null;
|
if (isBlank(directiveIndex)) directiveIndex = null;
|
||||||
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
|
if (isBlank(argumentToPureFunction)) argumentToPureFunction = false;
|
||||||
|
if (isBlank(fixedArgs)) fixedArgs = null;
|
||||||
|
|
||||||
return new ProtoRecord(mode, name, funcOrValue, args, null, contextIndex, directiveIndex,
|
return new ProtoRecord(mode, name, funcOrValue, args, fixedArgs, contextIndex, directiveIndex,
|
||||||
selfIndex, null, lastInBinding, false, argumentToPureFunction, false, 0);
|
selfIndex, null, lastInBinding, false, argumentToPureFunction, false, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("change detection - coalesce", () => {
|
describe("change detection - coalesce", () => {
|
||||||
it("should work with an empty list", () => { expect(coalesce([])).toEqual([]); });
|
it("should work with an empty list", () => { expect(coalesce([])).toEqual([]); });
|
||||||
|
|
||||||
it("should remove non-terminal duplicate records" +
|
it("should remove non-terminal duplicate records and update the context indices referencing them",
|
||||||
" and update the context indices referencing them",
|
|
||||||
() => {
|
() => {
|
||||||
var rs = coalesce(
|
var rs = coalesce(
|
||||||
[r("user", [], 0, 1), r("first", [], 1, 2), r("user", [], 0, 3), r("last", [], 3, 4)]);
|
[r("user", [], 0, 1), r("first", [], 1, 2), r("user", [], 0, 3), r("last", [], 3, 4)]);
|
||||||
@ -52,8 +53,7 @@ export function main() {
|
|||||||
expect(rs).toEqual([r("dup", [], 0, 1), r("user", [], 0, 2), r("first", [2], 2, 3)]);
|
expect(rs).toEqual([r("dup", [], 0, 1), r("user", [], 0, 2), r("first", [2], 2, 3)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove non-terminal duplicate records" +
|
it("should remove non-terminal duplicate records and update the args indices referencing them",
|
||||||
" and update the args indices referencing them",
|
|
||||||
() => {
|
() => {
|
||||||
var rs = coalesce([
|
var rs = coalesce([
|
||||||
r("user1", [], 0, 1),
|
r("user1", [], 0, 1),
|
||||||
@ -132,5 +132,104 @@ export function main() {
|
|||||||
expect(rs)
|
expect(rs)
|
||||||
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
|
.toEqual([r("user", [], 0, 1, {argumentToPureFunction: true}), r("name", [], 1, 2)]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('short-circuit', () => {
|
||||||
|
it('should not use short-circuitable records', () => {
|
||||||
|
var records = [
|
||||||
|
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [3]}),
|
||||||
|
r("a", [], 0, 2),
|
||||||
|
r("sk", [], 0, 3, {mode: RecordType.SkipRecords, fixedArgs: [4]}),
|
||||||
|
r("b", [], 0, 4),
|
||||||
|
r("cond", [2, 4], 0, 5),
|
||||||
|
r("a", [], 0, 6),
|
||||||
|
r("b", [], 0, 7),
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(coalesce(records)).toEqual(records);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not use short-circuitable records from nested short-circuits', () => {
|
||||||
|
var records = [
|
||||||
|
r("sknot outer", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [7]}),
|
||||||
|
r("sknot inner", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||||
|
r("a", [], 0, 3),
|
||||||
|
r("sk inner", [], 0, 4, {mode: RecordType.SkipRecords, fixedArgs: [5]}),
|
||||||
|
r("b", [], 0, 5),
|
||||||
|
r("cond-inner", [3, 5], 0, 6),
|
||||||
|
r("sk outer", [], 0, 7, {mode: RecordType.SkipRecords, fixedArgs: [8]}),
|
||||||
|
r("c", [], 0, 8),
|
||||||
|
r("cond-outer", [6, 8], 0, 9),
|
||||||
|
r("a", [], 0, 10),
|
||||||
|
r("b", [], 0, 11),
|
||||||
|
r("c", [], 0, 12),
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(coalesce(records)).toEqual(records);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should collapse the true branch', () => {
|
||||||
|
var rs = coalesce([
|
||||||
|
r("a", [], 0, 1),
|
||||||
|
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||||
|
r("a", [], 0, 3),
|
||||||
|
r("sk", [], 0, 4, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||||
|
r("a", [], 0, 5),
|
||||||
|
r("b", [], 5, 6),
|
||||||
|
r("cond", [3, 6], 0, 7),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(rs).toEqual([
|
||||||
|
r("a", [], 0, 1),
|
||||||
|
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIf, fixedArgs: [3]}),
|
||||||
|
r("b", [], 1, 3),
|
||||||
|
r("cond", [1, 3], 0, 4),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should collapse the false branch', () => {
|
||||||
|
var rs = coalesce([
|
||||||
|
r("a", [], 0, 1),
|
||||||
|
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [5]}),
|
||||||
|
r("a", [], 0, 3),
|
||||||
|
r("b", [], 3, 4),
|
||||||
|
r("sk", [], 0, 5, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||||
|
r("a", [], 0, 6),
|
||||||
|
r("cond", [4, 6], 0, 7),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(rs).toEqual([
|
||||||
|
r("a", [], 0, 1),
|
||||||
|
r("sknot", [], 0, 2, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [3]}),
|
||||||
|
r("b", [], 1, 3),
|
||||||
|
r("cond", [3, 1], 0, 4),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should optimize skips', () => {
|
||||||
|
var rs = coalesce([
|
||||||
|
// skipIfNot(1) + skip(N) -> skipIf(+N)
|
||||||
|
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [2]}),
|
||||||
|
r("sk", [], 0, 2, {mode: RecordType.SkipRecords, fixedArgs: [3]}),
|
||||||
|
r("a", [], 0, 3),
|
||||||
|
// skipIf(1) + skip(N) -> skipIfNot(N)
|
||||||
|
r("skif", [], 0, 4, {mode: RecordType.SkipRecordsIf, fixedArgs: [5]}),
|
||||||
|
r("sk", [], 0, 5, {mode: RecordType.SkipRecords, fixedArgs: [6]}),
|
||||||
|
r("b", [], 0, 6),
|
||||||
|
// remove empty skips
|
||||||
|
r("sknot", [], 0, 7, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [7]}),
|
||||||
|
r("skif", [], 0, 8, {mode: RecordType.SkipRecordsIf, fixedArgs: [8]}),
|
||||||
|
r("sk", [], 0, 9, {mode: RecordType.SkipRecords, fixedArgs: [9]}),
|
||||||
|
r("end", [], 0, 10),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(rs).toEqual([
|
||||||
|
r("sknot", [], 0, 1, {mode: RecordType.SkipRecordsIf, fixedArgs: [2]}),
|
||||||
|
r("a", [], 0, 2),
|
||||||
|
r("skif", [], 0, 3, {mode: RecordType.SkipRecordsIfNot, fixedArgs: [4]}),
|
||||||
|
r("b", [], 0, 4),
|
||||||
|
r("end", [], 0, 5),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import 'package:angular2/src/core/change_detection/binding_record.dart';
|
|||||||
import 'package:angular2/src/core/change_detection/codegen_facade.dart'
|
import 'package:angular2/src/core/change_detection/codegen_facade.dart'
|
||||||
show codify;
|
show codify;
|
||||||
import 'package:angular2/src/core/facade/exceptions.dart' show BaseException;
|
import 'package:angular2/src/core/facade/exceptions.dart' show BaseException;
|
||||||
|
import 'package:angular2/src/core/facade/collection.dart' show ListWrapper;
|
||||||
|
|
||||||
/// Responsible for generating change detector classes for Angular 2.
|
/// Responsible for generating change detector classes for Angular 2.
|
||||||
///
|
///
|
||||||
@ -90,6 +91,7 @@ class _CodegenState {
|
|||||||
final CodegenNameUtil _names;
|
final CodegenNameUtil _names;
|
||||||
final ChangeDetectorGenConfig _genConfig;
|
final ChangeDetectorGenConfig _genConfig;
|
||||||
final List<BindingTarget> _propertyBindingTargets;
|
final List<BindingTarget> _propertyBindingTargets;
|
||||||
|
final List<int> _endOfBlockIdxs = [];
|
||||||
|
|
||||||
String get _changeDetectionStrategyAsCode => _changeDetectionStrategy == null
|
String get _changeDetectionStrategyAsCode => _changeDetectionStrategy == null
|
||||||
? 'null'
|
? 'null'
|
||||||
@ -156,7 +158,7 @@ class _CodegenState {
|
|||||||
var $_IS_CHANGED_LOCAL = false;
|
var $_IS_CHANGED_LOCAL = false;
|
||||||
var $_CHANGES_LOCAL = null;
|
var $_CHANGES_LOCAL = null;
|
||||||
|
|
||||||
${_records.map(_genRecord).join('')}
|
${_genAllRecords()}
|
||||||
}
|
}
|
||||||
|
|
||||||
${_maybeGenHandleEventInternal()}
|
${_maybeGenHandleEventInternal()}
|
||||||
@ -186,6 +188,15 @@ class _CodegenState {
|
|||||||
''');
|
''');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _genAllRecords() {
|
||||||
|
_endOfBlockIdxs.clear();
|
||||||
|
List<String> res = [];
|
||||||
|
for (int i = 0; i < _records.length; i++) {
|
||||||
|
res.add(_genRecord(_records[i], i));
|
||||||
|
}
|
||||||
|
return res.join('');
|
||||||
|
}
|
||||||
|
|
||||||
String _genPropertyBindingTargets() {
|
String _genPropertyBindingTargets() {
|
||||||
var targets = _logic.genPropertyBindingTargets(
|
var targets = _logic.genPropertyBindingTargets(
|
||||||
_propertyBindingTargets, this._genConfig.genDebugInfo);
|
_propertyBindingTargets, this._genConfig.genDebugInfo);
|
||||||
@ -215,10 +226,29 @@ class _CodegenState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _genEventBinding(EventBinding eb) {
|
String _genEventBinding(EventBinding eb) {
|
||||||
var recs = eb.records.map((r) => _genEventBindingEval(eb, r)).join("\n");
|
List<String> codes = [];
|
||||||
|
_endOfBlockIdxs.clear();
|
||||||
|
|
||||||
|
ListWrapper.forEachWithIndex(eb.records, (r, i) {
|
||||||
|
var code;
|
||||||
|
var r = eb.records[i];
|
||||||
|
|
||||||
|
if (r.isConditionalSkipRecord()) {
|
||||||
|
code = _genConditionalSkip(r, _names.getEventLocalName(eb, i));
|
||||||
|
} else if (r.isUnconditionalSkipRecord()) {
|
||||||
|
code = _genUnconditionalSkip(r);
|
||||||
|
} else {
|
||||||
|
code = _genEventBindingEval(eb, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
code += this._genEndOfSkipBlock(i);
|
||||||
|
|
||||||
|
codes.add(code);
|
||||||
|
});
|
||||||
|
|
||||||
return '''
|
return '''
|
||||||
if (eventName == "${eb.eventName}" && elIndex == ${eb.elIndex}) {
|
if (eventName == "${eb.eventName}" && elIndex == ${eb.elIndex}) {
|
||||||
${recs}
|
${codes.join("\n")}
|
||||||
}''';
|
}''';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,20 +345,53 @@ class _CodegenState {
|
|||||||
return 'var ${declareNames.join(', ')};';
|
return 'var ${declareNames.join(', ')};';
|
||||||
}
|
}
|
||||||
|
|
||||||
String _genRecord(ProtoRecord r) {
|
String _genRecord(ProtoRecord r, int index) {
|
||||||
var rec = null;
|
var code = null;
|
||||||
if (r.isLifeCycleRecord()) {
|
if (r.isLifeCycleRecord()) {
|
||||||
rec = _genDirectiveLifecycle(r);
|
code = _genDirectiveLifecycle(r);
|
||||||
} else if (r.isPipeRecord()) {
|
} else if (r.isPipeRecord()) {
|
||||||
rec = _genPipeCheck(r);
|
code = _genPipeCheck(r);
|
||||||
|
} else if (r.isConditionalSkipRecord()) {
|
||||||
|
code = _genConditionalSkip(r, _names.getLocalName(r.contextIndex));
|
||||||
|
} else if (r.isUnconditionalSkipRecord()) {
|
||||||
|
code = _genUnconditionalSkip(r);
|
||||||
} else {
|
} else {
|
||||||
rec = _genReferenceCheck(r);
|
code = _genReferenceCheck(r);
|
||||||
}
|
}
|
||||||
return '''
|
|
||||||
|
code = '''
|
||||||
${this._maybeFirstInBinding(r)}
|
${this._maybeFirstInBinding(r)}
|
||||||
${rec}
|
${code}
|
||||||
${this._maybeGenLastInDirective(r)}
|
${this._maybeGenLastInDirective(r)}
|
||||||
|
${this._genEndOfSkipBlock(index)}
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _genConditionalSkip(ProtoRecord r, String condition) {
|
||||||
|
var maybeNegate = r.mode == RecordType.SkipRecordsIf ? '!' : '';
|
||||||
|
_endOfBlockIdxs.add(r.fixedArgs[0] - 1);
|
||||||
|
|
||||||
|
return 'if ($maybeNegate$condition) {';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _genUnconditionalSkip(ProtoRecord r) {
|
||||||
|
_endOfBlockIdxs.removeLast();
|
||||||
|
_endOfBlockIdxs.add(r.fixedArgs[0] - 1);
|
||||||
|
return '} else {';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _genEndOfSkipBlock(int protoIndex) {
|
||||||
|
if (!ListWrapper.isEmpty(this._endOfBlockIdxs)) {
|
||||||
|
var endOfBlock = ListWrapper.last(this._endOfBlockIdxs);
|
||||||
|
if (protoIndex == endOfBlock) {
|
||||||
|
this._endOfBlockIdxs.removeLast();
|
||||||
|
return '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
String _genDirectiveLifecycle(ProtoRecord r) {
|
String _genDirectiveLifecycle(ProtoRecord r) {
|
||||||
|
Reference in New Issue
Block a user