diff options
| author | Adam Mathes <adam@adammathes.com> | 2026-02-13 21:34:48 -0800 |
|---|---|---|
| committer | Adam Mathes <adam@adammathes.com> | 2026-02-13 21:34:48 -0800 |
| commit | 76cb9c2a39d477a64824a985ade40507e3bbade1 (patch) | |
| tree | 41e997aa9c6f538d3a136af61dae9424db2005a9 /vanilla/node_modules/css-tree/cjs/utils/List.cjs | |
| parent | 819a39a21ac992b1393244a4c283bbb125208c69 (diff) | |
| download | neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.gz neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.bz2 neko-76cb9c2a39d477a64824a985ade40507e3bbade1.zip | |
feat(vanilla): add testing infrastructure and tests (NK-wjnczv)
Diffstat (limited to 'vanilla/node_modules/css-tree/cjs/utils/List.cjs')
| -rw-r--r-- | vanilla/node_modules/css-tree/cjs/utils/List.cjs | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/vanilla/node_modules/css-tree/cjs/utils/List.cjs b/vanilla/node_modules/css-tree/cjs/utils/List.cjs new file mode 100644 index 0000000..ec25f46 --- /dev/null +++ b/vanilla/node_modules/css-tree/cjs/utils/List.cjs @@ -0,0 +1,473 @@ +'use strict'; + +// +// list +// ┌──────┐ +// ┌──────────────┼─head │ +// │ │ tail─┼──────────────┐ +// │ └──────┘ │ +// ▼ ▼ +// item item item item +// ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ +// null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │ +// │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null +// ├──────┤ ├──────┤ ├──────┤ ├──────┤ +// │ data │ │ data │ │ data │ │ data │ +// └──────┘ └──────┘ └──────┘ └──────┘ +// + +let releasedCursors = null; + +class List { + static createItem(data) { + return { + prev: null, + next: null, + data + }; + } + + constructor() { + this.head = null; + this.tail = null; + this.cursor = null; + } + createItem(data) { + return List.createItem(data); + } + + // cursor helpers + allocateCursor(prev, next) { + let cursor; + + if (releasedCursors !== null) { + cursor = releasedCursors; + releasedCursors = releasedCursors.cursor; + cursor.prev = prev; + cursor.next = next; + cursor.cursor = this.cursor; + } else { + cursor = { + prev, + next, + cursor: this.cursor + }; + } + + this.cursor = cursor; + + return cursor; + } + releaseCursor() { + const { cursor } = this; + + this.cursor = cursor.cursor; + cursor.prev = null; + cursor.next = null; + cursor.cursor = releasedCursors; + releasedCursors = cursor; + } + updateCursors(prevOld, prevNew, nextOld, nextNew) { + let { cursor } = this; + + while (cursor !== null) { + if (cursor.prev === prevOld) { + cursor.prev = prevNew; + } + + if (cursor.next === nextOld) { + cursor.next = nextNew; + } + + cursor = cursor.cursor; + } + } + *[Symbol.iterator]() { + for (let cursor = this.head; cursor !== null; cursor = cursor.next) { + yield cursor.data; + } + } + + // getters + get size() { + let size = 0; + + for (let cursor = this.head; cursor !== null; cursor = cursor.next) { + size++; + } + + return size; + } + get isEmpty() { + return this.head === null; + } + get first() { + return this.head && this.head.data; + } + get last() { + return this.tail && this.tail.data; + } + + // convertors + fromArray(array) { + let cursor = null; + this.head = null; + + for (let data of array) { + const item = List.createItem(data); + + if (cursor !== null) { + cursor.next = item; + } else { + this.head = item; + } + + item.prev = cursor; + cursor = item; + } + + this.tail = cursor; + return this; + } + toArray() { + return [...this]; + } + toJSON() { + return [...this]; + } + + // array-like methods + forEach(fn, thisArg = this) { + // push cursor + const cursor = this.allocateCursor(null, this.head); + + while (cursor.next !== null) { + const item = cursor.next; + cursor.next = item.next; + fn.call(thisArg, item.data, item, this); + } + + // pop cursor + this.releaseCursor(); + } + forEachRight(fn, thisArg = this) { + // push cursor + const cursor = this.allocateCursor(this.tail, null); + + while (cursor.prev !== null) { + const item = cursor.prev; + cursor.prev = item.prev; + fn.call(thisArg, item.data, item, this); + } + + // pop cursor + this.releaseCursor(); + } + reduce(fn, initialValue, thisArg = this) { + // push cursor + let cursor = this.allocateCursor(null, this.head); + let acc = initialValue; + let item; + + while (cursor.next !== null) { + item = cursor.next; + cursor.next = item.next; + + acc = fn.call(thisArg, acc, item.data, item, this); + } + + // pop cursor + this.releaseCursor(); + + return acc; + } + reduceRight(fn, initialValue, thisArg = this) { + // push cursor + let cursor = this.allocateCursor(this.tail, null); + let acc = initialValue; + let item; + + while (cursor.prev !== null) { + item = cursor.prev; + cursor.prev = item.prev; + + acc = fn.call(thisArg, acc, item.data, item, this); + } + + // pop cursor + this.releaseCursor(); + + return acc; + } + some(fn, thisArg = this) { + for (let cursor = this.head; cursor !== null; cursor = cursor.next) { + if (fn.call(thisArg, cursor.data, cursor, this)) { + return true; + } + } + + return false; + } + map(fn, thisArg = this) { + const result = new List(); + + for (let cursor = this.head; cursor !== null; cursor = cursor.next) { + result.appendData(fn.call(thisArg, cursor.data, cursor, this)); + } + + return result; + } + filter(fn, thisArg = this) { + const result = new List(); + + for (let cursor = this.head; cursor !== null; cursor = cursor.next) { + if (fn.call(thisArg, cursor.data, cursor, this)) { + result.appendData(cursor.data); + } + } + + return result; + } + + nextUntil(start, fn, thisArg = this) { + if (start === null) { + return; + } + + // push cursor + const cursor = this.allocateCursor(null, start); + + while (cursor.next !== null) { + const item = cursor.next; + cursor.next = item.next; + if (fn.call(thisArg, item.data, item, this)) { + break; + } + } + + // pop cursor + this.releaseCursor(); + } + prevUntil(start, fn, thisArg = this) { + if (start === null) { + return; + } + + // push cursor + const cursor = this.allocateCursor(start, null); + + while (cursor.prev !== null) { + const item = cursor.prev; + cursor.prev = item.prev; + if (fn.call(thisArg, item.data, item, this)) { + break; + } + } + + // pop cursor + this.releaseCursor(); + } + + // mutation + clear() { + this.head = null; + this.tail = null; + } + copy() { + const result = new List(); + + for (let data of this) { + result.appendData(data); + } + + return result; + } + prepend(item) { + // head + // ^ + // item + this.updateCursors(null, item, this.head, item); + + // insert to the beginning of the list + if (this.head !== null) { + // new item <- first item + this.head.prev = item; + // new item -> first item + item.next = this.head; + } else { + // if list has no head, then it also has no tail + // in this case tail points to the new item + this.tail = item; + } + + // head always points to new item + this.head = item; + return this; + } + prependData(data) { + return this.prepend(List.createItem(data)); + } + append(item) { + return this.insert(item); + } + appendData(data) { + return this.insert(List.createItem(data)); + } + insert(item, before = null) { + if (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 belong 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 the ending of the list + if (this.tail !== null) { + // last item -> new item + this.tail.next = item; + // last item <- new item + item.prev = this.tail; + } else { + // if list has no tail, then it also has no head + // in this case head points to new item + this.head = item; + } + + // tail always points to new item + this.tail = item; + } + + return this; + } + insertData(data, before) { + return this.insert(List.createItem(data), before); + } + remove(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 belong 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 belong to list'); + } + + this.tail = item.prev; + } + + item.prev = null; + item.next = null; + + return item; + } + push(data) { + this.insert(List.createItem(data)); + } + pop() { + return this.tail !== null ? this.remove(this.tail) : null; + } + unshift(data) { + this.prepend(List.createItem(data)); + } + shift() { + return this.head !== null ? this.remove(this.head) : null; + } + prependList(list) { + return this.insertList(list, this.head); + } + appendList(list) { + return this.insertList(list); + } + insertList(list, before) { + // ignore empty lists + if (list.head === null) { + return this; + } + + if (before !== undefined && before !== null) { + this.updateCursors(before.prev, list.tail, before, list.head); + + // insert in the middle of dist list + if (before.prev !== null) { + // before.prev <-> list.head + before.prev.next = list.head; + list.head.prev = before.prev; + } else { + this.head = list.head; + } + + before.prev = list.tail; + list.tail.next = before; + } else { + 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; + return this; + } + replace(oldItem, newItemOrList) { + if ('head' in newItemOrList) { + this.insertList(newItemOrList, oldItem); + } else { + this.insert(newItemOrList, oldItem); + } + + this.remove(oldItem); + } +} + +exports.List = List; |
