2 #include <ccan/talloc/talloc.h>
3 #include <ccan/str/str.h>
4 #include <ccan/str_talloc/str_talloc.h>
5 #include <ccan/grab_file/grab_file.h>
6 #include <ccan/noerr/noerr.h>
7 #include <ccan/foreach/foreach.h>
8 #include <ccan/asort/asort.h>
11 #include <sys/types.h>
23 const char *get_ccan_file_contents(struct ccan_file *f)
26 f->contents = grab_file(f, f->fullname, &f->contents_size);
28 err(1, "Reading file %s", f->fullname);
33 char **get_ccan_file_lines(struct ccan_file *f)
36 f->lines = strsplit(f, get_ccan_file_contents(f),
42 struct list_head *get_ccan_file_docs(struct ccan_file *f)
44 if (!f->doc_sections) {
45 get_ccan_file_lines(f);
46 f->doc_sections = extract_doc_sections(f->lines, f->num_lines);
48 return f->doc_sections;
51 struct ccan_file *new_ccan_file(const void *ctx, const char *dir, char *name)
55 assert(dir[0] == '/');
57 f = talloc(ctx, struct ccan_file);
60 f->doc_sections = NULL;
62 f->name = talloc_steal(f, name);
63 f->fullname = talloc_asprintf(f, "%s/%s", dir, f->name);
65 f->cov_compiled = NULL;
69 static void add_files(struct manifest *m, const char *dir)
79 err(1, "Opening directory %s", dir[0] ? dir : ".");
81 while ((ent = readdir(d)) != NULL) {
84 struct list_head *dest;
87 if (ent->d_name[0] == '.')
90 f = new_ccan_file(m, m->dir,
91 talloc_asprintf(m, "%s%s",
93 if (lstat(f->name, &st) != 0)
94 err(1, "lstat %s", f->name);
96 if (S_ISDIR(st.st_mode)) {
97 f->name = talloc_append_string(f->name, "/");
98 add_files(m, f->name);
101 if (!S_ISREG(st.st_mode)) {
106 if (streq(f->name, "_info")) {
111 is_c_src = strends(f->name, ".c");
112 if (!is_c_src && !strends(f->name, ".h")) {
113 dest = &m->other_files;
117 if (!strchr(f->name, '/')) {
122 } else if (strstarts(f->name, "test/")) {
124 if (strstarts(f->name, "test/api"))
125 dest = &m->api_tests;
126 else if (strstarts(f->name, "test/run"))
127 dest = &m->run_tests;
128 else if (strstarts(f->name, "test/compile_ok"))
129 dest = &m->compile_ok_tests;
130 else if (strstarts(f->name, "test/compile_fail"))
131 dest = &m->compile_fail_tests;
133 dest = &m->other_test_c_files;
135 dest = &m->other_test_files;
137 dest = &m->other_files;
139 list_add(dest, &f->list);
144 static int cmp_names(struct ccan_file *const *a, struct ccan_file *const *b,
147 return strcmp((*a)->name, (*b)->name);
150 static void sort_files(struct list_head *list)
152 struct ccan_file **files = NULL, *f;
156 while ((f = list_top(list, struct ccan_file, list)) != NULL) {
157 files = talloc_realloc(NULL, files, struct ccan_file *, num+1);
161 asort(files, num, cmp_names, NULL);
163 for (i = 0; i < num; i++)
164 list_add_tail(list, &files[i]->list);
168 struct manifest *get_manifest(const void *ctx, const char *dir)
170 struct manifest *m = talloc(ctx, struct manifest);
173 struct list_head *list;
177 list_head_init(&m->c_files);
178 list_head_init(&m->h_files);
179 list_head_init(&m->api_tests);
180 list_head_init(&m->run_tests);
181 list_head_init(&m->compile_ok_tests);
182 list_head_init(&m->compile_fail_tests);
183 list_head_init(&m->other_test_c_files);
184 list_head_init(&m->other_test_files);
185 list_head_init(&m->other_files);
186 list_head_init(&m->examples);
187 list_head_init(&m->mangled_examples);
188 list_head_init(&m->deps);
190 olddir = talloc_getcwd(NULL);
192 err(1, "Getting current directory");
195 err(1, "Failed to chdir to %s", dir);
197 m->dir = talloc_getcwd(m);
199 err(1, "Getting current directory");
201 len = strlen(m->dir);
202 while (len && m->dir[len-1] == '/')
203 m->dir[--len] = '\0';
205 m->basename = strrchr(m->dir, '/');
207 errx(1, "I don't expect to be run from the root directory");
210 /* We expect the ccan dir to be two levels above module dir. */
213 ccan_dir = talloc_strdup(NULL, m->dir);
214 p = strrchr(ccan_dir, '/');
216 p = strrchr(ccan_dir, '/');
222 /* Nicer to run tests in a predictable order. */
223 foreach_ptr(list, &m->api_tests, &m->run_tests, &m->compile_ok_tests,
224 &m->compile_fail_tests)
227 if (chdir(olddir) != 0)
228 err(1, "Returning to original directory '%s'", olddir);
236 * remove_comments - strip comments from a line, return copy.
237 * @line: line to copy
238 * @in_comment: are we already within a comment (from prev line).
239 * @unterminated: are we still in a comment for next line.
241 static char *remove_comments(const char *line, bool in_comment,
244 char *p, *ret = talloc_array(line, char, strlen(line) + 1);
249 /* Find first comment. */
250 const char *old_comment = strstr(line, "/*");
251 const char *new_comment = strstr(line, "//");
254 if (new_comment && old_comment)
255 comment = new_comment < old_comment
256 ? new_comment : old_comment;
257 else if (old_comment)
258 comment = old_comment;
259 else if (new_comment)
260 comment = new_comment;
264 *unterminated = false;
268 /* Copy up to comment. */
269 memcpy(p, line, comment - line);
271 line += comment - line + 2;
273 if (comment == new_comment) {
274 /* We're done: goes to EOL. */
276 *unterminated = false;
283 const char *end = strstr(line, "*/");
285 *unterminated = true;
296 static bool is_empty(const char *line)
298 return strspn(line, " \t") == strlen(line);
301 static bool continues(const char *line)
303 /* Technically, any odd number of these. But who cares? */
304 return strends(line, "\\");
307 /* Get token if it's equal to token. */
308 bool get_token(const char **line, const char *token)
312 *line += strspn(*line, " \t");
313 if (isalnum(token[0]) || token[0] == '_')
314 toklen = strspn(*line, IDENT_CHARS);
316 /* FIXME: real tokenizer handles ++ and other multi-chars. */
317 toklen = strlen(token);
320 if (toklen == strlen(token) && !strncmp(*line, token, toklen)) {
327 char *get_symbol_token(void *ctx, const char **line)
332 *line += strspn(*line, " \t");
333 toklen = strspn(*line, IDENT_CHARS);
336 ret = talloc_strndup(ctx, *line, toklen);
341 static bool parse_hash_if(struct pp_conditions *cond, const char **line)
343 bool brackets, defined;
345 cond->inverse = get_token(line, "!");
346 defined = get_token(line, "defined");
347 brackets = get_token(line, "(");
348 cond->symbol = get_symbol_token(cond, line);
351 if (brackets && !get_token(line, ")"))
354 cond->type = PP_COND_IF;
358 /* FIXME: Get serious! */
359 static struct pp_conditions *analyze_directive(struct ccan_file *f,
361 struct pp_conditions *parent)
363 struct pp_conditions *cond = talloc(f, struct pp_conditions);
366 line = remove_comments(line, false, &unused);
368 cond->parent = parent;
369 cond->type = PP_COND_IFDEF;
371 if (!get_token(&line, "#"))
374 if (get_token(&line, "if")) {
375 if (!parse_hash_if(cond, &line))
377 } else if (get_token(&line, "elif")) {
381 cond->parent = parent->parent;
382 /* FIXME: Not quite true. This implies !parent, but we don't
383 * do multiple conditionals yet. */
384 if (!parse_hash_if(cond, &line))
386 } else if (get_token(&line, "ifdef")) {
388 cond->inverse = false;
389 brackets = get_token(&line, "(");
390 cond->symbol = get_symbol_token(cond, &line);
393 if (brackets && !get_token(&line, ")"))
395 } else if (get_token(&line, "ifndef")) {
397 cond->inverse = true;
398 brackets = get_token(&line, "(");
399 cond->symbol = get_symbol_token(cond, &line);
402 if (brackets && !get_token(&line, ")"))
404 } else if (get_token(&line, "else")) {
410 cond->inverse = !cond->inverse;
412 } else if (get_token(&line, "endif")) {
418 return parent->parent;
420 /* Not a conditional. */
430 cond->type = PP_COND_UNKNOWN;
434 /* This parser is rough, but OK if code is reasonably neat. */
435 struct line_info *get_ccan_line_info(struct ccan_file *f)
437 bool continued = false, in_comment = false;
438 struct pp_conditions *cond = NULL;
444 get_ccan_file_lines(f);
445 f->line_info = talloc_array(f->lines, struct line_info, f->num_lines);
447 for (i = 0; i < f->num_lines; continued = continues(f->lines[i++])) {
451 /* Current conditions apply to this line. */
452 f->line_info[i].cond = cond;
453 f->line_info[i].continued = continued;
456 /* Same as last line. */
457 f->line_info[i].type = f->line_info[i-1].type;
458 /* Update in_comment. */
459 remove_comments(f->lines[i], in_comment, &in_comment);
463 /* Preprocessor directive? */
465 && f->lines[i][strspn(f->lines[i], " \t")] == '#') {
466 f->line_info[i].type = PREPROC_LINE;
467 cond = analyze_directive(f, f->lines[i], cond);
471 still_doc_line = (in_comment
472 && f->line_info[i-1].type == DOC_LINE);
474 p = remove_comments(f->lines[i], in_comment, &in_comment);
476 if (strstarts(f->lines[i], "/**") || still_doc_line)
477 f->line_info[i].type = DOC_LINE;
479 f->line_info[i].type = COMMENT_LINE;
481 f->line_info[i].type = CODE_LINE;
488 struct list_node list;
490 const unsigned int *value;
493 static struct symbol *find_symbol(struct list_head *syms, const char *sym)
497 list_for_each(syms, i, list)
498 if (streq(sym, i->name))
503 static enum line_compiled get_pp(struct pp_conditions *cond,
504 struct list_head *syms)
508 enum line_compiled parent, ret;
510 /* No conditions? Easy. */
514 /* Check we get here at all. */
515 parent = get_pp(cond->parent, syms);
516 if (parent == NOT_COMPILED)
519 if (cond->type == PP_COND_UNKNOWN)
520 return MAYBE_COMPILED;
522 sym = find_symbol(syms, cond->symbol);
524 return MAYBE_COMPILED;
526 switch (cond->type) {
528 /* Undefined is 0. */
529 val = sym->value ? *sym->value : 0;
530 if (!val == cond->inverse)
537 if (cond->inverse == !sym->value)
547 /* If parent didn't know, NO == NO, but YES == MAYBE. */
548 if (parent == MAYBE_COMPILED && ret == COMPILED)
549 ret = MAYBE_COMPILED;
553 static void add_symbol(struct list_head *head,
554 const char *symbol, const unsigned int *value)
556 struct symbol *sym = talloc(head, struct symbol);
559 list_add(head, &sym->list);
562 enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
564 const unsigned int *value,
567 enum line_compiled ret;
568 struct list_head *head;
571 head = talloc(NULL, struct list_head);
572 list_head_init(head);
575 add_symbol(head, symbol, value);
577 while ((symbol = va_arg(ap, const char *)) != NULL) {
578 value = va_arg(ap, const unsigned int *);
579 add_symbol(head, symbol, value);
581 ret = get_pp(cond, head);
586 void score_file_error(struct score *score, struct ccan_file *f, unsigned line,
589 struct file_error *fe = talloc(score, struct file_error);
593 list_add_tail(&score->per_file_errors, &fe->list);