435 lines
6.9 KiB
C
435 lines
6.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2019 Cyril Hrubis <chrubis@suse.cz>
|
|
* Copyright (c) 2020 Petr Vorel <pvorel@suse.cz>
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <libgen.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
|
|
#include "data_storage.h"
|
|
|
|
#define WARN(str) fprintf(stderr, "WARNING: " str "\n")
|
|
|
|
static void oneline_comment(FILE *f)
|
|
{
|
|
int c;
|
|
|
|
do {
|
|
c = getc(f);
|
|
} while (c != '\n');
|
|
}
|
|
|
|
static const char *eat_asterisk_space(const char *c)
|
|
{
|
|
unsigned int i = 0;
|
|
|
|
while (isspace(c[i]))
|
|
i++;
|
|
|
|
if (c[i] == '*') {
|
|
if (isspace(c[i+1]))
|
|
i++;
|
|
return &c[i+1];
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
static void multiline_comment(FILE *f, struct data_node *doc)
|
|
{
|
|
int c;
|
|
int state = 0;
|
|
char buf[4096];
|
|
unsigned int bufp = 0;
|
|
|
|
for (;;) {
|
|
c = getc(f);
|
|
|
|
if (doc) {
|
|
if (c == '\n') {
|
|
struct data_node *line;
|
|
buf[bufp] = 0;
|
|
line = data_node_string(eat_asterisk_space(buf));
|
|
if (data_node_array_add(doc, line))
|
|
WARN("doc string comment truncated");
|
|
bufp = 0;
|
|
continue;
|
|
}
|
|
|
|
if (bufp + 1 >= sizeof(buf))
|
|
continue;
|
|
|
|
buf[bufp++] = c;
|
|
}
|
|
|
|
switch (state) {
|
|
case 0:
|
|
if (c == '*')
|
|
state = 1;
|
|
break;
|
|
case 1:
|
|
switch (c) {
|
|
case '/':
|
|
return;
|
|
case '*':
|
|
continue;
|
|
default:
|
|
state = 0;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static const char doc_prefix[] = "\\\n";
|
|
|
|
static void maybe_doc_comment(FILE *f, struct data_node *doc)
|
|
{
|
|
int c, i;
|
|
|
|
for (i = 0; doc_prefix[i]; i++) {
|
|
c = getc(f);
|
|
|
|
if (c == doc_prefix[i])
|
|
continue;
|
|
|
|
if (c == '*')
|
|
ungetc(c, f);
|
|
|
|
multiline_comment(f, NULL);
|
|
return;
|
|
}
|
|
|
|
multiline_comment(f, doc);
|
|
}
|
|
|
|
static void maybe_comment(FILE *f, struct data_node *doc)
|
|
{
|
|
int c = getc(f);
|
|
|
|
switch (c) {
|
|
case '/':
|
|
oneline_comment(f);
|
|
break;
|
|
case '*':
|
|
maybe_doc_comment(f, doc);
|
|
break;
|
|
default:
|
|
ungetc(c, f);
|
|
break;
|
|
}
|
|
}
|
|
|
|
const char *next_token(FILE *f, struct data_node *doc)
|
|
{
|
|
size_t i = 0;
|
|
static char buf[4096];
|
|
int c;
|
|
int in_str = 0;
|
|
|
|
for (;;) {
|
|
c = fgetc(f);
|
|
|
|
if (c == EOF)
|
|
goto exit;
|
|
|
|
if (in_str) {
|
|
if (c == '"') {
|
|
if (i == 0 || buf[i-1] != '\\')
|
|
goto exit;
|
|
}
|
|
|
|
buf[i++] = c;
|
|
continue;
|
|
}
|
|
|
|
switch (c) {
|
|
case '{':
|
|
case '}':
|
|
case ';':
|
|
case '(':
|
|
case ')':
|
|
case '=':
|
|
case ',':
|
|
case '[':
|
|
case ']':
|
|
if (i) {
|
|
ungetc(c, f);
|
|
goto exit;
|
|
}
|
|
|
|
buf[i++] = c;
|
|
goto exit;
|
|
case '0' ... '9':
|
|
case 'a' ... 'z':
|
|
case 'A' ... 'Z':
|
|
case '.':
|
|
case '_':
|
|
case '-':
|
|
buf[i++] = c;
|
|
break;
|
|
case '/':
|
|
maybe_comment(f, doc);
|
|
break;
|
|
case '"':
|
|
in_str = 1;
|
|
break;
|
|
case ' ':
|
|
case '\n':
|
|
case '\t':
|
|
if (i)
|
|
goto exit;
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
if (i == 0 && !in_str)
|
|
return NULL;
|
|
|
|
buf[i] = 0;
|
|
return buf;
|
|
}
|
|
|
|
static int parse_array(FILE *f, struct data_node *node)
|
|
{
|
|
const char *token;
|
|
|
|
for (;;) {
|
|
if (!(token = next_token(f, NULL)))
|
|
return 1;
|
|
|
|
if (!strcmp(token, "{")) {
|
|
struct data_node *ret = data_node_array();
|
|
parse_array(f, ret);
|
|
|
|
if (data_node_array_len(ret))
|
|
data_node_array_add(node, ret);
|
|
else
|
|
data_node_free(ret);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(token, "}"))
|
|
return 0;
|
|
|
|
if (!strcmp(token, ","))
|
|
continue;
|
|
|
|
if (!strcmp(token, "NULL"))
|
|
continue;
|
|
|
|
struct data_node *str = data_node_string(token);
|
|
|
|
data_node_array_add(node, str);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_test_struct(FILE *f, struct data_node *doc, struct data_node *node)
|
|
{
|
|
const char *token;
|
|
char *id = NULL;
|
|
int state = 0;
|
|
struct data_node *ret;
|
|
|
|
for (;;) {
|
|
if (!(token = next_token(f, doc)))
|
|
return 1;
|
|
|
|
if (!strcmp(token, "}"))
|
|
return 0;
|
|
|
|
switch (state) {
|
|
case 0:
|
|
id = strdup(token);
|
|
state = 1;
|
|
continue;
|
|
case 1:
|
|
if (!strcmp(token, "="))
|
|
state = 2;
|
|
else
|
|
WARN("Expected '='");
|
|
continue;
|
|
case 2:
|
|
if (!strcmp(token, "(")) {
|
|
state = 3;
|
|
continue;
|
|
}
|
|
break;
|
|
case 3:
|
|
if (!strcmp(token, ")"))
|
|
state = 2;
|
|
continue;
|
|
|
|
case 4:
|
|
if (!strcmp(token, ","))
|
|
state = 0;
|
|
continue;
|
|
}
|
|
|
|
if (!strcmp(token, "{")) {
|
|
ret = data_node_array();
|
|
parse_array(f, ret);
|
|
} else {
|
|
ret = data_node_string(token);
|
|
}
|
|
|
|
const char *key = id;
|
|
if (key[0] == '.')
|
|
key++;
|
|
|
|
data_node_hash_add(node, key, ret);
|
|
free(id);
|
|
state = 4;
|
|
}
|
|
}
|
|
|
|
static const char *tokens[] = {
|
|
"static",
|
|
"struct",
|
|
"tst_test",
|
|
"test",
|
|
"=",
|
|
"{",
|
|
};
|
|
|
|
static struct data_node *parse_file(const char *fname)
|
|
{
|
|
int state = 0, found = 0;
|
|
const char *token;
|
|
|
|
if (access(fname, F_OK)) {
|
|
fprintf(stderr, "file %s does not exist\n", fname);
|
|
return NULL;
|
|
}
|
|
|
|
FILE *f = fopen(fname, "r");
|
|
|
|
struct data_node *res = data_node_hash();
|
|
struct data_node *doc = data_node_array();
|
|
|
|
while ((token = next_token(f, doc))) {
|
|
if (state < 6 && !strcmp(tokens[state], token))
|
|
state++;
|
|
else
|
|
state = 0;
|
|
|
|
if (state < 6)
|
|
continue;
|
|
|
|
found = 1;
|
|
parse_test_struct(f, doc, res);
|
|
}
|
|
|
|
|
|
if (data_node_array_len(doc)) {
|
|
data_node_hash_add(res, "doc", doc);
|
|
found = 1;
|
|
} else {
|
|
data_node_free(doc);
|
|
}
|
|
|
|
fclose(f);
|
|
|
|
if (!found) {
|
|
data_node_free(res);
|
|
return NULL;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static const char *filter_out[] = {
|
|
"bufs",
|
|
"cleanup",
|
|
"mntpoint",
|
|
"setup",
|
|
"tcnt",
|
|
"test",
|
|
"test_all",
|
|
NULL
|
|
};
|
|
|
|
static struct implies {
|
|
const char *flag;
|
|
const char **implies;
|
|
} implies[] = {
|
|
{"mount_device", (const char *[]) {"format_device", "needs_device",
|
|
"needs_tmpdir", NULL}},
|
|
{"format_device", (const char *[]) {"needs_device", "needs_tmpdir",
|
|
NULL}},
|
|
{"all_filesystems", (const char *[]) {"needs_device", "needs_tmpdir",
|
|
NULL}},
|
|
{"needs_device", (const char *[]) {"needs_tmpdir", NULL}},
|
|
{"needs_checkpoints", (const char *[]) {"needs_tmpdir", NULL}},
|
|
{"resource_files", (const char *[]) {"needs_tmpdir", NULL}},
|
|
{NULL, (const char *[]) {NULL}}
|
|
};
|
|
|
|
const char *strip_name(char *path)
|
|
{
|
|
char *name = basename(path);
|
|
size_t len = strlen(name);
|
|
|
|
if (len > 2 && name[len-1] == 'c' && name[len-2] == '.')
|
|
name[len-2] = '\0';
|
|
|
|
return name;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
unsigned int i, j;
|
|
struct data_node *res;
|
|
|
|
if (argc != 2) {
|
|
fprintf(stderr, "Usage: docparse filename.c\n");
|
|
return 1;
|
|
}
|
|
|
|
res = parse_file(argv[1]);
|
|
if (!res)
|
|
return 0;
|
|
|
|
/* Filter out useless data */
|
|
for (i = 0; filter_out[i]; i++)
|
|
data_node_hash_del(res, filter_out[i]);
|
|
|
|
/* Normalize the result */
|
|
for (i = 0; implies[i].flag; i++) {
|
|
if (data_node_hash_get(res, implies[i].flag)) {
|
|
for (j = 0; implies[i].implies[j]; j++) {
|
|
if (data_node_hash_get(res, implies[i].implies[j]))
|
|
fprintf(stderr, "%s: useless tag: %s\n",
|
|
argv[1], implies[i].implies[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; implies[i].flag; i++) {
|
|
if (data_node_hash_get(res, implies[i].flag)) {
|
|
for (j = 0; implies[i].implies[j]; j++) {
|
|
if (!data_node_hash_get(res, implies[i].implies[j]))
|
|
data_node_hash_add(res, implies[i].implies[j],
|
|
data_node_string("1"));
|
|
}
|
|
}
|
|
}
|
|
|
|
data_node_hash_add(res, "fname", data_node_string(argv[1]));
|
|
printf(" \"%s\": ", strip_name(argv[1]));
|
|
data_to_json(res, stdout, 2);
|
|
data_node_free(res);
|
|
|
|
return 0;
|
|
}
|