angular2
Version:
Angular 2 - a web framework for modern web apps
175 lines (149 loc) • 6.52 kB
text/typescript
import {isPresent, isBlank, looseIdentical} from 'angular2/src/facade/lang';
import {ListWrapper, Map} from 'angular2/src/facade/collection';
import {RecordType, ProtoRecord} from './proto_record';
/**
* Removes "duplicate" records. It assumes 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.
*
* @internal
*/
export function coalesce(srcRecords: ProtoRecord[]): ProtoRecord[] {
let dstRecords = [];
let excludedIdxs = [];
let indexMap: Map<number, number> = new Map<number, number>();
let skipDepth = 0;
let skipSources: ProtoRecord[] = ListWrapper.createFixedSize(srcRecords.length);
for (let protoIndex = 0; protoIndex < srcRecords.length; protoIndex++) {
let skipRecord = skipSources[protoIndex];
if (isPresent(skipRecord)) {
skipDepth--;
skipRecord.fixedArgs[0] = dstRecords.length;
}
let src = srcRecords[protoIndex];
let dst = _cloneAndUpdateIndexes(src, dstRecords, indexMap);
if (dst.isSkipRecord()) {
dstRecords.push(dst);
skipDepth++;
skipSources[dst.fixedArgs[0]] = dst;
} else {
let record = _mayBeAddRecord(dst, dstRecords, excludedIdxs, skipDepth > 0);
indexMap.set(src.selfIndex, record.selfIndex);
}
}
return _optimizeSkips(dstRecords);
}
/**
* - 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 dstRecords.find(
// TODO(vicb): optimize excludedIdxs.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,
r.directiveIndex, selfIndex, r.bindingRecord, r.lastInBinding,
r.lastInDirective, false, false, r.propertyBindingIndex);
}
function _haveSameDirIndex(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;
}