From 158691ae36fe1da78ec54dbcc0006f603398dae2 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Tue, 11 Nov 2014 15:55:48 +1030 Subject: [PATCH] cdump: new module. Signed-off-by: Rusty Russell --- Makefile-ccan | 1 + ccan/cdump/LICENSE | 1 + ccan/cdump/_info | 94 +++++ ccan/cdump/cdump.c | 543 +++++++++++++++++++++++++++++ ccan/cdump/cdump.h | 96 +++++ ccan/cdump/test/run-forward-decl.c | 43 +++ ccan/cdump/test/run-qualifiers.c | 130 +++++++ ccan/cdump/test/run.c | 152 ++++++++ 8 files changed, 1060 insertions(+) create mode 120000 ccan/cdump/LICENSE create mode 100644 ccan/cdump/_info create mode 100644 ccan/cdump/cdump.c create mode 100644 ccan/cdump/cdump.h create mode 100644 ccan/cdump/test/run-forward-decl.c create mode 100644 ccan/cdump/test/run-qualifiers.c create mode 100644 ccan/cdump/test/run.c diff --git a/Makefile-ccan b/Makefile-ccan index a2fa8f3a..acfbe19a 100644 --- a/Makefile-ccan +++ b/Makefile-ccan @@ -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 index 00000000..2354d129 --- /dev/null +++ b/ccan/cdump/LICENSE @@ -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 index 00000000..02ba19b5 --- /dev/null +++ b/ccan/cdump/_info @@ -0,0 +1,94 @@ +#include "config.h" +#include +#include + +/** + * 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 + * License: BSD-MIT + * + * Example: + * // Creates a simple print function for a structure. + * #include + * #include + * #include + * + * 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 index 00000000..20cdb4eb --- /dev/null +++ b/ccan/cdump/cdump.c @@ -0,0 +1,543 @@ +/* MIT (BSD) license - see LICENSE file for details */ +#include "cdump.h" +#include +#include + +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 index 00000000..ede6310e --- /dev/null +++ b/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 +#include + +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 index 00000000..96065e45 --- /dev/null +++ b/ccan/cdump/test/run-forward-decl.c @@ -0,0 +1,43 @@ +#include +/* Include the C files directly. */ +#include +#include + +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 index 00000000..964047b1 --- /dev/null +++ b/ccan/cdump/test/run-qualifiers.c @@ -0,0 +1,130 @@ +#include +/* Include the C files directly. */ +#include +#include + +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 index 00000000..88c6d491 --- /dev/null +++ b/ccan/cdump/test/run.c @@ -0,0 +1,152 @@ +#include +/* Include the C files directly. */ +#include +#include + +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(); +} -- 2.39.2