Browse Source

cdump: new module.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 11 years ago
parent
commit
158691ae36

+ 1 - 0
Makefile-ccan

@@ -41,6 +41,7 @@ MODS_WITH_SRC := antithread \
 	btree \
 	bytestring \
 	ccan_tokenizer \
+	cdump \
 	charset \
 	ciniparser \
 	crc \

+ 1 - 0
ccan/cdump/LICENSE

@@ -0,0 +1 @@
+../../licenses/BSD-MIT

+ 94 - 0
ccan/cdump/_info

@@ -0,0 +1,94 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * cdump - routines to parse simple C structures.
+ *
+ * This code is designed to produce data structures summarizing C code.
+ * It only operates on simple, well-formed C code (eg. specific headers
+ * which you want to autogenerate from), but it should be fairly easy to
+ * enhance if desired.
+ *
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ * License: BSD-MIT
+ *
+ * Example:
+ * // Creates a simple print function for a structure.
+ * #include <ccan/cdump/cdump.h>
+ * #include <ccan/tal/grab_file/grab_file.h>
+ * #include <ccan/err/err.h>
+ *
+ * static void print_as(const char *fmt, const char *member_name)
+ * {
+ * 	printf("\tprintf(\"%%s:%s\\n\", \"%s\", s->%s);\n",
+ * 	       fmt, member_name, member_name);
+ * }
+ *
+ * int main(int argc, char *argv[])
+ * {
+ * 	char *code, *problems;
+ * 	struct cdump_definitions *defs;
+ * 	int i, j;
+ *
+ * 	// Read code from stdin.
+ * 	code = grab_file(NULL, NULL);
+ *
+ * 	defs = cdump_extract(NULL, code, &problems);
+ * 	if (!defs)
+ * 		errx(1, "Parsing stdin: %s", problems);
+ *
+ * 	for (i = 1; i < argc; i++) {
+ * 		struct cdump_type *t = strmap_get(&defs->structs, argv[i]);
+ * 		if (!t)
+ * 			errx(1, "Could not find struct %s", argv[i]);
+ *
+ * 		printf("void print_struct_%s(const struct %s *s)\n"
+ * 			"{\n", argv[i], argv[i]);
+ * 		for (j = 0; j < tal_count(t->u.members); j++) {
+ * 			const struct cdump_member *m = t->u.members + j;
+ * 			switch (m->type->kind) {
+ * 			case CDUMP_STRUCT:
+ * 			case CDUMP_UNION:
+ * 			case CDUMP_ARRAY:
+ * 				// Too hard for this simple example.
+ * 				printf("\tprintf(\"%%s:???\\n\", \"%s\");\n",
+ * 				       m->name);
+ * 				break;
+ * 			case CDUMP_ENUM:
+ * 				print_as("%i", m->name);
+ * 				break;
+ * 			case CDUMP_POINTER:
+ * 				print_as("%p", m->name);
+ * 				break;
+ * 			case CDUMP_UNKNOWN:
+ * 				if (!strcmp(m->type->name, "int"))
+ * 					print_as("%i", m->name);
+ * 				else if (!strcmp(m->type->name, "long int"))
+ * 					print_as("%li", m->name);
+ * 				else if (!strcmp(m->type->name, "unsigned int"))
+ * 					print_as("%u", m->name);
+ * 				// etc...
+ * 				break;
+ * 			}
+ * 		}
+ * 		printf("}\n");
+ * 	}
+ * 	return 0;
+ * }
+ */
+int main(int argc, char *argv[])
+{
+	/* Expect exactly one argument */
+	if (argc != 2)
+		return 1;
+
+	if (strcmp(argv[1], "depends") == 0) {
+		printf("ccan/tal\n");
+		printf("ccan/tal/str\n");
+		printf("ccan/strmap\n");
+		return 0;
+	}
+
+	return 1;
+}

+ 543 - 0
ccan/cdump/cdump.c

@@ -0,0 +1,543 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#include "cdump.h"
+#include <ccan/tal/str/str.h>
+#include <assert.h>
+
+struct token {
+	const char *p;
+	size_t len;
+};
+
+static void add_token(struct token **toks, const char *p, size_t len)
+{
+	size_t n = tal_count(*toks);
+	tal_resize(toks, n+1);
+	(*toks)[n].p = p;
+	(*toks)[n].len = len;
+}
+
+/* Simplified tokenizer: comments and preproc directives removed,
+   identifiers are a token, others are single char tokens. */
+static struct token *tokenize(const void *ctx, const char *code)
+{
+	unsigned int i, len, tok_start = -1;
+	bool start_of_line = true;
+	struct token *toks = tal_arr(ctx, struct token, 0);
+
+	for (i = 0; code[i]; i += len) {
+		if (code[i] == '#' && start_of_line) {
+			/* Preprocessor line. */
+			len = strcspn(code+i, "\n");
+		} else if (code[i] == '/' && code[i+1] == '/') {
+			/* One line comment. */
+			len = strcspn(code+i, "\n");
+			if (tok_start != -1U) {
+				add_token(&toks, code+tok_start, i - tok_start);
+				tok_start = -1U;
+			}
+		} else if (code[i] == '/' && code[i+1] == '*') {
+			/* Multi-line comment. */
+			const char *end = strstr(code+i+2, "*/");
+			len = (end + 2) - (code + i);
+			if (!end)
+				len = strlen(code + i);
+			if (tok_start != -1U) {
+				add_token(&toks, code+tok_start, i - tok_start);
+				tok_start = -1U;
+			}
+		} else if (cisalnum(code[i]) || code[i] == '_') {
+			/* Identifier or part thereof */
+			if (tok_start == -1U)
+				tok_start = i;
+			len = 1;
+		} else if (!cisspace(code[i])) {
+			/* Punctuation: treat as single char token. */
+			if (tok_start != -1U) {
+				add_token(&toks, code+tok_start, i - tok_start);
+				tok_start = -1U;
+			}
+			add_token(&toks, code+i, 1);
+			len = 1;
+		} else {
+			/* Whitespace. */
+			if (tok_start != -1U) {
+				add_token(&toks, code+tok_start, i - tok_start);
+				tok_start = -1U;
+			}
+			len = 1;
+		}
+		if (code[i] == '\n')
+			start_of_line = true;
+		else if (!cisspace(code[i]))
+			start_of_line = false;
+	}
+
+	/* Add terminating NULL. */
+	tal_resizez(&toks, tal_count(toks) + 1);
+	return toks;
+}
+
+struct parse_state {
+	const char *code;
+	const struct token *toks;
+	struct cdump_definitions *defs;
+	char *complaints;
+};
+
+static bool tok_is(const struct token **toks, const char *target)
+{
+	return (*toks)->p && (*toks)->len == strlen(target)
+		&& memcmp((*toks)->p, target, (*toks)->len) == 0;
+}
+
+static const struct token *tok_peek(const struct token **toks)
+{
+	if (toks[0]->p)
+		return toks[0];
+	return NULL;
+}
+
+static const struct token *tok_take(const struct token **toks)
+{
+	if (!toks[0]->p)
+		return NULL;
+
+	return (*toks)++;
+}
+
+static const struct token *tok_take_if(const struct token **toks,
+				       const char *target)
+{
+	if (tok_is(toks, target))
+		return tok_take(toks);
+	return NULL;
+}
+
+static const char *tok_take_ident(const tal_t *ctx, const struct token **toks)
+{
+	const struct token *t = tok_peek(toks);
+
+	if (!t)
+		return NULL;
+
+	if (strspn(t->p, "_0123456789"
+		   "abcdefghijklmnopqrstuvwxyz"
+		   "ABCDEFGHIJKLMNOPQRSTUVWXYZ") < t->len)
+		return NULL;
+
+	t = tok_take(toks);
+	return tal_strndup(ctx, t->p, t->len);
+}
+
+static char *string_of_toks(const tal_t *ctx,
+			    const struct token *first,
+			    const struct token *until)
+{
+	const struct token *end = until - 1;
+	return tal_strndup(ctx, first->p, end->p - first->p + end->len);
+}
+
+static char *tok_take_until(const tal_t *ctx,
+			    const struct token **toks,
+			    const char *delims)
+{
+	const struct token *t, *start;
+
+	start = tok_peek(toks);
+	while ((t = tok_peek(toks)) != NULL) {
+		/* If this contains a delimiter, copy up to prev token. */
+		if (strcspn(t->p, delims) < t->len)
+			return string_of_toks(ctx, start, t);
+		tok_take(toks);
+	};
+
+	/* EOF without finding delimiter */
+	return NULL;
+}
+
+static bool type_defined(const struct cdump_type *t)
+{
+	switch (t->kind) {
+	case CDUMP_STRUCT:
+	case CDUMP_UNION:
+		return (t->u.members != NULL);
+	case CDUMP_ENUM:
+		return (t->u.enum_vals != NULL);
+
+	/* These shouldn't happen; we don't try to define them. */
+	case CDUMP_UNKNOWN:
+	case CDUMP_ARRAY:
+	case CDUMP_POINTER:
+		break;
+	}
+	abort();
+}
+
+/* May allocate a new type if not already found (steals @name) */
+static struct cdump_type *get_type(struct cdump_definitions *defs,
+				   enum cdump_type_kind kind,
+				   const char *name)
+{
+	struct cdump_map *m;
+	struct cdump_type *t;
+
+	switch (kind) {
+	case CDUMP_STRUCT:
+		m = &defs->structs;
+		break;
+	case CDUMP_UNION:
+		m = &defs->unions;
+		break;
+	case CDUMP_ENUM:
+		m = &defs->enums;
+		break;
+	case CDUMP_UNKNOWN:
+	case CDUMP_ARRAY:
+	case CDUMP_POINTER:
+		m = NULL;
+	}
+
+	/* Do we already have it? */
+	if (m) {
+		t = strmap_get(m, name);
+		if (t)
+			return t;
+	}
+
+	t = tal(defs, struct cdump_type);
+	t->kind = kind;
+	t->name = name ? tal_steal(t, name) : NULL;
+	/* These are actually the same, but be thorough */
+	t->u.members = NULL;
+	t->u.enum_vals = NULL;
+	if (m)
+		strmap_add(m, t->name, t);
+
+	return t;
+}
+
+static void complain(struct parse_state *ps, const char *complaint)
+{
+	unsigned int linenum;
+	const char *p = ps->code;
+
+	for (linenum = 1; p < ps->toks[0].p; linenum++) {
+		p = strchr(p+1, '\n');
+		if (!p)
+			break;
+	}
+
+	tal_append_fmt(&ps->complaints,
+		       "Line %u: '%.*s': %s\n",
+		       linenum, (int)ps->toks[0].len,
+		       ps->toks[0].p, complaint);
+}
+
+static void tok_take_unknown_statement(struct parse_state *ps)
+{
+	complain(ps, "Ignoring unknown statement until next semicolon");
+	tal_free(tok_take_until(NULL, &ps->toks, ";"));
+	tok_take_if(&ps->toks, ";");
+}
+
+/* [ ... */
+static bool tok_take_array(struct parse_state *ps, struct cdump_type **type)
+{
+	/* This will be some arbitrary expression! */
+	struct cdump_type *arr = get_type(ps->defs, CDUMP_ARRAY, NULL);
+
+	arr->u.arr.size = tok_take_until(arr, &ps->toks, "]");
+	if (!arr->u.arr.size) {
+		complain(ps, "Could not find closing array size ]");
+		return false;
+	}
+
+	arr->u.arr.type = *type;
+	*type = arr;
+
+	/* Swallow ] */
+	tok_take(&ps->toks);
+	return true;
+}
+
+static struct cdump_type *ptr_of(struct parse_state *ps,
+				 const struct cdump_type *ptr_to)
+{
+	struct cdump_type *ptr = get_type(ps->defs, CDUMP_POINTER, NULL);
+	ptr->u.ptr = ptr_to;
+	return ptr;
+}
+
+static bool tok_take_type(struct parse_state *ps, struct cdump_type **type)
+{
+	const char *name;
+	const struct token *types;
+	enum cdump_type_kind kind;
+
+	/* Ignoring weird typedefs, only these can be combined. */
+	types = ps->toks;
+	while (tok_take_if(&ps->toks, "int")
+	       || tok_take_if(&ps->toks, "long")
+	       || tok_take_if(&ps->toks, "short")
+	       || tok_take_if(&ps->toks, "double")
+	       || tok_take_if(&ps->toks, "float")
+	       || tok_take_if(&ps->toks, "char")
+	       || tok_take_if(&ps->toks, "signed")
+	       || tok_take_if(&ps->toks, "unsigned"));
+
+	/* Did we get some? */
+	if (ps->toks != types) {
+		name = string_of_toks(NULL, types, tok_peek(&ps->toks));
+		kind = CDUMP_UNKNOWN;
+	} else {
+		/* Try normal types (or simple typedefs, etc). */
+		if (tok_take_if(&ps->toks, "struct")) {
+			kind = CDUMP_STRUCT;
+		} else if (tok_take_if(&ps->toks, "union")) {
+			kind = CDUMP_UNION;
+		} else if (tok_take_if(&ps->toks, "enum")) {
+			kind = CDUMP_ENUM;
+		} else
+			kind = CDUMP_UNKNOWN;
+
+		name = tok_take_ident(ps->defs, &ps->toks);
+		if (!name) {
+			complain(ps, "Invalid typename");
+			return false;
+		}
+	}
+
+	*type = get_type(ps->defs, kind, name);
+	return true;
+}
+
+/* struct|union ... */
+static bool tok_take_conglom(struct parse_state *ps,
+			     enum cdump_type_kind conglom_kind)
+{
+	struct cdump_type *e;
+	const char *name;
+	size_t n;
+
+	assert(conglom_kind == CDUMP_STRUCT || conglom_kind == CDUMP_UNION);
+
+	name = tok_take_ident(ps->defs, &ps->toks);
+	if (!name) {
+		complain(ps, "Invalid struct/union name");
+		return false;
+	}
+
+	e = get_type(ps->defs, conglom_kind, name);
+	if (type_defined(e)) {
+		complain(ps, "Type already defined");
+		return false;
+	}
+
+	if (!tok_take_if(&ps->toks, "{")) {
+		complain(ps, "Expected { for struct/union");
+		return false;
+	}
+
+	e->u.members = tal_arr(e, struct cdump_member, n = 0);
+	while (!tok_is(&ps->toks, "}")) {
+		struct cdump_type *basetype;
+		const struct token *quals;
+		unsigned int num_quals = 0;
+
+		/* Anything can have these prepended. */
+		quals = ps->toks;
+		while (tok_take_if(&ps->toks, "const")
+		       || tok_take_if(&ps->toks, "volatile"))
+			num_quals++;
+
+		/* eg. "struct foo" or "varint_t" */
+		if (!tok_take_type(ps, &basetype)) {
+			complain(ps, "Expected typename inside struct/union");
+			return false;
+		}
+
+		do {
+			struct cdump_member *m;
+
+			tal_resize(&e->u.members, n+1);
+			m = &e->u.members[n++];
+			m->type = basetype;
+			if (num_quals) {
+				m->qualifiers
+					= string_of_toks(e, quals,
+							 quals + num_quals);
+			} else
+				m->qualifiers = NULL;
+
+			/* May have multiple asterisks. */
+			while (tok_take_if(&ps->toks, "*"))
+				m->type = ptr_of(ps, m->type);
+
+			m->name = tok_take_ident(e, &ps->toks);
+			if (!m->name) {
+				complain(ps, "Expected name for member");
+				return false;
+			}
+
+			/* May be an array. */
+			while (tok_take_if(&ps->toks, "[")) {
+				if (!tok_take_array(ps, &m->type))
+					return false;
+			}
+		} while (tok_take_if(&ps->toks, ","));
+
+		if (!tok_take_if(&ps->toks, ";")) {
+			complain(ps, "Expected ; at end of member");
+			return false;
+		}
+	}
+
+	if (tok_take_if(&ps->toks, "}") && tok_take_if(&ps->toks, ";"))
+		return true;
+	complain(ps, "Expected }; at end of struct/union");
+	return false;
+}
+
+/* enum ... */
+static bool tok_take_enum(struct parse_state *ps)
+{
+	size_t n = 0;
+	struct cdump_type *e;
+	const char *name;
+
+	name = tok_take_ident(ps->defs, &ps->toks);
+	if (!name) {
+		complain(ps, "Expected enum name");
+		return false;
+	}
+
+	e = get_type(ps->defs, CDUMP_ENUM, name);
+
+	/* Duplicate name? */
+	if (type_defined(e)) {
+		complain(ps, "enum already defined");
+		return false;
+	}
+
+	if (!tok_take_if(&ps->toks, "{")) {
+		complain(ps, "Expected { after enum name");
+		return false;
+	}
+
+	e->u.enum_vals = tal_arr(e, struct cdump_enum_val, n);
+	do {
+		struct cdump_enum_val *v;
+
+		tal_resize(&e->u.enum_vals, n+1);
+		v = &e->u.enum_vals[n++];
+
+		v->name = tok_take_ident(e, &ps->toks);
+		if (!v->name) {
+			complain(ps, "Expected enum value name");
+			return false;
+		}
+		if (tok_take_if(&ps->toks, "=")) {
+			v->value = tok_take_until(e, &ps->toks, ",}");
+			if (!v->value) {
+				complain(ps, "Expected , or } to end value");
+				return false;
+			}
+		} else
+			v->value = NULL;
+	} while (tok_take_if(&ps->toks, ","));
+
+	if (tok_take_if(&ps->toks, "}") && tok_take_if(&ps->toks, ";"))
+		return true;
+
+	complain(ps, "Expected }; at end of enum");
+	return false;
+}
+
+static bool gather_undefines(const char *name,
+			     struct cdump_type *t,
+			     struct cdump_map *undefs)
+{
+	if (!type_defined(t))
+		strmap_add(undefs, name, t);
+	return true;
+}
+
+static bool remove_from_map(const char *name,
+			    struct cdump_type *t,
+			    struct cdump_map *map)
+{
+	strmap_del(map, name, NULL);
+	return true;
+}
+
+static void remove_undefined(struct cdump_map *map)
+{
+	struct cdump_map undefs;
+
+	/* We can't delete inside iterator, so gather all the undefs
+	 * then remove them. */
+	strmap_init(&undefs);
+
+	strmap_iterate(map, gather_undefines, &undefs);
+	strmap_iterate(&undefs, remove_from_map, map);
+	strmap_clear(&undefs);
+}
+
+static void destroy_definitions(struct cdump_definitions *defs)
+{
+	strmap_clear(&defs->enums);
+	strmap_clear(&defs->structs);
+	strmap_clear(&defs->unions);
+}
+
+/* Simple LL(1) parser, inspired by Tridge's genstruct.pl. */
+struct cdump_definitions *cdump_extract(const tal_t *ctx, const char *code,
+					char **complaints)
+{
+	struct parse_state ps;
+	const struct token *toks;
+
+	ps.defs = tal(ctx, struct cdump_definitions);
+	ps.complaints = tal_strdup(ctx, "");
+	ps.code = code;
+
+	strmap_init(&ps.defs->enums);
+	strmap_init(&ps.defs->structs);
+	strmap_init(&ps.defs->unions);
+	tal_add_destructor(ps.defs, destroy_definitions);
+
+	toks = ps.toks = tokenize(ps.defs, code);
+	while (tok_peek(&ps.toks)) {
+		if (tok_take_if(&ps.toks, "struct")) {
+			if (!tok_take_conglom(&ps, CDUMP_STRUCT))
+				goto fail;
+		} else if (tok_take_if(&ps.toks, "union")) {
+			if (!tok_take_conglom(&ps, CDUMP_UNION))
+				goto fail;
+		} else if (tok_take_if(&ps.toks, "enum")) {
+			if (!tok_take_enum(&ps))
+				goto fail;
+		} else
+			tok_take_unknown_statement(&ps);
+	}
+
+	/* Now, remove any undefined types! */
+	remove_undefined(&ps.defs->enums);
+	remove_undefined(&ps.defs->structs);
+	remove_undefined(&ps.defs->unions);
+	tal_free(toks);
+
+out:
+	if (streq(ps.complaints, ""))
+		ps.complaints = tal_free(ps.complaints);
+
+	if (complaints)
+		*complaints = ps.complaints;
+	else
+		tal_free(ps.complaints);
+	return ps.defs;
+
+fail:
+	ps.defs = tal_free(ps.defs);
+	goto out;
+}

+ 96 - 0
ccan/cdump/cdump.h

@@ -0,0 +1,96 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#ifndef CCAN_CDUMP_H
+#define CCAN_CDUMP_H
+#include <ccan/strmap/strmap.h>
+#include <ccan/tal/tal.h>
+
+enum cdump_type_kind {
+	CDUMP_STRUCT,
+	CDUMP_UNION,
+	CDUMP_ENUM,
+	CDUMP_ARRAY,
+	CDUMP_POINTER,
+	CDUMP_UNKNOWN
+};
+
+struct cdump_member {
+	const char *name;
+	/* const, volatile */
+	const char *qualifiers;
+	struct cdump_type *type;
+};
+
+struct cdump_enum_val {
+	const char *name;
+	/* Either NULL, or whatever follows '=' sign */
+	const char *value;
+};
+
+struct cdump_array {
+	const char *size;
+	struct cdump_type *type;
+};
+
+struct cdump_type {
+	enum cdump_type_kind kind;
+	const char *name;
+	union {
+		/* CDUMP_STRUCT / CDUMP_UNION: array */
+		struct cdump_member *members;
+		/* CDUMP_ENUM: array */
+		struct cdump_enum_val *enum_vals;
+		/* CDUMP_ARRAY */
+		struct cdump_array arr;
+		/* CDUMP_POINTER */
+		const struct cdump_type *ptr;
+	} u;
+};
+
+/* The map of typenames to definitions */
+struct cdump_map {
+	STRMAP_MEMBERS(struct cdump_type *);
+};
+
+struct cdump_definitions {
+	struct cdump_map enums;
+	struct cdump_map structs;
+	struct cdump_map unions;
+};
+
+/**
+ * cdump_extract - extract definitions from simple C code.
+ * @ctx: context to tal() the return and @problems from (or NULL)
+ * @code: a nul-terminated string of C definitions
+ * @problems: a pointer to a char * to report problems (or NULL)
+ *
+ * This function parses @code and extracts enum, struct and union definitions
+ * into the return.  If there is a parse error, it will return NULL and
+ * allocate a problem string for human consumption.
+ *
+ * Example:
+ *	// Returns name of first field of 'struct @name' in @code.
+ *	static const char *first_field_of_struct(const char *code,
+ *						 const char *name)
+ *	{
+ *		char *problems;
+ *		struct cdump_definitions *defs;
+ *		struct cdump_type *t;
+ *
+ *		defs = cdump_extract(NULL, code, &problems);
+ *		if (!defs) {
+ *			fprintf(stderr, "%s", problems);
+ *			tal_free(problems);
+ *			return NULL;
+ *		}
+ *		t = strmap_get(&defs->structs, name);
+ *		if (!t) {
+ *			fprintf(stderr, "Couldn't find struct %s", name);
+ *			return NULL;
+ *		}
+ *		assert(t->kind == CDUMP_STRUCT);
+ *		return t->u.members[0].name;
+ *	}
+ */
+struct cdump_definitions *cdump_extract(const tal_t *ctx, const char *code,
+					char **problems);
+#endif /* CCAN_CDUMP_H */

+ 43 - 0
ccan/cdump/test/run-forward-decl.c

@@ -0,0 +1,43 @@
+#include <ccan/cdump/cdump.h>
+/* Include the C files directly. */
+#include <ccan/cdump/cdump.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	struct cdump_definitions *defs;
+	const struct cdump_type *t, *t2;
+	char *ctx = tal(NULL, char), *problems;
+
+	/* This is how many tests you plan to run */
+	plan_tests(16);
+
+	defs = cdump_extract(ctx, "struct foo { struct bar *bar; };\n"
+			     "struct bar { int x; };", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+
+	t = strmap_get(&defs->structs, "foo");
+	ok1(t);
+	t2 = strmap_get(&defs->structs, "bar");
+	ok1(t2);
+
+	ok1(t2->kind == CDUMP_STRUCT);
+	ok1(streq(t2->name, "bar"));
+	ok1(tal_count(t2->u.members) == 1);
+	ok1(t2->u.members[0].type->kind == CDUMP_UNKNOWN);
+	ok1(streq(t2->u.members[0].type->name, "int"));
+
+	ok1(t->kind == CDUMP_STRUCT);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.members) == 1);
+	ok1(streq(t->u.members[0].name, "bar"));
+	ok1(t->u.members[0].type->kind == CDUMP_POINTER);
+	ok1(t->u.members[0].type->u.ptr == t2);
+
+	tal_free(ctx);
+
+	/* This exits depending on whether all tests passed */
+	return exit_status();
+}

+ 130 - 0
ccan/cdump/test/run-qualifiers.c

@@ -0,0 +1,130 @@
+#include <ccan/cdump/cdump.h>
+/* Include the C files directly. */
+#include <ccan/cdump/cdump.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	struct cdump_definitions *defs;
+	const struct cdump_type *t, *p;
+	char *ctx = tal(NULL, char), *problems;
+
+	/* This is how many tests you plan to run */
+	plan_tests(63);
+
+	defs = cdump_extract(ctx,
+			     "struct foo {\n"
+			     "	long l;\n"
+			     "	long int li;\n"
+			     "	unsigned long *ulp;\n"
+			     "	unsigned long int *ulip;\n"
+			     "};", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+
+	ok1(strmap_empty(&defs->enums));
+	ok1(strmap_empty(&defs->unions));
+	t = strmap_get(&defs->structs, "foo");
+	ok1(t);
+	ok1(t->kind == CDUMP_STRUCT);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.members) == 4);
+
+	ok1(streq(t->u.members[0].name, "l"));
+	p = t->u.members[0].type;
+	ok1(p->kind == CDUMP_UNKNOWN);
+	ok1(streq(p->name, "long"));
+
+	ok1(streq(t->u.members[1].name, "li"));
+	p = t->u.members[1].type;
+	ok1(p->kind == CDUMP_UNKNOWN);
+	ok1(streq(p->name, "long int"));
+
+	ok1(streq(t->u.members[2].name, "ulp"));
+	p = t->u.members[2].type;
+	ok1(p->kind == CDUMP_POINTER);
+	p = p->u.ptr;
+	ok1(p->kind == CDUMP_UNKNOWN);
+	ok1(streq(p->name, "unsigned long"));
+
+	ok1(streq(t->u.members[3].name, "ulip"));
+	p = t->u.members[3].type;
+	ok1(p->kind == CDUMP_POINTER);
+	p = p->u.ptr;
+	ok1(p->kind == CDUMP_UNKNOWN);
+	ok1(streq(p->name, "unsigned long int"));
+
+	defs = cdump_extract(ctx,
+			     "struct foo {\n"
+			     "	volatile long vl;\n"
+			     "	const long cl;\n"
+			     "	volatile const long long int *vclli;\n"
+			     "};", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+
+	ok1(strmap_empty(&defs->enums));
+	ok1(strmap_empty(&defs->unions));
+	t = strmap_get(&defs->structs, "foo");
+	ok1(t);
+	ok1(t->kind == CDUMP_STRUCT);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.members) == 3);
+
+	ok1(streq(t->u.members[0].name, "vl"));
+	ok1(streq(t->u.members[0].qualifiers, "volatile"));
+	p = t->u.members[0].type;
+	ok1(p->kind == CDUMP_UNKNOWN);
+	ok1(streq(p->name, "long"));
+
+	ok1(streq(t->u.members[1].name, "cl"));
+	ok1(streq(t->u.members[1].qualifiers, "const"));
+	p = t->u.members[1].type;
+	ok1(p->kind == CDUMP_UNKNOWN);
+	ok1(streq(p->name, "long"));
+
+	ok1(streq(t->u.members[2].name, "vclli"));
+	ok1(streq(t->u.members[2].qualifiers, "volatile const"));
+	p = t->u.members[2].type;
+	ok1(p->kind == CDUMP_POINTER);
+	p = p->u.ptr;
+	ok1(p->kind == CDUMP_UNKNOWN);
+	ok1(streq(p->name, "long long int"));
+
+	defs = cdump_extract(ctx,
+			     "struct foo {\n"
+			     "	volatile struct bar *a, b;\n"
+			     "};", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+
+	ok1(strmap_empty(&defs->enums));
+	ok1(strmap_empty(&defs->unions));
+	t = strmap_get(&defs->structs, "foo");
+	ok1(t);
+	ok1(t->kind == CDUMP_STRUCT);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.members) == 2);
+
+	ok1(streq(t->u.members[0].name, "a"));
+	ok1(streq(t->u.members[0].qualifiers, "volatile"));
+	p = t->u.members[0].type;
+	ok1(p->kind == CDUMP_POINTER);
+	p = p->u.ptr;
+	ok1(p->kind == CDUMP_STRUCT);
+	ok1(streq(p->name, "bar"));
+
+	ok1(streq(t->u.members[1].name, "b"));
+	ok1(streq(t->u.members[1].qualifiers, "volatile"));
+	p = t->u.members[1].type;
+	ok1(p->kind == CDUMP_STRUCT);
+	ok1(streq(p->name, "bar"));
+
+	tal_free(ctx);
+
+	/* This exits depending on whether all tests passed */
+	return exit_status();
+}

+ 152 - 0
ccan/cdump/test/run.c

@@ -0,0 +1,152 @@
+#include <ccan/cdump/cdump.h>
+/* Include the C files directly. */
+#include <ccan/cdump/cdump.c>
+#include <ccan/tap/tap.h>
+
+int main(void)
+{
+	struct cdump_definitions *defs;
+	const struct cdump_type *t, *p;
+	char *ctx = tal(NULL, char), *problems;
+
+	/* This is how many tests you plan to run */
+	plan_tests(94);
+
+	defs = cdump_extract(ctx, "enum foo { BAR };", NULL);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+
+	ok1(strmap_empty(&defs->structs));
+	ok1(strmap_empty(&defs->unions));
+	t = strmap_get(&defs->enums, "foo");
+	ok1(t);
+	ok1(t->kind == CDUMP_ENUM);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.enum_vals) == 1);
+	ok1(streq(t->u.enum_vals[0].name, "BAR"));
+	ok1(!t->u.enum_vals[0].value);
+
+	defs = cdump_extract(ctx, "enum foo { BAR = 7 };", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+
+	ok1(strmap_empty(&defs->structs));
+	ok1(strmap_empty(&defs->unions));
+	t = strmap_get(&defs->enums, "foo");
+	ok1(t);
+	ok1(t->kind == CDUMP_ENUM);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.enum_vals) == 1);
+	ok1(streq(t->u.enum_vals[0].name, "BAR"));
+	ok1(streq(t->u.enum_vals[0].value, "7"));
+
+	defs = cdump_extract(ctx, "enum foo { BAR = 7, BAZ, FUZZ };", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+
+	ok1(strmap_empty(&defs->structs));
+	ok1(strmap_empty(&defs->unions));
+	t = strmap_get(&defs->enums, "foo");
+	ok1(t);
+	ok1(t->kind == CDUMP_ENUM);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.enum_vals) == 3);
+	ok1(streq(t->u.enum_vals[0].name, "BAR"));
+	ok1(streq(t->u.enum_vals[0].value, "7"));
+	ok1(streq(t->u.enum_vals[1].name, "BAZ"));
+	ok1(!t->u.enum_vals[1].value);
+	ok1(streq(t->u.enum_vals[2].name, "FUZZ"));
+	ok1(!t->u.enum_vals[2].value);
+
+	defs = cdump_extract(ctx, "struct foo { int x; };", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+
+	ok1(strmap_empty(&defs->enums));
+	ok1(strmap_empty(&defs->unions));
+	t = strmap_get(&defs->structs, "foo");
+	ok1(t);
+	ok1(t->kind == CDUMP_STRUCT);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.members) == 1);
+	ok1(streq(t->u.members[0].name, "x"));
+	ok1(t->u.members[0].type->kind == CDUMP_UNKNOWN);
+	ok1(streq(t->u.members[0].type->name, "int"));
+
+	defs = cdump_extract(ctx, "struct foo { int x[5<< 1]; struct foo *next; struct unknown **ptrs[10]; };", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+
+	ok1(strmap_empty(&defs->enums));
+	ok1(strmap_empty(&defs->unions));
+	t = strmap_get(&defs->structs, "foo");
+	ok1(t);
+	ok1(t->kind == CDUMP_STRUCT);
+	ok1(streq(t->name, "foo"));
+	ok1(tal_count(t->u.members) == 3);
+
+	ok1(streq(t->u.members[0].name, "x"));
+	ok1(t->u.members[0].type->kind == CDUMP_ARRAY);
+	ok1(streq(t->u.members[0].type->u.arr.size, "5<< 1"));
+	ok1(t->u.members[0].type->u.arr.type->kind == CDUMP_UNKNOWN);
+	ok1(streq(t->u.members[0].type->u.arr.type->name, "int"));
+
+	ok1(streq(t->u.members[1].name, "next"));
+	ok1(t->u.members[1].type->kind == CDUMP_POINTER);
+	ok1(t->u.members[1].type->u.ptr == t);
+
+	ok1(streq(t->u.members[2].name, "ptrs"));
+	p = t->u.members[2].type;
+	ok1(p->kind == CDUMP_ARRAY);
+	ok1(streq(p->u.arr.size, "10"));
+	p = p->u.arr.type;
+	ok1(p->kind == CDUMP_POINTER);
+	p = p->u.ptr;
+	ok1(p->kind == CDUMP_POINTER);
+	p = p->u.ptr;
+	ok1(p->kind == CDUMP_STRUCT);
+	ok1(streq(p->name, "unknown"));
+	ok1(p->u.members == NULL);
+
+	/* We don't put undefined structs into definition maps. */
+	ok1(!strmap_get(&defs->structs, "unknown"));
+
+	/* unions and comments. */
+	defs = cdump_extract(ctx, "#if 0\n"
+			     "/* Normal comment */\n"
+			     "struct foo { int x[5 * 7/* Comment */]; };\n"
+			     "// One-line comment\n"
+			     "union bar { enum sometype x; union yun// Comment\n"
+			     " y;};\n"
+			     "#endif", &problems);
+	ok1(defs);
+	ok1(tal_parent(defs) == ctx);
+	ok1(!problems);
+	t = strmap_get(&defs->structs, "foo");
+	ok1(t);
+	ok1(tal_count(t->u.members) == 1);
+	ok1(streq(t->u.members[0].name, "x"));
+	ok1(t->u.members[0].type->kind == CDUMP_ARRAY);
+	ok1(streq(t->u.members[0].type->u.arr.size, "5 * 7"));
+	ok1(t->u.members[0].type->u.arr.type->kind == CDUMP_UNKNOWN);
+	ok1(streq(t->u.members[0].type->u.arr.type->name, "int"));
+
+	t = strmap_get(&defs->unions, "bar");
+	ok1(t);
+	ok1(tal_count(t->u.members) == 2);
+	ok1(streq(t->u.members[0].name, "x"));
+	ok1(t->u.members[0].type->kind == CDUMP_ENUM);
+	ok1(streq(t->u.members[0].type->name, "sometype"));
+	ok1(!t->u.members[0].type->u.enum_vals);
+	ok1(streq(t->u.members[1].name, "y"));
+	ok1(t->u.members[1].type->kind == CDUMP_UNION);
+	ok1(streq(t->u.members[1].type->name, "yun"));
+	ok1(!t->u.members[1].type->u.members);
+
+	/* This exits depending on whether all tests passed */
+	return exit_status();
+}