#include "cx/linked_list.h"
#include "cx/utils.h"
#include <string.h>
#include <assert.h>
#define CX_LL_PTR(cur, off) (*(
void**)(((
char*)(cur))+(off)))
#define ll_prev(node)
CX_LL_PTR(node, loc_prev)
#define ll_next(node)
CX_LL_PTR(node, loc_next)
#define ll_advance(node)
CX_LL_PTR(node, loc_advance)
#define ll_data(node) (((
char*)(node))+loc_data)
void *cx_linked_list_at(
void const *start,
size_t start_index,
ptrdiff_t loc_advance,
size_t index
) {
assert(start !=
NULL);
assert(loc_advance >=
0);
size_t i = start_index;
void const *cur = start;
while (i != index && cur !=
NULL) {
cur = ll_advance(cur);
i < index ? i++ : i--;
}
return (
void *) cur;
}
ssize_t cx_linked_list_find(
void const *start,
ptrdiff_t loc_advance,
ptrdiff_t loc_data,
cx_compare_func cmp_func,
void const *elem
) {
assert(start !=
NULL);
assert(loc_advance >=
0);
assert(loc_data >=
0);
assert(cmp_func);
void const *node = start;
ssize_t index =
0;
do {
void *current = ll_data(node);
if (cmp_func(current, elem) ==
0) {
return index;
}
node = ll_advance(node);
index++;
}
while (node !=
NULL);
return -
1;
}
void *cx_linked_list_first(
void const *node,
ptrdiff_t loc_prev
) {
return cx_linked_list_last(node, loc_prev);
}
void *cx_linked_list_last(
void const *node,
ptrdiff_t loc_next
) {
assert(node !=
NULL);
assert(loc_next >=
0);
void const *cur = node;
void const *last;
do {
last = cur;
}
while ((cur = ll_next(cur)) !=
NULL);
return (
void *) last;
}
void *cx_linked_list_prev(
void const *begin,
ptrdiff_t loc_next,
void const *node
) {
assert(begin !=
NULL);
assert(node !=
NULL);
assert(loc_next >=
0);
if (begin == node)
return NULL;
void const *cur = begin;
void const *next;
while (
1) {
next = ll_next(cur);
if (next == node)
return (
void *) cur;
cur = next;
}
}
void cx_linked_list_link(
void *left,
void *right,
ptrdiff_t loc_prev,
ptrdiff_t loc_next
) {
assert(loc_next >=
0);
ll_next(left) = right;
if (loc_prev >=
0) {
ll_prev(right) = left;
}
}
void cx_linked_list_unlink(
void *left,
void *right,
ptrdiff_t loc_prev,
ptrdiff_t loc_next
) {
assert (loc_next >=
0);
assert(ll_next(left) == right);
ll_next(left) =
NULL;
if (loc_prev >=
0) {
assert(ll_prev(right) == left);
ll_prev(right) =
NULL;
}
}
void cx_linked_list_add(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *new_node
) {
void *last;
if (end ==
NULL) {
assert(begin !=
NULL);
last = *begin ==
NULL ?
NULL : cx_linked_list_last(*begin, loc_next);
}
else {
last = *end;
}
cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, last, new_node, new_node);
}
void cx_linked_list_prepend(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *new_node
) {
cx_linked_list_insert_chain(begin, end, loc_prev, loc_next,
NULL, new_node, new_node);
}
void cx_linked_list_insert(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *node,
void *new_node
) {
cx_linked_list_insert_chain(begin, end, loc_prev, loc_next, node, new_node, new_node);
}
void cx_linked_list_insert_chain(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *node,
void *insert_begin,
void *insert_end
) {
if (insert_end ==
NULL) {
insert_end = cx_linked_list_last(insert_begin, loc_next);
}
void *successor;
if (node ==
NULL) {
assert(begin !=
NULL || (end !=
NULL && loc_prev >=
0));
if (begin !=
NULL) {
successor = *begin;
*begin = insert_begin;
}
else {
successor = *end ==
NULL ?
NULL : cx_linked_list_first(*end, loc_prev);
}
}
else {
successor = ll_next(node);
cx_linked_list_link(node, insert_begin, loc_prev, loc_next);
}
if (successor ==
NULL) {
if (end !=
NULL) {
*end = insert_end;
}
}
else {
cx_linked_list_link(insert_end, successor, loc_prev, loc_next);
}
}
void cx_linked_list_remove(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
void *node
) {
assert(node !=
NULL);
assert(loc_next >=
0);
assert(loc_prev >=
0 || begin !=
NULL);
void *next = ll_next(node);
void *prev;
if (loc_prev >=
0) {
prev = ll_prev(node);
}
else {
prev = cx_linked_list_prev(*begin, loc_next, node);
}
if (prev ==
NULL) {
if (begin !=
NULL) {
*begin = next;
}
}
else {
ll_next(prev) = next;
}
if (next ==
NULL) {
if (end !=
NULL) {
*end = prev;
}
}
else if (loc_prev >=
0) {
ll_prev(next) = prev;
}
}
size_t cx_linked_list_size(
void const *node,
ptrdiff_t loc_next
) {
assert(loc_next >=
0);
size_t size =
0;
while (node !=
NULL) {
node = ll_next(node);
size++;
}
return size;
}
#ifndef CX_LINKED_LIST_SORT_SBO_SIZE
#define CX_LINKED_LIST_SORT_SBO_SIZE 1024
#endif
static void cx_linked_list_sort_merge(
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
ptrdiff_t loc_data,
size_t length,
void *ls,
void *le,
void *re,
cx_compare_func cmp_func,
void **begin,
void **end
) {
void *sbo[
CX_LINKED_LIST_SORT_SBO_SIZE];
void **sorted = length >=
CX_LINKED_LIST_SORT_SBO_SIZE ?
malloc(
sizeof(
void *) * length) : sbo;
if (sorted ==
NULL) abort();
void *rc, *lc;
lc = ls;
rc = le;
size_t n =
0;
while (lc && lc != le && rc != re) {
if (cmp_func(ll_data(lc), ll_data(rc)) <=
0) {
sorted[n] = lc;
lc = ll_next(lc);
}
else {
sorted[n] = rc;
rc = ll_next(rc);
}
n++;
}
while (lc && lc != le) {
sorted[n] = lc;
lc = ll_next(lc);
n++;
}
while (rc && rc != re) {
sorted[n] = rc;
rc = ll_next(rc);
n++;
}
if (loc_prev >=
0) ll_prev(sorted[
0]) =
NULL;
cx_for_n (i, length -
1) {
cx_linked_list_link(sorted[i], sorted[i +
1], loc_prev, loc_next);
}
ll_next(sorted[length -
1]) =
NULL;
*begin = sorted[
0];
*end = sorted[length-
1];
if (sorted != sbo) {
free(sorted);
}
}
void cx_linked_list_sort(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next,
ptrdiff_t loc_data,
cx_compare_func cmp_func
) {
assert(begin !=
NULL);
assert(loc_next >=
0);
assert(loc_data >=
0);
assert(cmp_func);
void *lc, *ls, *le, *re;
ls = *begin;
if (ls ==
NULL)
return;
lc = ls;
size_t ln =
1;
while (ll_next(lc) !=
NULL && cmp_func(ll_data(ll_next(lc)), ll_data(lc)) >
0) {
lc = ll_next(lc);
ln++;
}
le = ll_next(lc);
if (le !=
NULL) {
void *rc;
size_t rn =
1;
rc = le;
while (ll_next(rc) !=
NULL && cmp_func(ll_data(ll_next(rc)), ll_data(rc)) >
0) {
rc = ll_next(rc);
rn++;
}
re = ll_next(rc);
void *sorted_begin, *sorted_end;
cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
ln + rn, ls, le, re, cmp_func,
&sorted_begin, &sorted_end);
size_t remainder_length = cx_linked_list_size(re, loc_next);
if (remainder_length >
0) {
void *remainder = re;
cx_linked_list_sort(&remainder,
NULL, loc_prev, loc_next, loc_data, cmp_func);
cx_linked_list_sort_merge(loc_prev, loc_next, loc_data,
ln + rn + remainder_length,
sorted_begin, remainder,
NULL, cmp_func,
&sorted_begin, &sorted_end);
}
*begin = sorted_begin;
if (end) *end = sorted_end;
}
}
int cx_linked_list_compare(
void const *begin_left,
void const *begin_right,
ptrdiff_t loc_advance,
ptrdiff_t loc_data,
cx_compare_func cmp_func
) {
void const *left = begin_left, *right = begin_right;
while (left !=
NULL && right !=
NULL) {
void const *left_data = ll_data(left);
void const *right_data = ll_data(right);
int result = cmp_func(left_data, right_data);
if (result !=
0)
return result;
left = ll_advance(left);
right = ll_advance(right);
}
if (left !=
NULL) {
return 1; }
else if (right !=
NULL) {
return -
1; }
else {
return 0; }
}
void cx_linked_list_reverse(
void **begin,
void **end,
ptrdiff_t loc_prev,
ptrdiff_t loc_next
) {
assert(begin !=
NULL);
assert(loc_next >=
0);
void *prev =
NULL;
void *cur = *begin;
while (cur !=
NULL) {
void *next = ll_next(cur);
ll_next(cur) = prev;
if (loc_prev >=
0) {
ll_prev(cur) = next;
}
prev = cur;
cur = next;
}
if (end !=
NULL) {
*end = *begin;
}
*begin = prev;
}
bool
CX_DISABLE_LINKED_LIST_SWAP_SBO = false;
typedef struct cx_linked_list_node cx_linked_list_node;
struct cx_linked_list_node {
cx_linked_list_node *prev;
cx_linked_list_node *next;
char payload[];
};
#define CX_LL_LOC_PREV offsetof(cx_linked_list_node, prev)
#define CX_LL_LOC_NEXT offsetof(cx_linked_list_node, next)
#define CX_LL_LOC_DATA offsetof(cx_linked_list_node, payload)
typedef struct {
struct cx_list_s base;
cx_linked_list_node *begin;
cx_linked_list_node *end;
} cx_linked_list;
static cx_linked_list_node *cx_ll_node_at(
cx_linked_list
const *list,
size_t index
) {
if (index >= list->base.size) {
return NULL;
}
else if (index > list->base.size /
2) {
return cx_linked_list_at(list->end, list->base.size -
1,
CX_LL_LOC_PREV, index);
}
else {
return cx_linked_list_at(list->begin,
0,
CX_LL_LOC_NEXT, index);
}
}
static int cx_ll_insert_at(
struct cx_list_s *list,
cx_linked_list_node *node,
void const *elem
) {
cx_linked_list_node *new_node = cxMalloc(list->allocator,
sizeof(cx_linked_list_node) + list->item_size);
if (new_node ==
NULL)
return 1;
new_node->prev = new_node->next =
NULL;
memcpy(new_node->payload, elem, list->item_size);
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_insert_chain(
(
void **) &ll->begin, (
void **) &ll->end,
CX_LL_LOC_PREV,
CX_LL_LOC_NEXT,
node, new_node, new_node
);
list->size++;
return 0;
}
static size_t cx_ll_insert_array(
struct cx_list_s *list,
size_t index,
void const *array,
size_t n
) {
if (index > list->size || n ==
0)
return 0;
cx_linked_list_node *node = index ==
0 ?
NULL : cx_ll_node_at((cx_linked_list *) list, index -
1);
if (
0 != cx_ll_insert_at(list, node, array)) {
return 1;
}
if (n ==
1)
return 1;
node = node ==
NULL ? ((cx_linked_list *) list)->begin : node->next;
char const *source = array;
for (
size_t i =
1; i < n; i++) {
source += list->item_size;
if (
0 != cx_ll_insert_at(list, node, source)) {
return i;
}
node = node->next;
}
return n;
}
static int cx_ll_insert_element(
struct cx_list_s *list,
size_t index,
void const *element
) {
return 1 != cx_ll_insert_array(list, index, element,
1);
}
static int cx_ll_remove(
struct cx_list_s *list,
size_t index
) {
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_node *node = cx_ll_node_at(ll, index);
if (node ==
NULL)
return 1;
cx_invoke_destructor(list, node->payload);
cx_linked_list_remove((
void **) &ll->begin, (
void **) &ll->end,
CX_LL_LOC_PREV,
CX_LL_LOC_NEXT, node);
list->size--;
cxFree(list->allocator, node);
return 0;
}
static void cx_ll_clear(
struct cx_list_s *list) {
if (list->size ==
0)
return;
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_node *node = ll->begin;
while (node !=
NULL) {
cx_invoke_destructor(list, node->payload);
cx_linked_list_node *next = node->next;
cxFree(list->allocator, node);
node = next;
}
ll->begin = ll->end =
NULL;
list->size =
0;
}
#ifndef CX_LINKED_LIST_SWAP_SBO_SIZE
#define CX_LINKED_LIST_SWAP_SBO_SIZE 128
#endif
static int cx_ll_swap(
struct cx_list_s *list,
size_t i,
size_t j
) {
if (i >= list->size || j >= list->size)
return 1;
if (i == j)
return 0;
cx_linked_list *ll = (cx_linked_list *) list;
size_t mid = list->size /
2;
size_t left, right;
if (i < j) {
left = i;
right = j;
}
else {
left = j;
right = i;
}
cx_linked_list_node *nleft, *nright;
if (left < mid && right < mid) {
nleft = cx_ll_node_at(ll, left);
nright = nleft;
for (
size_t c = left; c < right; c++) {
nright = nright->next;
}
}
else if (left >= mid && right >= mid) {
nright = cx_ll_node_at(ll, right);
nleft = nright;
for (
size_t c = right; c > left; c--) {
nleft = nleft->prev;
}
}
else {
size_t closest;
size_t other;
size_t diff2boundary = list->size - right -
1;
if (left <= diff2boundary) {
closest = left;
other = right;
nleft = cx_ll_node_at(ll, left);
}
else {
closest = right;
other = left;
diff2boundary = left;
nright = cx_ll_node_at(ll, right);
}
if (right - left <= diff2boundary) {
if (closest == left) {
nright = nleft;
for (
size_t c = left; c < right; c++) {
nright = nright->next;
}
}
else {
nleft = nright;
for (
size_t c = right; c > left; c--) {
nleft = nleft->prev;
}
}
}
else {
if (closest == left) {
nright = cx_ll_node_at(ll, other);
}
else {
nleft = cx_ll_node_at(ll, other);
}
}
}
if (list->item_size >
CX_LINKED_LIST_SWAP_SBO_SIZE ||
CX_DISABLE_LINKED_LIST_SWAP_SBO) {
cx_linked_list_node *prev = nleft->prev;
cx_linked_list_node *next = nright->next;
cx_linked_list_node *midstart = nleft->next;
cx_linked_list_node *midend = nright->prev;
if (prev ==
NULL) {
ll->begin = nright;
}
else {
prev->next = nright;
}
nright->prev = prev;
if (midstart == nright) {
nright->next = nleft;
nleft->prev = nright;
}
else {
nright->next = midstart;
midstart->prev = nright;
midend->next = nleft;
nleft->prev = midend;
}
nleft->next = next;
if (next ==
NULL) {
ll->end = nleft;
}
else {
next->prev = nleft;
}
}
else {
char buf[
CX_LINKED_LIST_SWAP_SBO_SIZE];
memcpy(buf, nleft->payload, list->item_size);
memcpy(nleft->payload, nright->payload, list->item_size);
memcpy(nright->payload, buf, list->item_size);
}
return 0;
}
static void *cx_ll_at(
struct cx_list_s
const *list,
size_t index
) {
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_node *node = cx_ll_node_at(ll, index);
return node ==
NULL ?
NULL : node->payload;
}
static ssize_t cx_ll_find(
struct cx_list_s
const *list,
void const *elem
) {
return cx_linked_list_find(((cx_linked_list *) list)->begin,
CX_LL_LOC_NEXT,
CX_LL_LOC_DATA,
list->cmpfunc, elem);
}
static void cx_ll_sort(
struct cx_list_s *list) {
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_sort((
void **) &ll->begin, (
void **) &ll->end,
CX_LL_LOC_PREV,
CX_LL_LOC_NEXT,
CX_LL_LOC_DATA,
list->cmpfunc);
}
static void cx_ll_reverse(
struct cx_list_s *list) {
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_reverse((
void **) &ll->begin, (
void **) &ll->end,
CX_LL_LOC_PREV,
CX_LL_LOC_NEXT);
}
static int cx_ll_compare(
struct cx_list_s
const *list,
struct cx_list_s
const *other
) {
cx_linked_list *left = (cx_linked_list *) list;
cx_linked_list *right = (cx_linked_list *) other;
return cx_linked_list_compare(left->begin, right->begin,
CX_LL_LOC_NEXT,
CX_LL_LOC_DATA,
list->cmpfunc);
}
static bool cx_ll_iter_valid(
void const *it) {
struct cx_iterator_s
const *iter = it;
return iter->elem_handle !=
NULL;
}
static void cx_ll_iter_next(
void *it) {
struct cx_iterator_base_s *itbase = it;
if (itbase->remove) {
itbase->remove = false;
struct cx_mut_iterator_s *iter = it;
struct cx_list_s *list = iter->src_handle;
cx_linked_list *ll = iter->src_handle;
cx_linked_list_node *node = iter->elem_handle;
iter->elem_handle = node->next;
cx_invoke_destructor(list, node->payload);
cx_linked_list_remove((
void **) &ll->begin, (
void **) &ll->end,
CX_LL_LOC_PREV,
CX_LL_LOC_NEXT, node);
list->size--;
cxFree(list->allocator, node);
}
else {
struct cx_iterator_s *iter = it;
iter->index++;
cx_linked_list_node *node = iter->elem_handle;
iter->elem_handle = node->next;
}
}
static void cx_ll_iter_prev(
void *it) {
struct cx_iterator_base_s *itbase = it;
if (itbase->remove) {
itbase->remove = false;
struct cx_mut_iterator_s *iter = it;
struct cx_list_s *list = iter->src_handle;
cx_linked_list *ll = iter->src_handle;
cx_linked_list_node *node = iter->elem_handle;
iter->elem_handle = node->prev;
iter->index--;
cx_invoke_destructor(list, node->payload);
cx_linked_list_remove((
void **) &ll->begin, (
void **) &ll->end,
CX_LL_LOC_PREV,
CX_LL_LOC_NEXT, node);
list->size--;
cxFree(list->allocator, node);
}
else {
struct cx_iterator_s *iter = it;
iter->index--;
cx_linked_list_node *node = iter->elem_handle;
iter->elem_handle = node->prev;
}
}
static void *cx_ll_iter_current(
void const *it) {
struct cx_iterator_s
const *iter = it;
cx_linked_list_node *node = iter->elem_handle;
return node->payload;
}
static bool cx_ll_iter_flag_rm(
void *it) {
struct cx_iterator_base_s *iter = it;
if (iter->mutating) {
iter->remove = true;
return true;
}
else {
return false;
}
}
static CxIterator cx_ll_iterator(
struct cx_list_s
const *list,
size_t index,
bool backwards
) {
CxIterator iter;
iter.index = index;
iter.src_handle = list;
iter.elem_handle = cx_ll_node_at((cx_linked_list
const *) list, index);
iter.base.valid = cx_ll_iter_valid;
iter.base.current = cx_ll_iter_current;
iter.base.next = backwards ? cx_ll_iter_prev : cx_ll_iter_next;
iter.base.flag_removal = cx_ll_iter_flag_rm;
iter.base.mutating = false;
iter.base.remove = false;
return iter;
}
static int cx_ll_insert_iter(
CxMutIterator *iter,
void const *elem,
int prepend
) {
struct cx_list_s *list = iter->src_handle;
cx_linked_list_node *node = iter->elem_handle;
if (node !=
NULL) {
assert(prepend >=
0 && prepend <=
1);
cx_linked_list_node *choice[
2] = {node, node->prev};
int result = cx_ll_insert_at(list, choice[prepend], elem);
iter->index += prepend * (
0 == result);
return result;
}
else {
int result = cx_ll_insert_element(list, list->size, elem);
iter->index = list->size;
return result;
}
}
static void cx_ll_destructor(CxList *list) {
cx_linked_list *ll = (cx_linked_list *) list;
cx_linked_list_node *node = ll->begin;
while (node) {
cx_invoke_destructor(list, node->payload);
void *next = node->next;
cxFree(list->allocator, node);
node = next;
}
cxFree(list->allocator, list);
}
static cx_list_class cx_linked_list_class = {
cx_ll_destructor,
cx_ll_insert_element,
cx_ll_insert_array,
cx_ll_insert_iter,
cx_ll_remove,
cx_ll_clear,
cx_ll_swap,
cx_ll_at,
cx_ll_find,
cx_ll_sort,
cx_ll_compare,
cx_ll_reverse,
cx_ll_iterator,
};
CxList *cxLinkedListCreate(
CxAllocator
const *allocator,
cx_compare_func comparator,
size_t item_size
) {
if (allocator ==
NULL) {
allocator = cxDefaultAllocator;
}
cx_linked_list *list = cxCalloc(allocator,
1,
sizeof(cx_linked_list));
if (list ==
NULL)
return NULL;
list->base.cl = &cx_linked_list_class;
list->base.allocator = allocator;
list->base.cmpfunc = comparator;
if (item_size >
0) {
list->base.item_size = item_size;
}
else {
cxListStorePointers((CxList *) list);
}
return (CxList *) list;
}