/*
* Test that libcdecl can parse its own output.
* Copyright © 2012, 2020, 2022-2023 Nick Bowler
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "test.h"
#define PROGNAME "crossparse"
static const char *progname = PROGNAME;
static const char sopts[] = "f:ECVH";
static const struct option lopts[] = {
{ "cdecl", 0, NULL, 'C' },
{ "english", 0, NULL, 'E' },
{ "file", 2, NULL, 'f' },
{ "version", 0, NULL, 'V' },
{ "help", 0, NULL, 'H' },
{ 0 }
};
static void print_usage(FILE *f)
{
fprintf(f, "Usage: %s [options]\n", progname);
}
static void print_help(void)
{
print_usage(stdout);
puts("Test that libcdecl can parse its own output.\n");
test_print_options(lopts);
}
enum {
MODE_CDECL,
MODE_ENGLISH,
};
typedef struct cdecl *parse_func(const char *);
typedef size_t render_func(char *, size_t, struct cdecl *);
char *rerender(const char *str, const char *parse_name, parse_func *parse,
const char *render_name, render_func *render)
{
struct cdecl *decl = NULL;
char *buf = NULL;
size_t len;
decl = parse(str);
if (!decl) {
fprintf(stderr, "%s: %s: failed to parse input: %s\n",
progname, parse_name, cdecl_get_error()->str);
goto err;
}
len = render(NULL, 0, decl);
buf = malloc_nofail(len+1);
if (render(buf, len+1, decl) != len) {
fprintf(stderr, "%s: %s: inconsistent length returned\n",
progname, render_name);
goto err;
}
cdecl_free(decl);
return buf;
err:
cdecl_free(decl);
free(buf);
fprintf(stderr, "%s: the failed input was: %s\n", progname, str);
return NULL;
}
#define rerender(str, p, r) (rerender(str, #p, p, #r, r))
static bool test_crossparse(const char *str, int mode)
{
char *buf1 = NULL, *buf2 = NULL, *buf3 = NULL;
bool ret = false;
if (mode == MODE_ENGLISH) {
buf1 = rerender(str, cdecl_parse_english, cdecl_declare);
if (!buf1)
goto out;
buf2 = rerender(buf1, cdecl_parse_decl, cdecl_explain);
if (!buf2)
goto out;
buf3 = rerender(buf2, cdecl_parse_english, cdecl_declare);
if (!buf3)
goto out;
} else {
buf1 = rerender(str, cdecl_parse_decl, cdecl_explain);
if (!buf1)
goto out;
buf2 = rerender(buf1, cdecl_parse_english, cdecl_declare);
if (!buf2)
goto out;
buf3 = rerender(buf2, cdecl_parse_decl, cdecl_explain);
if (!buf3)
goto out;
}
if (!strcmp(buf1, buf3))
ret = true;
out:
free(buf1);
free(buf2);
free(buf3);
if (!ret) {
fprintf(stderr, "%s: failed cross-parse check of: %s\n",
progname, str);
}
return ret;
}
int main(int argc, char **argv)
{
int opt, mode = MODE_CDECL;
int ret = EXIT_SUCCESS;
const char *filename = NULL;
FILE *infile = NULL;
if (argc > 0)
progname = argv[0];
while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) {
switch (opt) {
case 'C':
mode = MODE_CDECL;
break;
case 'E':
mode = MODE_ENGLISH;
break;
case 'f':
infile = stdin;
filename = optarg;
break;
case 'V':
test_print_version(PROGNAME);
return EXIT_SUCCESS;
case 'H':
print_help();
return EXIT_SUCCESS;
default:
print_usage(stderr);
return EXIT_FAILURE;
}
}
if (infile) {
char *line = NULL;
size_t n;
if (filename) {
infile = fopen(filename, "r");
if (!infile) {
fprintf(stderr, "%s: %s: %s\n", progname,
filename, strerror(errno));
return EXIT_FAILURE;
}
}
while (getline(&line, &n, infile) >= 0) {
char *c = strchr(line, '\n');
if (c)
*c = '\0';
if (!test_crossparse(line, mode))
ret = EXIT_FAILURE;
}
free(line);
fclose(infile);
} else if (argv[optind]) {
for (int i = optind; i < argc; i++) {
if (!test_crossparse(argv[i], mode))
ret = EXIT_FAILURE;
}
} else {
print_usage(stderr);
return EXIT_FAILURE;
}
return ret;
}