Rusty Russell
9 years ago
28 changed files with 2845 additions and 0 deletions
@ -0,0 +1 @@ |
|||
../../licenses/BSD-MIT |
@ -0,0 +1,94 @@ |
|||
#include "config.h" |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
/** |
|||
* cdump - routines to parse simple C structures. |
|||
* |
|||
* This code is designed to produce data structures summarizing C code. |
|||
* It only operates on simple, well-formed C code (eg. specific headers |
|||
* which you want to autogenerate from), but it should be fairly easy to |
|||
* enhance if desired. |
|||
* |
|||
* Author: Rusty Russell <rusty@rustcorp.com.au> |
|||
* License: BSD-MIT |
|||
* |
|||
* Example: |
|||
* // Creates a simple print function for a structure. |
|||
* #include <ccan/cdump/cdump.h> |
|||
* #include <ccan/tal/grab_file/grab_file.h> |
|||
* #include <ccan/err/err.h> |
|||
* |
|||
* static void print_as(const char *fmt, const char *member_name) |
|||
* { |
|||
* printf("\tprintf(\"%%s:%s\\n\", \"%s\", s->%s);\n", |
|||
* fmt, member_name, member_name); |
|||
* } |
|||
* |
|||
* int main(int argc, char *argv[]) |
|||
* { |
|||
* char *code, *problems; |
|||
* struct cdump_definitions *defs; |
|||
* int i, j; |
|||
* |
|||
* // Read code from stdin. |
|||
* code = grab_file(NULL, NULL); |
|||
* |
|||
* defs = cdump_extract(NULL, code, &problems); |
|||
* if (!defs) |
|||
* errx(1, "Parsing stdin: %s", problems); |
|||
* |
|||
* for (i = 1; i < argc; i++) { |
|||
* struct cdump_type *t = strmap_get(&defs->structs, argv[i]); |
|||
* if (!t) |
|||
* errx(1, "Could not find struct %s", argv[i]); |
|||
* |
|||
* printf("void print_struct_%s(const struct %s *s)\n" |
|||
* "{\n", argv[i], argv[i]); |
|||
* for (j = 0; j < tal_count(t->u.members); j++) { |
|||
* const struct cdump_member *m = t->u.members + j; |
|||
* switch (m->type->kind) { |
|||
* case CDUMP_STRUCT: |
|||
* case CDUMP_UNION: |
|||
* case CDUMP_ARRAY: |
|||
* // Too hard for this simple example. |
|||
* printf("\tprintf(\"%%s:???\\n\", \"%s\");\n", |
|||
* m->name); |
|||
* break; |
|||
* case CDUMP_ENUM: |
|||
* print_as("%i", m->name); |
|||
* break; |
|||
* case CDUMP_POINTER: |
|||
* print_as("%p", m->name); |
|||
* break; |
|||
* case CDUMP_UNKNOWN: |
|||
* if (!strcmp(m->type->name, "int")) |
|||
* print_as("%i", m->name); |
|||
* else if (!strcmp(m->type->name, "long int")) |
|||
* print_as("%li", m->name); |
|||
* else if (!strcmp(m->type->name, "unsigned int")) |
|||
* print_as("%u", m->name); |
|||
* // etc... |
|||
* break; |
|||
* } |
|||
* } |
|||
* printf("}\n"); |
|||
* } |
|||
* return 0; |
|||
* } |
|||
*/ |
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
/* Expect exactly one argument */ |
|||
if (argc != 2) |
|||
return 1; |
|||
|
|||
if (strcmp(argv[1], "depends") == 0) { |
|||
printf("ccan/tal\n"); |
|||
printf("ccan/tal/str\n"); |
|||
printf("ccan/strmap\n"); |
|||
return 0; |
|||
} |
|||
|
|||
return 1; |
|||
} |
@ -0,0 +1,699 @@ |
|||
/* MIT (BSD) license - see LICENSE file for details */ |
|||
#include "cdump.h" |
|||
#include <ccan/tal/str/str.h> |
|||
#include <assert.h> |
|||
|
|||
struct token { |
|||
const char *p; |
|||
size_t len; |
|||
}; |
|||
|
|||
static void add_token(struct token **toks, const char *p, size_t len) |
|||
{ |
|||
size_t n = tal_count(*toks); |
|||
tal_resize(toks, n+1); |
|||
(*toks)[n].p = p; |
|||
(*toks)[n].len = len; |
|||
} |
|||
|
|||
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; |
|||
} |
@ -0,0 +1,106 @@ |
|||
/* MIT (BSD) license - see LICENSE file for details */ |
|||
#ifndef CCAN_CDUMP_H |
|||
#define CCAN_CDUMP_H |
|||
#include <ccan/strmap/strmap.h> |
|||
#include <ccan/tal/tal.h> |
|||
|
|||
enum cdump_type_kind { |
|||
CDUMP_STRUCT, |
|||
CDUMP_UNION, |
|||
CDUMP_ENUM, |
|||
CDUMP_ARRAY, |
|||
CDUMP_POINTER, |
|||
CDUMP_UNKNOWN |
|||
}; |
|||
|
|||
struct cdump_member { |
|||
const char *name; |
|||
const 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 */ |
@ -0,0 +1,176 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/cdump/cdump.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct cdump_definitions *defs; |
|||
const struct cdump_type *t, *p; |
|||
char *ctx = tal(NULL, char), *problems; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(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(); |
|||
} |
@ -0,0 +1,54 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/cdump/cdump.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct cdump_definitions *defs; |
|||
const struct cdump_type *t; |
|||
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(); |
|||
} |
@ -0,0 +1,68 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/cdump/cdump.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct cdump_definitions *defs; |
|||
const struct cdump_type *t; |
|||
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(); |
|||
} |
@ -0,0 +1,34 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/cdump/cdump.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct cdump_definitions *defs; |
|||
const struct cdump_type *t; |
|||
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(); |
|||
} |
@ -0,0 +1,43 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/cdump/cdump.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct cdump_definitions *defs; |
|||
const struct cdump_type *t, *t2; |
|||
char *ctx = tal(NULL, char), *problems; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(16); |
|||
|
|||
defs = cdump_extract(ctx, "struct foo { struct bar *bar; };\n" |
|||
"struct bar { int x; };", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
|
|||
t = strmap_get(&defs->structs, "foo"); |
|||
ok1(t); |
|||
t2 = strmap_get(&defs->structs, "bar"); |
|||
ok1(t2); |
|||
|
|||
ok1(t2->kind == CDUMP_STRUCT); |
|||
ok1(streq(t2->name, "bar")); |
|||
ok1(tal_count(t2->u.members) == 1); |
|||
ok1(t2->u.members[0].type->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(t2->u.members[0].type->name, "int")); |
|||
|
|||
ok1(t->kind == CDUMP_STRUCT); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.members) == 1); |
|||
ok1(streq(t->u.members[0].name, "bar")); |
|||
ok1(t->u.members[0].type->kind == CDUMP_POINTER); |
|||
ok1(t->u.members[0].type->u.ptr == t2); |
|||
|
|||
tal_free(ctx); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -0,0 +1,75 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/cdump/cdump.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct cdump_definitions *defs; |
|||
const struct cdump_type *t; |
|||
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(); |
|||
} |
@ -0,0 +1,130 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/cdump/cdump.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct cdump_definitions *defs; |
|||
const struct cdump_type *t, *p; |
|||
char *ctx = tal(NULL, char), *problems; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(63); |
|||
|
|||
defs = cdump_extract(ctx, |
|||
"struct foo {\n" |
|||
" long l;\n" |
|||
" long int li;\n" |
|||
" unsigned long *ulp;\n" |
|||
" unsigned long int *ulip;\n" |
|||
"};", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
|
|||
ok1(strmap_empty(&defs->enums)); |
|||
ok1(strmap_empty(&defs->unions)); |
|||
t = strmap_get(&defs->structs, "foo"); |
|||
ok1(t); |
|||
ok1(t->kind == CDUMP_STRUCT); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.members) == 4); |
|||
|
|||
ok1(streq(t->u.members[0].name, "l")); |
|||
p = t->u.members[0].type; |
|||
ok1(p->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(p->name, "long")); |
|||
|
|||
ok1(streq(t->u.members[1].name, "li")); |
|||
p = t->u.members[1].type; |
|||
ok1(p->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(p->name, "long int")); |
|||
|
|||
ok1(streq(t->u.members[2].name, "ulp")); |
|||
p = t->u.members[2].type; |
|||
ok1(p->kind == CDUMP_POINTER); |
|||
p = p->u.ptr; |
|||
ok1(p->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(p->name, "unsigned long")); |
|||
|
|||
ok1(streq(t->u.members[3].name, "ulip")); |
|||
p = t->u.members[3].type; |
|||
ok1(p->kind == CDUMP_POINTER); |
|||
p = p->u.ptr; |
|||
ok1(p->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(p->name, "unsigned long int")); |
|||
|
|||
defs = cdump_extract(ctx, |
|||
"struct foo {\n" |
|||
" volatile long vl;\n" |
|||
" const long cl;\n" |
|||
" volatile const long long int *vclli;\n" |
|||
"};", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
|
|||
ok1(strmap_empty(&defs->enums)); |
|||
ok1(strmap_empty(&defs->unions)); |
|||
t = strmap_get(&defs->structs, "foo"); |
|||
ok1(t); |
|||
ok1(t->kind == CDUMP_STRUCT); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.members) == 3); |
|||
|
|||
ok1(streq(t->u.members[0].name, "vl")); |
|||
ok1(streq(t->u.members[0].qualifiers, "volatile")); |
|||
p = t->u.members[0].type; |
|||
ok1(p->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(p->name, "long")); |
|||
|
|||
ok1(streq(t->u.members[1].name, "cl")); |
|||
ok1(streq(t->u.members[1].qualifiers, "const")); |
|||
p = t->u.members[1].type; |
|||
ok1(p->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(p->name, "long")); |
|||
|
|||
ok1(streq(t->u.members[2].name, "vclli")); |
|||
ok1(streq(t->u.members[2].qualifiers, "volatile const")); |
|||
p = t->u.members[2].type; |
|||
ok1(p->kind == CDUMP_POINTER); |
|||
p = p->u.ptr; |
|||
ok1(p->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(p->name, "long long int")); |
|||
|
|||
defs = cdump_extract(ctx, |
|||
"struct foo {\n" |
|||
" volatile struct bar *a, b;\n" |
|||
"};", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
|
|||
ok1(strmap_empty(&defs->enums)); |
|||
ok1(strmap_empty(&defs->unions)); |
|||
t = strmap_get(&defs->structs, "foo"); |
|||
ok1(t); |
|||
ok1(t->kind == CDUMP_STRUCT); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.members) == 2); |
|||
|
|||
ok1(streq(t->u.members[0].name, "a")); |
|||
ok1(streq(t->u.members[0].qualifiers, "volatile")); |
|||
p = t->u.members[0].type; |
|||
ok1(p->kind == CDUMP_POINTER); |
|||
p = p->u.ptr; |
|||
ok1(p->kind == CDUMP_STRUCT); |
|||
ok1(streq(p->name, "bar")); |
|||
|
|||
ok1(streq(t->u.members[1].name, "b")); |
|||
ok1(streq(t->u.members[1].qualifiers, "volatile")); |
|||
p = t->u.members[1].type; |
|||
ok1(p->kind == CDUMP_STRUCT); |
|||
ok1(streq(p->name, "bar")); |
|||
|
|||
tal_free(ctx); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -0,0 +1,152 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
/* Include the C files directly. */ |
|||
#include <ccan/cdump/cdump.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
int main(void) |
|||
{ |
|||
struct cdump_definitions *defs; |
|||
const struct cdump_type *t, *p; |
|||
char *ctx = tal(NULL, char), *problems; |
|||
|
|||
/* This is how many tests you plan to run */ |
|||
plan_tests(94); |
|||
|
|||
defs = cdump_extract(ctx, "enum foo { BAR };", NULL); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
|
|||
ok1(strmap_empty(&defs->structs)); |
|||
ok1(strmap_empty(&defs->unions)); |
|||
t = strmap_get(&defs->enums, "foo"); |
|||
ok1(t); |
|||
ok1(t->kind == CDUMP_ENUM); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.enum_vals) == 1); |
|||
ok1(streq(t->u.enum_vals[0].name, "BAR")); |
|||
ok1(!t->u.enum_vals[0].value); |
|||
|
|||
defs = cdump_extract(ctx, "enum foo { BAR = 7 };", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
|
|||
ok1(strmap_empty(&defs->structs)); |
|||
ok1(strmap_empty(&defs->unions)); |
|||
t = strmap_get(&defs->enums, "foo"); |
|||
ok1(t); |
|||
ok1(t->kind == CDUMP_ENUM); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.enum_vals) == 1); |
|||
ok1(streq(t->u.enum_vals[0].name, "BAR")); |
|||
ok1(streq(t->u.enum_vals[0].value, "7")); |
|||
|
|||
defs = cdump_extract(ctx, "enum foo { BAR = 7, BAZ, FUZZ };", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
|
|||
ok1(strmap_empty(&defs->structs)); |
|||
ok1(strmap_empty(&defs->unions)); |
|||
t = strmap_get(&defs->enums, "foo"); |
|||
ok1(t); |
|||
ok1(t->kind == CDUMP_ENUM); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.enum_vals) == 3); |
|||
ok1(streq(t->u.enum_vals[0].name, "BAR")); |
|||
ok1(streq(t->u.enum_vals[0].value, "7")); |
|||
ok1(streq(t->u.enum_vals[1].name, "BAZ")); |
|||
ok1(!t->u.enum_vals[1].value); |
|||
ok1(streq(t->u.enum_vals[2].name, "FUZZ")); |
|||
ok1(!t->u.enum_vals[2].value); |
|||
|
|||
defs = cdump_extract(ctx, "struct foo { int x; };", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
|
|||
ok1(strmap_empty(&defs->enums)); |
|||
ok1(strmap_empty(&defs->unions)); |
|||
t = strmap_get(&defs->structs, "foo"); |
|||
ok1(t); |
|||
ok1(t->kind == CDUMP_STRUCT); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.members) == 1); |
|||
ok1(streq(t->u.members[0].name, "x")); |
|||
ok1(t->u.members[0].type->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(t->u.members[0].type->name, "int")); |
|||
|
|||
defs = cdump_extract(ctx, "struct foo { int x[5<< 1]; struct foo *next; struct unknown **ptrs[10]; };", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
|
|||
ok1(strmap_empty(&defs->enums)); |
|||
ok1(strmap_empty(&defs->unions)); |
|||
t = strmap_get(&defs->structs, "foo"); |
|||
ok1(t); |
|||
ok1(t->kind == CDUMP_STRUCT); |
|||
ok1(streq(t->name, "foo")); |
|||
ok1(tal_count(t->u.members) == 3); |
|||
|
|||
ok1(streq(t->u.members[0].name, "x")); |
|||
ok1(t->u.members[0].type->kind == CDUMP_ARRAY); |
|||
ok1(streq(t->u.members[0].type->u.arr.size, "5<< 1")); |
|||
ok1(t->u.members[0].type->u.arr.type->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(t->u.members[0].type->u.arr.type->name, "int")); |
|||
|
|||
ok1(streq(t->u.members[1].name, "next")); |
|||
ok1(t->u.members[1].type->kind == CDUMP_POINTER); |
|||
ok1(t->u.members[1].type->u.ptr == t); |
|||
|
|||
ok1(streq(t->u.members[2].name, "ptrs")); |
|||
p = t->u.members[2].type; |
|||
ok1(p->kind == CDUMP_ARRAY); |
|||
ok1(streq(p->u.arr.size, "10")); |
|||
p = p->u.arr.type; |
|||
ok1(p->kind == CDUMP_POINTER); |
|||
p = p->u.ptr; |
|||
ok1(p->kind == CDUMP_POINTER); |
|||
p = p->u.ptr; |
|||
ok1(p->kind == CDUMP_STRUCT); |
|||
ok1(streq(p->name, "unknown")); |
|||
ok1(p->u.members == NULL); |
|||
|
|||
/* We don't put undefined structs into definition maps. */ |
|||
ok1(!strmap_get(&defs->structs, "unknown")); |
|||
|
|||
/* unions and comments. */ |
|||
defs = cdump_extract(ctx, "#if 0\n" |
|||
"/* Normal comment */\n" |
|||
"struct foo { int x[5 * 7/* Comment */]; };\n" |
|||
"// One-line comment\n" |
|||
"union bar { enum sometype x; union yun// Comment\n" |
|||
" y;};\n" |
|||
"#endif", &problems); |
|||
ok1(defs); |
|||
ok1(tal_parent(defs) == ctx); |
|||
ok1(!problems); |
|||
t = strmap_get(&defs->structs, "foo"); |
|||
ok1(t); |
|||
ok1(tal_count(t->u.members) == 1); |
|||
ok1(streq(t->u.members[0].name, "x")); |
|||
ok1(t->u.members[0].type->kind == CDUMP_ARRAY); |
|||
ok1(streq(t->u.members[0].type->u.arr.size, "5 * 7")); |
|||
ok1(t->u.members[0].type->u.arr.type->kind == CDUMP_UNKNOWN); |
|||
ok1(streq(t->u.members[0].type->u.arr.type->name, "int")); |
|||
|
|||
t = strmap_get(&defs->unions, "bar"); |
|||
ok1(t); |
|||
ok1(tal_count(t->u.members) == 2); |
|||
ok1(streq(t->u.members[0].name, "x")); |
|||
ok1(t->u.members[0].type->kind == CDUMP_ENUM); |
|||
ok1(streq(t->u.members[0].type->name, "sometype")); |
|||
ok1(!t->u.members[0].type->u.enum_vals); |
|||
ok1(streq(t->u.members[1].name, "y")); |
|||
ok1(t->u.members[1].type->kind == CDUMP_UNION); |
|||
ok1(streq(t->u.members[1].type->name, "yun")); |
|||
ok1(!t->u.members[1].type->u.members); |
|||
|
|||
/* This exits depending on whether all tests passed */ |
|||
return exit_status(); |
|||
} |
@ -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 $@ $< |
@ -0,0 +1,52 @@ |
|||
#include <ccan/cdump/cdump.h> |
|||
#include <ccan/tal/grab_file/grab_file.h> |
|||
#include <ccan/err/err.h> |
|||
|
|||
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 <filename> [<enums>...]"); |
|||
|
|||
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; |
|||
} |
@ -0,0 +1,63 @@ |
|||
#include "config.h" |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
/** |
|||
* 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 <rusty@rustcorp.com.au> |
|||
* Ccanlint: |
|||
* license_depends_compat FAIL |
|||
* |
|||
* Example: |
|||
* #include <ccan/strmap/strmap.h> |
|||
* #include <stdio.h> |
|||
* |
|||
* 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; |
|||
} |
@ -0,0 +1,247 @@ |
|||
/* This code is based on ccan/strset.c. */ |
|||
#include <ccan/strmap/strmap.h> |
|||
#include <ccan/short_types/short_types.h> |
|||
#include <ccan/str/str.h> |
|||
#include <ccan/ilog/ilog.h> |
|||
#include <assert.h> |
|||
#include <stdlib.h> |
|||
#include <errno.h> |
|||
|
|||
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; |
|||
} |
@ -0,0 +1,226 @@ |
|||
#ifndef CCAN_STRMAP_H |
|||
#define CCAN_STRMAP_H |
|||
#include "config.h" |
|||
#include <ccan/tcon/tcon.h> |
|||
#include <ccan/typesafe_cb/typesafe_cb.h> |
|||
#include <stdlib.h> |
|||
#include <stdbool.h> |
|||
|
|||
/**
|
|||
* 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 */ |
@ -0,0 +1,32 @@ |
|||
#include <ccan/strmap/strmap.h> |
|||
#include <ccan/strmap/strmap.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
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(); |
|||
} |
@ -0,0 +1,96 @@ |
|||
#include <ccan/strmap/strmap.h> |
|||
#include <ccan/strmap/strmap.c> |
|||
#include <ccan/tap/tap.h> |
|||
#include <stdio.h> |
|||
|
|||
#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(); |
|||
} |
@ -0,0 +1,97 @@ |
|||
#include <ccan/strmap/strmap.h> |
|||
#include <ccan/strmap/strmap.c> |
|||
#include <ccan/tap/tap.h> |
|||
#include <stdio.h> |
|||
|
|||
/* 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(); |
|||
} |
@ -0,0 +1,80 @@ |
|||
#include <ccan/strmap/strmap.h> |
|||
#include <ccan/strmap/strmap.c> |
|||
#include <ccan/tap/tap.h> |
|||
|
|||
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(); |
|||
} |
@ -0,0 +1 @@ |
|||
../../licenses/CC0 |
@ -0,0 +1,75 @@ |
|||
#include "config.h" |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
/** |
|||
* 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 <ccan/tcon/tcon.h> |
|||
* #include <stdio.h> |
|||
* |
|||
* // 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 <rusty@rustcorp.com.au> |
|||
*/ |
|||
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; |
|||
} |
@ -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 */ |
@ -0,0 +1,29 @@ |
|||
#include <ccan/tcon/tcon.h> |
|||
#include <stdlib.h> |
|||
|
|||
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; |
|||
} |
@ -0,0 +1,25 @@ |
|||
#include <ccan/tcon/tcon.h> |
|||
#include <stdlib.h> |
|||
|
|||
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; |
|||
} |
@ -0,0 +1,21 @@ |
|||
#include <ccan/tcon/tcon.h> |
|||
#include <stdlib.h> |
|||
|
|||
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; |
|||
} |
@ -0,0 +1,27 @@ |
|||
#include <ccan/tcon/tcon.h> |
|||
#include <stdlib.h> |
|||
|
|||
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; |
|||
} |
Loading…
Reference in new issue