tlist: typesafe variant of list module.
authorRusty Russell <rusty@rustcorp.com.au>
Sat, 1 Jan 2011 06:44:05 +0000 (17:14 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Sat, 1 Jan 2011 06:44:05 +0000 (17:14 +1030)
I chose not to do the "macro creates set of routines" approach, as
we can be almost as safe with a struct containing a pointer to the member
type.

ccan/tlist/LICENSE [new symlink]
ccan/tlist/_info [new file with mode: 0644]
ccan/tlist/test/compile_fail-tlist_add.c [new file with mode: 0644]
ccan/tlist/test/compile_fail-tlist_add_tail.c [new file with mode: 0644]
ccan/tlist/test/compile_fail-tlist_del_from.c [new file with mode: 0644]
ccan/tlist/test/compile_fail-tlist_for_each.c [new file with mode: 0644]
ccan/tlist/test/compile_fail-tlist_for_each_safe.c [new file with mode: 0644]
ccan/tlist/test/compile_fail-tlist_tail.c [new file with mode: 0644]
ccan/tlist/test/compile_fail-tlist_top.c [new file with mode: 0644]
ccan/tlist/test/run.c [new file with mode: 0644]
ccan/tlist/tlist.h [new file with mode: 0644]

diff --git a/ccan/tlist/LICENSE b/ccan/tlist/LICENSE
new file mode 120000 (symlink)
index 0000000..7455044
--- /dev/null
@@ -0,0 +1 @@
+../../licenses/LGPL-3
\ No newline at end of file
diff --git a/ccan/tlist/_info b/ccan/tlist/_info
new file mode 100644 (file)
index 0000000..7586fdf
--- /dev/null
@@ -0,0 +1,73 @@
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+
+/**
+ * tlist - typesafe double linked list routines
+ *
+ * The list header contains routines for manipulating double linked lists;
+ * this extends it so you can create list head types which only accomodate
+ * a specific entry type.
+ *
+ * You use TLIST_TYPE() to define the specific struct tlist_<name>, then use
+ * the tlist_* variants of the various list_* operations.
+ *
+ * Example:
+ *     #include <err.h>
+ *     #include <stdio.h>
+ *     #include <stdlib.h>
+ *     #include <ccan/tlist/tlist.h>
+ *
+ *     // Defines struct tlist_children
+ *     TLIST_TYPE(children, struct child);
+ *     struct parent {
+ *             const char *name;
+ *             struct tlist_children children;
+ *             unsigned int num_children;
+ *     };
+ *
+ *     struct child {
+ *             const char *name;
+ *             struct list_node list;
+ *     };
+ *
+ *     int main(int argc, char *argv[])
+ *     {
+ *             struct parent p;
+ *             struct child *c;
+ *             unsigned int i;
+ *
+ *             if (argc < 2)
+ *                     errx(1, "Usage: %s parent children...", argv[0]);
+ *
+ *             p.name = argv[1];
+ *             tlist_init(&p.children);
+ *             for (i = 2; i < argc; i++) {
+ *                     c = malloc(sizeof(*c));
+ *                     c->name = argv[i];
+ *                     tlist_add(&p.children, c, list);
+ *                     p.num_children++;
+ *             }
+ *
+ *             printf("%s has %u children:", p.name, p.num_children);
+ *             tlist_for_each(&p.children, c, list)
+ *                     printf("%s ", c->name);
+ *             printf("\n");
+ *             return 0;
+ *     }
+ *
+ * License: LGPL
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ */
+int main(int argc, char *argv[])
+{
+       if (argc != 2)
+               return 1;
+
+       if (strcmp(argv[1], "depends") == 0) {
+               printf("ccan/list\n");
+               return 0;
+       }
+
+       return 1;
+}
diff --git a/ccan/tlist/test/compile_fail-tlist_add.c b/ccan/tlist/test/compile_fail-tlist_add.c
new file mode 100644 (file)
index 0000000..1b87bfd
--- /dev/null
@@ -0,0 +1,35 @@
+#include <ccan/tlist/tlist.h>
+
+TLIST_TYPE(children, struct child);
+TLIST_TYPE(cousins, struct cousin);
+
+struct child {
+       const char *name;
+       struct list_node list;
+};
+
+struct cousin {
+       const char *name;
+       struct list_node list;
+};
+
+int main(int argc, char *argv[])
+{
+       struct tlist_children children;
+       struct tlist_cousins cousins;
+       struct child child = { "child" };
+       struct cousin cousin = { "cousin" };
+
+       tlist_init(&children);
+       tlist_init(&cousins);
+       tlist_add(&children, &child, list);
+       tlist_add(&cousins, &cousin, list);
+       tlist_del_from(&cousins, &cousin, list);
+#ifdef FAIL
+#if !HAVE_FLEXIBLE_ARRAY_MEMBER
+#error Need flexible array members to check type
+#endif
+       tlist_add(&children, &cousin, list);
+#endif
+       return 0;
+}
diff --git a/ccan/tlist/test/compile_fail-tlist_add_tail.c b/ccan/tlist/test/compile_fail-tlist_add_tail.c
new file mode 100644 (file)
index 0000000..33dff3d
--- /dev/null
@@ -0,0 +1,35 @@
+#include <ccan/tlist/tlist.h>
+
+TLIST_TYPE(children, struct child);
+TLIST_TYPE(cousins, struct cousin);
+
+struct child {
+       const char *name;
+       struct list_node list;
+};
+
+struct cousin {
+       const char *name;
+       struct list_node list;
+};
+
+int main(int argc, char *argv[])
+{
+       struct tlist_children children;
+       struct tlist_cousins cousins;
+       struct child child = { "child" };
+       struct cousin cousin = { "cousin" };
+
+       tlist_init(&children);
+       tlist_init(&cousins);
+       tlist_add(&children, &child, list);
+       tlist_add(&cousins, &cousin, list);
+       tlist_del_from(&cousins, &cousin, list);
+#ifdef FAIL
+#if !HAVE_FLEXIBLE_ARRAY_MEMBER
+#error Need flexible array members to check type
+#endif
+       tlist_add_tail(&children, &cousin, list);
+#endif
+       return 0;
+}
diff --git a/ccan/tlist/test/compile_fail-tlist_del_from.c b/ccan/tlist/test/compile_fail-tlist_del_from.c
new file mode 100644 (file)
index 0000000..d06a72f
--- /dev/null
@@ -0,0 +1,34 @@
+#include <ccan/tlist/tlist.h>
+
+TLIST_TYPE(children, struct child);
+TLIST_TYPE(cousins, struct cousin);
+
+struct child {
+       const char *name;
+       struct list_node list;
+};
+
+struct cousin {
+       const char *name;
+       struct list_node list;
+};
+
+int main(int argc, char *argv[])
+{
+       struct tlist_children children;
+       struct tlist_cousins cousins;
+       struct child child = { "child" };
+       struct cousin cousin = { "cousin" };
+
+       tlist_init(&children);
+       tlist_init(&cousins);
+       tlist_add(&children, &child, list);
+       tlist_add(&cousins, &cousin, list);
+#ifdef FAIL
+#if !HAVE_FLEXIBLE_ARRAY_MEMBER
+#error Need flexible array members to check type
+#endif
+       tlist_del_from(&children, &cousin, list);
+#endif
+       return 0;
+}
diff --git a/ccan/tlist/test/compile_fail-tlist_for_each.c b/ccan/tlist/test/compile_fail-tlist_for_each.c
new file mode 100644 (file)
index 0000000..e2a267b
--- /dev/null
@@ -0,0 +1,33 @@
+#include <ccan/tlist/tlist.h>
+
+TLIST_TYPE(children, struct child);
+
+struct child {
+       const char *name;
+       struct list_node list;
+};
+
+struct cousin {
+       const char *name;
+       struct list_node list;
+};
+
+int main(int argc, char *argv[])
+{
+       struct tlist_children children;
+       struct child child = { "child" };
+#ifdef FAIL
+#if !HAVE_FLEXIBLE_ARRAY_MEMBER
+#error Need flexible array members to check type
+#endif
+       struct cousin *c;
+#else
+       struct child *c;
+#endif
+
+       tlist_init(&children);
+       tlist_add(&children, &child, list);
+
+       tlist_for_each(&children, c, list);
+       return 0;
+}
diff --git a/ccan/tlist/test/compile_fail-tlist_for_each_safe.c b/ccan/tlist/test/compile_fail-tlist_for_each_safe.c
new file mode 100644 (file)
index 0000000..651c6ce
--- /dev/null
@@ -0,0 +1,33 @@
+#include <ccan/tlist/tlist.h>
+
+TLIST_TYPE(children, struct child);
+
+struct child {
+       const char *name;
+       struct list_node list;
+};
+
+struct cousin {
+       const char *name;
+       struct list_node list;
+};
+
+int main(int argc, char *argv[])
+{
+       struct tlist_children children;
+       struct child child = { "child" };
+#ifdef FAIL
+#if !HAVE_FLEXIBLE_ARRAY_MEMBER
+#error Need flexible array members to check type
+#endif
+       struct cousin *c, *n;
+#else
+       struct child *c, *n;
+#endif
+
+       tlist_init(&children);
+       tlist_add(&children, &child, list);
+
+       tlist_for_each_safe(&children, c, n, list);
+       return 0;
+}
diff --git a/ccan/tlist/test/compile_fail-tlist_tail.c b/ccan/tlist/test/compile_fail-tlist_tail.c
new file mode 100644 (file)
index 0000000..ab43ae9
--- /dev/null
@@ -0,0 +1,35 @@
+#include <ccan/tlist/tlist.h>
+
+TLIST_TYPE(children, struct child);
+
+struct child {
+       const char *name;
+       struct list_node list;
+};
+
+struct cousin {
+       const char *name;
+       struct list_node list;
+};
+
+int main(int argc, char *argv[])
+{
+       struct tlist_children children;
+       struct child child = { "child" };
+       void *c;
+
+       tlist_init(&children);
+       tlist_add(&children, &child, list);
+
+       c = tlist_tail(&children,
+#ifdef FAIL
+#if !HAVE_FLEXIBLE_ARRAY_MEMBER
+#error Need flexible array members to check type
+#endif
+                     struct cousin,
+#else
+                     struct child,
+#endif
+                     list);
+       return 0;
+}
diff --git a/ccan/tlist/test/compile_fail-tlist_top.c b/ccan/tlist/test/compile_fail-tlist_top.c
new file mode 100644 (file)
index 0000000..622a578
--- /dev/null
@@ -0,0 +1,35 @@
+#include <ccan/tlist/tlist.h>
+
+TLIST_TYPE(children, struct child);
+
+struct child {
+       const char *name;
+       struct list_node list;
+};
+
+struct cousin {
+       const char *name;
+       struct list_node list;
+};
+
+int main(int argc, char *argv[])
+{
+       struct tlist_children children;
+       struct child child = { "child" };
+       void *c;
+
+       tlist_init(&children);
+       tlist_add(&children, &child, list);
+
+       c = tlist_top(&children,
+#ifdef FAIL
+#if !HAVE_FLEXIBLE_ARRAY_MEMBER
+#error Need flexible array members to check type
+#endif
+                     struct cousin,
+#else
+                     struct child,
+#endif
+                     list);
+       return 0;
+}
diff --git a/ccan/tlist/test/run.c b/ccan/tlist/test/run.c
new file mode 100644 (file)
index 0000000..32bbdd8
--- /dev/null
@@ -0,0 +1,128 @@
+#define CCAN_LIST_DEBUG 1
+#include <ccan/tlist/tlist.h>
+#include <ccan/tap/tap.h>
+
+TLIST_TYPE(children, struct child);
+
+struct parent {
+       const char *name;
+       struct tlist_children children;
+       unsigned int num_children;
+};
+
+struct child {
+       const char *name;
+       struct list_node list;
+};
+
+int main(int argc, char *argv[])
+{
+       struct parent parent;
+       struct child c1, c2, c3, *c, *n;
+       unsigned int i;
+       struct tlist_children tlist = TLIST_INIT(tlist);
+
+       plan_tests(44);
+       /* Test TLIST_INIT, and tlist_empty */
+       ok1(tlist_empty(&tlist));
+       ok1(tlist_check(&tlist, NULL));
+
+       parent.num_children = 0;
+       tlist_init(&parent.children);
+       /* Test tlist_init */
+       ok1(tlist_empty(&parent.children));
+       ok1(tlist_check(&parent.children, NULL));
+
+       c2.name = "c2";
+       tlist_add(&parent.children, &c2, list);
+       /* Test tlist_add and !tlist_empty. */
+       ok1(!tlist_empty(&parent.children));
+       ok1(c2.list.next == &parent.children.raw.n);
+       ok1(c2.list.prev == &parent.children.raw.n);
+       ok1(parent.children.raw.n.next == &c2.list);
+       ok1(parent.children.raw.n.prev == &c2.list);
+       /* Test tlist_check */
+       ok1(tlist_check(&parent.children, NULL));
+
+       c1.name = "c1";
+       tlist_add(&parent.children, &c1, list);
+       /* Test list_add and !list_empty. */
+       ok1(!tlist_empty(&parent.children));
+       ok1(c2.list.next == &parent.children.raw.n);
+       ok1(c2.list.prev == &c1.list);
+       ok1(parent.children.raw.n.next == &c1.list);
+       ok1(parent.children.raw.n.prev == &c2.list);
+       ok1(c1.list.next == &c2.list);
+       ok1(c1.list.prev == &parent.children.raw.n);
+       /* Test tlist_check */
+       ok1(tlist_check(&parent.children, NULL));
+
+       c3.name = "c3";
+       tlist_add_tail(&parent.children, &c3, list);
+       /* Test list_add_tail and !list_empty. */
+       ok1(!tlist_empty(&parent.children));
+       ok1(parent.children.raw.n.next == &c1.list);
+       ok1(parent.children.raw.n.prev == &c3.list);
+       ok1(c1.list.next == &c2.list);
+       ok1(c1.list.prev == &parent.children.raw.n);
+       ok1(c2.list.next == &c3.list);
+       ok1(c2.list.prev == &c1.list);
+       ok1(c3.list.next == &parent.children.raw.n);
+       ok1(c3.list.prev == &c2.list);
+       /* Test tlist_check */
+       ok1(tlist_check(&parent.children, NULL));
+
+       /* Test tlist_top */
+       ok1(tlist_top(&parent.children, struct child, list) == &c1);
+
+       /* Test list_tail */
+       ok1(tlist_tail(&parent.children, struct child, list) == &c3);
+
+       /* Test tlist_for_each. */
+       i = 0;
+       tlist_for_each(&parent.children, c, list) {
+               switch (i++) {
+               case 0:
+                       ok1(c == &c1);
+                       break;
+               case 1:
+                       ok1(c == &c2);
+                       break;
+               case 2:
+                       ok1(c == &c3);
+                       break;
+               }
+               if (i > 2)
+                       break;
+       }
+       ok1(i == 3);
+
+       /* Test tlist_for_each_safe, tlist_del and tlist_del_from. */
+       i = 0;
+       tlist_for_each_safe(&parent.children, c, n, list) {
+               switch (i++) {
+               case 0:
+                       ok1(c == &c1);  
+                       tlist_del(c, list);
+                       break;
+               case 1:
+                       ok1(c == &c2);
+                       tlist_del_from(&parent.children, c, list);
+                       break;
+               case 2:
+                       ok1(c == &c3);
+                       tlist_del_from(&parent.children, c, list);
+                       break;
+               }
+               ok1(tlist_check(&parent.children, NULL));
+               if (i > 2)
+                       break;
+       }
+       ok1(i == 3);
+       ok1(tlist_empty(&parent.children));
+
+       /* Test list_top/list_tail on empty list. */
+       ok1(tlist_top(&parent.children, struct child, list) == NULL);
+       ok1(tlist_tail(&parent.children, struct child, list) == NULL);
+       return exit_status();
+}
diff --git a/ccan/tlist/tlist.h b/ccan/tlist/tlist.h
new file mode 100644 (file)
index 0000000..82471d6
--- /dev/null
@@ -0,0 +1,252 @@
+#ifndef CCAN_TLIST_H
+#define CCAN_TLIST_H
+#include <ccan/list/list.h>
+
+#if HAVE_FLEXIBLE_ARRAY_MEMBER
+/**
+ * TLIST_TYPE - declare a typed list type (struct tlist)
+ * @suffix: the name to use (struct tlist_@suffix)
+ * @type: the type the list will contain (void for any type)
+ *
+ * This declares a structure "struct tlist_@suffix" to use for
+ * lists containing this type.  The actual list can be accessed using
+ * ".raw" or tlist_raw().
+ *
+ * Example:
+ *     // Defines struct tlist_children
+ *     TLIST_TYPE(children, struct child);
+ *     struct parent {
+ *             const char *name;
+ *             struct tlist_children children;
+ *             unsigned int num_children;
+ *     };
+ *
+ *     struct child {
+ *             const char *name;
+ *             struct list_node list;
+ *     };
+ */
+#define TLIST_TYPE(suffix, type)                                       \
+       struct tlist_##suffix {                                         \
+               struct list_head raw;                                   \
+               const type *tcheck[];                                   \
+       }
+
+/**
+ * tlist_raw - access the raw list inside a typed list head.
+ * @h: the head of the typed list (struct tlist_@suffix)
+ * @test_var: a pointer to the expected element type.
+ *
+ * This elaborate macro usually causes the compiler to emit a warning
+ * if the variable is of an unexpected type.  It is used internally
+ * where we need to access the raw underlying list.
+ */
+#define tlist_raw(h, test_var) \
+       (sizeof((h)->tcheck[0] == (test_var)) ? &(h)->raw : &(h)->raw)
+#else
+#define TLIST_TYPE(suffix, type)                                       \
+       struct tlist_##suffix {                                         \
+               struct list_head raw;                                   \
+       }
+#define tlist_raw(h, test_var) (&(h)->raw)
+#endif
+
+/**
+ * TLIST_INIT - initalizer for an empty tlist
+ * @name: the name of the list.
+ *
+ * Explicit initializer for an empty list.
+ *
+ * See also:
+ *     tlist_init()
+ *
+ * Example:
+ *     static struct tlist_children my_list = TLIST_INIT(my_list);
+ */
+#define TLIST_INIT(name) { LIST_HEAD_INIT(name.raw) }
+
+/**
+ * tlist_check - check head of a list for consistency
+ * @h: the tlist_head
+ * @abortstr: the location to print on aborting, or NULL.
+ *
+ * Because list_nodes have redundant information, consistency checking between
+ * the back and forward links can be done.  This is useful as a debugging check.
+ * If @abortstr is non-NULL, that will be printed in a diagnostic if the list
+ * is inconsistent, and the function will abort.
+ *
+ * Returns non-NULL if the list is consistent, NULL otherwise (it
+ * can never return NULL if @abortstr is set).
+ *
+ * See also: list_check()
+ *
+ * Example:
+ *     static void dump_parent(struct parent *p)
+ *     {
+ *             struct child *c;
+ *
+ *             printf("%s (%u children):\n", p->name, p->num_children);
+ *             tlist_check(&p->children, "bad child list");
+ *             tlist_for_each(&p->children, c, list)
+ *                     printf(" -> %s\n", c->name);
+ *     }
+ */
+#define tlist_check(h, abortstr) \
+       list_check(&(h)->raw, (abortstr))
+
+/**
+ * tlist_init - initialize a tlist
+ * @h: the tlist to set to the empty list
+ *
+ * Example:
+ *     ...
+ *     struct parent *parent = malloc(sizeof(*parent));
+ *
+ *     tlist_init(&parent->children);
+ *     parent->num_children = 0;
+ */
+#define tlist_init(h) list_head_init(&(h)->raw)
+
+/**
+ * tlist_add - add an entry at the start of a linked list.
+ * @h: the tlist to add the node to
+ * @n: the entry to add to the list.
+ * @member: the member of n to add to the list.
+ *
+ * The entry's list_node does not need to be initialized; it will be
+ * overwritten.
+ * Example:
+ *     struct child *child = malloc(sizeof(*child));
+ *
+ *     child->name = "marvin";
+ *     tlist_add(&parent->children, child, list);
+ *     parent->num_children++;
+ */
+#define tlist_add(h, n, member) list_add(tlist_raw((h), (n)), &(n)->member)
+
+/**
+ * tlist_add_tail - add an entry at the end of a linked list.
+ * @h: the tlist to add the node to
+ * @n: the entry to add to the list.
+ * @member: the member of n to add to the list.
+ *
+ * The list_node does not need to be initialized; it will be overwritten.
+ * Example:
+ *     tlist_add_tail(&parent->children, child, list);
+ *     parent->num_children++;
+ */
+#define tlist_add_tail(h, n, member) \
+       list_add_tail(tlist_raw((h), (n)), &(n)->member)
+
+/**
+ * tlist_del_from - delete an entry from a linked list.
+ * @h: the tlist @n is in
+ * @n: the entry to delete
+ * @member: the member of n to remove from the list.
+ *
+ * This explicitly indicates which list a node is expected to be in,
+ * which is better documentation and can catch more bugs.
+ *
+ * Note that this leaves @n->@member in an undefined state; it
+ * can be added to another list, but not deleted again.
+ *
+ * See also: tlist_del()
+ *
+ * Example:
+ *     tlist_del_from(&parent->children, child, list);
+ *     parent->num_children--;
+ */
+#define tlist_del_from(h, n, member) \
+       list_del_from(tlist_raw((h), (n)), &(n)->member)
+
+/**
+ * tlist_del - delete an entry from an unknown linked list.
+ * @n: the entry to delete from the list.
+ * @member: the member of @n which is in the list.
+ *
+ * Example:
+ *     tlist_del(child, list);
+ *     parent->num_children--;
+ */
+#define tlist_del(n, member) \
+       list_del(&(n)->member)
+
+/**
+ * tlist_empty - is a list empty?
+ * @h: the tlist
+ *
+ * If the list is empty, returns true.
+ *
+ * Example:
+ *     assert(tlist_empty(&parent->children) == (parent->num_children == 0));
+ */
+#define tlist_empty(h) list_empty(&(h)->raw)
+
+/**
+ * tlist_top - get the first entry in a list
+ * @h: the tlist
+ * @type: the type of the entry
+ * @member: the list_node member of the type
+ *
+ * If the list is empty, returns NULL.
+ *
+ * Example:
+ *     struct child *first;
+ *     first = tlist_top(&parent->children, struct child, list);
+ */
+#define tlist_top(h, type, member) \
+       list_top(tlist_raw((h), (type *)NULL), type, member)
+
+/**
+ * tlist_tail - get the last entry in a list
+ * @h: the tlist
+ * @type: the type of the entry
+ * @member: the list_node member of the type
+ *
+ * If the list is empty, returns NULL.
+ *
+ * Example:
+ *     struct child *last;
+ *     last = tlist_tail(&parent->children, struct child, list);
+ */
+#define tlist_tail(h, type, member) \
+       list_tail(tlist_raw((h), (type *)NULL), type, member)
+
+/**
+ * tlist_for_each - iterate through a list.
+ * @h: the tlist
+ * @i: an iterator of suitable type for this list.
+ * @member: the list_node member of @i
+ *
+ * This is a convenient wrapper to iterate @i over the entire list.  It's
+ * a for loop, so you can break and continue as normal.
+ *
+ * Example:
+ *     tlist_for_each(&parent->children, child, list)
+ *             printf("Name: %s\n", child->name);
+ */
+#define tlist_for_each(h, i, member)                                   \
+       list_for_each(tlist_raw((h), (i)), (i), member)
+
+/**
+ * tlist_for_each_safe - iterate through a list, maybe during deletion
+ * @h: the tlist
+ * @i: an iterator of suitable type for this list.
+ * @nxt: another iterator to store the next entry.
+ * @member: the list_node member of the structure
+ *
+ * This is a convenient wrapper to iterate @i over the entire list.  It's
+ * a for loop, so you can break and continue as normal.  The extra variable
+ * @nxt is used to hold the next element, so you can delete @i from the list.
+ *
+ * Example:
+ *     struct child *next;
+ *     tlist_for_each_safe(&parent->children, child, next, list) {
+ *             tlist_del(child, list);
+ *             parent->num_children--;
+ *     }
+ */
+#define tlist_for_each_safe(h, i, nxt, member)                         \
+       list_for_each_safe(tlist_raw((h), (i)), (i), (nxt), member)
+
+#endif /* CCAN_TLIST_H */