From 1b2de5c83053db27ad77e1987cf537d6719fb710 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 25 Sep 2015 11:51:18 +0930 Subject: [PATCH] CCAN: add cdump. Used to print enums. Signed-off-by: Rusty Russell --- ccan/ccan/cdump/LICENSE | 1 + ccan/ccan/cdump/_info | 94 +++ ccan/ccan/cdump/cdump.c | 699 +++++++++++++++++++ ccan/ccan/cdump/cdump.h | 106 +++ ccan/ccan/cdump/test/run-CDUMP.c | 176 +++++ ccan/ccan/cdump/test/run-arraysize.c | 54 ++ ccan/ccan/cdump/test/run-attributes.c | 68 ++ ccan/ccan/cdump/test/run-enum-comma.c | 34 + ccan/ccan/cdump/test/run-forward-decl.c | 43 ++ ccan/ccan/cdump/test/run-multiline.c | 75 ++ ccan/ccan/cdump/test/run-qualifiers.c | 130 ++++ ccan/ccan/cdump/test/run.c | 152 ++++ ccan/ccan/cdump/tools/Makefile | 27 + ccan/ccan/cdump/tools/cdump-enumstr.c | 52 ++ ccan/ccan/strmap/_info | 63 ++ ccan/ccan/strmap/strmap.c | 247 +++++++ ccan/ccan/strmap/strmap.h | 226 ++++++ ccan/ccan/strmap/test/run-iterate-const.c | 32 + ccan/ccan/strmap/test/run-order.c | 96 +++ ccan/ccan/strmap/test/run-prefix.c | 97 +++ ccan/ccan/strmap/test/run.c | 80 +++ ccan/ccan/tcon/LICENSE | 1 + ccan/ccan/tcon/_info | 75 ++ ccan/ccan/tcon/tcon.h | 115 +++ ccan/ccan/tcon/test/compile_fail-tcon_cast.c | 29 + ccan/ccan/tcon/test/compile_fail.c | 25 + ccan/ccan/tcon/test/compile_ok-void.c | 21 + ccan/ccan/tcon/test/compile_ok.c | 27 + 28 files changed, 2845 insertions(+) create mode 120000 ccan/ccan/cdump/LICENSE create mode 100644 ccan/ccan/cdump/_info create mode 100644 ccan/ccan/cdump/cdump.c create mode 100644 ccan/ccan/cdump/cdump.h create mode 100644 ccan/ccan/cdump/test/run-CDUMP.c create mode 100644 ccan/ccan/cdump/test/run-arraysize.c create mode 100644 ccan/ccan/cdump/test/run-attributes.c create mode 100644 ccan/ccan/cdump/test/run-enum-comma.c create mode 100644 ccan/ccan/cdump/test/run-forward-decl.c create mode 100644 ccan/ccan/cdump/test/run-multiline.c create mode 100644 ccan/ccan/cdump/test/run-qualifiers.c create mode 100644 ccan/ccan/cdump/test/run.c create mode 100644 ccan/ccan/cdump/tools/Makefile create mode 100644 ccan/ccan/cdump/tools/cdump-enumstr.c create mode 100644 ccan/ccan/strmap/_info create mode 100644 ccan/ccan/strmap/strmap.c create mode 100644 ccan/ccan/strmap/strmap.h create mode 100644 ccan/ccan/strmap/test/run-iterate-const.c create mode 100644 ccan/ccan/strmap/test/run-order.c create mode 100644 ccan/ccan/strmap/test/run-prefix.c create mode 100644 ccan/ccan/strmap/test/run.c create mode 120000 ccan/ccan/tcon/LICENSE create mode 100644 ccan/ccan/tcon/_info create mode 100644 ccan/ccan/tcon/tcon.h create mode 100644 ccan/ccan/tcon/test/compile_fail-tcon_cast.c create mode 100644 ccan/ccan/tcon/test/compile_fail.c create mode 100644 ccan/ccan/tcon/test/compile_ok-void.c create mode 100644 ccan/ccan/tcon/test/compile_ok.c diff --git a/ccan/ccan/cdump/LICENSE b/ccan/ccan/cdump/LICENSE new file mode 120000 index 000000000..2354d1294 --- /dev/null +++ b/ccan/ccan/cdump/LICENSE @@ -0,0 +1 @@ +../../licenses/BSD-MIT \ No newline at end of file diff --git a/ccan/ccan/cdump/_info b/ccan/ccan/cdump/_info new file mode 100644 index 000000000..02ba19b5b --- /dev/null +++ b/ccan/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/ccan/cdump/cdump.c b/ccan/ccan/cdump/cdump.c new file mode 100644 index 000000000..f080b968b --- /dev/null +++ b/ccan/ccan/cdump/cdump.c @@ -0,0 +1,699 @@ +/* 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; +} + +static size_t to_eol(const char *p) +{ + size_t len = strcspn(p, "\n"); + + /* And any \ continuations. */ + while (p[len] && p[len-1] == '\\') + len += strcspn(p+len+1, "\n") + 1; + return 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 = to_eol(code + i); + } else if (code[i] == '/' && code[i+1] == '/') { + /* One line comment. */ + len = to_eol(code + i); + 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 const struct token *tok_peek(const struct token **toks) +{ + /* Ignore removed tokens (eg. comments) */ + while (toks[0]->len == 0) { + if (!toks[0]->p) + return NULL; + (*toks)++; + } + return toks[0]; +} + +static bool tok_is(const struct token **toks, const char *target) +{ + const struct token *t = tok_peek(toks); + return (t && t->len == strlen(target) + && memcmp(t->p, target, t->len) == 0); +} + +static const struct token *tok_take(const struct token **toks) +{ + const struct token *t = tok_peek(toks); + if (t) + (*toks)++; + + return t; +} + +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) +{ + char *str, *p; + + /* Careful to skip erased tokens (eg. comments) */ + str = p = tal_arr(ctx, char, until->p - first->p + 1); + while (first != until) { + const struct token *next = first + 1; + + if (first->len) { + memcpy(p, first->p, first->len); + p += first->len; + /* Insert space if they weren't adjacent, unless last */ + if (next != until) { + if (first->p + first->len != next->p) + *(p++) = ' '; + } + } + first = next; + } + *p = '\0'; + + return str; +} + +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_expr(struct parse_state *ps, const char *term) +{ + while (!tok_is(&ps->toks, term)) { + if (tok_take_if(&ps->toks, "(")) { + if (!tok_take_expr(ps, ")")) + return false; + } else if (tok_take_if(&ps->toks, "[")) { + if (!tok_take_expr(ps, "]")) + return false; + } else if (!tok_take(&ps->toks)) + return false; + } + return tok_take(&ps->toks); +} + +static char *tok_take_expr_str(const tal_t *ctx, + struct parse_state *ps, + const char *term) +{ + const struct token *start = tok_peek(&ps->toks); + + if (!tok_take_expr(ps, term)) + return NULL; + + return string_of_toks(ctx, start, ps->toks - 1); +} + +/* [ ... */ +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_expr_str(arr, ps, "]"); + if (!arr->u.arr.size) { + complain(ps, "Could not find closing array size ]"); + return false; + } + + arr->u.arr.type = *type; + *type = arr; + + 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; +} + +/* CDUMP */ +static bool tok_maybe_take_cdump_note(const tal_t *ctx, + struct parse_state *ps, const char **note) +{ + *note = NULL; + if (tok_take_if(&ps->toks, "CDUMP")) { + if (!tok_take_if(&ps->toks, "(")) { + complain(ps, "Expected ( after CDUMP"); + return false; + } + *note = tok_take_expr_str(ctx, ps, ")"); + if (!*note) { + complain(ps, "Expected ) after CDUMP("); + return false; + } + } + return true; +} + +/* __attribute__((...)) */ +static bool tok_ignore_attribute(struct parse_state *ps) +{ + if (!tok_take_if(&ps->toks, "__attribute__")) + return true; + + if (!tok_take_if(&ps->toks, "(") || !tok_take_if(&ps->toks, "(")) { + complain(ps, "Expected (( after __attribute__"); + return false; + } + + if (!tok_take_expr(ps, ")")) { + complain(ps, "Expected expression after __attribute__(("); + return false; + } + if (!tok_take_if(&ps->toks, ")")) { + complain(ps, "Expected )) __attribute__(("); + return false; + } + 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_maybe_take_cdump_note(e, ps, &e->note)) + return false; + + if (!tok_ignore_attribute(ps)) + 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; + + if (!tok_ignore_attribute(ps)) + return false; + + /* 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); + + if (!tok_ignore_attribute(ps)) + return false; + + 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; + } + + /* CDUMP() */ + if (!tok_maybe_take_cdump_note(e->u.members, + ps, &m->note)) + return false; + + if (!tok_ignore_attribute(ps)) + 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, "}")) { + complain(ps, "Expected } at end of struct/union"); + return false; + } + + if (!tok_ignore_attribute(ps)) + return false; + + if (!tok_take_if(&ps->toks, ";")) { + complain(ps, "Expected ; at end of struct/union"); + return false; + } + return true; +} + +/* 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; + } + + /* CDUMP() */ + if (!tok_maybe_take_cdump_note(e, ps, &e->note)) + return false; + + if (!tok_ignore_attribute(ps)) + 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; + + /* GCC extension: comma and end of enum */ + if (tok_is(&ps->toks, "}")) + break; + + 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; + } + + /* CDUMP() */ + if (!tok_maybe_take_cdump_note(e->u.enum_vals, ps, &v->note)) + 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, "}")) { + complain(ps, "Expected } at end of enum"); + return false; + } + + if (!tok_ignore_attribute(ps)) + return false; + + if (!tok_take_if(&ps->toks, ";")) { + complain(ps, "Expected ; at end of enum"); + return false; + } + return true; +} + +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_ignore_attribute(&ps)) + goto fail; + 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/ccan/cdump/cdump.h b/ccan/ccan/cdump/cdump.h new file mode 100644 index 000000000..312767b41 --- /dev/null +++ b/ccan/ccan/cdump/cdump.h @@ -0,0 +1,106 @@ +/* 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 char *note; + /* const, volatile */ + const char *qualifiers; + struct cdump_type *type; +}; + +struct cdump_enum_val { + const char *name; + const char *note; + /* 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; + const char *note; + 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. + * + * Annotations can be attached to structures, unions, enums, members + * and enum values using CDUMP(). This comes after the name (or + * after [] for array member declarations) and usually is removed from + * C compilation using "#define CDUMP(x)". + * + * 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); + * if (t->note) + * printf("Note on struct %s: %s\n", name, t->note); + * 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/ccan/cdump/test/run-CDUMP.c b/ccan/ccan/cdump/test/run-CDUMP.c new file mode 100644 index 000000000..8e9783b0d --- /dev/null +++ b/ccan/ccan/cdump/test/run-CDUMP.c @@ -0,0 +1,176 @@ +#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(111); + + defs = cdump_extract(ctx, "enum foo CDUMP(foo note) { BAR CDUMP(bar note) };", 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->note, "foo note")); + 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); + ok1(streq(t->u.enum_vals[0].note, "bar note")); + + defs = cdump_extract(ctx, "enum foo { BAR CDUMP(bar note) = 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")); + ok1(streq(t->u.enum_vals[0].note, "bar note")); + + defs = cdump_extract(ctx, "enum foo {\n" + "BAR CDUMP(bar note) = 7,\n" + "BAZ CDUMP(baz note),\n" + "FUZZ CDUMP(fuzz note) };", + &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(t->note == NULL); + 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[0].note, "bar note")); + ok1(streq(t->u.enum_vals[1].name, "BAZ")); + ok1(streq(t->u.enum_vals[1].note, "baz note")); + ok1(!t->u.enum_vals[1].value); + ok1(streq(t->u.enum_vals[2].name, "FUZZ")); + ok1(streq(t->u.enum_vals[2].note, "fuzz note")); + ok1(!t->u.enum_vals[2].value); + + defs = cdump_extract(ctx, "struct foo CDUMP(foo note) { int x CDUMP(x note); };", &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(streq(t->note, "foo note")); + ok1(tal_count(t->u.members) == 1); + ok1(streq(t->u.members[0].name, "x")); + ok1(streq(t->u.members[0].note, "x note")); + 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] CDUMP(x note); struct foo *next CDUMP(next note); struct unknown **ptrs[10] CDUMP(ptrs note); };", &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(streq(t->u.members[0].note, "x note")); + 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(streq(t->u.members[1].note, "next note")); + 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")); + ok1(streq(t->u.members[2].note, "ptrs note")); + 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 */]CDUMP(x note/*nocomment*/); };\n" + "// One-line comment\n" + "union bar CDUMP(bar note) { enum sometype x CDUMP(x note// Comment\n" + "); 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(t->note == NULL); + ok1(tal_count(t->u.members) == 1); + ok1(streq(t->u.members[0].name, "x")); + ok1(streq(t->u.members[0].note, "x note")); + 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(streq(t->note, "bar note")); + + ok1(tal_count(t->u.members) == 2); + ok1(streq(t->u.members[0].name, "x")); + ok1(streq(t->u.members[0].note, "x note")); + 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].note == NULL); + 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(); +} diff --git a/ccan/ccan/cdump/test/run-arraysize.c b/ccan/ccan/cdump/test/run-arraysize.c new file mode 100644 index 000000000..e89af97b3 --- /dev/null +++ b/ccan/ccan/cdump/test/run-arraysize.c @@ -0,0 +1,54 @@ +#include +/* Include the C files directly. */ +#include +#include + +int main(void) +{ + struct cdump_definitions *defs; + const struct cdump_type *t; + char *ctx = tal(NULL, char), *problems; + + /* This is how many tests you plan to run */ + plan_tests(20); + + /* unions and comments. */ + defs = cdump_extract(ctx, + "struct foo {\n" + " int x[5 */* Comment */7];\n" + " int y[5// Comment\n" + " * 7];\n" + " int z[5 *\n" + "#ifdef FOO\n" + " 7\n" + "#endif\n" + "];\n" + "};\n", &problems); + + ok1(defs); + ok1(tal_parent(defs) == ctx); + ok1(!problems); + t = strmap_get(&defs->structs, "foo"); + ok1(t); + 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 * 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")); + + ok1(streq(t->u.members[1].name, "y")); + ok1(t->u.members[1].type->kind == CDUMP_ARRAY); + ok1(streq(t->u.members[1].type->u.arr.size, "5 * 7")); + ok1(t->u.members[1].type->u.arr.type->kind == CDUMP_UNKNOWN); + ok1(streq(t->u.members[1].type->u.arr.type->name, "int")); + + ok1(streq(t->u.members[2].name, "z")); + ok1(t->u.members[2].type->kind == CDUMP_ARRAY); + ok1(streq(t->u.members[2].type->u.arr.size, "5 * 7")); + ok1(t->u.members[2].type->u.arr.type->kind == CDUMP_UNKNOWN); + ok1(streq(t->u.members[2].type->u.arr.type->name, "int")); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/cdump/test/run-attributes.c b/ccan/ccan/cdump/test/run-attributes.c new file mode 100644 index 000000000..1034bde99 --- /dev/null +++ b/ccan/ccan/cdump/test/run-attributes.c @@ -0,0 +1,68 @@ +#include +/* Include the C files directly. */ +#include +#include + +int main(void) +{ + struct cdump_definitions *defs; + const struct cdump_type *t; + char *ctx = tal(NULL, char), *problems; + + /* This is how many tests you plan to run */ + plan_tests(37); + + defs = cdump_extract(ctx, "__attribute__((xxx)) enum foo __attribute__((xxx)) { BAR } __attribute__((xxx));", 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, "__attribute__((xxx)) struct foo __attribute__((xxx)) { int __attribute__((xxx)) x __attribute__((xxx)); } __attribute__((xxx));", &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, __attribute__((xxx)) y; };", &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, "x")); + ok1(t->u.members[0].type->kind == CDUMP_UNKNOWN); + ok1(streq(t->u.members[0].type->name, "int")); + + ok1(streq(t->u.members[1].name, "y")); + ok1(t->u.members[1].type->kind == CDUMP_UNKNOWN); + ok1(streq(t->u.members[1].type->name, "int")); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/cdump/test/run-enum-comma.c b/ccan/ccan/cdump/test/run-enum-comma.c new file mode 100644 index 000000000..4aaea5e6e --- /dev/null +++ b/ccan/ccan/cdump/test/run-enum-comma.c @@ -0,0 +1,34 @@ +#include +/* Include the C files directly. */ +#include +#include + +int main(void) +{ + struct cdump_definitions *defs; + const struct cdump_type *t; + char *problems; + + /* This is how many tests you plan to run */ + plan_tests(12); + + defs = cdump_extract(NULL, "enum foo { BAR, BAZ, };", &problems); + ok1(defs); + 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) == 2); + ok1(streq(t->u.enum_vals[0].name, "BAR")); + ok1(!t->u.enum_vals[0].value); + ok1(streq(t->u.enum_vals[1].name, "BAZ")); + ok1(!t->u.enum_vals[1].value); + tal_free(defs); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/cdump/test/run-forward-decl.c b/ccan/ccan/cdump/test/run-forward-decl.c new file mode 100644 index 000000000..96065e45e --- /dev/null +++ b/ccan/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/ccan/cdump/test/run-multiline.c b/ccan/ccan/cdump/test/run-multiline.c new file mode 100644 index 000000000..50f4e75bd --- /dev/null +++ b/ccan/ccan/cdump/test/run-multiline.c @@ -0,0 +1,75 @@ +#include +/* Include the C files directly. */ +#include +#include + +int main(void) +{ + struct cdump_definitions *defs; + const struct cdump_type *t; + char *ctx = tal(NULL, char), *problems; + + /* This is how many tests you plan to run */ + plan_tests(30); + + /* Multi-line preprocessor statement. */ + defs = cdump_extract(ctx, + "#if \\\n" + "SOME\\\n" + "THING\n" + "enum foo { BAR };", &problems); + 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 {\n" + "#if \\\n" + "SOME\\\n" + "THING\n" + " BAR };", &problems); + 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); + + /* Multi-line "one-line" comment. */ + defs = cdump_extract(ctx, + "enum foo {\n" + "// Comment \\\n" + "SOME\\\n" + "THING\n" + " BAR };", &problems); + 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); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/cdump/test/run-qualifiers.c b/ccan/ccan/cdump/test/run-qualifiers.c new file mode 100644 index 000000000..964047b1b --- /dev/null +++ b/ccan/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/ccan/cdump/test/run.c b/ccan/ccan/cdump/test/run.c new file mode 100644 index 000000000..88c6d491e --- /dev/null +++ b/ccan/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(); +} diff --git a/ccan/ccan/cdump/tools/Makefile b/ccan/ccan/cdump/tools/Makefile new file mode 100644 index 000000000..3b30d6619 --- /dev/null +++ b/ccan/ccan/cdump/tools/Makefile @@ -0,0 +1,27 @@ +CCAN_OBJS := ccan-tal.o ccan-tal-str.o ccan-tal-grab_file.o ccan-cdump.o ccan-take.o ccan-list.o ccan-read_write_all.o ccan-strmap.o ccan-noerr.o +CCANDIR:=../../.. +CFLAGS := -I$(CCANDIR) -Wall + +cdump-enumstr: cdump-enumstr.o $(CCAN_OBJS) + +clean: + $(RM) cdump-enumstr.o $(CCAN_OBJS) + +ccan-tal.o: $(CCANDIR)/ccan/tal/tal.c + $(CC) $(CFLAGS) -c -o $@ $< +ccan-strmap.o: $(CCANDIR)/ccan/strmap/strmap.c + $(CC) $(CFLAGS) -c -o $@ $< +ccan-noerr.o: $(CCANDIR)/ccan/noerr/noerr.c + $(CC) $(CFLAGS) -c -o $@ $< +ccan-cdump.o: $(CCANDIR)/ccan/cdump/cdump.c + $(CC) $(CFLAGS) -c -o $@ $< +ccan-tal-str.o: $(CCANDIR)/ccan/tal/str/str.c + $(CC) $(CFLAGS) -c -o $@ $< +ccan-tal-grab_file.o: $(CCANDIR)/ccan/tal/grab_file/grab_file.c + $(CC) $(CFLAGS) -c -o $@ $< +ccan-take.o: $(CCANDIR)/ccan/take/take.c + $(CC) $(CFLAGS) -c -o $@ $< +ccan-list.o: $(CCANDIR)/ccan/list/list.c + $(CC) $(CFLAGS) -c -o $@ $< +ccan-read_write_all.o: $(CCANDIR)/ccan/read_write_all/read_write_all.c + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/ccan/ccan/cdump/tools/cdump-enumstr.c b/ccan/ccan/cdump/tools/cdump-enumstr.c new file mode 100644 index 000000000..f0b70ecdb --- /dev/null +++ b/ccan/ccan/cdump/tools/cdump-enumstr.c @@ -0,0 +1,52 @@ +#include +#include +#include + +static bool dump_map(const char *name, struct cdump_type *t, void *unused) +{ + size_t i; + + printf("struct {\n" + " enum %s v;\n" + " const char *name;\n" + "} enum_%s_names[] = {\n", name, name); + + for (i = 0; i < tal_count(t->u.enum_vals); i++) + printf(" { %s, \"%s\" },\n", + t->u.enum_vals[i].name, + t->u.enum_vals[i].name); + printf(" { 0, NULL } };\n"); + return true; +} + +int main(int argc, char *argv[]) +{ + char *code, *problems; + struct cdump_definitions *defs; + + if (argc < 2) + errx(1, "Usage: cdump-enumstr [...]"); + + code = grab_file(NULL, streq(argv[1], "-") ? NULL : argv[1]); + if (!code) + err(1, "Reading %s", argv[1]); + + defs = cdump_extract(code, code, &problems); + if (!defs) + errx(1, "Parsing %s:\n%s", argv[1], problems); + + if (argc == 2) + strmap_iterate(&defs->enums, dump_map, NULL); + else { + unsigned int i; + struct cdump_type *t; + + for (i = 2; i < argc; i++) { + t = strmap_get(&defs->enums, argv[i]); + if (!t) + errx(1, "Enum %s not found", argv[i]); + dump_map(argv[i], t, NULL); + } + } + return 0; +} diff --git a/ccan/ccan/strmap/_info b/ccan/ccan/strmap/_info new file mode 100644 index 000000000..33176b304 --- /dev/null +++ b/ccan/ccan/strmap/_info @@ -0,0 +1,63 @@ +#include "config.h" +#include +#include + +/** + * strmap - an ordered map of strings to values + * + * This code implements an ordered map of strings as a critbit tree. See: + * + * http://cr.yp.to/critbit.html + * http://github.com/agl/critbit (which this code is based on) + * + * License: CC0 (but some dependencies are LGPL!) + * Author: Rusty Russell + * Ccanlint: + * license_depends_compat FAIL + * + * Example: + * #include + * #include + * + * static bool dump(const char *member, size_t value, void *unused) + * { + * printf("%s at %zu. ", member, value); + * // true means keep going with iteration. + * return true; + * } + * + * int main(int argc, char *argv[]) + * { + * size_t i; + * struct { STRMAP_MEMBERS(size_t); } map; + * + * strmap_init(&map); + * for (i = 1; i < argc; i++) + * // This only adds the first time for this arg. + * strmap_add(&map, argv[i], i); + * + * strmap_iterate(&map, dump, NULL); + * printf("\n"); + * return 0; + * } + * // Given 'foo' outputs 'foo at 1. ' + * // Given 'foo bar' outputs 'bar at 2. foo at 1. ' + * // Given 'foo foo bar zebra' outputs 'bar at 3. foo at 1. zebra at 4. ' + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + printf("ccan/ilog\n" + "ccan/short_types\n" + "ccan/str\n" + "ccan/tcon\n" + "ccan/typesafe_cb\n"); + return 0; + } + + return 1; +} diff --git a/ccan/ccan/strmap/strmap.c b/ccan/ccan/strmap/strmap.c new file mode 100644 index 000000000..9fa51d0d4 --- /dev/null +++ b/ccan/ccan/strmap/strmap.c @@ -0,0 +1,247 @@ +/* This code is based on ccan/strset.c. */ +#include +#include +#include +#include +#include +#include +#include + +struct node { + /* These point to strings or nodes. */ + struct strmap child[2]; + /* The byte number where first bit differs. */ + size_t byte_num; + /* The bit where these children differ. */ + u8 bit_num; +}; + +/* Closest member to this in a non-empty map. */ +static struct strmap *closest(struct strmap *n, const char *member) +{ + size_t len = strlen(member); + const u8 *bytes = (const u8 *)member; + + /* Anything with NULL value is a node. */ + while (!n->v) { + u8 direction = 0; + + if (n->u.n->byte_num < len) { + u8 c = bytes[n->u.n->byte_num]; + direction = (c >> n->u.n->bit_num) & 1; + } + n = &n->u.n->child[direction]; + } + return n; +} + +void *strmap_get_(const struct strmap *map, const char *member) +{ + struct strmap *n; + + /* Not empty map? */ + if (map->u.n) { + n = closest((struct strmap *)map, member); + if (streq(member, n->u.s)) + return n->v; + } + errno = ENOENT; + return NULL; +} + +bool strmap_add_(struct strmap *map, const char *member, const void *value) +{ + size_t len = strlen(member); + const u8 *bytes = (const u8 *)member; + struct strmap *n; + struct node *newn; + size_t byte_num; + u8 bit_num, new_dir; + + assert(value); + + /* Empty map? */ + if (!map->u.n) { + map->u.s = member; + map->v = (void *)value; + return true; + } + + /* Find closest existing member. */ + n = closest(map, member); + + /* Find where they differ. */ + for (byte_num = 0; n->u.s[byte_num] == member[byte_num]; byte_num++) { + if (member[byte_num] == '\0') { + /* All identical! */ + errno = EEXIST; + return false; + } + } + + /* Find which bit differs (if we had ilog8, we'd use it) */ + bit_num = ilog32_nz((u8)n->u.s[byte_num] ^ bytes[byte_num]) - 1; + assert(bit_num < CHAR_BIT); + + /* Which direction do we go at this bit? */ + new_dir = ((bytes[byte_num]) >> bit_num) & 1; + + /* Allocate new node. */ + newn = malloc(sizeof(*newn)); + if (!newn) { + errno = ENOMEM; + return false; + } + newn->byte_num = byte_num; + newn->bit_num = bit_num; + newn->child[new_dir].v = (void *)value; + newn->child[new_dir].u.s = member; + + /* Find where to insert: not closest, but first which differs! */ + n = map; + while (!n->v) { + u8 direction = 0; + + if (n->u.n->byte_num > byte_num) + break; + /* Subtle: bit numbers are "backwards" for comparison */ + if (n->u.n->byte_num == byte_num && n->u.n->bit_num < bit_num) + break; + + if (n->u.n->byte_num < len) { + u8 c = bytes[n->u.n->byte_num]; + direction = (c >> n->u.n->bit_num) & 1; + } + n = &n->u.n->child[direction]; + } + + newn->child[!new_dir] = *n; + n->u.n = newn; + n->v = NULL; + return true; +} + +char *strmap_del_(struct strmap *map, const char *member, void **valuep) +{ + size_t len = strlen(member); + const u8 *bytes = (const u8 *)member; + struct strmap *parent = NULL, *n; + const char *ret = NULL; + u8 direction = 0; /* prevent bogus gcc warning. */ + + /* Empty map? */ + if (!map->u.n) { + errno = ENOENT; + return NULL; + } + + /* Find closest, but keep track of parent. */ + n = map; + /* Anything with NULL value is a node. */ + while (!n->v) { + u8 c = 0; + + parent = n; + if (n->u.n->byte_num < len) { + c = bytes[n->u.n->byte_num]; + direction = (c >> n->u.n->bit_num) & 1; + } else + direction = 0; + n = &n->u.n->child[direction]; + } + + /* Did we find it? */ + if (!streq(member, n->u.s)) { + errno = ENOENT; + return NULL; + } + + ret = n->u.s; + if (valuep) + *valuep = n->v; + + if (!parent) { + /* We deleted last node. */ + map->u.n = NULL; + } else { + struct node *old = parent->u.n; + /* Raise other node to parent. */ + *parent = old->child[!direction]; + free(old); + } + + return (char *)ret; +} + +static bool iterate(struct strmap n, + bool (*handle)(const char *, void *, void *), + const void *data) +{ + if (n.v) + return handle(n.u.s, n.v, (void *)data); + + return iterate(n.u.n->child[0], handle, data) + && iterate(n.u.n->child[1], handle, data); +} + +void strmap_iterate_(const struct strmap *map, + bool (*handle)(const char *, void *, void *), + const void *data) +{ + /* Empty map? */ + if (!map->u.n) + return; + + iterate(*map, handle, data); +} + +const struct strmap *strmap_prefix_(const struct strmap *map, + const char *prefix) +{ + const struct strmap *n, *top; + size_t len = strlen(prefix); + const u8 *bytes = (const u8 *)prefix; + + /* Empty map -> return empty map. */ + if (!map->u.n) + return map; + + top = n = map; + + /* We walk to find the top, but keep going to check prefix matches. */ + while (!n->v) { + u8 c = 0, direction; + + if (n->u.n->byte_num < len) + c = bytes[n->u.n->byte_num]; + + direction = (c >> n->u.n->bit_num) & 1; + n = &n->u.n->child[direction]; + if (c) + top = n; + } + + if (!strstarts(n->u.s, prefix)) { + /* Convenient return for prefixes which do not appear in map. */ + static const struct strmap empty_map; + return &empty_map; + } + + return top; +} + +static void clear(struct strmap n) +{ + if (!n.v) { + clear(n.u.n->child[0]); + clear(n.u.n->child[1]); + free(n.u.n); + } +} + +void strmap_clear_(struct strmap *map) +{ + if (map->u.n) + clear(*map); + map->u.n = NULL; +} diff --git a/ccan/ccan/strmap/strmap.h b/ccan/ccan/strmap/strmap.h new file mode 100644 index 000000000..8fabc359a --- /dev/null +++ b/ccan/ccan/strmap/strmap.h @@ -0,0 +1,226 @@ +#ifndef CCAN_STRMAP_H +#define CCAN_STRMAP_H +#include "config.h" +#include +#include +#include +#include + +/** + * struct strmap - representation of a string map + * + * It's exposed here to allow you to embed it and so we can inline the + * trivial functions. + */ +struct strmap { + union { + struct node *n; + const char *s; + } u; + void *v; +}; + +/** + * STRMAP_MEMBERS - declare members for a type-specific strmap. + * @type: type for this map's values, or void * for any pointer. + * + * You use this to create your own typed strmap for a particular type. + * You can use an integer type, *but* remember you can't use "0" as a + * value! + * + * Example: + * struct strmap_intp { + * STRMAP_MEMBERS(int *); + * }; + */ +#define STRMAP_MEMBERS(type) \ + struct strmap raw; \ + TCON(type canary) + + +/** + * strmap_init - initialize a string map (empty) + * @map: the typed strmap to initialize. + * + * For completeness; if you've arranged for it to be NULL already you don't + * need this. + * + * Example: + * struct strmap_intp map; + * + * strmap_init(&map); + */ +#define strmap_init(map) strmap_init_(&(map)->raw) + +static inline void strmap_init_(struct strmap *map) +{ + map->u.n = NULL; +} + +/** + * strmap_empty - is this string map empty? + * @map: the typed strmap to check. + * + * Example: + * if (!strmap_empty(&map)) + * abort(); + */ +#define strmap_empty(map) strmap_empty_(&(map)->raw) + +static inline bool strmap_empty_(const struct strmap *map) +{ + return map->u.n == NULL; +} + +/** + * strmap_get - get a value from a string map + * @map: the typed strmap to search. + * @member: the string to search for. + * + * Returns the value, or NULL if it isn't in the map (and sets errno = ENOENT). + * + * Example: + * int *val = strmap_get(&map, "hello"); + * if (val) + * printf("hello => %i\n", *val); + */ +#define strmap_get(map, member) \ + tcon_cast((map), canary, strmap_get_(&(map)->raw, (member))) +void *strmap_get_(const struct strmap *map, const char *member); + +/** + * strmap_add - place a member in the string map. + * @map: the typed strmap to add to. + * @member: the string to place in the map. + * @v: the (non-NULL) value. + * + * This returns false if we run out of memory (errno = ENOMEM), or + * (more normally) if that string already appears in the map (EEXIST). + * + * Note that the pointer is placed in the map, the string is not copied. If + * you want a copy in the map, use strdup(). Similarly for the value. + * + * Example: + * val = malloc(sizeof *val); + * *val = 17; + * if (!strmap_add(&map, "goodbye", val)) + * printf("goodbye was already in the map\n"); + */ +#define strmap_add(map, member, value) \ + strmap_add_(&tcon_check((map), canary, (value))->raw, \ + (member), (void *)(value)) + +bool strmap_add_(struct strmap *map, const char *member, const void *value); + +/** + * strmap_del - remove a member from the string map. + * @map: the typed strmap to delete from. + * @member: the string to remove from the map. + * @valuep: the value (if non-NULL) + * + * This returns the string which was passed to strmap_map(), or NULL if + * it was not in the map (and sets errno = ENOENT). + * + * This means that if you allocated a string (eg. using strdup()), you + * can free it here. Similarly, the value is returned in @valuep if + * @valuep is not NULL. + * + * Example: + * if (!strmap_del(&map, "goodbye", NULL)) + * printf("goodbye was not in the map?\n"); + */ +#define strmap_del(map, member, valuep) \ + strmap_del_(&tcon_check_ptr((map), canary, valuep)->raw, \ + (member), (void **)valuep) +char *strmap_del_(struct strmap *map, const char *member, void **valuep); + +/** + * strmap_clear - remove every member from the map. + * @map: the typed strmap to clear. + * + * The map will be empty after this. + * + * Example: + * strmap_clear(&map); + */ +#define strmap_clear(map) strmap_clear_(&(map)->raw) + +void strmap_clear_(struct strmap *map); + +/** + * strmap_iterate - ordered iteration over a map + * @map: the typed strmap to iterate through. + * @handle: the function to call. + * @arg: the argument for the function (types should match). + * + * @handle's prototype should be: + * bool @handle(const char *member, type value, typeof(arg) arg) + * + * If @handle returns false, the iteration will stop. + * You should not alter the map within the @handle function! + * + * Example: + * struct strmap_intp { + * STRMAP_MEMBERS(int *); + * }; + * static bool dump_some(const char *member, int *value, int *num) + * { + * // Only dump out num nodes. + * if (*(num--) == 0) + * return false; + * printf("%s=>%i\n", member, *value); + * return true; + * } + * + * static void dump_map(const struct strmap_intp *map) + * { + * int max = 100; + * strmap_iterate(map, dump_some, &max); + * if (max < 0) + * printf("... (truncated to 100 entries)\n"); + * } + */ +#define strmap_iterate(map, handle, arg) \ + strmap_iterate_(&(map)->raw, \ + typesafe_cb_cast(bool (*)(const char *, \ + void *, void *), \ + bool (*)(const char *, \ + tcon_type((map), canary), \ + __typeof__(arg)), (handle)), \ + (arg)) +void strmap_iterate_(const struct strmap *map, + bool (*handle)(const char *, void *, void *), + const void *data); + +/** + * strmap_prefix - return a submap matching a prefix + * @map: the map. + * @prefix: the prefix. + * + * This returns a pointer into @map, so don't alter @map while using + * the return value. You can use strmap_iterate(), strmap_get() or + * strmap_empty() on the returned pointer. + * + * Example: + * static void dump_prefix(const struct strmap_intp *map, + * const char *prefix) + * { + * int max = 100; + * printf("Nodes with prefix %s:\n", prefix); + * strmap_iterate(strmap_prefix(map, prefix), dump_some, &max); + * if (max < 0) + * printf("... (truncated to 100 entries)\n"); + * } + */ +#if HAVE_TYPEOF +#define strmap_prefix(map, prefix) \ + ((const __typeof__(map))strmap_prefix_(&(map)->raw, (prefix))) +#else +#define strmap_prefix(map, prefix) \ + ((const void *)strmap_prefix_(&(map)->raw, (prefix))) +#endif + +const struct strmap *strmap_prefix_(const struct strmap *map, + const char *prefix); + +#endif /* CCAN_STRMAP_H */ diff --git a/ccan/ccan/strmap/test/run-iterate-const.c b/ccan/ccan/strmap/test/run-iterate-const.c new file mode 100644 index 000000000..07c814a3d --- /dev/null +++ b/ccan/ccan/strmap/test/run-iterate-const.c @@ -0,0 +1,32 @@ +#include +#include +#include + +static bool found = false; + +/* Make sure const args work. */ +static bool find_string(const char *str, char *member, const char *cmp) +{ + if (strcmp(member, cmp) == 0) + found = true; + return false; +} + +int main(void) +{ + struct strmap_charp { + STRMAP_MEMBERS(char *); + } map; + + plan_tests(3); + + strmap_init(&map); + ok1(strmap_add(&map, "hello", "hello")); + ok1(strmap_add(&map, "world", "world")); + strmap_iterate(&map, find_string, (const char *)"hello"); + ok1(found); + strmap_clear(&map); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/strmap/test/run-order.c b/ccan/ccan/strmap/test/run-order.c new file mode 100644 index 000000000..68062eab0 --- /dev/null +++ b/ccan/ccan/strmap/test/run-order.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include + +#define NUM 1000 + +static bool in_order(const char *member, char *value, unsigned int *count) +{ + int i = atoi(member); + ok1(i == atoi(value)); + ok1(*count == i); + (*count)++; + return true; +} + +static bool in_order_by_2(const char *member, char *value, unsigned int *count) +{ + int i = atoi(member); + ok1(i == atoi(value)); + ok1(*count == i); + (*count) += 2; + return true; +} + +static bool dump(const char *member, char *value, bool *ok) +{ + diag("%s=>%s", member, value); + if (value != member + 1) + *ok = false; + return true; +} + +int main(void) +{ + struct strmap_charp { + STRMAP_MEMBERS(char *); + } map; + unsigned int i; + char *str[NUM]; + bool dump_ok; + + plan_tests(1 + NUM * 4 + 3 * NUM); + strmap_init(&map); + + for (i = 0; i < NUM; i++) { + char template[10]; + sprintf(template, "%08u", i); + str[i] = strdup(template); + } + + for (i = 0; i < NUM; i++) + strmap_add(&map, str[i], str[i]+1); + + dump_ok = true; + strmap_iterate(&map, dump, &dump_ok); + ok1(dump_ok); + + /* Iterate. */ + i = 0; + strmap_iterate(&map, in_order, &i); + + /* Preserve order after deletion. */ + for (i = 0; i < NUM; i += 2) { + char *v; + ok1(strmap_del(&map, str[i], &v) == str[i]); + ok1(v == str[i] + 1); + } + + i = 1; + strmap_iterate(&map, in_order_by_2, &i); + + for (i = 1; i < NUM; i += 2) { + char *v; + ok1(strmap_del(&map, str[i], &v) == str[i]); + ok1(v == str[i] + 1); + } + + /* empty traverse. */ + strmap_iterate(&map, in_order_by_2, (unsigned int *)NULL); + + /* insert backwards, should be fine. */ + for (i = 0; i < NUM; i++) + strmap_add(&map, str[NUM-1-i], str[NUM-1-i]+1); + + i = 0; + strmap_iterate(&map, in_order, &i); + + strmap_clear(&map); + + for (i = 0; i < NUM; i++) + free(str[i]); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/strmap/test/run-prefix.c b/ccan/ccan/strmap/test/run-prefix.c new file mode 100644 index 000000000..9c5156e6e --- /dev/null +++ b/ccan/ccan/strmap/test/run-prefix.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include + +/* Must be > 100, see below. */ +#define NUM 200 + +static bool in_order(const char *index, char *value, unsigned int *count) +{ + int i = atoi(index); + ok1(i == atoi(value)); + ok1(*count == i); + (*count)++; + return true; +} + +static bool find_empty(const char *index, char *value, char *empty) +{ + if (index == empty) + pass("Found empty entry!"); + return true; +} + +int main(void) +{ + struct map { + STRMAP_MEMBERS(char *); + }; + struct map map; + const struct map *sub; + unsigned int i; + char *str[NUM], *empty; + + plan_tests(8 + 2 * (1 + 10 + 100) + 1); + strmap_init(&map); + + for (i = 0; i < NUM; i++) { + char template[10]; + sprintf(template, "%08u", i); + str[i] = strdup(template); + } + + /* All prefixes of an empty map are empty. */ + sub = strmap_prefix(&map, "a"); + ok1(strmap_empty(sub)); + sub = strmap_prefix(&map, ""); + ok1(strmap_empty(sub)); + + for (i = 0; i < NUM; i++) + strmap_add(&map, str[i], str[i]+1); + + /* Nothing */ + sub = strmap_prefix(&map, "a"); + ok1(strmap_empty(sub)); + + /* Everything */ + sub = strmap_prefix(&map, "0"); + ok1(sub->raw.u.n == map.raw.u.n); + sub = strmap_prefix(&map, ""); + ok1(sub->raw.u.n == map.raw.u.n); + + /* Single. */ + sub = strmap_prefix(&map, "00000000"); + i = 0; + strmap_iterate(sub, in_order, &i); + ok1(i == 1); + + /* First 10. */ + sub = strmap_prefix(&map, "0000000"); + i = 0; + strmap_iterate(sub, in_order, &i); + ok1(i == 10); + + /* First 100. */ + sub = strmap_prefix(&map, "000000"); + i = 0; + strmap_iterate(sub, in_order, &i); + ok1(i == 100); + + /* Everything, *plus* empty string. */ + empty = strdup(""); + strmap_add(&map, empty, empty); + + sub = strmap_prefix(&map, ""); + /* Check we get *our* empty string back! */ + strmap_iterate(sub, find_empty, empty); + + strmap_clear(&map); + + for (i = 0; i < NUM; i++) + free(str[i]); + free(empty); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/strmap/test/run.c b/ccan/ccan/strmap/test/run.c new file mode 100644 index 000000000..aaa861812 --- /dev/null +++ b/ccan/ccan/strmap/test/run.c @@ -0,0 +1,80 @@ +#include +#include +#include + +int main(void) +{ + struct strmap_charp { + STRMAP_MEMBERS(char *); + } map; + const char str[] = "hello"; + const char val[] = "there"; + const char none[] = ""; + char *dup = strdup(str); + char *v; + + /* This is how many tests you plan to run */ + plan_tests(42); + + strmap_init(&map); + + ok1(!strmap_get(&map, str)); + ok1(errno == ENOENT); + ok1(!strmap_get(&map, none)); + ok1(errno == ENOENT); + ok1(!strmap_del(&map, str, NULL)); + ok1(errno == ENOENT); + ok1(!strmap_del(&map, none, NULL)); + ok1(errno == ENOENT); + + ok1(strmap_add(&map, str, val)); + ok1(strmap_get(&map, str) == val); + /* We compare the string, not the pointer. */ + ok1(strmap_get(&map, dup) == val); + ok1(!strmap_get(&map, none)); + ok1(errno == ENOENT); + + /* Add a duplicate should fail. */ + ok1(!strmap_add(&map, dup, val)); + ok1(errno == EEXIST); + ok1(strmap_get(&map, dup) == val); + + /* Delete should return original string. */ + ok1(strmap_del(&map, dup, &v) == str); + ok1(v == val); + ok1(!strmap_get(&map, str)); + ok1(errno == ENOENT); + ok1(!strmap_get(&map, none)); + ok1(errno == ENOENT); + + /* Try insert and delete of empty string. */ + ok1(strmap_add(&map, none, none)); + ok1(strmap_get(&map, none) == none); + ok1(!strmap_get(&map, str)); + ok1(errno == ENOENT); + + /* Delete should return original string. */ + ok1(strmap_del(&map, "", &v) == none); + ok1(v == none); + ok1(!strmap_get(&map, str)); + ok1(errno == ENOENT); + ok1(!strmap_get(&map, none)); + ok1(errno == ENOENT); + + /* Both at once... */ + ok1(strmap_add(&map, none, none)); + ok1(strmap_add(&map, str, val)); + ok1(strmap_get(&map, str) == val); + ok1(strmap_get(&map, none) == none); + ok1(strmap_del(&map, "does not exist", NULL) == NULL); + ok1(strmap_del(&map, "", NULL) == none); + ok1(strmap_get(&map, str) == val); + ok1(strmap_del(&map, dup, &v) == str); + ok1(v == val); + + ok1(strmap_empty(&map)); + free(dup); + + /* This exits depending on whether all tests passed */ + return exit_status(); +} diff --git a/ccan/ccan/tcon/LICENSE b/ccan/ccan/tcon/LICENSE new file mode 120000 index 000000000..b7951dabd --- /dev/null +++ b/ccan/ccan/tcon/LICENSE @@ -0,0 +1 @@ +../../licenses/CC0 \ No newline at end of file diff --git a/ccan/ccan/tcon/_info b/ccan/ccan/tcon/_info new file mode 100644 index 000000000..c07e41ed0 --- /dev/null +++ b/ccan/ccan/tcon/_info @@ -0,0 +1,75 @@ +#include "config.h" +#include +#include + +/** + * tcon - routines for creating typesafe generic containers + * + * This code lets users create a structure with a typecanary; your API + * is then a set of macros which check the type canary before calling + * the generic routines. + * + * Example: + * #include + * #include + * + * // A simple container class. Can only contain one thing though! + * struct container { + * void *contents; + * }; + * static inline void container_add_raw(struct container *c, void *p) + * { + * c->contents = p; + * } + * static inline void *container_get_raw(struct container *c) + * { + * return c->contents; + * } + * + * // This lets the user define their container type; includes a + * // "type canary" to check types against. + * #define DEFINE_TYPED_CONTAINER_STRUCT(name, type) \ + * struct name { struct container raw; TCON(type canary); } + * + * // These macros make sure the container type and pointer match. + * #define container_add(c, p) \ + * container_add_raw(&tcon_check((c), canary, (p))->raw, (p)) + * #define container_get(c) \ + * tcon_cast((c), canary, container_get_raw(&(c)->raw)) + * + * // Now, let's define two different containers. + * DEFINE_TYPED_CONTAINER_STRUCT(int_container, int *); + * DEFINE_TYPED_CONTAINER_STRUCT(string_container, char *); + * + * int main(int argc, char *argv[]) + * { + * struct int_container ic; + * struct string_container sc; + * + * // We would get a warning if we used the wrong types... + * container_add(&ic, &argc); + * container_add(&sc, argv[argc-1]); + * + * printf("Last arg is %s of %i arguments\n", + * container_get(&sc), *container_get(&ic) - 1); + * return 0; + * } + * // Given "foo" outputs "Last arg is foo of 1 arguments" + * // Given "foo bar" outputs "Last arg is bar of 2 arguments" + * + * License: CC0 (Public domain) + * + * Author: Rusty Russell + */ +int main(int argc, char *argv[]) +{ + /* Expect exactly one argument */ + if (argc != 2) + return 1; + + if (strcmp(argv[1], "depends") == 0) { + return 0; + } + + return 1; +} diff --git a/ccan/ccan/tcon/tcon.h b/ccan/ccan/tcon/tcon.h new file mode 100644 index 000000000..cf82f3e78 --- /dev/null +++ b/ccan/ccan/tcon/tcon.h @@ -0,0 +1,115 @@ +/* CC0 (Public domain) - see LICENSE file for details */ +#ifndef CCAN_TCON_H +#define CCAN_TCON_H +#include "config.h" + +/** + * TCON - declare a _tcon type containing canary variables. + * @decls: the semi-colon separated list of type canaries. + * + * This declares a _tcon member for a structure. It should be the + * last element in your structure; with sufficient compiler support it + * will not use any actual storage. tcon_check() will compare + * expressions with one of these "type canaries" to cause warnings if + * the container is misused. + * + * A type of "void *" will allow tcon_check() to pass on any (pointer) type. + * + * Example: + * // Simply typesafe linked list. + * struct list_head { + * struct list_head *prev, *next; + * }; + * + * struct string_list { + * struct list_head raw; + * TCON(char *canary); + * }; + * + * // More complex: mapping from one type to another. + * struct map { + * void *contents; + * }; + * + * struct int_to_string_map { + * struct map raw; + * TCON(char *charp_canary; int int_canary); + * }; + */ +#if HAVE_FLEXIBLE_ARRAY_MEMBER +#define TCON(decls) struct { decls; } _tcon[] +#else +#define TCON(decls) struct { decls; } _tcon[1] +#endif + +/** + * tcon_check - typecheck a typed container + * @x: the structure containing the TCON. + * @canary: which canary to check against. + * @expr: the expression whose type must match the TCON (not evaluated) + * + * This macro is used to check that the expression is the type + * expected for this structure (note the "useless" sizeof() argument + * which contains this comparison with the type canary). + * + * It evaluates to @x so you can chain it. + * + * Example: + * #define tlist_add(h, n, member) \ + * list_add(&tcon_check((h), canary, (n))->raw, &(n)->member) + */ +#define tcon_check(x, canary, expr) \ + (sizeof((x)->_tcon[0].canary == (expr)) ? (x) : (x)) + +/** + * tcon_check_ptr - typecheck a typed container + * @x: the structure containing the TCON. + * @canary: which canary to check against. + * @expr: the expression whose type must match &TCON (not evaluated) + * + * This macro is used to check that the expression is a pointer to the type + * expected for this structure (note the "useless" sizeof() argument + * which contains this comparison with the type canary), or NULL. + * + * It evaluates to @x so you can chain it. + */ +#define tcon_check_ptr(x, canary, expr) \ + (sizeof(&(x)->_tcon[0].canary == (expr)) ? (x) : (x)) + + +/** + * tcon_type - the type within a container (or void *) + * @x: the structure containing the TCON. + * @canary: which canary to check against. + */ +#if HAVE_TYPEOF +#define tcon_type(x, canary) __typeof__((x)->_tcon[0].canary) +#else +#define tcon_type(x, canary) void * +#endif + +/** + * tcon_ptr_type - pointer to the type within a container (or void *) + * @x: the structure containing the TCON. + * @canary: which canary to check against. + */ +#if HAVE_TYPEOF +#define tcon_ptr_type(x, canary) __typeof__(&(x)->_tcon[0].canary) +#else +#define tcon_ptr_type(x, canary) void * +#endif + +/** + * tcon_cast - cast to a canary type for this container (or void *) + * @x: a structure containing the TCON. + * @canary: which canary to cast to. + * @expr: the value to cast + * + * This is used to cast to the correct type for this container. If the + * platform doesn't HAVE_TYPEOF, then it casts to void * (which will + * cause a warning if the user doesn't expect a pointer type). + */ +#define tcon_cast(x, canary, expr) ((tcon_type((x), canary))(expr)) +#define tcon_cast_ptr(x, canary, expr) ((tcon_ptr_type((x), canary))(expr)) + +#endif /* CCAN_TCON_H */ diff --git a/ccan/ccan/tcon/test/compile_fail-tcon_cast.c b/ccan/ccan/tcon/test/compile_fail-tcon_cast.c new file mode 100644 index 000000000..3df0333ca --- /dev/null +++ b/ccan/ccan/tcon/test/compile_fail-tcon_cast.c @@ -0,0 +1,29 @@ +#include +#include + +struct container { + void *p; +}; + +struct int_and_charp_container { + struct container raw; + TCON(int *tc1; char *tc2); +}; + +int main(int argc, char *argv[]) +{ + struct int_and_charp_container icon; +#ifdef FAIL +#if !HAVE_TYPEOF +#error We cannot detect type problems without HAVE_TYPEOF +#endif + char * +#else + int * +#endif + x; + + icon.raw.p = NULL; + x = tcon_cast(&icon, tc1, icon.raw.p); + return x != NULL ? 0 : 1; +} diff --git a/ccan/ccan/tcon/test/compile_fail.c b/ccan/ccan/tcon/test/compile_fail.c new file mode 100644 index 000000000..683bbd62f --- /dev/null +++ b/ccan/ccan/tcon/test/compile_fail.c @@ -0,0 +1,25 @@ +#include +#include + +struct container { + void *p; +}; + +struct int_container { + struct container raw; + TCON(int *canary); +}; + +int main(int argc, char *argv[]) +{ + struct int_container icon; +#ifdef FAIL + char * +#else + int * +#endif + x = NULL; + + tcon_check(&icon, canary, x)->raw.p = x; + return 0; +} diff --git a/ccan/ccan/tcon/test/compile_ok-void.c b/ccan/ccan/tcon/test/compile_ok-void.c new file mode 100644 index 000000000..26b712f6b --- /dev/null +++ b/ccan/ccan/tcon/test/compile_ok-void.c @@ -0,0 +1,21 @@ +#include +#include + +struct container { + void *p; +}; + +struct void_container { + struct container raw; + TCON(void *canary); +}; + +int main(int argc, char *argv[]) +{ + struct void_container vcon; + + tcon_check(&vcon, canary, NULL)->raw.p = NULL; + tcon_check(&vcon, canary, argv[0])->raw.p = NULL; + tcon_check(&vcon, canary, main)->raw.p = NULL; + return 0; +} diff --git a/ccan/ccan/tcon/test/compile_ok.c b/ccan/ccan/tcon/test/compile_ok.c new file mode 100644 index 000000000..447f0ee50 --- /dev/null +++ b/ccan/ccan/tcon/test/compile_ok.c @@ -0,0 +1,27 @@ +#include +#include + +struct container { + void *p; +}; + +struct int_container { + struct container raw; + TCON(int tc); +}; + +struct charp_and_int_container { + struct container raw; + TCON(int tc1; char *tc2); +}; + +int main(int argc, char *argv[]) +{ + struct int_container icon; + struct charp_and_int_container cicon; + + tcon_check(&icon, tc, 7)->raw.p = NULL; + tcon_check(&cicon, tc1, 7)->raw.p = argv[0]; + tcon_check(&cicon, tc2, argv[0])->raw.p = argv[0]; + return 0; +}