From: JOSH MARANTZ Read: (N/A)
Date: 01-15-91 (04:01) Number: 6083 of 6151 To: ALL Refer#: NONE

ú  Newsgroup: comp.lang.c
ú Message-ID: <[email-masked]>
ú       From: JOSH MARANTZ
ú    Subject: Function prototype generator

The following is a program I wrote to parse a K&R C program and 
generate function prototypes for all the functions.  I've found 
that it works well in many circumstances.  It is immune to most 
programming style issues that plague quick & dirty C parsers, but 
it has some limitations.  It takes the C file foo.i after its 
been processed by CPP, and generates two files: foo.ext and 
foo.sta, to hold the external definitions and the static 
definitions. 

The only known limitation is that I found it too hard to parse 
function declarations that take function pointers as arguments: 

int foo(x)
    int (*x) ();
{
}

You can, however, do this:

typedef int (*intproc) ();

int foo(x)
    intproc x;
{
}

I've found this is not a bad limitation in practice, and I always 
define new types function pointers to be passed as arguments. 

It generates prototypes of the form

extern int foo vlARGS((intproc));

You should define vlARGS to as

#ifdef __STDC__
#define vlARGS(a) a
#else
#define vlARGS(a)
#endif

Then you can use the same headers for both Ansi and K&R.

Other than the known limitation, this program has been a 
workhorse for me for a couple of years now.  I never write 
function declarations manually anymore. 

I've avoided posting it because I'm a little ashamed of the 
style, and I haven't had time to fix it up, and because of the 
procedure pointer limitation.  But it seems like the demand for 
such a beast is high, so here it is. 

First, a script to run it on unix (unix is not required -- this 
runs fine under DOS using the MS-C preprocessor): 

mkproto:
----------------------------------------------------------------
#!/bin/csh
/lib/cpp $1.c $argv[2-] > $1.i
proto $1
----------------------------------------------------------------

Now, the proto.c file:

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

#define MAX_VARS 20
#define MAX_CHARS 80

char inname[MAX_CHARS];
char word[MAX_CHARS];
char var[MAX_CHARS];
char type[MAX_CHARS];
char proc_name[MAX_CHARS];
char vars[MAX_VARS][MAX_CHARS];
char types[MAX_VARS][MAX_CHARS];

int braces = 0, parens = 0, brackets = 0, line_count = 1;
int bslash = 0, squote = 0, dquote = 0;
int num_var, num_matches;

static FILE *infile, *statics, *externs;

#define IS_IDENT(c) \
    (isalnum ((c)) || ((c) == '$') || ((c) == '_') || ((c) == '*') || \
     ((c) == '[') || ((c) == ']') || ((c) == '+'))

static FILE *open_file(name, ext, mode)
    char *name, *ext, *mode;
{
    char fname[80];
    FILE *f;

    sprintf (fname, "%s.%s", name, ext);
    if ((f = fopen (fname, mode)) == NULL) {
        perror (fname);
        exit (1);
    } /* if */

    return (f);
} /* static FILE *open_file */

main(argc, argv)
    int argc;
    char *argv[];
{
    if (argc != 2) {
        fprintf (stderr, "Usage:  %s module\n", argv[0]);
        fprintf (stderr, "Reads for cpp output module.i.\n");
        fprintf (stderr, "Writes external prototypes to module.ext.\n");
        fprintf (stderr, "Writes static prototypes to module.sta.\n");
        exit (1);
    } /* if */

    sprintf (inname, "%s.i", argv[1]);
    infile = open_file (argv[1], "i", "r");
    externs = open_file (argv[1], "ext", "w");
    statics = open_file (argv[1], "sta", "w");

    while (getword () != EOF) {
        if ((strcmp (word, "extern") == 0) ||
            (strcmp (word, "{") == 0) ||
            (strcmp (word, ";") == 0) ||
            (strcmp (word, ",") == 0) ||
            (strcmp (word, "pragma") == 0) ||
            (strcmp (word, "typedef") == 0))
            next_statement ();
        else if (IS_IDENT (word[0]))
            try_proc ();
        else
            error ("Unexpected token");
    } /* while */
} /* main */

next_statement() {
    while ((braces != 0) ||
           ((strcmp (word, ";") != 0) && (strcmp (word, "}") != 0)))
        getword ();
} /* next_statement */

getword() {
    int c, i, done, x;

    i = 0;
    done = 0;
    while (!done) {
        c = getc (infile);

        if (c == EOF) {
            if (braces || parens || brackets)
                fprintf (stderr, "%s: %d: {}=%d, ()=%d, []=%d\n",
                         inname, line_count, braces, parens, brackets);
            fclose (infile);
            fclose (statics);
            fclose (externs);
            exit (0);
        } /* if */
        else if (IS_IDENT (c) && !dquote && !squote) {
            word[i] = c;
            i++;
        } /* if */
        else if (isspace (c) || (c == '\f') && !dquote && !squote) {
            if (c == '\n') line_count++;
            if (i > 0) {
                word[i] = 0;
                c = 0;
                done = 1;
            } /* if */
        } /* else if */
        else if ((c == '#') && !dquote && !squote) {
            if ((fgets (word, MAX_CHARS - 1, infile) == NULL) ||
                ((sscanf (word, " %d \"%s\"", &line_count, inname) != 2) &&
                 (sscanf (word, "line %d \"%s\"", &line_count, inname) != 2)))
            {
                x = strlen (word) - 1;
                if ((x >= 0) && (word[x] == '\n'))
                    word[x] = '\0';
                error ("Unknown # directive");
                line_count++;
            }
            else {
                x = strlen (inname) - 1;
                if ((x >= 0) && (inname[x] == '"'))
                    inname[x] = '\0';
            }
        } /* else if */
        else {
            if (i == 0) {
                switch (c) {
                    case '{': if (!dquote && !squote) braces++;     break;
                    case '}': if (!dquote && !squote) braces--;     break;
                    case '(': if (!dquote && !squote) parens++;     break;
                    case ')': if (!dquote && !squote) parens--;     break;
                    case '[': if (!dquote && !squote) brackets++;   break;
                    case ']': if (!dquote && !squote) brackets--;   break;
                    case '"': if (!bslash && !squote) dquote = !dquote; break;
                    case 39:  if (!bslash && !dquote) squote = !squote; break;
                    case '\\': bslash = !bslash;                    break;

                } /* switch */
                if (c != '\\')
                    bslash = 0;
                word[0] = c;
                word[1] = '\0';
                if (!dquote && !squote)
                    done = 1;
            } /* if */
            else {
                ungetc (c, infile);
                word[i] = 0;
                c = 0;
                done = 1;
            } /* else */
        } /* else if */
    } /* while */
    return (c);
} /* getword */

try_proc() {
    int i, end_of_type;
    FILE *f;

    if (strcmp (word, "static") == 0) {
        proc_name[0] = '\0';
        f = statics;
    } /* if */
    else {
        strcpy (proc_name, "extern ");
        f = externs;
    } /* else */

    do {
        strcat (proc_name, word);
        strcat (proc_name, " ");
    } while (getword () == 0);

    if ((strcmp (word, ";") == 0) ||        /* variable/type declaration */
        (strcmp (word, ",") == 0) ||        /* multi var/type declaration */
        (strcmp (word, "[") == 0) ||        /* array initializer */
        (strcmp (word, "=") == 0) ||        /* static initializer */
        (strcmp (word, "{") == 0) ||        /* struct/union definition */
        (strcmp (word, ":") == 0))          /* label */
    {
        next_statement ();
        return;
    } /* if */

    if (strcmp (word, "(") != 0) {error ("( expected"); return;}

    /* Accumulate variable names */
    num_var = 0;
    do {
        if (getword () != 0) {
            if ((num_var == 0) && (strcmp (word, ")") == 0))
                break;
            error ("expected arg");
            return;
        }
        if (word[0] == '*') {               /* Procedure ptr declaration */
            next_statement ();
            return;
        }
        strcpy (vars[num_var], word);
        strcpy (types[num_var], "int");
        num_var++;
    } while (getword () == ',');

    if (IS_IDENT (word[0]) ||               /* identifier */
        (strcmp (word, "(") == 0))          /* function argument decl */
    {
        next_statement ();
        return;
    } /* if */

    if (strcmp (word, ")") != 0) {error (") expected"); return;}

    if (num_var == 0) {
        getword ();

        if (strcmp (word, "{") == 0) {      /* Proc with no args */
            fprintf (f, "%svlARGS((void));\n", proc_name);
            next_statement ();
        } /* if */

        else if ((strcmp (word, ";") == 0) ||
                 (strcmp (word, ",") == 0))
            ;                               /* proc declaration w/o extern */

        else
            error ("Unexpected termination of procedure declaration");

        return;
    } /* if */

    /* Find argument type declarations */
    end_of_type = 9999;
    for (num_matches = 0; num_matches < num_var;) {

        type[0] = 0;

        while (getword () == 0) {
            end_of_type = strlen (type) - 1;
            strcat (type, word);
            strcpy (var, word);
            strcat (type, " ");
        } /* while */

        if (end_of_type < 1) {
            error ("Empty declaration");
            return;
        } /* if */

        /*  If we hit a ; the first time through, we are looking
            at an existing prototyped procedure declaration!  Punt!
        */
        if ((strcmp (word, ";") == 0) && (end_of_type == 9999))
            return;

        if ((strcmp (word, ";") != 0) && (strcmp (word, ",") != 0)) {
            strcpy (word, proc_name);
            error ("arg type not found, declaration supressed");
            return;
        } /* if */

        else {
            type[end_of_type] = '\0';
            match_variable (var);

            while (strcmp (word, ",") == 0) {
                getword ();
                match_variable (word);
                getword ();
            } /* while */

            if (strcmp (word, ";") != 0) {
                error ("Arg decl did not end with ';'");
                return;
            } /* if */
        } /* else */
    } /* for */

    fprintf (f, "%svlARGS((", proc_name);
    for (i = 0; i < num_var - 1; i++)
        fprintf (f, "%s, ", types[i]);
    fprintf (f, "%s));\n", types[num_var - 1]);

    next_statement ();
} /* try_proc */

match_variable(compare)
    char *compare;
{
    int i;
    char prefix[10], suffix[10];

    prefix[0] = suffix[0] = '\0';

    while (*compare == ' ') compare++;
    while (*compare == '*') {
        compare++;
        strcat (prefix, "*");
    } /* while */

    while ((strlen (compare) > 2) &&
           (strcmp (&compare[strlen (compare) - 2], "[]") == 0))
    {
        compare[strlen (compare) - 2] = '\0';
        strcat (suffix, "[]");
    } /* while */

    for (i = 0; i < num_var; i++) {
        if (strcmp (compare, vars[i]) == 0) {
            if (prefix[0] == '\0')
                sprintf (types[i], "%s%s", type, suffix);
            else
                sprintf (types[i], "%s %s%s", type, prefix, suffix);
            num_matches++;
            return;
        } /* if */
    } /* for */

    sprintf (word, "%s(%s)", proc_name, compare);
    error ("Failed to match type");
} /* match_variable */

error(s)
    char *s;
{
    fprintf (stderr, "%s: %d: %s: %s\n", inname, line_count, s, word);
    next_statement ();
} /* error */
----------------------------------------------------------------

Good luck, and feel free to make improvements and send them back to me!

Joshua Marantz
Viewlogic Systems, Inc.

[email-masked]