chore(packaging): move files to match target file structure
This commit is contained in:
53
modules/angular2/src/change_detection/abstract_change_detector.js
vendored
Normal file
53
modules/angular2/src/change_detection/abstract_change_detector.js
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
import {isPresent} from 'facade/src/lang';
|
||||
import {List, ListWrapper} from 'facade/src/collection';
|
||||
import {ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
|
||||
|
||||
export class AbstractChangeDetector extends ChangeDetector {
|
||||
children:List;
|
||||
parent:ChangeDetector;
|
||||
status:string;
|
||||
|
||||
constructor() {
|
||||
this.children = [];
|
||||
this.status = CHECK_ALWAYS;
|
||||
}
|
||||
|
||||
addChild(cd:ChangeDetector) {
|
||||
ListWrapper.push(this.children, cd);
|
||||
cd.parent = this;
|
||||
}
|
||||
|
||||
removeChild(cd:ChangeDetector) {
|
||||
ListWrapper.remove(this.children, cd);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.parent.removeChild(this);
|
||||
}
|
||||
|
||||
detectChanges() {
|
||||
this._detectChanges(false);
|
||||
}
|
||||
|
||||
checkNoChanges() {
|
||||
this._detectChanges(true);
|
||||
}
|
||||
|
||||
_detectChanges(throwOnChange:boolean) {
|
||||
if (this.mode === DETACHED || this.mode === CHECKED) return;
|
||||
|
||||
this.detectChangesInRecords(throwOnChange);
|
||||
this._detectChangesInChildren(throwOnChange);
|
||||
|
||||
if (this.mode === CHECK_ONCE) this.mode = CHECKED;
|
||||
}
|
||||
|
||||
detectChangesInRecords(throwOnChange:boolean){}
|
||||
|
||||
_detectChangesInChildren(throwOnChange:boolean) {
|
||||
var children = this.children;
|
||||
for(var i = 0; i < children.length; ++i) {
|
||||
children[i]._detectChanges(throwOnChange);
|
||||
}
|
||||
}
|
||||
}
|
653
modules/angular2/src/change_detection/array_changes.js
vendored
Normal file
653
modules/angular2/src/change_detection/array_changes.js
vendored
Normal file
@ -0,0 +1,653 @@
|
||||
import {
|
||||
isListLikeIterable,
|
||||
iterateListLike,
|
||||
ListWrapper,
|
||||
MapWrapper
|
||||
} from 'facade/src/collection';
|
||||
|
||||
import {
|
||||
int,
|
||||
isBlank,
|
||||
isPresent,
|
||||
stringify,
|
||||
getMapKey,
|
||||
looseIdentical,
|
||||
} from 'facade/src/lang';
|
||||
|
||||
export class ArrayChanges {
|
||||
_collection;
|
||||
_length:int;
|
||||
_linkedRecords:_DuplicateMap;
|
||||
_unlinkedRecords:_DuplicateMap;
|
||||
_previousItHead:CollectionChangeRecord<V>;
|
||||
_itHead:CollectionChangeRecord<V>;
|
||||
_itTail:CollectionChangeRecord<V>;
|
||||
_additionsHead:CollectionChangeRecord<V>;
|
||||
_additionsTail:CollectionChangeRecord<V>;
|
||||
_movesHead:CollectionChangeRecord<V>;
|
||||
_movesTail:CollectionChangeRecord<V> ;
|
||||
_removalsHead:CollectionChangeRecord<V>;
|
||||
_removalsTail:CollectionChangeRecord<V>;
|
||||
|
||||
constructor() {
|
||||
this._collection = null;
|
||||
this._length = null;
|
||||
/// Keeps track of the used records at any point in time (during & across `_check()` calls)
|
||||
this._linkedRecords = null;
|
||||
/// Keeps track of the removed records at any point in time during `_check()` calls.
|
||||
this._unlinkedRecords = null;
|
||||
|
||||
this._previousItHead = null;
|
||||
this._itHead = null;
|
||||
this._itTail = null;
|
||||
this._additionsHead = null;
|
||||
this._additionsTail = null;
|
||||
this._movesHead = null;
|
||||
this._movesTail = null;
|
||||
this._removalsHead = null;
|
||||
this._removalsTail = null;
|
||||
}
|
||||
|
||||
static supports(obj):boolean {
|
||||
return isListLikeIterable(obj);
|
||||
}
|
||||
|
||||
supportsObj(obj):boolean {
|
||||
return ArrayChanges.supports(obj);
|
||||
}
|
||||
|
||||
get collection() {
|
||||
return this._collection;
|
||||
}
|
||||
|
||||
get length():int {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
|
||||
check(collection):boolean {
|
||||
this._reset();
|
||||
|
||||
var record:CollectionChangeRecord = this._itHead;
|
||||
var mayBeDirty:boolean = false;
|
||||
var index:int, item;
|
||||
|
||||
if (ListWrapper.isList(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:int):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:int):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 [CollectionChangeRecord]s from the previous collection
|
||||
*
|
||||
* - [record] The first excess [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:int):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:int):CollectionChangeRecord {
|
||||
this._unlink(record);
|
||||
this._insertAfter(record, prevRecord, index);
|
||||
this._addToMoves(record, index);
|
||||
return record;
|
||||
}
|
||||
|
||||
_addAfter(record:CollectionChangeRecord, prevRecord:CollectionChangeRecord,
|
||||
index:int):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:int):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:int):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) {
|
||||
ListWrapper.push(list, record);
|
||||
}
|
||||
|
||||
var previous = [];
|
||||
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
|
||||
ListWrapper.push(previous, record);
|
||||
}
|
||||
|
||||
var additions = [];
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
ListWrapper.push(additions, record);
|
||||
}
|
||||
var moves = [];
|
||||
for (record = this._movesHead; record !== null; record = record._nextMoved) {
|
||||
ListWrapper.push(moves, record);
|
||||
}
|
||||
|
||||
var removals = [];
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
ListWrapper.push(removals, 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:int;
|
||||
previousIndex:int;
|
||||
item;
|
||||
|
||||
_nextPrevious:CollectionChangeRecord;
|
||||
_prev:CollectionChangeRecord; _next:CollectionChangeRecord;
|
||||
_prevDup:CollectionChangeRecord; _nextDup:CollectionChangeRecord;
|
||||
_prevRemoved:CollectionChangeRecord; _nextRemoved:CollectionChangeRecord;
|
||||
_nextAdded:CollectionChangeRecord;
|
||||
_nextMoved:CollectionChangeRecord;
|
||||
|
||||
constructor(item) {
|
||||
this.currentIndex = null;
|
||||
this.previousIndex = null;
|
||||
this.item = item;
|
||||
|
||||
this._nextPrevious = null;
|
||||
this._prev = null;
|
||||
this._next = null;
|
||||
this._prevDup = null;
|
||||
this._nextDup = null;
|
||||
this._prevRemoved = null;
|
||||
this._nextRemoved = null;
|
||||
this._nextAdded = null;
|
||||
this._nextMoved = null;
|
||||
}
|
||||
|
||||
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;
|
||||
_tail:CollectionChangeRecord;
|
||||
|
||||
constructor() {
|
||||
this._head = null;
|
||||
this._tail = 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) {
|
||||
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, afterIndex:int):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 [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;
|
||||
constructor() {
|
||||
this.map = MapWrapper.create();
|
||||
}
|
||||
|
||||
put(record:CollectionChangeRecord) {
|
||||
// todo(vicb) handle corner cases
|
||||
var key = getMapKey(record.item);
|
||||
|
||||
var duplicates = MapWrapper.get(this.map, key);
|
||||
if (!isPresent(duplicates)) {
|
||||
duplicates = new _DuplicateItemRecordList();
|
||||
MapWrapper.set(this.map, 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, afterIndex = null):CollectionChangeRecord {
|
||||
var key = getMapKey(value);
|
||||
|
||||
var recordList = MapWrapper.get(this.map, key);
|
||||
return isBlank(recordList) ? null : recordList.get(value, afterIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an [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 = MapWrapper.get(this.map, 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() {
|
||||
MapWrapper.clear(this.map);
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return '_DuplicateMap(' + stringify(this.map) + ')';
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
library change_detectoin.change_detection_jit_generator;
|
||||
|
||||
class ChangeDetectorJITGenerator {
|
||||
ChangeDetectorJITGenerator(typeName, records) {
|
||||
}
|
||||
|
||||
generate() {
|
||||
throw "Not supported in Dart";
|
||||
}
|
||||
}
|
@ -0,0 +1,401 @@
|
||||
import {isPresent, isBlank, BaseException, Type} from 'facade/src/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||||
import {AbstractChangeDetector} from './abstract_change_detector';
|
||||
import {ChangeDetectionUtil} from './change_detection_util';
|
||||
|
||||
import {
|
||||
ProtoRecord,
|
||||
RECORD_TYPE_SELF,
|
||||
RECORD_TYPE_PROPERTY,
|
||||
RECORD_TYPE_INVOKE_METHOD,
|
||||
RECORD_TYPE_CONST,
|
||||
RECORD_TYPE_INVOKE_CLOSURE,
|
||||
RECORD_TYPE_PRIMITIVE_OP,
|
||||
RECORD_TYPE_KEYED_ACCESS,
|
||||
RECORD_TYPE_INVOKE_FORMATTER,
|
||||
RECORD_TYPE_STRUCTURAL_CHECK,
|
||||
RECORD_TYPE_INTERPOLATE,
|
||||
ProtoChangeDetector
|
||||
} from './proto_change_detector';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* For example: An expression `address.city` will result in the following class:
|
||||
*
|
||||
* var ChangeDetector0 = function ChangeDetector0(dispatcher, formatters, protos) {
|
||||
* AbstractChangeDetector.call(this);
|
||||
* this.dispatcher = dispatcher;
|
||||
* this.formatters = formatters;
|
||||
* this.protos = protos;
|
||||
*
|
||||
* this.context = null;
|
||||
* this.address0 = null;
|
||||
* this.city1 = null;
|
||||
* }
|
||||
* ChangeDetector0.prototype = Object.create(AbstractChangeDetector.prototype);
|
||||
*
|
||||
* ChangeDetector0.prototype.detectChangesInRecords = function(throwOnChange) {
|
||||
* var address0;
|
||||
* var city1;
|
||||
* var change;
|
||||
* var changes = null;
|
||||
* var temp;
|
||||
* var context = this.context;
|
||||
*
|
||||
* temp = ChangeDetectionUtil.findContext("address", context);
|
||||
* if (temp instanceof ContextWithVariableBindings) {
|
||||
* address0 = temp.get('address');
|
||||
* } else {
|
||||
* address0 = temp.address;
|
||||
* }
|
||||
*
|
||||
* if (address0 !== this.address0) {
|
||||
* this.address0 = address0;
|
||||
* }
|
||||
*
|
||||
* city1 = address0.city;
|
||||
* if (city1 !== this.city1) {
|
||||
* changes = ChangeDetectionUtil.addRecord(changes,
|
||||
* ChangeDetectionUtil.simpleChangeRecord(this.protos[1].bindingMemento, this.city1, city1));
|
||||
* this.city1 = city1;
|
||||
* }
|
||||
*
|
||||
* if (changes.length > 0) {
|
||||
* if(throwOnChange) ChangeDetectionUtil.throwOnChange(this.protos[1], changes[0]);
|
||||
* this.dispatcher.onRecordChange('address.city', changes);
|
||||
* changes = null;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* ChangeDetector0.prototype.setContext = function(context) {
|
||||
* this.context = context;
|
||||
* }
|
||||
*
|
||||
* return ChangeDetector0;
|
||||
*
|
||||
*
|
||||
* The only thing the generated class depends on is the super class AbstractChangeDetector.
|
||||
*
|
||||
* The implementation comprises two parts:
|
||||
* * ChangeDetectorJITGenerator has the logic of how everything fits together.
|
||||
* * template functions (e.g., constructorTemplate) define what code is generated.
|
||||
*/
|
||||
|
||||
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
|
||||
var UTIL = "ChangeDetectionUtil";
|
||||
var DISPATCHER_ACCESSOR = "this.dispatcher";
|
||||
var FORMATTERS_ACCESSOR = "this.formatters";
|
||||
var PROTOS_ACCESSOR = "this.protos";
|
||||
var CHANGE_LOCAL = "change";
|
||||
var CHANGES_LOCAL = "changes";
|
||||
var TEMP_LOCAL = "temp";
|
||||
|
||||
function typeTemplate(type:string, cons:string, detectChanges:string, setContext:string):string {
|
||||
return `
|
||||
${cons}
|
||||
${detectChanges}
|
||||
${setContext};
|
||||
|
||||
return function(dispatcher, formatters) {
|
||||
return new ${type}(dispatcher, formatters, protos);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function constructorTemplate(type:string, fieldsDefinitions:string):string {
|
||||
return `
|
||||
var ${type} = function ${type}(dispatcher, formatters, protos) {
|
||||
${ABSTRACT_CHANGE_DETECTOR}.call(this);
|
||||
${DISPATCHER_ACCESSOR} = dispatcher;
|
||||
${FORMATTERS_ACCESSOR} = formatters;
|
||||
${PROTOS_ACCESSOR} = protos;
|
||||
${fieldsDefinitions}
|
||||
}
|
||||
|
||||
${type}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
|
||||
`;
|
||||
}
|
||||
|
||||
function setContextTemplate(type:string):string {
|
||||
return `
|
||||
${type}.prototype.setContext = function(context) {
|
||||
this.context = context;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function detectChangesTemplate(type:string, body:string):string {
|
||||
return `
|
||||
${type}.prototype.detectChangesInRecords = function(throwOnChange) {
|
||||
${body}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
function bodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string {
|
||||
return `
|
||||
${localDefinitions}
|
||||
${changeDefinitions}
|
||||
var ${TEMP_LOCAL};
|
||||
var ${CHANGE_LOCAL};
|
||||
var ${CHANGES_LOCAL} = null;
|
||||
|
||||
context = this.context;
|
||||
${records}
|
||||
`;
|
||||
}
|
||||
|
||||
function notifyTemplate(index:number):string{
|
||||
return `
|
||||
if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
|
||||
if(throwOnChange) ${UTIL}.throwOnChange(${PROTOS_ACCESSOR}[${index}], ${CHANGES_LOCAL}[0]);
|
||||
${DISPATCHER_ACCESSOR}.onRecordChange(${PROTOS_ACCESSOR}[${index}].groupMemento, ${CHANGES_LOCAL});
|
||||
${CHANGES_LOCAL} = null;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
function structuralCheckTemplate(selfIndex:number, field:string, context:string, notify:string):string{
|
||||
return `
|
||||
${CHANGE_LOCAL} = ${UTIL}.structuralCheck(${field}, ${context});
|
||||
if (${CHANGE_LOCAL}) {
|
||||
${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL},
|
||||
${UTIL}.changeRecord(${PROTOS_ACCESSOR}[${selfIndex}].bindingMemento, ${CHANGE_LOCAL}));
|
||||
${field} = ${CHANGE_LOCAL}.currentValue;
|
||||
}
|
||||
${notify}
|
||||
`;
|
||||
}
|
||||
|
||||
function referenceCheckTemplate(assignment, newValue, oldValue, change, addRecord, notify) {
|
||||
return `
|
||||
${assignment}
|
||||
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
|
||||
${change} = true;
|
||||
${addRecord}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
${notify}
|
||||
`;
|
||||
}
|
||||
|
||||
function assignmentTemplate(field:string, value:string) {
|
||||
return `${field} = ${value};`;
|
||||
}
|
||||
|
||||
function propertyReadTemplate(name:string, context:string, newValue:string) {
|
||||
return `
|
||||
${TEMP_LOCAL} = ${UTIL}.findContext("${name}", ${context});
|
||||
if (${TEMP_LOCAL} instanceof ContextWithVariableBindings) {
|
||||
${newValue} = ${TEMP_LOCAL}.get('${name}');
|
||||
} else {
|
||||
${newValue} = ${TEMP_LOCAL}.${name};
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function localDefinitionsTemplate(names:List):string {
|
||||
return names.map((n) => `var ${n};`).join("\n");
|
||||
}
|
||||
|
||||
function changeDefinitionsTemplate(names:List):string {
|
||||
return names.map((n) => `var ${n} = false;`).join("\n");
|
||||
}
|
||||
|
||||
function fieldDefinitionsTemplate(names:List):string {
|
||||
return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n");
|
||||
}
|
||||
|
||||
function ifChangedGuardTemplate(changeNames:List, body:string):string {
|
||||
var cond = changeNames.join(" || ");
|
||||
return `
|
||||
if (${cond}) {
|
||||
${body}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
|
||||
return `${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL},
|
||||
${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
|
||||
}
|
||||
|
||||
|
||||
export class ChangeDetectorJITGenerator {
|
||||
typeName:string;
|
||||
records:List<ProtoRecord>;
|
||||
localNames:List<String>;
|
||||
changeNames:List<String>;
|
||||
fieldNames:List<String>;
|
||||
|
||||
constructor(typeName:string, records:List<ProtoRecord>) {
|
||||
this.typeName = typeName;
|
||||
this.records = records;
|
||||
|
||||
this.localNames = this.getLocalNames(records);
|
||||
this.changeNames = this.getChangeNames(this.localNames);
|
||||
this.fieldNames = this.getFieldNames(this.localNames);
|
||||
}
|
||||
|
||||
getLocalNames(records:List<ProtoRecord>):List<String> {
|
||||
var index = 0;
|
||||
var names = records.map((r) => {
|
||||
var sanitizedName = r.name.replace(new RegExp("\\W", "g"), '');
|
||||
return `${sanitizedName}${index++}`
|
||||
});
|
||||
return ["context"].concat(names);
|
||||
}
|
||||
|
||||
getChangeNames(localNames:List<String>):List<String> {
|
||||
return localNames.map((n) => `change_${n}`);
|
||||
}
|
||||
|
||||
getFieldNames(localNames:List<String>):List<String> {
|
||||
return localNames.map((n) => `this.${n}`);
|
||||
}
|
||||
|
||||
|
||||
generate():Function {
|
||||
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genSetContext());
|
||||
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'ContextWithVariableBindings', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, ContextWithVariableBindings, this.records);
|
||||
}
|
||||
|
||||
genConstructor():string {
|
||||
return constructorTemplate(this.typeName, fieldDefinitionsTemplate(this.fieldNames));
|
||||
}
|
||||
|
||||
genSetContext():string {
|
||||
return setContextTemplate(this.typeName);
|
||||
}
|
||||
|
||||
genDetectChanges():string {
|
||||
var body = this.genBody();
|
||||
return detectChangesTemplate(this.typeName, body);
|
||||
}
|
||||
|
||||
genBody():string {
|
||||
var rec = this.records.map((r) => this.genRecord(r)).join("\n");
|
||||
return bodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec);
|
||||
}
|
||||
|
||||
genLocalDefinitions():string {
|
||||
return localDefinitionsTemplate(this.localNames);
|
||||
}
|
||||
|
||||
genChangeDefinitions():string {
|
||||
return changeDefinitionsTemplate(this.changeNames);
|
||||
}
|
||||
|
||||
genRecord(r:ProtoRecord):string {
|
||||
if (r.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
|
||||
return this.getStructuralCheck(r);
|
||||
} else {
|
||||
return this.genReferenceCheck(r);
|
||||
}
|
||||
}
|
||||
|
||||
getStructuralCheck(r:ProtoRecord):string {
|
||||
var field = this.fieldNames[r.selfIndex];
|
||||
var context = this.localNames[r.contextIndex];
|
||||
return structuralCheckTemplate(r.selfIndex - 1, field, context, this.genNotify(r));
|
||||
}
|
||||
|
||||
genReferenceCheck(r:ProtoRecord):string {
|
||||
var newValue = this.localNames[r.selfIndex];
|
||||
var oldValue = this.fieldNames[r.selfIndex];
|
||||
var change = this.changeNames[r.selfIndex];
|
||||
var assignment = this.genUpdateCurrentValue(r);
|
||||
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
|
||||
var notify = this.genNotify(r);
|
||||
|
||||
var check = referenceCheckTemplate(assignment, newValue, oldValue, change, r.lastInBinding ? addRecord : '', notify);;
|
||||
if (r.isPureFunction()) {
|
||||
return this.ifChangedGuard(r, check);
|
||||
} else {
|
||||
return check;
|
||||
}
|
||||
}
|
||||
|
||||
genUpdateCurrentValue(r:ProtoRecord):string {
|
||||
var context = this.localNames[r.contextIndex];
|
||||
var newValue = this.localNames[r.selfIndex];
|
||||
var args = this.genArgs(r);
|
||||
|
||||
switch (r.mode) {
|
||||
case RECORD_TYPE_SELF:
|
||||
return assignmentTemplate(newValue, context);
|
||||
|
||||
case RECORD_TYPE_CONST:
|
||||
return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
|
||||
|
||||
case RECORD_TYPE_PROPERTY:
|
||||
if (r.contextIndex == 0) { // only the first property read can be a local
|
||||
return propertyReadTemplate(r.name, context, newValue);
|
||||
} else {
|
||||
return assignmentTemplate(newValue, `${context}.${r.name}`);
|
||||
}
|
||||
|
||||
case RECORD_TYPE_INVOKE_METHOD:
|
||||
return assignmentTemplate(newValue, `${context}.${r.name}(${args})`);
|
||||
|
||||
case RECORD_TYPE_INVOKE_CLOSURE:
|
||||
return assignmentTemplate(newValue, `${context}(${args})`);
|
||||
|
||||
case RECORD_TYPE_PRIMITIVE_OP:
|
||||
return assignmentTemplate(newValue, `${UTIL}.${r.name}(${args})`);
|
||||
|
||||
case RECORD_TYPE_INTERPOLATE:
|
||||
return assignmentTemplate(newValue, this.genInterpolation(r));
|
||||
|
||||
case RECORD_TYPE_INVOKE_FORMATTER:
|
||||
return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
|
||||
|
||||
case RECORD_TYPE_KEYED_ACCESS:
|
||||
var key = this.localNames[r.args[0]];
|
||||
return assignmentTemplate(newValue, `${context}[${key}]`);
|
||||
|
||||
default:
|
||||
throw new BaseException(`Unknown operation ${r.mode}`);
|
||||
}
|
||||
}
|
||||
|
||||
ifChangedGuard(r:ProtoRecord, body:string):string {
|
||||
return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body);
|
||||
}
|
||||
|
||||
genInterpolation(r:ProtoRecord):string{
|
||||
var res = "";
|
||||
for (var i = 0; i < r.args.length; ++i) {
|
||||
res += this.genLiteral(r.fixedArgs[i]);
|
||||
res += " + ";
|
||||
res += this.localNames[r.args[i]];
|
||||
res += " + ";
|
||||
}
|
||||
res += this.genLiteral(r.fixedArgs[r.args.length]);
|
||||
return res;
|
||||
}
|
||||
|
||||
genLiteral(value):string {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
genNotify(r):string{
|
||||
return r.lastInGroup ? notifyTemplate(r.selfIndex - 1) : '';
|
||||
}
|
||||
|
||||
genArgs(r:ProtoRecord):string {
|
||||
return r.args.map((arg) => this.localNames[arg]).join(", ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
225
modules/angular2/src/change_detection/change_detection_util.js
vendored
Normal file
225
modules/angular2/src/change_detection/change_detection_util.js
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
import {isPresent, isBlank, BaseException, Type} from 'facade/src/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
|
||||
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||||
import {ArrayChanges} from './array_changes';
|
||||
import {KeyValueChanges} from './keyvalue_changes';
|
||||
import {ProtoRecord} from './proto_change_detector';
|
||||
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
|
||||
import {ChangeRecord, ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
|
||||
|
||||
export var uninitialized = new Object();
|
||||
|
||||
export class SimpleChange {
|
||||
previousValue:any;
|
||||
currentValue:any;
|
||||
|
||||
constructor(previousValue:any, currentValue:any) {
|
||||
this.previousValue = previousValue;
|
||||
this.currentValue = currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
]
|
||||
|
||||
var _changeRecordsIndex = 0;
|
||||
var _changeRecords = [
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null),
|
||||
new ChangeRecord(null, null)
|
||||
]
|
||||
|
||||
function _simpleChange(previousValue, currentValue) {
|
||||
var index = _simpleChangesIndex++ % 20;
|
||||
var s = _simpleChanges[index];
|
||||
s.previousValue = previousValue;
|
||||
s.currentValue = currentValue;
|
||||
return s;
|
||||
}
|
||||
|
||||
function _changeRecord(bindingMemento, change) {
|
||||
var index = _changeRecordsIndex++ % 20;
|
||||
var s = _changeRecords[index];
|
||||
s.bindingMemento = bindingMemento;
|
||||
s.change = change;
|
||||
return s;
|
||||
}
|
||||
|
||||
var _singleElementList = [null];
|
||||
|
||||
function _isBlank(val):boolean {
|
||||
return isBlank(val) || val === uninitialized;
|
||||
}
|
||||
|
||||
export class ChangeDetectionUtil {
|
||||
static unitialized() {
|
||||
return uninitialized;
|
||||
}
|
||||
|
||||
static arrayFn0() { return []; }
|
||||
static arrayFn1(a1) { return [a1]; }
|
||||
static arrayFn2(a1, a2) { return [a1, a2]; }
|
||||
static arrayFn3(a1, a2, a3) { return [a1, a2, a3]; }
|
||||
static arrayFn4(a1, a2, a3, a4) { return [a1, a2, a3, a4]; }
|
||||
static arrayFn5(a1, a2, a3, a4, a5) { return [a1, a2, a3, a4, a5]; }
|
||||
static arrayFn6(a1, a2, a3, a4, a5, a6) { return [a1, a2, a3, a4, a5, a6]; }
|
||||
static arrayFn7(a1, a2, a3, a4, a5, a6, a7) { return [a1, a2, a3, a4, a5, a6, a7]; }
|
||||
static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8) { return [a1, a2, a3, a4, a5, a6, a7, a8]; }
|
||||
static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9) { return [a1, a2, a3, a4, a5, a6, a7, a8, a9]; }
|
||||
|
||||
static operation_negate(value) {return !value;}
|
||||
static operation_add(left, right) {return left + right;}
|
||||
static operation_subtract(left, right) {return left - right;}
|
||||
static operation_multiply(left, right) {return left * right;}
|
||||
static operation_divide(left, right) {return left / right;}
|
||||
static operation_remainder(left, right) {return left % right;}
|
||||
static operation_equals(left, right) {return left == right;}
|
||||
static operation_not_equals(left, right) {return left != right;}
|
||||
static operation_less_then(left, right) {return left < right;}
|
||||
static operation_greater_then(left, right) {return left > right;}
|
||||
static operation_less_or_equals_then(left, right) {return left <= right;}
|
||||
static operation_greater_or_equals_then(left, right) {return left >= right;}
|
||||
static operation_logical_and(left, right) {return left && right;}
|
||||
static operation_logical_or(left, right) {return left || right;}
|
||||
static cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
|
||||
|
||||
static mapFn(keys:List) {
|
||||
function buildMap(values) {
|
||||
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) {
|
||||
return obj[args[0]];
|
||||
}
|
||||
|
||||
static structuralCheck(self, context) {
|
||||
if (_isBlank(self) && _isBlank(context)) {
|
||||
return null;
|
||||
} else if (_isBlank(context)) {
|
||||
return new SimpleChange(null, null);
|
||||
}
|
||||
|
||||
if (_isBlank(self)) {
|
||||
if (ArrayChanges.supports(context)) {
|
||||
self = new ArrayChanges();
|
||||
} else if (KeyValueChanges.supports(context)) {
|
||||
self = new KeyValueChanges();
|
||||
}
|
||||
}
|
||||
|
||||
if (isBlank(self) || !self.supportsObj(context)) {
|
||||
throw new BaseException(`Unsupported type (${context})`);
|
||||
}
|
||||
|
||||
if (self.check(context)) {
|
||||
return new SimpleChange(null, self); // TODO: don't wrap and return self instead
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static findContext(name:string, c){
|
||||
while (c instanceof ContextWithVariableBindings) {
|
||||
if (c.hasBinding(name)) {
|
||||
return c;
|
||||
}
|
||||
c = c.parent;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static throwOnChange(proto:ProtoRecord, change) {
|
||||
throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
|
||||
}
|
||||
|
||||
static simpleChange(previousValue:any, currentValue:any):SimpleChange {
|
||||
return _simpleChange(previousValue, currentValue);
|
||||
}
|
||||
|
||||
static changeRecord(memento:any, change:any):ChangeRecord {
|
||||
return _changeRecord(memento, change);
|
||||
}
|
||||
|
||||
static simpleChangeRecord(memento:any, previousValue:any, currentValue:any):ChangeRecord {
|
||||
return _changeRecord(memento, _simpleChange(previousValue, currentValue));
|
||||
}
|
||||
|
||||
static addRecord(updatedRecords:List, changeRecord:ChangeRecord):List {
|
||||
if (isBlank(updatedRecords)) {
|
||||
updatedRecords = _singleElementList;
|
||||
updatedRecords[0] = changeRecord;
|
||||
|
||||
} else if (updatedRecords === _singleElementList) {
|
||||
updatedRecords = [_singleElementList[0], changeRecord];
|
||||
|
||||
} else {
|
||||
ListWrapper.push(updatedRecords, changeRecord);
|
||||
}
|
||||
return updatedRecords;
|
||||
}
|
||||
|
||||
static markPathToRootAsCheckOnce(cd:ChangeDetector) {
|
||||
var c = cd;
|
||||
while(isPresent(c) && c.mode != DETACHED) {
|
||||
if (c.mode === CHECKED) c.mode = CHECK_ONCE;
|
||||
c = c.parent;
|
||||
}
|
||||
}
|
||||
}
|
88
modules/angular2/src/change_detection/coalesce.js
vendored
Normal file
88
modules/angular2/src/change_detection/coalesce.js
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
import {isPresent} from 'facade/src/lang';
|
||||
import {List, ListWrapper, Map, MapWrapper} from 'facade/src/collection';
|
||||
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_change_detector';
|
||||
|
||||
/**
|
||||
* 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:List<ProtoRecord>):List<ProtoRecord> {
|
||||
var res = ListWrapper.create();
|
||||
var indexMap = MapWrapper.create();
|
||||
|
||||
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) {
|
||||
ListWrapper.push(res, _selfRecord(record, matchingRecord.selfIndex, res.length + 1));
|
||||
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
|
||||
|
||||
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
|
||||
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
|
||||
|
||||
} else {
|
||||
ListWrapper.push(res, record);
|
||||
MapWrapper.set(indexMap, r.selfIndex, record.selfIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):ProtoRecord {
|
||||
return new ProtoRecord(
|
||||
RECORD_TYPE_SELF,
|
||||
"self",
|
||||
null,
|
||||
[],
|
||||
r.fixedArgs,
|
||||
contextIndex,
|
||||
selfIndex,
|
||||
r.bindingMemento,
|
||||
r.groupMemento,
|
||||
r.expressionAsString,
|
||||
r.lastInBinding,
|
||||
r.lastInGroup
|
||||
);
|
||||
}
|
||||
|
||||
function _findMatching(r:ProtoRecord, rs:List<ProtoRecord>){
|
||||
return ListWrapper.find(rs, (rr) =>
|
||||
rr.mode === r.mode &&
|
||||
rr.funcOrValue === r.funcOrValue &&
|
||||
rr.contextIndex === r.contextIndex &&
|
||||
ListWrapper.equals(rr.args, r.args)
|
||||
);
|
||||
}
|
||||
|
||||
function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) {
|
||||
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,
|
||||
selfIndex,
|
||||
r.bindingMemento,
|
||||
r.groupMemento,
|
||||
r.expressionAsString,
|
||||
r.lastInBinding,
|
||||
r.lastInGroup
|
||||
);
|
||||
}
|
||||
|
||||
function _map(indexMap:Map, value:number) {
|
||||
var r = MapWrapper.get(indexMap, value)
|
||||
return isPresent(r) ? r : value;
|
||||
}
|
215
modules/angular2/src/change_detection/dynamic_change_detector.js
vendored
Normal file
215
modules/angular2/src/change_detection/dynamic_change_detector.js
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
import {isPresent, isBlank, BaseException, FunctionWrapper} from 'facade/src/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
|
||||
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||||
|
||||
import {AbstractChangeDetector} from './abstract_change_detector';
|
||||
import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util';
|
||||
|
||||
import {ArrayChanges} from './array_changes';
|
||||
import {KeyValueChanges} from './keyvalue_changes';
|
||||
|
||||
import {
|
||||
ProtoRecord,
|
||||
RECORD_TYPE_SELF,
|
||||
RECORD_TYPE_PROPERTY,
|
||||
RECORD_TYPE_INVOKE_METHOD,
|
||||
RECORD_TYPE_CONST,
|
||||
RECORD_TYPE_INVOKE_CLOSURE,
|
||||
RECORD_TYPE_PRIMITIVE_OP,
|
||||
RECORD_TYPE_KEYED_ACCESS,
|
||||
RECORD_TYPE_INVOKE_FORMATTER,
|
||||
RECORD_TYPE_STRUCTURAL_CHECK,
|
||||
RECORD_TYPE_INTERPOLATE,
|
||||
ProtoChangeDetector
|
||||
} from './proto_change_detector';
|
||||
|
||||
import {ChangeDetector, ChangeDispatcher} from './interfaces';
|
||||
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
|
||||
|
||||
export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
dispatcher:any;
|
||||
formatters:Map;
|
||||
values:List;
|
||||
changes:List;
|
||||
protos:List<ProtoRecord>;
|
||||
|
||||
constructor(dispatcher:any, formatters:Map, protoRecords:List<ProtoRecord>) {
|
||||
super();
|
||||
this.dispatcher = dispatcher;
|
||||
this.formatters = formatters;
|
||||
|
||||
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
|
||||
ListWrapper.fill(this.values, uninitialized);
|
||||
|
||||
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
|
||||
|
||||
this.protos = protoRecords;
|
||||
}
|
||||
|
||||
setContext(context:any) {
|
||||
this.values[0] = context;
|
||||
}
|
||||
|
||||
detectChangesInRecords(throwOnChange:boolean) {
|
||||
var protos:List<ProtoRecord> = this.protos;
|
||||
|
||||
var updatedRecords = null;
|
||||
var currentGroup = null;
|
||||
|
||||
for (var i = 0; i < protos.length; ++i) {
|
||||
var proto:ProtoRecord = protos[i];
|
||||
var change = this._check(proto);
|
||||
|
||||
if (isPresent(change)) {
|
||||
currentGroup = proto.groupMemento;
|
||||
var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
|
||||
updatedRecords = ChangeDetectionUtil.addRecord(updatedRecords, record);
|
||||
}
|
||||
|
||||
if (proto.lastInGroup && isPresent(updatedRecords)) {
|
||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, updatedRecords[0]);
|
||||
|
||||
this.dispatcher.onRecordChange(currentGroup, updatedRecords);
|
||||
updatedRecords = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_check(proto:ProtoRecord) {
|
||||
try {
|
||||
if (proto.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
|
||||
return this._structuralCheck(proto);
|
||||
} else {
|
||||
return this._referenceCheck(proto);
|
||||
}
|
||||
} catch (e) {
|
||||
throw new ChangeDetectionError(proto, e);
|
||||
}
|
||||
}
|
||||
|
||||
_referenceCheck(proto:ProtoRecord) {
|
||||
if (this._pureFuncAndArgsDidNotChange(proto)) {
|
||||
this._setChanged(proto, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
var prevValue = this._readSelf(proto);
|
||||
var currValue = this._calculateCurrValue(proto);
|
||||
|
||||
if (!isSame(prevValue, currValue)) {
|
||||
this._writeSelf(proto, currValue);
|
||||
this._setChanged(proto, true);
|
||||
|
||||
if (proto.lastInBinding) {
|
||||
return ChangeDetectionUtil.simpleChange(prevValue, currValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
this._setChanged(proto, false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_calculateCurrValue(proto:ProtoRecord) {
|
||||
switch (proto.mode) {
|
||||
case RECORD_TYPE_SELF:
|
||||
return this._readContext(proto);
|
||||
|
||||
case RECORD_TYPE_CONST:
|
||||
return proto.funcOrValue;
|
||||
|
||||
case RECORD_TYPE_PROPERTY:
|
||||
var context = this._readContext(proto);
|
||||
var c = ChangeDetectionUtil.findContext(proto.name, context);
|
||||
if (c instanceof ContextWithVariableBindings) {
|
||||
return c.get(proto.name);
|
||||
} else {
|
||||
var propertyGetter:Function = proto.funcOrValue;
|
||||
return propertyGetter(c);
|
||||
}
|
||||
break;
|
||||
|
||||
case RECORD_TYPE_INVOKE_METHOD:
|
||||
var methodInvoker:Function = proto.funcOrValue;
|
||||
return methodInvoker(this._readContext(proto), this._readArgs(proto));
|
||||
|
||||
case RECORD_TYPE_KEYED_ACCESS:
|
||||
var arg = this._readArgs(proto)[0];
|
||||
return this._readContext(proto)[arg];
|
||||
|
||||
case RECORD_TYPE_INVOKE_CLOSURE:
|
||||
return FunctionWrapper.apply(this._readContext(proto), this._readArgs(proto));
|
||||
|
||||
case RECORD_TYPE_INTERPOLATE:
|
||||
case RECORD_TYPE_PRIMITIVE_OP:
|
||||
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
|
||||
|
||||
case RECORD_TYPE_INVOKE_FORMATTER:
|
||||
var formatter = MapWrapper.get(this.formatters, proto.funcOrValue);
|
||||
return FunctionWrapper.apply(formatter, this._readArgs(proto));
|
||||
|
||||
default:
|
||||
throw new BaseException(`Unknown operation ${proto.mode}`);
|
||||
}
|
||||
}
|
||||
|
||||
_structuralCheck(proto:ProtoRecord) {
|
||||
var self = this._readSelf(proto);
|
||||
var context = this._readContext(proto);
|
||||
|
||||
var change = ChangeDetectionUtil.structuralCheck(self, context);
|
||||
if (isPresent(change)) {
|
||||
this._writeSelf(proto, change.currentValue);
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
_readContext(proto:ProtoRecord) {
|
||||
return this.values[proto.contextIndex];
|
||||
}
|
||||
|
||||
_readSelf(proto:ProtoRecord) {
|
||||
return this.values[proto.selfIndex];
|
||||
}
|
||||
|
||||
_writeSelf(proto:ProtoRecord, value) {
|
||||
this.values[proto.selfIndex] = value;
|
||||
}
|
||||
|
||||
_setChanged(proto:ProtoRecord, value:boolean) {
|
||||
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) {
|
||||
var res = ListWrapper.createFixedSize(proto.args.length);
|
||||
var args = proto.args;
|
||||
for (var i = 0; i < args.length; ++i) {
|
||||
res[i] = this.values[args[i]];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
var _singleElementList = [null];
|
||||
|
||||
function isSame(a, b) {
|
||||
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;
|
||||
}
|
30
modules/angular2/src/change_detection/exceptions.js
vendored
Normal file
30
modules/angular2/src/change_detection/exceptions.js
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
import {ProtoRecord} from './proto_change_detector';
|
||||
|
||||
export class ExpressionChangedAfterItHasBeenChecked extends Error {
|
||||
message:string;
|
||||
|
||||
constructor(proto:ProtoRecord, change:any) {
|
||||
this.message = `Expression '${proto.expressionAsString}' has changed after it was checked. ` +
|
||||
`Previous value: '${change.previousValue}'. Current value: '${change.currentValue}'`;
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeDetectionError extends Error {
|
||||
message:string;
|
||||
originalException:any;
|
||||
location:string;
|
||||
|
||||
constructor(proto:ProtoRecord, originalException:any) {
|
||||
this.originalException = originalException;
|
||||
this.location = proto.expressionAsString;
|
||||
this.message = `${this.originalException} in [${this.location}]`;
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return this.message;
|
||||
}
|
||||
}
|
63
modules/angular2/src/change_detection/interfaces.js
vendored
Normal file
63
modules/angular2/src/change_detection/interfaces.js
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
import {List} from 'facade/src/collection';
|
||||
|
||||
export class ChangeRecord {
|
||||
bindingMemento:any;
|
||||
change:any;
|
||||
|
||||
constructor(bindingMemento, change) {
|
||||
this.bindingMemento = bindingMemento;
|
||||
this.change = change;
|
||||
}
|
||||
|
||||
//REMOVE IT
|
||||
get currentValue() {
|
||||
return this.change.currentValue;
|
||||
}
|
||||
|
||||
get previousValue() {
|
||||
return this.change.previousValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
|
||||
* will become CHECKED.
|
||||
*/
|
||||
export const CHECK_ONCE="CHECK_ONCE";
|
||||
|
||||
/**
|
||||
* CHECKED means that the change detector should be skipped until its mode changes to
|
||||
* CHECK_ONCE or CHECK_ALWAYS.
|
||||
*/
|
||||
export const CHECKED="CHECKED";
|
||||
|
||||
/**
|
||||
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
|
||||
* will remain CHECK_ALWAYS.
|
||||
*/
|
||||
export const CHECK_ALWAYS="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="DETACHED";
|
||||
|
||||
export class ChangeDispatcher {
|
||||
onRecordChange(groupMemento, records:List<ChangeRecord>) {}
|
||||
}
|
||||
|
||||
export class ChangeDetector {
|
||||
parent:ChangeDetector;
|
||||
mode:string;
|
||||
|
||||
addChild(cd:ChangeDetector) {}
|
||||
removeChild(cd:ChangeDetector) {}
|
||||
remove() {}
|
||||
setContext(context:any) {}
|
||||
markPathToRootAsCheckOnce() {}
|
||||
|
||||
detectChanges() {}
|
||||
checkNoChanges() {}
|
||||
}
|
363
modules/angular2/src/change_detection/keyvalue_changes.js
vendored
Normal file
363
modules/angular2/src/change_detection/keyvalue_changes.js
vendored
Normal file
@ -0,0 +1,363 @@
|
||||
import {ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {stringify, looseIdentical, isJsObject} from 'facade/src/lang';
|
||||
|
||||
export class KeyValueChanges {
|
||||
_records:Map;
|
||||
|
||||
_mapHead:KVChangeRecord;
|
||||
_previousMapHead:KVChangeRecord;
|
||||
_changesHead:KVChangeRecord;
|
||||
_changesTail:KVChangeRecord;
|
||||
_additionsHead:KVChangeRecord;
|
||||
_additionsTail:KVChangeRecord;
|
||||
_removalsHead:KVChangeRecord;
|
||||
_removalsTail:KVChangeRecord;
|
||||
|
||||
constructor() {
|
||||
this._records = MapWrapper.create();
|
||||
this._mapHead = null;
|
||||
this._previousMapHead = null;
|
||||
this._changesHead = null;
|
||||
this._changesTail = null;
|
||||
this._additionsHead = null;
|
||||
this._additionsTail = null;
|
||||
this._removalsHead = null;
|
||||
this._removalsTail = null;
|
||||
}
|
||||
|
||||
static supports(obj):boolean {
|
||||
return obj instanceof Map || isJsObject(obj);
|
||||
}
|
||||
|
||||
supportsObj(obj):boolean {
|
||||
return KeyValueChanges.supports(obj);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
check(map):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 (MapWrapper.contains(records, key)) {
|
||||
newSeqRecord = MapWrapper.get(records, key);
|
||||
} else {
|
||||
newSeqRecord = new KVChangeRecord(key);
|
||||
MapWrapper.set(records, 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) {
|
||||
ListWrapper.push(items, stringify(record));
|
||||
}
|
||||
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
||||
ListWrapper.push(previous, stringify(record));
|
||||
}
|
||||
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
||||
ListWrapper.push(changes, stringify(record));
|
||||
}
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
ListWrapper.push(additions, stringify(record));
|
||||
}
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
ListWrapper.push(removals, 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 {
|
||||
key;
|
||||
_previousValue;
|
||||
_currentValue;
|
||||
|
||||
_nextPrevious:KVChangeRecord;
|
||||
_next:KVChangeRecord;
|
||||
_nextAdded:KVChangeRecord;
|
||||
_nextRemoved:KVChangeRecord;
|
||||
_prevRemoved:KVChangeRecord;
|
||||
_nextChanged:KVChangeRecord;
|
||||
|
||||
constructor(key) {
|
||||
this.key = key;
|
||||
this._previousValue = null;
|
||||
this._currentValue = null;
|
||||
|
||||
this._nextPrevious = null;
|
||||
this._next = null;
|
||||
this._nextAdded = null;
|
||||
this._nextRemoved = null;
|
||||
this._prevRemoved = null;
|
||||
this._nextChanged = null;
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return looseIdentical(this._previousValue, this._currentValue) ?
|
||||
stringify(this.key) :
|
||||
(stringify(this.key) + '[' + stringify(this._previousValue) + '->' +
|
||||
stringify(this._currentValue) + ']');
|
||||
}
|
||||
}
|
467
modules/angular2/src/change_detection/parser/ast.js
vendored
Normal file
467
modules/angular2/src/change_detection/parser/ast.js
vendored
Normal file
@ -0,0 +1,467 @@
|
||||
import {FIELD, autoConvertAdd, isBlank, isPresent, FunctionWrapper, BaseException} from "facade/src/lang";
|
||||
import {List, Map, ListWrapper, StringMapWrapper} from "facade/src/collection";
|
||||
import {ContextWithVariableBindings} from "./context_with_variable_bindings";
|
||||
|
||||
export class AST {
|
||||
eval(context) {
|
||||
throw new BaseException("Not supported");
|
||||
}
|
||||
|
||||
get isAssignable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
throw new BaseException("Not supported");
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return "AST";
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyExpr extends AST {
|
||||
eval(context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
//do nothing
|
||||
}
|
||||
}
|
||||
|
||||
export class Structural extends AST {
|
||||
value:AST;
|
||||
constructor(value:AST) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return value.eval(context);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitStructural(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ImplicitReceiver extends AST {
|
||||
eval(context) {
|
||||
return context;
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitImplicitReceiver(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple expressions separated by a semicolon.
|
||||
*/
|
||||
export class Chain extends AST {
|
||||
expressions:List;
|
||||
constructor(expressions:List) {
|
||||
this.expressions = expressions;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var result;
|
||||
for (var i = 0; i < this.expressions.length; i++) {
|
||||
var last = this.expressions[i].eval(context);
|
||||
if (isPresent(last)) result = last;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitChain(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Conditional extends AST {
|
||||
condition:AST;
|
||||
trueExp:AST;
|
||||
falseExp:AST;
|
||||
constructor(condition:AST, trueExp:AST, falseExp:AST){
|
||||
this.condition = condition;
|
||||
this.trueExp = trueExp;
|
||||
this.falseExp = falseExp;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
if(this.condition.eval(context)) {
|
||||
return this.trueExp.eval(context);
|
||||
} else {
|
||||
return this.falseExp.eval(context);
|
||||
}
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitConditional(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class AccessMember extends AST {
|
||||
receiver:AST;
|
||||
name:string;
|
||||
getter:Function;
|
||||
setter:Function;
|
||||
constructor(receiver:AST, name:string, getter:Function, setter:Function) {
|
||||
this.receiver = receiver;
|
||||
this.name = name;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var evaluatedContext = this.receiver.eval(context);
|
||||
|
||||
while (evaluatedContext instanceof ContextWithVariableBindings) {
|
||||
if (evaluatedContext.hasBinding(this.name)) {
|
||||
return evaluatedContext.get(this.name);
|
||||
}
|
||||
evaluatedContext = evaluatedContext.parent;
|
||||
}
|
||||
|
||||
return this.getter(evaluatedContext);
|
||||
}
|
||||
|
||||
get isAssignable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
var evaluatedContext = this.receiver.eval(context);
|
||||
|
||||
while (evaluatedContext instanceof ContextWithVariableBindings) {
|
||||
if (evaluatedContext.hasBinding(this.name)) {
|
||||
throw new BaseException(`Cannot reassign a variable binding ${this.name}`)
|
||||
}
|
||||
evaluatedContext = evaluatedContext.parent;
|
||||
}
|
||||
|
||||
return this.setter(evaluatedContext, value);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitAccessMember(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyedAccess extends AST {
|
||||
obj:AST;
|
||||
key:AST;
|
||||
constructor(obj:AST, key:AST) {
|
||||
this.obj = obj;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var obj = this.obj.eval(context);
|
||||
var key = this.key.eval(context);
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
get isAssignable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
var obj = this.obj.eval(context);
|
||||
var key = this.key.eval(context);
|
||||
obj[key] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitKeyedAccess(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Formatter extends AST {
|
||||
exp:AST;
|
||||
name:string;
|
||||
args:List<AST>;
|
||||
allArgs:List<AST>;
|
||||
constructor(exp:AST, name:string, args:List) {
|
||||
this.exp = exp;
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
this.allArgs = ListWrapper.concat([exp], args);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitFormatter(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class LiteralPrimitive extends AST {
|
||||
value;
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitLiteralPrimitive(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class LiteralArray extends AST {
|
||||
expressions:List;
|
||||
constructor(expressions:List) {
|
||||
this.expressions = expressions;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return ListWrapper.map(this.expressions, (e) => e.eval(context));
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitLiteralArray(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class LiteralMap extends AST {
|
||||
keys:List;
|
||||
values:List;
|
||||
constructor(keys:List, values:List) {
|
||||
this.keys = keys;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var res = StringMapWrapper.create();
|
||||
for(var i = 0; i < this.keys.length; ++i) {
|
||||
StringMapWrapper.set(res, this.keys[i], this.values[i].eval(context));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitLiteralMap(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Interpolation extends AST {
|
||||
strings:List;
|
||||
expressions:List;
|
||||
constructor(strings:List, expressions:List) {
|
||||
this.strings = strings;
|
||||
this.expressions = expressions;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
throw new BaseException("evaluating an Interpolation is not supported");
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
visitor.visitInterpolation(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Binary extends AST {
|
||||
operation:string;
|
||||
left:AST;
|
||||
right:AST;
|
||||
constructor(operation:string, left:AST, right:AST) {
|
||||
this.operation = operation;
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var left = this.left.eval(context);
|
||||
switch (this.operation) {
|
||||
case '&&': return left && this.right.eval(context);
|
||||
case '||': return left || this.right.eval(context);
|
||||
}
|
||||
var right = this.right.eval(context);
|
||||
|
||||
switch (this.operation) {
|
||||
case '+' : return left + right;
|
||||
case '-' : return left - right;
|
||||
case '*' : return left * right;
|
||||
case '/' : return left / right;
|
||||
case '%' : return left % right;
|
||||
case '==' : return left == right;
|
||||
case '!=' : return left != right;
|
||||
case '<' : return left < right;
|
||||
case '>' : return left > right;
|
||||
case '<=' : return left <= right;
|
||||
case '>=' : return left >= right;
|
||||
case '^' : return left ^ right;
|
||||
case '&' : return left & right;
|
||||
}
|
||||
throw 'Internal error [$operation] not handled';
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitBinary(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class PrefixNot extends AST {
|
||||
expression:AST;
|
||||
constructor(expression:AST) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return !this.expression.eval(context);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitPrefixNot(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Assignment extends AST {
|
||||
target:AST;
|
||||
value:AST;
|
||||
constructor(target:AST, value:AST) {
|
||||
this.target = target;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return this.target.assign(context, this.value.eval(context));
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitAssignment(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class MethodCall extends AST {
|
||||
receiver:AST;
|
||||
fn:Function;
|
||||
args:List;
|
||||
name:string;
|
||||
constructor(receiver:AST, name:string, fn:Function, args:List) {
|
||||
this.receiver = receiver;
|
||||
this.fn = fn;
|
||||
this.args = args;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var evaluatedContext = this.receiver.eval(context);
|
||||
var evaluatedArgs = evalList(context, this.args);
|
||||
|
||||
while (evaluatedContext instanceof ContextWithVariableBindings) {
|
||||
if (evaluatedContext.hasBinding(this.name)) {
|
||||
var fn = evaluatedContext.get(this.name);
|
||||
return FunctionWrapper.apply(fn, evaluatedArgs);
|
||||
}
|
||||
evaluatedContext = evaluatedContext.parent;
|
||||
}
|
||||
|
||||
return this.fn(evaluatedContext, evaluatedArgs);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitMethodCall(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class FunctionCall extends AST {
|
||||
target:AST;
|
||||
args:List;
|
||||
constructor(target:AST, args:List) {
|
||||
this.target = target;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
var obj = this.target.eval(context);
|
||||
if (! (obj instanceof Function)) {
|
||||
throw new BaseException(`${obj} is not a function`);
|
||||
}
|
||||
return FunctionWrapper.apply(obj, evalList(context, this.args));
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return visitor.visitFunctionCall(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class ASTWithSource extends AST {
|
||||
ast:AST;
|
||||
source:string;
|
||||
location:string;
|
||||
constructor(ast:AST, source:string, location:string) {
|
||||
this.source = source;
|
||||
this.location = location;
|
||||
this.ast = ast;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return this.ast.eval(context);
|
||||
}
|
||||
|
||||
get isAssignable() {
|
||||
return this.ast.isAssignable;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
return this.ast.assign(context, value);
|
||||
}
|
||||
|
||||
visit(visitor) {
|
||||
return this.ast.visit(visitor);
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return `${this.source} in ${this.location}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class TemplateBinding {
|
||||
key:string;
|
||||
keyIsVar:boolean;
|
||||
name:string;
|
||||
expression:ASTWithSource;
|
||||
constructor(key:string, keyIsVar:boolean, name:string, expression:ASTWithSource) {
|
||||
this.key = key;
|
||||
this.keyIsVar = keyIsVar;
|
||||
// only either name or expression will be filled.
|
||||
this.name = name;
|
||||
this.expression = expression;
|
||||
}
|
||||
}
|
||||
|
||||
//INTERFACE
|
||||
export class AstVisitor {
|
||||
visitAccessMember(ast:AccessMember) {}
|
||||
visitAssignment(ast:Assignment) {}
|
||||
visitBinary(ast:Binary) {}
|
||||
visitChain(ast:Chain){}
|
||||
visitStructural(ast:Structural) {}
|
||||
visitConditional(ast:Conditional) {}
|
||||
visitFormatter(ast:Formatter) {}
|
||||
visitFunctionCall(ast:FunctionCall) {}
|
||||
visitImplicitReceiver(ast:ImplicitReceiver) {}
|
||||
visitKeyedAccess(ast:KeyedAccess) {}
|
||||
visitLiteralArray(ast:LiteralArray) {}
|
||||
visitLiteralMap(ast:LiteralMap) {}
|
||||
visitLiteralPrimitive(ast:LiteralPrimitive) {}
|
||||
visitMethodCall(ast:MethodCall) {}
|
||||
visitPrefixNot(ast:PrefixNot) {}
|
||||
}
|
||||
|
||||
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]];
|
||||
function evalList(context, exps:List){
|
||||
var length = exps.length;
|
||||
var result = _evalListCache[length];
|
||||
for (var i = 0; i < length; i++) {
|
||||
result[i] = exps[i].eval(context);
|
||||
}
|
||||
return result;
|
||||
}
|
38
modules/angular2/src/change_detection/parser/context_with_variable_bindings.js
vendored
Normal file
38
modules/angular2/src/change_detection/parser/context_with_variable_bindings.js
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
import {MapWrapper} from 'facade/src/collection';
|
||||
import {BaseException} from 'facade/src/lang';
|
||||
|
||||
export class ContextWithVariableBindings {
|
||||
parent:any;
|
||||
/// varBindings' keys are read-only. adding/removing keys is not supported.
|
||||
varBindings:Map;
|
||||
|
||||
constructor(parent:any, varBindings:Map) {
|
||||
this.parent = parent;
|
||||
this.varBindings = varBindings;
|
||||
}
|
||||
|
||||
hasBinding(name:string):boolean {
|
||||
return MapWrapper.contains(this.varBindings, name);
|
||||
}
|
||||
|
||||
get(name:string) {
|
||||
return MapWrapper.get(this.varBindings, name);
|
||||
}
|
||||
|
||||
set(name:string, value) {
|
||||
// TODO(rado): consider removing this check if we can guarantee this is not
|
||||
// exposed to the public API.
|
||||
if (this.hasBinding(name)) {
|
||||
MapWrapper.set(this.varBindings, name, value);
|
||||
} else {
|
||||
throw new BaseException(
|
||||
'VariableBindings do not support setting of new keys post-construction.');
|
||||
}
|
||||
}
|
||||
|
||||
clearValues() {
|
||||
for (var k of MapWrapper.keys(this.varBindings)) {
|
||||
MapWrapper.set(this.varBindings, k, null);
|
||||
}
|
||||
}
|
||||
}
|
481
modules/angular2/src/change_detection/parser/lexer.js
vendored
Normal file
481
modules/angular2/src/change_detection/parser/lexer.js
vendored
Normal file
@ -0,0 +1,481 @@
|
||||
import {List, ListWrapper, SetWrapper} from "facade/src/collection";
|
||||
import {int, FIELD, NumberWrapper, StringJoiner, StringWrapper} from "facade/src/lang";
|
||||
|
||||
export const TOKEN_TYPE_CHARACTER = 1;
|
||||
export const TOKEN_TYPE_IDENTIFIER = 2;
|
||||
export const TOKEN_TYPE_KEYWORD = 3;
|
||||
export const TOKEN_TYPE_STRING = 4;
|
||||
export const TOKEN_TYPE_OPERATOR = 5;
|
||||
export const TOKEN_TYPE_NUMBER = 6;
|
||||
|
||||
export class Lexer {
|
||||
text:string;
|
||||
tokenize(text:string):List {
|
||||
var scanner = new _Scanner(text);
|
||||
var tokens = [];
|
||||
var token = scanner.scanToken();
|
||||
while (token != null) {
|
||||
ListWrapper.push(tokens, token);
|
||||
token = scanner.scanToken();
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
export class Token {
|
||||
index:int;
|
||||
type:int;
|
||||
_numValue:number;
|
||||
_strValue:string;
|
||||
constructor(index:int, type:int, numValue:number, strValue:string) {
|
||||
/**
|
||||
* NOTE: To ensure that this constructor creates the same hidden class each time, ensure that
|
||||
* all the fields are assigned to in the exact same order in each run of this constructor.
|
||||
*/
|
||||
this.index = index;
|
||||
this.type = type;
|
||||
this._numValue = numValue;
|
||||
this._strValue = strValue;
|
||||
}
|
||||
|
||||
isCharacter(code:int):boolean {
|
||||
return (this.type == TOKEN_TYPE_CHARACTER && this._numValue == code);
|
||||
}
|
||||
|
||||
isNumber():boolean {
|
||||
return (this.type == TOKEN_TYPE_NUMBER);
|
||||
}
|
||||
|
||||
isString():boolean {
|
||||
return (this.type == TOKEN_TYPE_STRING);
|
||||
}
|
||||
|
||||
isOperator(operater:string):boolean {
|
||||
return (this.type == TOKEN_TYPE_OPERATOR && this._strValue == operater);
|
||||
}
|
||||
|
||||
isIdentifier():boolean {
|
||||
return (this.type == TOKEN_TYPE_IDENTIFIER);
|
||||
}
|
||||
|
||||
isKeyword():boolean {
|
||||
return (this.type == TOKEN_TYPE_KEYWORD);
|
||||
}
|
||||
|
||||
isKeywordVar():boolean {
|
||||
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "var");
|
||||
}
|
||||
|
||||
isKeywordNull():boolean {
|
||||
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "null");
|
||||
}
|
||||
|
||||
isKeywordUndefined():boolean {
|
||||
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "undefined");
|
||||
}
|
||||
|
||||
isKeywordTrue():boolean {
|
||||
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "true");
|
||||
}
|
||||
|
||||
isKeywordFalse():boolean {
|
||||
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "false");
|
||||
}
|
||||
|
||||
toNumber():number {
|
||||
// -1 instead of NULL ok?
|
||||
return (this.type == TOKEN_TYPE_NUMBER) ? this._numValue : -1;
|
||||
}
|
||||
|
||||
toString():string {
|
||||
var type:int = this.type;
|
||||
if (type >= TOKEN_TYPE_CHARACTER && type <= TOKEN_TYPE_STRING) {
|
||||
return this._strValue;
|
||||
} else if (type == TOKEN_TYPE_NUMBER) {
|
||||
return this._numValue.toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newCharacterToken(index:int, code:int):Token {
|
||||
return new Token(index, TOKEN_TYPE_CHARACTER, code, StringWrapper.fromCharCode(code));
|
||||
}
|
||||
|
||||
function newIdentifierToken(index:int, text:string):Token {
|
||||
return new Token(index, TOKEN_TYPE_IDENTIFIER, 0, text);
|
||||
}
|
||||
|
||||
function newKeywordToken(index:int, text:string):Token {
|
||||
return new Token(index, TOKEN_TYPE_KEYWORD, 0, text);
|
||||
}
|
||||
|
||||
function newOperatorToken(index:int, text:string):Token {
|
||||
return new Token(index, TOKEN_TYPE_OPERATOR, 0, text);
|
||||
}
|
||||
|
||||
function newStringToken(index:int, text:string):Token {
|
||||
return new Token(index, TOKEN_TYPE_STRING, 0, text);
|
||||
}
|
||||
|
||||
function newNumberToken(index:int, n:number):Token {
|
||||
return new Token(index, TOKEN_TYPE_NUMBER, n, "");
|
||||
}
|
||||
|
||||
|
||||
export var EOF:Token = new Token(-1, 0, 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, $B = 66, $C = 67, $D = 68, $E = 69, $F = 70, $G = 71, $H = 72,
|
||||
$I = 73, $J = 74, $K = 75, $L = 76, $M = 77, $N = 78, $O = 79, $P = 80,
|
||||
$Q = 81, $R = 82, $S = 83, $T = 84, $U = 85, $V = 86, $W = 87, $X = 88,
|
||||
$Y = 89, $Z = 90;
|
||||
|
||||
export const $LBRACKET = 91;
|
||||
export const $BACKSLASH = 92;
|
||||
export const $RBRACKET = 93;
|
||||
const $CARET = 94;
|
||||
const $_ = 95;
|
||||
|
||||
const $a = 97, $b = 98, $c = 99, $d = 100, $e = 101, $f = 102, $g = 103,
|
||||
$h = 104, $i = 105, $j = 106, $k = 107, $l = 108, $m = 109, $n = 110,
|
||||
$o = 111, $p = 112, $q = 113, $r = 114, $s = 115, $t = 116, $u = 117,
|
||||
$v = 118, $w = 119, $x = 120, $y = 121, $z = 122;
|
||||
|
||||
export const $LBRACE = 123;
|
||||
export const $BAR = 124;
|
||||
export const $RBRACE = 125;
|
||||
const $TILDE = 126;
|
||||
const $NBSP = 160;
|
||||
|
||||
|
||||
export class ScannerError extends Error {
|
||||
message:string;
|
||||
constructor(message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
|
||||
class _Scanner {
|
||||
input:string;
|
||||
length:int;
|
||||
peek:int;
|
||||
index:int;
|
||||
|
||||
constructor(input:string) {
|
||||
this.input = input;
|
||||
this.length = input.length;
|
||||
this.peek = 0;
|
||||
this.index = -1;
|
||||
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:int = 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:
|
||||
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
|
||||
case $PLUS:
|
||||
case $MINUS:
|
||||
case $STAR:
|
||||
case $SLASH:
|
||||
case $PERCENT:
|
||||
case $CARET:
|
||||
case $QUESTION:
|
||||
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
|
||||
case $LT:
|
||||
case $GT:
|
||||
case $BANG:
|
||||
case $EQ:
|
||||
return this.scanComplexOperator(start, $EQ, StringWrapper.fromCharCode(peek), '=');
|
||||
case $AMPERSAND:
|
||||
return this.scanComplexOperator(start, $AMPERSAND, '&', '&');
|
||||
case $BAR:
|
||||
return this.scanComplexOperator(start, $BAR, '|', '|');
|
||||
case $TILDE:
|
||||
return this.scanComplexOperator(start, $SLASH, '~', '/');
|
||||
case $NBSP:
|
||||
while (isWhitespace(this.peek)) this.advance();
|
||||
return this.scanToken();
|
||||
}
|
||||
|
||||
this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
scanCharacter(start:int, code:int):Token {
|
||||
assert(this.peek == code);
|
||||
this.advance();
|
||||
return newCharacterToken(start, code);
|
||||
}
|
||||
|
||||
|
||||
scanOperator(start:int, str:string):Token {
|
||||
assert(this.peek == StringWrapper.charCodeAt(str, 0));
|
||||
assert(SetWrapper.has(OPERATORS, str));
|
||||
this.advance();
|
||||
return newOperatorToken(start, str);
|
||||
}
|
||||
|
||||
scanComplexOperator(start:int, code:int, one:string, two:string):Token {
|
||||
assert(this.peek == StringWrapper.charCodeAt(one, 0));
|
||||
this.advance();
|
||||
var str:string = one;
|
||||
if (this.peek == code) {
|
||||
this.advance();
|
||||
str += two;
|
||||
}
|
||||
assert(SetWrapper.has(OPERATORS, str));
|
||||
return newOperatorToken(start, str);
|
||||
}
|
||||
|
||||
scanIdentifier():Token {
|
||||
assert(isIdentifierStart(this.peek));
|
||||
var start:int = 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:int):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:int = this.index;
|
||||
var quote:int = this.peek;
|
||||
this.advance(); // Skip initial quote.
|
||||
|
||||
var buffer:StringJoiner;
|
||||
var marker:int = 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:int;
|
||||
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:int = 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:int) {
|
||||
var position:int = this.index + offset;
|
||||
throw new ScannerError(`Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
|
||||
}
|
||||
}
|
||||
|
||||
function isWhitespace(code:int):boolean {
|
||||
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
|
||||
}
|
||||
|
||||
function isIdentifierStart(code:int):boolean {
|
||||
return ($a <= code && code <= $z) ||
|
||||
($A <= code && code <= $Z) ||
|
||||
(code == $_) ||
|
||||
(code == $$);
|
||||
}
|
||||
|
||||
function isIdentifierPart(code:int):boolean {
|
||||
return ($a <= code && code <= $z) ||
|
||||
($A <= code && code <= $Z) ||
|
||||
($0 <= code && code <= $9) ||
|
||||
(code == $_) ||
|
||||
(code == $$);
|
||||
}
|
||||
|
||||
function isDigit(code:int):boolean {
|
||||
return $0 <= code && code <= $9;
|
||||
}
|
||||
|
||||
function isExponentStart(code:int):boolean {
|
||||
return code == $e || code == $E;
|
||||
}
|
||||
|
||||
function isExponentSign(code:int):boolean {
|
||||
return code == $MINUS || code == $PLUS;
|
||||
}
|
||||
|
||||
function unescape(code:int):int {
|
||||
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',
|
||||
]);
|
521
modules/angular2/src/change_detection/parser/parser.js
vendored
Normal file
521
modules/angular2/src/change_detection/parser/parser.js
vendored
Normal file
@ -0,0 +1,521 @@
|
||||
import {FIELD, int, isBlank, isPresent, BaseException, StringWrapper, RegExpWrapper} from 'facade/src/lang';
|
||||
import {ListWrapper, List} from 'facade/src/collection';
|
||||
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET,
|
||||
$COMMA, $LBRACE, $RBRACE, $LPAREN, $RPAREN} from './lexer';
|
||||
import {reflector, Reflector} from 'reflection/src/reflection';
|
||||
import {
|
||||
AST,
|
||||
EmptyExpr,
|
||||
ImplicitReceiver,
|
||||
AccessMember,
|
||||
LiteralPrimitive,
|
||||
Expression,
|
||||
Binary,
|
||||
PrefixNot,
|
||||
Conditional,
|
||||
Formatter,
|
||||
Assignment,
|
||||
Chain,
|
||||
KeyedAccess,
|
||||
LiteralArray,
|
||||
LiteralMap,
|
||||
Interpolation,
|
||||
MethodCall,
|
||||
FunctionCall,
|
||||
TemplateBindings,
|
||||
TemplateBinding,
|
||||
ASTWithSource
|
||||
} from './ast';
|
||||
|
||||
var _implicitReceiver = new ImplicitReceiver();
|
||||
// TODO(tbosch): Cannot make this const/final right now because of the transpiler...
|
||||
var INTERPOLATION_REGEXP = RegExpWrapper.create('\\{\\{(.*?)\\}\\}');
|
||||
var QUOTE_REGEXP = RegExpWrapper.create("'");
|
||||
|
||||
export class Parser {
|
||||
_lexer:Lexer;
|
||||
_reflector:Reflector;
|
||||
constructor(lexer:Lexer, providedReflector:Reflector = null){
|
||||
this._lexer = lexer;
|
||||
this._reflector = isPresent(providedReflector) ? providedReflector : reflector;
|
||||
}
|
||||
|
||||
parseAction(input:string, location:any):ASTWithSource {
|
||||
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 {
|
||||
var tokens = this._lexer.tokenize(input);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
|
||||
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 = parts[i];
|
||||
if (i%2 === 0) {
|
||||
// fixed string
|
||||
ListWrapper.push(strings, part);
|
||||
} else {
|
||||
var tokens = this._lexer.tokenize(part);
|
||||
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
|
||||
ListWrapper.push(expressions, ast);
|
||||
}
|
||||
}
|
||||
return new ASTWithSource(new Interpolation(strings, expressions), input, location);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _ParseAST {
|
||||
input:string;
|
||||
location:any;
|
||||
tokens:List<Token>;
|
||||
reflector:Reflector;
|
||||
parseAction:boolean;
|
||||
index:int;
|
||||
constructor(input:string, location:any, tokens:List, reflector:Reflector, parseAction:boolean) {
|
||||
this.input = input;
|
||||
this.location = location;
|
||||
this.tokens = tokens;
|
||||
this.index = 0;
|
||||
this.reflector = reflector;
|
||||
this.parseAction = parseAction;
|
||||
}
|
||||
|
||||
peek(offset:int):Token {
|
||||
var i = this.index + offset;
|
||||
return i < this.tokens.length ? this.tokens[i] : EOF;
|
||||
}
|
||||
|
||||
get next():Token {
|
||||
return this.peek(0);
|
||||
}
|
||||
|
||||
get inputIndex():int {
|
||||
return (this.index < this.tokens.length) ? this.next.index : this.input.length;
|
||||
}
|
||||
|
||||
advance() {
|
||||
this.index ++;
|
||||
}
|
||||
|
||||
optionalCharacter(code:int):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:int) {
|
||||
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.parseFormatter();
|
||||
ListWrapper.push(exprs, 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);
|
||||
}
|
||||
|
||||
parseFormatter() {
|
||||
var result = this.parseExpression();
|
||||
while (this.optionalOperator("|")) {
|
||||
if (this.parseAction) {
|
||||
this.error("Cannot have a formatter in an action expression");
|
||||
}
|
||||
var name = this.expectIdentifierOrKeyword();
|
||||
var args = ListWrapper.create();
|
||||
while (this.optionalCharacter($COLON)) {
|
||||
ListWrapper.push(args, this.parseExpression());
|
||||
}
|
||||
result = new Formatter(result, name, args);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseExpression() {
|
||||
var start = this.inputIndex;
|
||||
var result = this.parseConditional();
|
||||
|
||||
while (this.next.isOperator('=')) {
|
||||
if (!result.isAssignable) {
|
||||
var end = this.inputIndex;
|
||||
var expression = this.input.substring(start, end);
|
||||
this.error(`Expression ${expression} is not assignable`);
|
||||
}
|
||||
|
||||
if (!this.parseAction) {
|
||||
this.error("Binding expression cannot contain assignments");
|
||||
}
|
||||
|
||||
this.expectOperator('=');
|
||||
result = new Assignment(result, this.parseConditional());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
parseConditional() {
|
||||
var start = this.inputIndex;
|
||||
var result = this.parseLogicalOr();
|
||||
|
||||
if (this.optionalOperator('?')) {
|
||||
var yes = this.parseExpression();
|
||||
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.parseExpression();
|
||||
return new Conditional(result, yes, no);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
parseLogicalOr() {
|
||||
// '||'
|
||||
var result = this.parseLogicalAnd();
|
||||
while (this.optionalOperator('||')) {
|
||||
result = new Binary('||', result, this.parseLogicalAnd());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseLogicalAnd() {
|
||||
// '&&'
|
||||
var result = this.parseEquality();
|
||||
while (this.optionalOperator('&&')) {
|
||||
result = new Binary('&&', result, this.parseEquality());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseEquality() {
|
||||
// '==','!='
|
||||
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 {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parseRelational() {
|
||||
// '<', '>', '<=', '>='
|
||||
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() {
|
||||
// '+', '-'
|
||||
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() {
|
||||
// '*', '%', '/'
|
||||
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() {
|
||||
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);
|
||||
|
||||
} else if (this.optionalCharacter($LBRACKET)) {
|
||||
var key = this.parseExpression();
|
||||
this.expectCharacter($RBRACKET);
|
||||
result = new KeyedAccess(result, key);
|
||||
|
||||
} else if (this.optionalCharacter($LPAREN)) {
|
||||
var args = this.parseCallArguments();
|
||||
this.expectCharacter($RPAREN);
|
||||
result = new FunctionCall(result, args);
|
||||
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parsePrimary() {
|
||||
if (this.optionalCharacter($LPAREN)) {
|
||||
var result = this.parseFormatter();
|
||||
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.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);
|
||||
|
||||
} else if (this.next.isNumber()) {
|
||||
var value = this.next.toNumber();
|
||||
this.advance();
|
||||
return new LiteralPrimitive(value);
|
||||
|
||||
} else if (this.next.isString()) {
|
||||
var value = this.next.toString();
|
||||
this.advance();
|
||||
return new LiteralPrimitive(value);
|
||||
|
||||
} else if (this.index >= this.tokens.length) {
|
||||
this.error(`Unexpected end of expression: ${this.input}`);
|
||||
|
||||
} else {
|
||||
this.error(`Unexpected token ${this.next}`);
|
||||
}
|
||||
}
|
||||
|
||||
parseExpressionList(terminator:int):List {
|
||||
var result = [];
|
||||
if (!this.next.isCharacter(terminator)) {
|
||||
do {
|
||||
ListWrapper.push(result, this.parseExpression());
|
||||
} while (this.optionalCharacter($COMMA));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
parseLiteralMap() {
|
||||
var keys = [];
|
||||
var values = [];
|
||||
this.expectCharacter($LBRACE);
|
||||
if (!this.optionalCharacter($RBRACE)) {
|
||||
do {
|
||||
var key = this.expectIdentifierOrKeywordOrString();
|
||||
ListWrapper.push(keys, key);
|
||||
this.expectCharacter($COLON);
|
||||
ListWrapper.push(values, this.parseExpression());
|
||||
} while (this.optionalCharacter($COMMA));
|
||||
this.expectCharacter($RBRACE);
|
||||
}
|
||||
return new LiteralMap(keys, values);
|
||||
}
|
||||
|
||||
parseAccessMemberOrMethodCall(receiver):AST {
|
||||
var id = this.expectIdentifierOrKeyword();
|
||||
|
||||
if (this.optionalCharacter($LPAREN)) {
|
||||
var args = this.parseCallArguments();
|
||||
this.expectCharacter($RPAREN);
|
||||
var fn = this.reflector.method(id);
|
||||
return new MethodCall(receiver, id, fn, args);
|
||||
|
||||
} else {
|
||||
var getter = this.reflector.getter(id);
|
||||
var setter = this.reflector.setter(id);
|
||||
return new AccessMember(receiver, id, getter, setter);
|
||||
}
|
||||
}
|
||||
|
||||
parseCallArguments() {
|
||||
if (this.next.isCharacter($RPAREN)) return [];
|
||||
var positionals = [];
|
||||
do {
|
||||
ListWrapper.push(positionals, this.parseExpression());
|
||||
} while (this.optionalCharacter($COMMA))
|
||||
return positionals;
|
||||
}
|
||||
|
||||
/**
|
||||
* An identifier, a keyword, a string with an optional `-` inbetween.
|
||||
*/
|
||||
expectTemplateBindingKey() {
|
||||
var result = '';
|
||||
var operatorFound = false;
|
||||
do {
|
||||
result += this.expectIdentifierOrKeywordOrString();
|
||||
operatorFound = this.optionalOperator('-');
|
||||
if (operatorFound) {
|
||||
result += '-';
|
||||
}
|
||||
} while (operatorFound);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
parseTemplateBindings() {
|
||||
var bindings = [];
|
||||
while (this.index < this.tokens.length) {
|
||||
var keyIsVar:boolean = this.optionalKeywordVar();
|
||||
var key = this.expectTemplateBindingKey();
|
||||
this.optionalCharacter($COLON);
|
||||
var name = null;
|
||||
var expression = null;
|
||||
if (this.next !== EOF) {
|
||||
if (keyIsVar) {
|
||||
if (this.optionalOperator("=")) {
|
||||
name = this.expectTemplateBindingKey();
|
||||
} else {
|
||||
name = '\$implicit';
|
||||
}
|
||||
} else if (!this.peekKeywordVar()) {
|
||||
var start = this.inputIndex;
|
||||
var ast = this.parseExpression();
|
||||
var source = this.input.substring(start, this.inputIndex);
|
||||
expression = new ASTWithSource(ast, source, this.location);
|
||||
}
|
||||
}
|
||||
ListWrapper.push(bindings, new TemplateBinding(key, keyIsVar, name, expression));
|
||||
if (!this.optionalCharacter($SEMICOLON)) {
|
||||
this.optionalCharacter($COMMA);
|
||||
};
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
error(message:string, index:int = 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 BaseException(`Parser Error: ${message} ${location} [${this.input}] in ${this.location}`);
|
||||
}
|
||||
}
|
390
modules/angular2/src/change_detection/proto_change_detector.js
vendored
Normal file
390
modules/angular2/src/change_detection/proto_change_detector.js
vendored
Normal file
@ -0,0 +1,390 @@
|
||||
import {isPresent, isBlank, BaseException, Type, isString} from 'facade/src/lang';
|
||||
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/src/collection';
|
||||
|
||||
import {
|
||||
AccessMember,
|
||||
Assignment,
|
||||
AST,
|
||||
ASTWithSource,
|
||||
AstVisitor,
|
||||
Binary,
|
||||
Chain,
|
||||
Structural,
|
||||
Conditional,
|
||||
Formatter,
|
||||
FunctionCall,
|
||||
ImplicitReceiver,
|
||||
Interpolation,
|
||||
KeyedAccess,
|
||||
LiteralArray,
|
||||
LiteralMap,
|
||||
LiteralPrimitive,
|
||||
MethodCall,
|
||||
PrefixNot
|
||||
} from './parser/ast';
|
||||
|
||||
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||||
import {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
|
||||
import {ChangeDetectionUtil} from './change_detection_util';
|
||||
import {DynamicChangeDetector} from './dynamic_change_detector';
|
||||
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
|
||||
|
||||
import {ArrayChanges} from './array_changes';
|
||||
import {KeyValueChanges} from './keyvalue_changes';
|
||||
import {coalesce} from './coalesce';
|
||||
|
||||
export const RECORD_TYPE_SELF = 0;
|
||||
export const RECORD_TYPE_CONST = 1;
|
||||
export const RECORD_TYPE_PRIMITIVE_OP = 2;
|
||||
export const RECORD_TYPE_PROPERTY = 3;
|
||||
export const RECORD_TYPE_INVOKE_METHOD = 4;
|
||||
export const RECORD_TYPE_INVOKE_CLOSURE = 5;
|
||||
export const RECORD_TYPE_KEYED_ACCESS = 6;
|
||||
export const RECORD_TYPE_INVOKE_FORMATTER = 7;
|
||||
export const RECORD_TYPE_STRUCTURAL_CHECK = 8;
|
||||
export const RECORD_TYPE_INTERPOLATE = 9;
|
||||
|
||||
export class ProtoRecord {
|
||||
mode:number;
|
||||
name:string;
|
||||
funcOrValue:any;
|
||||
args:List;
|
||||
fixedArgs:List;
|
||||
contextIndex:number;
|
||||
selfIndex:number;
|
||||
bindingMemento:any;
|
||||
groupMemento:any;
|
||||
lastInBinding:boolean;
|
||||
lastInGroup:boolean;
|
||||
expressionAsString:string;
|
||||
|
||||
constructor(mode:number,
|
||||
name:string,
|
||||
funcOrValue,
|
||||
args:List,
|
||||
fixedArgs:List,
|
||||
contextIndex:number,
|
||||
selfIndex:number,
|
||||
bindingMemento:any,
|
||||
groupMemento:any,
|
||||
expressionAsString:string,
|
||||
lastInBinding:boolean,
|
||||
lastInGroup:boolean) {
|
||||
|
||||
this.mode = mode;
|
||||
this.name = name;
|
||||
this.funcOrValue = funcOrValue;
|
||||
this.args = args;
|
||||
this.fixedArgs = fixedArgs;
|
||||
this.contextIndex = contextIndex;
|
||||
this.selfIndex = selfIndex;
|
||||
this.bindingMemento = bindingMemento;
|
||||
this.groupMemento = groupMemento;
|
||||
this.lastInBinding = lastInBinding;
|
||||
this.lastInGroup = lastInGroup;
|
||||
this.expressionAsString = expressionAsString;
|
||||
}
|
||||
|
||||
isPureFunction():boolean {
|
||||
return this.mode === RECORD_TYPE_INTERPOLATE ||
|
||||
this.mode === RECORD_TYPE_INVOKE_FORMATTER ||
|
||||
this.mode === RECORD_TYPE_PRIMITIVE_OP;
|
||||
}
|
||||
}
|
||||
|
||||
export class ProtoChangeDetector {
|
||||
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false){}
|
||||
instantiate(dispatcher:any, formatters:Map):ChangeDetector{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||
_records:List<ProtoRecord>;
|
||||
_recordBuilder:ProtoRecordBuilder;
|
||||
|
||||
constructor() {
|
||||
this._records = null;
|
||||
this._recordBuilder = new ProtoRecordBuilder();
|
||||
}
|
||||
|
||||
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
|
||||
this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
|
||||
}
|
||||
|
||||
instantiate(dispatcher:any, formatters:Map) {
|
||||
this._createRecordsIfNecessary();
|
||||
return new DynamicChangeDetector(dispatcher, formatters, this._records);
|
||||
}
|
||||
|
||||
_createRecordsIfNecessary() {
|
||||
if (isBlank(this._records)) {
|
||||
var records = this._recordBuilder.records;
|
||||
this._records = coalesce(records);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _jitProtoChangeDetectorClassCounter:number = 0;
|
||||
export class JitProtoChangeDetector extends ProtoChangeDetector {
|
||||
_factory:Function;
|
||||
_recordBuilder:ProtoRecordBuilder;
|
||||
|
||||
constructor() {
|
||||
this._factory = null;
|
||||
this._recordBuilder = new ProtoRecordBuilder();
|
||||
}
|
||||
|
||||
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
|
||||
this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
|
||||
}
|
||||
|
||||
instantiate(dispatcher:any, formatters:Map) {
|
||||
this._createFactoryIfNecessary();
|
||||
return this._factory(dispatcher, formatters);
|
||||
}
|
||||
|
||||
_createFactoryIfNecessary() {
|
||||
if (isBlank(this._factory)) {
|
||||
var c = _jitProtoChangeDetectorClassCounter++;
|
||||
var records = coalesce(this._recordBuilder.records);
|
||||
var typeName = `ChangeDetector${c}`;
|
||||
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProtoRecordBuilder {
|
||||
records:List<ProtoRecord>;
|
||||
|
||||
constructor() {
|
||||
this.records = [];
|
||||
}
|
||||
|
||||
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
|
||||
if (structural) ast = new Structural(ast);
|
||||
|
||||
var last = ListWrapper.last(this.records);
|
||||
if (isPresent(last) && last.groupMemento == groupMemento) {
|
||||
last.lastInGroup = false;
|
||||
}
|
||||
|
||||
var pr = _ConvertAstIntoProtoRecords.convert(ast, bindingMemento, groupMemento, this.records.length);
|
||||
if (! ListWrapper.isEmpty(pr)) {
|
||||
var last = ListWrapper.last(pr);
|
||||
last.lastInBinding = true;
|
||||
last.lastInGroup = true;
|
||||
|
||||
this.records = ListWrapper.concat(this.records, pr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ConvertAstIntoProtoRecords {
|
||||
protoRecords:List;
|
||||
bindingMemento:any;
|
||||
groupMemento:any;
|
||||
contextIndex:number;
|
||||
expressionAsString:string;
|
||||
|
||||
constructor(bindingMemento:any, groupMemento:any, contextIndex:number, expressionAsString:string) {
|
||||
this.protoRecords = [];
|
||||
this.bindingMemento = bindingMemento;
|
||||
this.groupMemento = groupMemento;
|
||||
this.contextIndex = contextIndex;
|
||||
this.expressionAsString = expressionAsString;
|
||||
}
|
||||
|
||||
static convert(ast:AST, bindingMemento:any, groupMemento:any, contextIndex:number) {
|
||||
var c = new _ConvertAstIntoProtoRecords(bindingMemento, groupMemento, contextIndex, ast.toString());
|
||||
ast.visit(c);
|
||||
return c.protoRecords;
|
||||
}
|
||||
|
||||
visitImplicitReceiver(ast:ImplicitReceiver) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
visitInterpolation(ast:Interpolation) {
|
||||
var args = this._visitAll(ast.expressions);
|
||||
return this._addRecord(RECORD_TYPE_INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
|
||||
args, ast.strings, 0);
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast:LiteralPrimitive) {
|
||||
return this._addRecord(RECORD_TYPE_CONST, "literal", ast.value, [], null, 0);
|
||||
}
|
||||
|
||||
visitAccessMember(ast:AccessMember) {
|
||||
var receiver = ast.receiver.visit(this);
|
||||
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
|
||||
}
|
||||
|
||||
visitFormatter(ast:Formatter) {
|
||||
return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), null, 0);
|
||||
}
|
||||
|
||||
visitMethodCall(ast:MethodCall) {
|
||||
var receiver = ast.receiver.visit(this);
|
||||
var args = this._visitAll(ast.args);
|
||||
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
|
||||
}
|
||||
|
||||
visitFunctionCall(ast:FunctionCall) {
|
||||
var target = ast.target.visit(this);
|
||||
var args = this._visitAll(ast.args);
|
||||
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
|
||||
}
|
||||
|
||||
visitLiteralArray(ast:LiteralArray) {
|
||||
var primitiveName = `arrayFn${ast.expressions.length}`;
|
||||
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length),
|
||||
this._visitAll(ast.expressions), null, 0);
|
||||
}
|
||||
|
||||
visitLiteralMap(ast:LiteralMap) {
|
||||
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
|
||||
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null, 0);
|
||||
}
|
||||
|
||||
visitBinary(ast:Binary) {
|
||||
var left = ast.left.visit(this);
|
||||
var right = ast.right.visit(this);
|
||||
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
|
||||
_operationToFunction(ast.operation), [left, right], null, 0);
|
||||
}
|
||||
|
||||
visitPrefixNot(ast:PrefixNot) {
|
||||
var exp = ast.expression.visit(this)
|
||||
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "operation_negate",
|
||||
ChangeDetectionUtil.operation_negate, [exp], null, 0);
|
||||
}
|
||||
|
||||
visitConditional(ast:Conditional) {
|
||||
var c = ast.condition.visit(this);
|
||||
var t = ast.trueExp.visit(this);
|
||||
var f = ast.falseExp.visit(this);
|
||||
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "cond",
|
||||
ChangeDetectionUtil.cond, [c,t,f], null, 0);
|
||||
}
|
||||
|
||||
visitStructural(ast:Structural) {
|
||||
var value = ast.value.visit(this);
|
||||
return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value);
|
||||
}
|
||||
|
||||
visitKeyedAccess(ast:KeyedAccess) {
|
||||
var obj = ast.obj.visit(this);
|
||||
var key = ast.key.visit(this);
|
||||
return this._addRecord(RECORD_TYPE_KEYED_ACCESS, "keyedAccess",
|
||||
ChangeDetectionUtil.keyedAccess, [key], null, obj);
|
||||
}
|
||||
|
||||
_visitAll(asts:List) {
|
||||
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.contextIndex;
|
||||
ListWrapper.push(this.protoRecords,
|
||||
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex,
|
||||
this.bindingMemento, this.groupMemento, this.expressionAsString, false, false));
|
||||
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) {
|
||||
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_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_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) {
|
||||
return isPresent(v) ? '' + v : '';
|
||||
}
|
||||
|
||||
function _interpolationFn(strings:List) {
|
||||
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`);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user