diff --git a/sys/include/slist.h b/sys/include/slist.h new file mode 100644 index 000000000000..de0eb0eb0365 --- /dev/null +++ b/sys/include/slist.h @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2014 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @file + * @brief simple single linked list implementation with iterators + * + * A list entry is a struct of an arbitrary type but with the only constraint that + * it's first entry is a pointer of the same type as the struct with the name next. + * + * A list is defined by it's first entry, called head. Since the first entry may + * change, a pointer to the first entry is used to refer to the list. + * + * The list automatically allocates and deallocates memory when list elements + * are added or removed + * + * @author Benjamin Valentin + */ + + +#ifndef SIMPLE_LIST_H_ +#define SIMPLE_LIST_H_ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct simple_list_elem; + +/** + * @brief allocates memory for a new list entry and appends it before the + * head. The new entry is the new head. + * + * @param head pointer to the list + * @return the new list entry, NULL if no new list entry could be allocated + */ +#define simple_list_add_head(head) \ + __simple_list_add_head((struct simple_list_elem**) \ + (head), calloc(1, sizeof **(head))) + + +/** + * @brief appends a preallocated element to the top of the list. The new + * entry is the new head. + * + * @param head pointer to the list + * @param node preallocated list element + * @return the new list entry (node) + */ +#define simple_list_set_head(head, node) \ + __simple_list_add_head((struct simple_list_elem**) (head), (node)) + +/** +* @brief allocates memory for a new list entry and appends it at the end of +* the list. +* +* @param head pointer to pointer to the first list element +* @param head pointer to the list +* @return the new list entry, NULL if no new list entry could be allocated +*/ +#define simple_list_add_tail(head) \ + __simple_list_add_tail((struct simple_list_elem**) \ + (head), calloc(1, sizeof **(head))) + +/** + * @brief appends a preallocated element to the end of the list. + * + * @param head pointer to the list + * @param node preallocated list element + * @return the new list entry (node) + */ +#define simple_list_set_tail(head, node) \ + __simple_list_add_tail((struct simple_list_elem**) (head), (node)) + +/** +* @brief allocates memory for a new list entry and adds it before an +* existing entry. The new entry is added before the existing element +* where old_entry->value > value +* If no such entry could be found, the new entry will be added at the +* end of the list +* +* @param head pointer to the list +* @param value value to compare list entries +* has to be the same name as the element in the list entry +* structure that is be used for comparison +* +* @return pointer to the new element, NULL if no new list element could be +* allocated +*/ +#define simple_list_add_before(head, value) (\ + *(head) == NULL ? \ + simple_list_add_head((head)) \ + : \ + __simple_list_add_before((struct simple_list_elem**) (head), \ + calloc(1, sizeof **(head)), \ + (value), \ + (char*) &(*(head))->value - (char*) *(head)) ) + +/** +* @brief adds an preallocated list element before an existing one. +* The new entry is added before the existing element where +* old_entry->value > value +* If no such entry could be found, the new entry will be added at the +* end of the list * +* +* @param head pointer to the list +* @param value value to compare list entries +* has to be the same name as the element in the list entry +* structure that is be used for comparison +* @param node preallocated list element +* +* @return the new list entry (node) +*/ +#define simple_list_set_before(head, node, value) (\ + *(head) == NULL ? \ + __simple_list_add_before((head)) \ + : \ + __simple_list_add_before((struct simple_list_elem**) (head), \ + (node), \ + (value), \ + (char*) &(*(head))->value - (char*) *(head)) ) + +/** +* @brief searches for a list element by simple comparison of a struct value +* +* @param head pointer to the list +* @param value the member value of a list entry that is to be found +* has to be the same name as the value in the list element +* struct +* +* @return pointer the list entry if found, otherwise NULL +*/ +#define simple_list_find(head, value) (\ + (head) == NULL ? \ + NULL \ + : \ + __simple_list_find( (struct simple_list_elem*) (head), \ + (value), \ + (char*) &((head)->value) - (char*) (head), \ + (0)) ) + +/** +* @brief searches for a list element by comparing a buffer in the list +* element struct +* +* @param head pointer to the list +* @param value pointer to the buffer that is to be found in the list +* has to be the same name as the value in the list element +* struct +* +* @return pointer the list entry if found, otherwise NULL +*/ +#define simple_list_find_memcmp(head, value) (\ + (head) == NULL ? \ + NULL \ + : \ + __simple_list_find( (struct simple_list_elem*) (head), \ + (value), \ + (char*) &((head)->value) - (char*) (head), \ + sizeof(*(value))) ) + +/** +* @brief searches for a list element by applying a comparator function to +* each list entry +* +* @param head pointer to the list +* @param value input to the comparator function +* @param comperator a function that takes (value, node) and returns 0 if +* they match +* @return pointer the list entry if found, otherwise NULL +*/ +#define simple_list_find_cmp(head, value, comperator) (\ + (head) == NULL ? \ + NULL \ + : \ + __simple_list_find_cmp( (struct simple_list_elem*) (head), \ + (value), \ + (char*) &((head)->value) - (char*) (head), \ + (comperator)) ) +/** + * @brief removes an entry from the list and frees it's memory + * + * @param head pointer to the list + * @param node entry to be removed + * @returns a non-zero value if the element was found and removed + */ +#define simple_list_remove(head, node) \ + __simple_list_remove((struct simple_list_elem**) \ + (head), (struct simple_list_elem*) (node), 0) + +/** + * @brief removes an entry from the list, doesn't free it's memory but returns + * the element + * + * @param head pointer to the list + * @param node entry to be extracted + * @returns pointer to the element, NULL if it couldn't be found + */ +#define simple_list_extract(head, node) \ + __simple_list_remove((struct simple_list_elem**) \ + (head), (struct simple_list_elem*) (node), 1) + +/** + * @brief removes all entries from the list and frees their memory + * + * @param head pointer to the list + */ +#define simple_list_clear(head) \ + __simple_list_clear((struct simple_list_elem**) (head)) + +/** + * @brief starts a loop to iterate over all list entries. Read-only list + * access only. needs to be provided with a local loop variable + * + * @param head pointer to the list + * @param node to the current entry (loop variable) + */ +#define simple_list_for_each(head, node) \ + for ((node) = (head); (node); (node) = (node)->next) + +/** + * @brief starts a loop to iterate over all list elements with the + * possibility to remove elements + * to remove an element, use simple_list_for_each_remove + * needs to be provided with a local loop variable as well as two + * local auxiliary variables + * + * @param head pointer to the list + * @param node to the current entry (loop variable) + * @param prev internal variable, pointer to previous list entry - do not + * modify + * @param skipped internal variable, integer - do not modify + */ +#define simple_list_for_each_safe(head, node, prev, skipped) \ + for ((skipped) = 0, (prev) = NULL, (node) = (head); \ + (node); \ + (prev) = ((skipped) ? (prev) : (node)), \ + (node) = ((skipped) ? (node) : (node)->next), (skipped) = 0) + +/** + * @brief removes an element in a simple_list_for_each_safe context + * + * @param head pointer to the list + * @param node pointer to the current entry (loop variable) + * @param prev internal variable, provided by simple_list_for_each_safe + */ +#define simple_list_for_each_remove(head, node, prev) \ + do { \ + if (!(prev)) { \ + (skipped) = 1; \ + *(head) = (*(head))->next; \ + } else { \ + (prev)->next = (node)->next; \ + } \ + free(node); \ + (node) = (prev) ? (prev) : *(head); \ + } while (0) + +void *__simple_list_add_head(struct simple_list_elem **head, void *mem); +void *__simple_list_add_tail(struct simple_list_elem **head, void *mem); +void *__simple_list_add_before(struct simple_list_elem **head, void *mem, + int needle, size_t offset); +void *__simple_list_find(struct simple_list_elem *head, void *needle, + size_t offset, size_t size); +void *__simple_list_find_cmp(struct simple_list_elem *head, void *needle, + size_t offset, int compare(void *, void *)); +void *__simple_list_remove(struct simple_list_elem **head, struct simple_list_elem *node, int keep); +void __simple_list_clear(struct simple_list_elem **head); + +#ifdef __cplusplus +} +#endif + +#endif /* SIMPLE_LIST_H_ */ diff --git a/sys/slist/Makefile b/sys/slist/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/sys/slist/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/slist/slist.c b/sys/slist/slist.c new file mode 100644 index 000000000000..8c01eddd5ab8 --- /dev/null +++ b/sys/slist/slist.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2014 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** +* @file +* @author Benjamin Valentin +*/ + +#include +#include + +#include "slist.h" + +struct simple_list_elem { + struct simple_list_elem *next; +}; + +void *__simple_list_add_tail(struct simple_list_elem **head, void *mem) +{ + struct simple_list_elem *_head = *head; + + /* check out-of-memory condition */ + if (mem == NULL) { + return NULL; + } + + if (_head == NULL) { + *head = mem; + return *head; + } + + while (_head->next != NULL) { + _head = _head->next; + } + + _head = _head->next = mem; + return _head; +} + +void *__simple_list_add_head(struct simple_list_elem **head, void *mem) +{ + struct simple_list_elem *_head = *head; + + /* check out-of-memory condition */ + if (mem == NULL) { + return NULL; + } + + *head = mem; + (*head)->next = _head; + + return *head; +} + +void *__simple_list_add_before(struct simple_list_elem **head, void *mem, + int needle, size_t offset) +{ + struct simple_list_elem *_head = *head; + struct simple_list_elem *prev = 0; + + /* check out-of-memory condition */ + if (mem == NULL) { + return NULL; + } + + if (_head == NULL) { + *head = mem; + return *head; + } + + while (_head != NULL) { + int *buff = (void *) ((char *) _head + offset); + + if (*buff > needle) { + if (prev != NULL) { + prev->next = mem; + prev->next->next = _head; + return prev->next; + } + + prev = mem; + prev->next = _head; + *head = prev; + return prev; + } + + prev = _head; + _head = _head->next; + } + + _head = prev->next = mem; + return _head; +} + +void *__simple_list_find(struct simple_list_elem *head, void *needle, + size_t offset, size_t size) +{ + while (head != NULL) { + void **buff = (void *) ((char *) head + offset); + + if (size == 0 && *buff == needle) { + return head; + } + + if (size > 0 && memcmp(*buff, needle, size) == 0) { + return head; + } + + head = head->next; + } + + return 0; +} + +void *__simple_list_find_cmp(struct simple_list_elem *head, void *needle, + size_t offset, int compare(void *, void *)) +{ + while (head != NULL) { + void **buff = (void *) ((char *) head + offset); + + if (compare(*buff, needle) == 0) { + return head; + } + + head = head->next; + } + + return 0; +} + +void *__simple_list_remove(struct simple_list_elem **head, + struct simple_list_elem *node, int keep) +{ + struct simple_list_elem *_head = *head; + struct simple_list_elem *prev = 0; + + while (_head != NULL && _head != node) { + prev = _head; + _head = _head->next; + } + + /* not found */ + if (_head != node) { + return NULL; + } + + /* remove head */ + if (prev == NULL) { + *head = _head->next; + } + else { + prev->next = node->next; + } + + if (keep) { + return node; + } + + free(node); + return (void *) 1; +} + +void __simple_list_clear(struct simple_list_elem **head) +{ + struct simple_list_elem *tmp, *_head = *head; + + while (_head != NULL) { + tmp = _head; + _head = _head->next; + free(tmp); + } + + *head = NULL; +} diff --git a/tests/slist/Makefile b/tests/slist/Makefile new file mode 100644 index 000000000000..c531bcb32c6b --- /dev/null +++ b/tests/slist/Makefile @@ -0,0 +1,6 @@ +export APPLICATION = slist +include ../Makefile.tests_common + +USEMODULE += slist + +include $(RIOTBASE)/Makefile.include diff --git a/tests/slist/main.c b/tests/slist/main.c new file mode 100644 index 000000000000..da17d36c31ef --- /dev/null +++ b/tests/slist/main.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2014 Benjamin Valentin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief slist (simple list) test application + * + * @author Benjamin Valentin + * + * @} + */ + +#include +#include +#include +#include "slist.h" + +int fail = 0; +void cunit_named_check(int cond, const char *name, int line, + const char *format, ...) +{ + va_list ap; + + if (cond) { + return; + } + + fail++; + + va_start(ap, format); + + printf("\t%s (%d) fail: ", name, line); + vprintf(format, ap); + puts("\n"); + va_end(ap); +} +#define CHECK_TRUE(cond, format, args...) \ + cunit_named_check(cond, __func__, __LINE__, format, ##args); + +char foo[] = "Hello World!"; +char bar[] = "Hello CUnit!"; +char baz[] = "c-c-c-combobreaker"; + +char print_buffer[128]; + +struct test_list { + struct test_list *next; + char *buffer; + int value; +}; + +struct number_list { + struct number_list *next; + int value; +}; + +struct test_list *_get_by_buffer(struct test_list *head, char *buffer) +{ + return simple_list_find(head, buffer); +} + +struct test_list *_get_by_value(struct test_list *head, int value) +{ + return simple_list_find(head, value); +} + +struct test_list *_add_test_list(struct test_list **head, char *buffer, int value) +{ + struct test_list *node = simple_list_add_tail(head); + + node->buffer = buffer; + node->value = value; + + return node; +} + +char *_print_result(struct test_list *result) +{ + if (result) { + snprintf(print_buffer, sizeof print_buffer, "%d, %s\n", result->value, + result->buffer); + } + else { + snprintf(print_buffer, sizeof print_buffer, "Not found\n"); + } + + return print_buffer; +} + +int _is_equal(struct test_list *node, const int value, const char *buffer) +{ + return node != 0 && node->value == value && !strcmp(node->buffer, buffer); +} + +void test_simple_list_fill(struct test_list *_head) +{ + CHECK_TRUE(_is_equal(_get_by_buffer(_head, bar), 42, bar), "%s", + _print_result(_get_by_buffer(_head, bar))); + CHECK_TRUE(_is_equal(_get_by_value(_head, 23), 23, foo), "%s", + _print_result(_get_by_value(_head, 23))); +} + +void test_simple_list_remove(struct test_list **__head) +{ + struct test_list *_head; + simple_list_remove(__head, _get_by_buffer(*__head, foo)); + _head = *__head; + + CHECK_TRUE(_is_equal(_head, 42, bar), "%s", _print_result(_head)); + CHECK_TRUE(_is_equal(_head->next, 1337, baz), "%s", _print_result(_head->next)); +} + +void test_simple_list_find(struct test_list *_head) +{ + char buffer[sizeof bar]; + memcpy(buffer, bar, sizeof buffer); + + CHECK_TRUE(_is_equal(simple_list_find_memcmp(_head, buffer), 42, bar), "%s", + _print_result(simple_list_find_memcmp(_head, buffer))); + + CHECK_TRUE(_is_equal(simple_list_find_cmp(_head, buffer, + (int ( *)(void *, void *)) strcmp), 42, bar), "%s", + _print_result(simple_list_find_cmp(_head, buffer, + (int ( *)(void *, void *)) strcmp))); +} + +void _add_number_list(struct number_list **head, int value) +{ + struct number_list *node = simple_list_add_before(head, value); + node->value = value; +} + +void test_number_list(void) +{ + struct number_list *head = 0; + + _add_number_list(&head, 23); + _add_number_list(&head, 42); + _add_number_list(&head, 17); + _add_number_list(&head, 32); + _add_number_list(&head, 1); + + int prev = 0; + struct number_list *node; + simple_list_for_each(head, node) { + CHECK_TRUE(node->value >= prev, "%d < %d", node->value, prev); + prev = node->value; + } +} + +void test_for_each_remove(void) +{ + struct number_list *head = 0; + + int i = 0; + int max = 11; + + for (i = 1; i < max; ++i) { + if (i == 2) { + _add_number_list(&head, 3); + } + else { + _add_number_list(&head, i); + } + } + + char skipped; + struct number_list *node, *prev; + simple_list_for_each_safe(head, node, prev, skipped) { + if (node->value % 2) { + printf("removing %d\n", node->value); + simple_list_for_each_remove(&head, node, prev); + } + else { + printf("keeping %d\n", node->value); + } + } + + i = 0; + simple_list_for_each(head, node) { + CHECK_TRUE(node->value % 2 == 0, "%d", node->value); + ++i; + } + CHECK_TRUE(i == max / 2 - 1, "missed an entry"); + + simple_list_clear(&head); + + CHECK_TRUE(head == NULL, "list not cleared properly"); +} + +int main(void) +{ + struct test_list *_head = 0; + + _add_test_list(&_head, foo, 23); + _add_test_list(&_head, bar, 42); + _add_test_list(&_head, baz, 1337); + + struct test_list *node; + simple_list_for_each(_head, node) + printf("%s\n", _print_result(node)); + + test_simple_list_fill(_head); + test_simple_list_remove(&_head); + test_simple_list_find(_head); + + test_number_list(); + test_for_each_remove(); + + puts("-----------------"); + printf("End, %d errors\n", fail); + + return fail; +}