diff options
Diffstat (limited to 'node_modules/csso/lib/utils/list.js')
| -rw-r--r-- | node_modules/csso/lib/utils/list.js | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/node_modules/csso/lib/utils/list.js b/node_modules/csso/lib/utils/list.js new file mode 100644 index 00000000..30d14871 --- /dev/null +++ b/node_modules/csso/lib/utils/list.js @@ -0,0 +1,389 @@ +// +// item item item item +// /------\ /------\ /------\ /------\ +// | data | | data | | data | | data | +// null <--+-prev |<---+-prev |<---+-prev |<---+-prev | +// | next-+--->| next-+--->| next-+--->| next-+--> null +// \------/ \------/ \------/ \------/ +// ^ ^ +// | list | +// | /------\ | +// \--------------+-head | | +// | tail-+--------------/ +// \------/ +// + +function createItem(data) { + return { + data: data, + next: null, + prev: null + }; +} + +var List = function(values) { + this.cursor = null; + this.head = null; + this.tail = null; + + if (Array.isArray(values)) { + var cursor = null; + + for (var i = 0; i < values.length; i++) { + var item = createItem(values[i]); + + if (cursor !== null) { + cursor.next = item; + } else { + this.head = item; + } + + item.prev = cursor; + cursor = item; + } + + this.tail = cursor; + } +}; + +Object.defineProperty(List.prototype, 'size', { + get: function() { + var size = 0; + var cursor = this.head; + + while (cursor) { + size++; + cursor = cursor.next; + } + + return size; + } +}); + +List.createItem = createItem; +List.prototype.createItem = createItem; + +List.prototype.toArray = function() { + var cursor = this.head; + var result = []; + + while (cursor) { + result.push(cursor.data); + cursor = cursor.next; + } + + return result; +}; +List.prototype.toJSON = function() { + return this.toArray(); +}; + +List.prototype.isEmpty = function() { + return this.head === null; +}; + +List.prototype.first = function() { + return this.head && this.head.data; +}; + +List.prototype.last = function() { + return this.tail && this.tail.data; +}; + +List.prototype.each = function(fn, context) { + var item; + var cursor = { + prev: null, + next: this.head, + cursor: this.cursor + }; + + if (context === undefined) { + context = this; + } + + // push cursor + this.cursor = cursor; + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + fn.call(context, item.data, item, this); + } + + // pop cursor + this.cursor = this.cursor.cursor; +}; + +List.prototype.eachRight = function(fn, context) { + var item; + var cursor = { + prev: this.tail, + next: null, + cursor: this.cursor + }; + + if (context === undefined) { + context = this; + } + + // push cursor + this.cursor = cursor; + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + fn.call(context, item.data, item, this); + } + + // pop cursor + this.cursor = this.cursor.cursor; +}; + +List.prototype.nextUntil = function(start, fn, context) { + if (start === null) { + return; + } + + var item; + var cursor = { + prev: null, + next: start, + cursor: this.cursor + }; + + if (context === undefined) { + context = this; + } + + // push cursor + this.cursor = cursor; + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + if (fn.call(context, item.data, item, this)) { + break; + } + } + + // pop cursor + this.cursor = this.cursor.cursor; +}; + +List.prototype.prevUntil = function(start, fn, context) { + if (start === null) { + return; + } + + var item; + var cursor = { + prev: start, + next: null, + cursor: this.cursor + }; + + if (context === undefined) { + context = this; + } + + // push cursor + this.cursor = cursor; + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + if (fn.call(context, item.data, item, this)) { + break; + } + } + + // pop cursor + this.cursor = this.cursor.cursor; +}; + +List.prototype.some = function(fn, context) { + var cursor = this.head; + + if (context === undefined) { + context = this; + } + + while (cursor !== null) { + if (fn.call(context, cursor.data, cursor, this)) { + return true; + } + + cursor = cursor.next; + } + + return false; +}; + +List.prototype.map = function(fn, context) { + var result = []; + var cursor = this.head; + + if (context === undefined) { + context = this; + } + + while (cursor !== null) { + result.push(fn.call(context, cursor.data, cursor, this)); + cursor = cursor.next; + } + + return result; +}; + +List.prototype.copy = function() { + var result = new List(); + var cursor = this.head; + + while (cursor !== null) { + result.insert(createItem(cursor.data)); + cursor = cursor.next; + } + + return result; +}; + +List.prototype.updateCursors = function(prevOld, prevNew, nextOld, nextNew) { + var cursor = this.cursor; + + while (cursor !== null) { + if (prevNew === true || cursor.prev === prevOld) { + cursor.prev = prevNew; + } + + if (nextNew === true || cursor.next === nextOld) { + cursor.next = nextNew; + } + + cursor = cursor.cursor; + } +}; + +List.prototype.insert = function(item, before) { + if (before !== undefined && before !== null) { + // prev before + // ^ + // item + this.updateCursors(before.prev, item, before, item); + + if (before.prev === null) { + // insert to the beginning of list + if (this.head !== before) { + throw new Error('before doesn\'t below to list'); + } + + // since head points to before therefore list doesn't empty + // no need to check tail + this.head = item; + before.prev = item; + item.next = before; + + this.updateCursors(null, item); + } else { + + // insert between two items + before.prev.next = item; + item.prev = before.prev; + + before.prev = item; + item.next = before; + } + } else { + // tail + // ^ + // item + this.updateCursors(this.tail, item, null, item); + + // insert to end of the list + if (this.tail !== null) { + // if list has a tail, then it also has a head, but head doesn't change + + // last item -> new item + this.tail.next = item; + + // last item <- new item + item.prev = this.tail; + } else { + // if list has no a tail, then it also has no a head + // in this case points head to new item + this.head = item; + } + + // tail always start point to new item + this.tail = item; + } +}; + +List.prototype.remove = function(item) { + // item + // ^ + // prev next + this.updateCursors(item, item.prev, item, item.next); + + if (item.prev !== null) { + item.prev.next = item.next; + } else { + if (this.head !== item) { + throw new Error('item doesn\'t below to list'); + } + + this.head = item.next; + } + + if (item.next !== null) { + item.next.prev = item.prev; + } else { + if (this.tail !== item) { + throw new Error('item doesn\'t below to list'); + } + + this.tail = item.prev; + } + + item.prev = null; + item.next = null; + + return item; +}; + +List.prototype.appendList = function(list) { + // ignore empty lists + if (list.head === null) { + return; + } + + this.updateCursors(this.tail, list.tail, null, list.head); + + // insert to end of the list + if (this.tail !== null) { + // if destination list has a tail, then it also has a head, + // but head doesn't change + + // dest tail -> source head + this.tail.next = list.head; + + // dest tail <- source head + list.head.prev = this.tail; + } else { + // if list has no a tail, then it also has no a head + // in this case points head to new item + this.head = list.head; + } + + // tail always start point to new item + this.tail = list.tail; + + list.head = null; + list.tail = null; +}; + +module.exports = List; |
