]> git.ozlabs.org Git - ccan/commitdiff
cdump: new module.
authorRusty Russell <rusty@rustcorp.com.au>
Tue, 11 Nov 2014 05:25:48 +0000 (15:55 +1030)
committerRusty Russell <rusty@rustcorp.com.au>
Tue, 11 Nov 2014 05:28:40 +0000 (15:58 +1030)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Makefile-ccan
ccan/cdump/LICENSE [new symlink]
ccan/cdump/_info [new file with mode: 0644]
ccan/cdump/cdump.c [new file with mode: 0644]
ccan/cdump/cdump.h [new file with mode: 0644]
ccan/cdump/test/run-forward-decl.c [new file with mode: 0644]
ccan/cdump/test/run-qualifiers.c [new file with mode: 0644]
ccan/cdump/test/run.c [new file with mode: 0644]

index a2fa8f3a5da1a40978bad37f01a54e74fe727458..acfbe19ad4d0ba6c34251cc9cdf22bb949e256ba 100644 (file)
@@ -41,6 +41,7 @@ MODS_WITH_SRC := antithread \
        btree \
        bytestring \
        ccan_tokenizer \
+       cdump \
        charset \
        ciniparser \
        crc \
diff --git a/ccan/cdump/LICENSE b/ccan/cdump/LICENSE
new file mode 120000 (symlink)
index 0000000..2354d12
--- /dev/null
@@ -0,0 +1 @@
+../../licenses/BSD-MIT
\ No newline at end of file
diff --git a/ccan/cdump/_info b/ccan/cdump/_info
new file mode 100644 (file)
index 0000000..02ba19b
--- /dev/null
@@ -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;
+}
diff --git a/ccan/cdump/cdump.c b/ccan/cdump/cdump.c
new file mode 100644 (file)
index 0000000..20cdb4e
--- /dev/null
@@ -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;
+}
diff --git a/ccan/cdump/cdump.h b/ccan/cdump/cdump.h
new file mode 100644 (file)
index 0000000..ede6310
--- /dev/null
@@ -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 */
diff --git a/ccan/cdump/test/run-forward-decl.c b/ccan/cdump/test/run-forward-decl.c
new file mode 100644 (file)
index 0000000..96065e4
--- /dev/null
@@ -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();
+}
diff --git a/ccan/cdump/test/run-qualifiers.c b/ccan/cdump/test/run-qualifiers.c
new file mode 100644 (file)
index 0000000..964047b
--- /dev/null
@@ -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();
+}
diff --git a/ccan/cdump/test/run.c b/ccan/cdump/test/run.c
new file mode 100644 (file)
index 0000000..88c6d49
--- /dev/null
@@ -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();
+}