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;
176 list_head_init(&m->c_files);
177 list_head_init(&m->h_files);
178 list_head_init(&m->api_tests);
179 list_head_init(&m->run_tests);
180 list_head_init(&m->compile_ok_tests);
181 list_head_init(&m->compile_fail_tests);
182 list_head_init(&m->other_test_c_files);
183 list_head_init(&m->other_test_files);
184 list_head_init(&m->other_files);
185 list_head_init(&m->examples);
186 list_head_init(&m->mangled_examples);
187 list_head_init(&m->dep_dirs);
189 olddir = talloc_getcwd(NULL);
191 err(1, "Getting current directory");
194 err(1, "Failed to chdir to %s", dir);
196 m->dir = talloc_getcwd(m);
198 err(1, "Getting current directory");
200 len = strlen(m->dir);
201 while (len && m->dir[len-1] == '/')
202 m->dir[--len] = '\0';
204 m->basename = strrchr(m->dir, '/');
206 errx(1, "I don't expect to be run from the root directory");
209 /* We expect the ccan dir to be two levels above module dir. */
212 ccan_dir = talloc_strdup(NULL, m->dir);
213 p = strrchr(ccan_dir, '/');
215 p = strrchr(ccan_dir, '/');
221 /* Nicer to run tests in a predictable order. */
222 foreach_ptr(list, &m->api_tests, &m->run_tests, &m->compile_ok_tests,
223 &m->compile_fail_tests)
226 if (chdir(olddir) != 0)
227 err(1, "Returning to original directory '%s'", olddir);
235 * remove_comments - strip comments from a line, return copy.
236 * @line: line to copy
237 * @in_comment: are we already within a comment (from prev line).
238 * @unterminated: are we still in a comment for next line.
240 static char *remove_comments(const char *line, bool in_comment,
243 char *p, *ret = talloc_array(line, char, strlen(line) + 1);
248 /* Find first comment. */
249 const char *old_comment = strstr(line, "/*");
250 const char *new_comment = strstr(line, "//");
253 if (new_comment && old_comment)
254 comment = new_comment < old_comment
255 ? new_comment : old_comment;
256 else if (old_comment)
257 comment = old_comment;
258 else if (new_comment)
259 comment = new_comment;
263 *unterminated = false;
267 /* Copy up to comment. */
268 memcpy(p, line, comment - line);
270 line += comment - line + 2;
272 if (comment == new_comment) {
273 /* We're done: goes to EOL. */
275 *unterminated = false;
282 const char *end = strstr(line, "*/");
284 *unterminated = true;
295 static bool is_empty(const char *line)
297 return strspn(line, " \t") == strlen(line);
300 static bool continues(const char *line)
302 /* Technically, any odd number of these. But who cares? */
303 return strends(line, "\\");
306 /* Get token if it's equal to token. */
307 bool get_token(const char **line, const char *token)
311 *line += strspn(*line, " \t");
312 if (isalnum(token[0]) || token[0] == '_')
313 toklen = strspn(*line, IDENT_CHARS);
315 /* FIXME: real tokenizer handles ++ and other multi-chars. */
316 toklen = strlen(token);
319 if (toklen == strlen(token) && !strncmp(*line, token, toklen)) {
326 char *get_symbol_token(void *ctx, const char **line)
331 *line += strspn(*line, " \t");
332 toklen = strspn(*line, IDENT_CHARS);
335 ret = talloc_strndup(ctx, *line, toklen);
340 static bool parse_hash_if(struct pp_conditions *cond, const char **line)
342 bool brackets, defined;
344 cond->inverse = get_token(line, "!");
345 defined = get_token(line, "defined");
346 brackets = get_token(line, "(");
347 cond->symbol = get_symbol_token(cond, line);
350 if (brackets && !get_token(line, ")"))
353 cond->type = PP_COND_IF;
357 /* FIXME: Get serious! */
358 static struct pp_conditions *analyze_directive(struct ccan_file *f,
360 struct pp_conditions *parent)
362 struct pp_conditions *cond = talloc(f, struct pp_conditions);
365 line = remove_comments(line, false, &unused);
367 cond->parent = parent;
368 cond->type = PP_COND_IFDEF;
370 if (!get_token(&line, "#"))
373 if (get_token(&line, "if")) {
374 if (!parse_hash_if(cond, &line))
376 } else if (get_token(&line, "elif")) {
380 cond->parent = parent->parent;
381 /* FIXME: Not quite true. This implies !parent, but we don't
382 * do multiple conditionals yet. */
383 if (!parse_hash_if(cond, &line))
385 } else if (get_token(&line, "ifdef")) {
387 cond->inverse = false;
388 brackets = get_token(&line, "(");
389 cond->symbol = get_symbol_token(cond, &line);
392 if (brackets && !get_token(&line, ")"))
394 } else if (get_token(&line, "ifndef")) {
396 cond->inverse = true;
397 brackets = get_token(&line, "(");
398 cond->symbol = get_symbol_token(cond, &line);
401 if (brackets && !get_token(&line, ")"))
403 } else if (get_token(&line, "else")) {
409 cond->inverse = !cond->inverse;
411 } else if (get_token(&line, "endif")) {
417 return parent->parent;
419 /* Not a conditional. */
429 cond->type = PP_COND_UNKNOWN;
433 /* This parser is rough, but OK if code is reasonably neat. */
434 struct line_info *get_ccan_line_info(struct ccan_file *f)
436 bool continued = false, in_comment = false;
437 struct pp_conditions *cond = NULL;
443 get_ccan_file_lines(f);
444 f->line_info = talloc_array(f->lines, struct line_info, f->num_lines);
446 for (i = 0; i < f->num_lines; continued = continues(f->lines[i++])) {
450 /* Current conditions apply to this line. */
451 f->line_info[i].cond = cond;
452 f->line_info[i].continued = continued;
455 /* Same as last line. */
456 f->line_info[i].type = f->line_info[i-1].type;
457 /* Update in_comment. */
458 remove_comments(f->lines[i], in_comment, &in_comment);
462 /* Preprocessor directive? */
464 && f->lines[i][strspn(f->lines[i], " \t")] == '#') {
465 f->line_info[i].type = PREPROC_LINE;
466 cond = analyze_directive(f, f->lines[i], cond);
470 still_doc_line = (in_comment
471 && f->line_info[i-1].type == DOC_LINE);
473 p = remove_comments(f->lines[i], in_comment, &in_comment);
475 if (strstarts(f->lines[i], "/**") || still_doc_line)
476 f->line_info[i].type = DOC_LINE;
478 f->line_info[i].type = COMMENT_LINE;
480 f->line_info[i].type = CODE_LINE;
487 struct list_node list;
489 const unsigned int *value;
492 static struct symbol *find_symbol(struct list_head *syms, const char *sym)
496 list_for_each(syms, i, list)
497 if (streq(sym, i->name))
502 static enum line_compiled get_pp(struct pp_conditions *cond,
503 struct list_head *syms)
507 enum line_compiled parent, ret;
509 /* No conditions? Easy. */
513 /* Check we get here at all. */
514 parent = get_pp(cond->parent, syms);
515 if (parent == NOT_COMPILED)
518 if (cond->type == PP_COND_UNKNOWN)
519 return MAYBE_COMPILED;
521 sym = find_symbol(syms, cond->symbol);
523 return MAYBE_COMPILED;
525 switch (cond->type) {
527 /* Undefined is 0. */
528 val = sym->value ? *sym->value : 0;
529 if (!val == cond->inverse)
536 if (cond->inverse == !sym->value)
546 /* If parent didn't know, NO == NO, but YES == MAYBE. */
547 if (parent == MAYBE_COMPILED && ret == COMPILED)
548 ret = MAYBE_COMPILED;
552 static void add_symbol(struct list_head *head,
553 const char *symbol, const unsigned int *value)
555 struct symbol *sym = talloc(head, struct symbol);
558 list_add(head, &sym->list);
561 enum line_compiled get_ccan_line_pp(struct pp_conditions *cond,
563 const unsigned int *value,
566 enum line_compiled ret;
567 struct list_head *head;
570 head = talloc(NULL, struct list_head);
571 list_head_init(head);
574 add_symbol(head, symbol, value);
576 while ((symbol = va_arg(ap, const char *)) != NULL) {
577 value = va_arg(ap, const unsigned int *);
578 add_symbol(head, symbol, value);
580 ret = get_pp(cond, head);
585 void score_file_error(struct score *score, struct ccan_file *f, unsigned line,
588 struct file_error *fe = talloc(score, struct file_error);
592 list_add_tail(&score->per_file_errors, &fe->list);