Function prototype generator

Josh Marantz josh at concept.viewlogic.com
Tue Jan 15 10:01:52 AEST 1991


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.
josh at viewlogic.com
        Why not pass the time by playing a little solitaire?



More information about the Comp.lang.c mailing list