/*
 * gen-ctype-checked.c:  generates ctype-checked.h on stdout
 *
 * This code is Copyright (c) 2013, by the authors of nmh.
 * See the COPYRIGHT file in the root directory of the nmh
 * distribution for complete copyright information.
 */

/*
 * This is intended to catch any calls using a char argument to any of
 * the is*(int) functions in <ctype.h>.  It does that by defining a
 * macro that uses the argument as an array index, along with a
 * compiler warning such as gcc -Wchar-subscripts (include with
 * -Wall).  This program creates sbr/ctype-checked.h and
 * sbr/ctype-checked.c.  The header file is put into sbr/ because h/
 * is unwritable during make distcheck.
 */

#include <ctype.h>
#include <stdio.h>

#define CTYPE_FUNCTIONS \
    X(isalnum) \
    X(isalpha) \
    X(iscntrl) \
    X(isdigit) \
    X(isgraph) \
    X(islower) \
    X(isprint) \
    X(ispunct) \
    X(isspace) \
    X(isupper) \
    X(isxdigit) \
    X(tolower) \
    X(toupper) \

#if (defined (_BSD_SOURCE) && _BSD_SOURCE) || \
    (defined (_SVID_SOURCE) && _SVID_SOURCE) || \
    (defined (_XOPEN_SOURCE) && _XOPEN_SOURCE)
# define CTYPE_FUNCTION_ISASCII X(isascii) \
                                X(toascii)
#else
# define CTYPE_FUNCTION_ISASCII
#endif

#if (defined (_XOPEN_SOURCE) && _XOPEN_SOURCE >= 600) || \
    (defined (_ISOC99_SOURCE) && _ISOC99_SOURCE) || \
    (defined (_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L)
# define CTYPE_FUNCTIONS_C99 X(isblank)
#else
# define CTYPE_FUNCTIONS_C99
#endif

struct ctype_func { const char *name; int (*function) (int); };

#define X(function) { #function, function },
struct ctype_func functions[] =
    { CTYPE_FUNCTIONS CTYPE_FUNCTION_ISASCII CTYPE_FUNCTIONS_C99 { NULL, 0} };
#undef X


int
main () {
    const char *copyright="\
/*\n\
 * %s:  checks type of arguments and argument\n\
 *     values for each of the ctype functions\n\
 *\n\
 * Generated by etc/gen-ctype-checked.\n\
 *\n\
 * This code is Copyright (c) 2013, by the authors of nmh.\n\
 * See the COPYRIGHT file in the root directory of the nmh\n\
 * distribution for complete copyright information.\n\
 */\n\n";
    char headername[] = "sbr/ctype-checked.h";
    const char cfilename[] =  "sbr/ctype-checked.c";
    FILE *header = fopen (headername, "w");
    FILE *cfile = fopen (cfilename, "w");
    int status = 0;

    if (header != 0  &&  cfile != 0) {
        struct ctype_func *f;

        fprintf (header, copyright, headername);
        fputs ("#ifndef CTYPE_CHECKED_H\n\
#define CTYPE_CHECKED_H\n\
#include <ctype.h>\n\
\n", header);

        fprintf (cfile, copyright, cfilename);
        fputs ("#include <config.h>\n\n", cfile);
        fputs ("#ifndef NDEBUG\n", cfile);
        fputs ("#include <sbr/ctype-checked.h>\n\n", cfile);

        for (f = functions; f->function; ++f) {
            const char *cp = f->name;
            unsigned int i;

            fprintf (header, "extern int %s_type_checked[];\n", cp);
            fprintf (header, "#ifdef %s\n#undef %s\n#endif\n", cp, cp);
            fprintf (header, "#define %s(c) %s_type_checked[(c)]\n\n", cp, cp);

            fprintf (cfile, "int %s_type_checked[] = {\n    ", cp);
            for (i = 0; i < 256; ++i) {
                fprintf (cfile, "%d", (*f->function) (i));
                if ((i+1) % 8) {
                    fputs (", ", cfile);
                } else {
                    if (i == 255) {
                        fputs (" };\n\n", cfile);
                    } else {
                        fputs (",\n    ", cfile);
                    }
                }
            }
        }

        /* Need to put a symbol in the .c file with NDEBUG so the
           compilation unit isn't empty. */
        fputs ("#else  /* NDEBUG */\n\nvoid ctype_checked();\n\n", cfile);
        fputs ("#endif /* NDEBUG */\n", cfile);
        fprintf (header, "#endif /* CTYPE_CHECKED_H */\n");
    } else {
        perror ("fopen in sbr/");
        status = -1;
    }

    if (header) { fclose(header); }
    if (cfile) { fclose(cfile); }

    return status;
}
