[comp.lang.c] Function prototype generator

dalenber@p.cs.uiuc.edu (06/24/88)

For the past few weeks I have been writing a C-function prototype
generator. I would say that it is now about 85% finished; it does just about 
everything I want it to do. The only area where it is really lacking is in
handling array arguments to functions. For example, given the function
definition:

	int func (arg)
	char arg[];
	{
	/* ... */
	}

It will generate the prototype:
	extern int func (char arg[]);

For this case, it is obvious that the argument in the prototype should
be "char *arg", but what about multi-dimensional arrays? Should:

	int func (arg)
	int arg[][10];
	{
	/* ... */
	}

produce:
	extern int func (char **arg); ?

I seem to remember some notes about "pointer to array" being a legal type
in "ANSI C"; how would definitions of this type differ from those above?
Would it be legal to define a function returning a pointer to an array of
int? And if so, how would such a thing be written? Should I just convert
all array references to pointer references?

If any ANSI-C gurus can answer these questions, I'll be eternally greatful
(well, maybe not eternally :-) ).


The second reason I am posting this note is ask the C community at large,
"what features would you want in a prototype generator?"

At present, the program accepts function definitions in either the old 
fashioned style used in the examples above, or the new ANSI style, 
formatted in any manner, and massages the information into prototype
format. It produces output in the form

	#ifdef __STDC__
	<function prototypes>
	#else
	<old-fashioned function allusions>
	#endif

The token "__STDC__" can be set at run time to be any symbol.
Also at run time, options can be given to exclude any static functions
from the output, as well as the prototype for "main".

What I'd like to know is, what else should it do? I'm particularly
unsure how to handle multiple files. Should I generate one large
output on standard-output, or into a user specified file; or should
I automatically put the output for "file.c" into "file.p" (or something
of that nature). A good user interface is very important, and I know
that what I find natural may seem odd to others.

Please send me your comments, and ideas. In a few weeks, after I've
finished implementing everything, I'll post the source in
comp.sources.{unix or misc} for all the world to enjoy.

Much thanks in advance for any help I receive.


Russel Dalenberg

	UUCP:	{pur-ee,convex,ihnp4}!uiucdcs!dalenber
	ARPA:	dalenber@p.cs.uiuc.edu
	CSNET:	dalenber@UIUC.CSNET

jfh@rpp386.UUCP (John F. Haugh II) (06/26/88)

i already have one of these which was recently posted someplace on the
net.  is this a second version or what???

- john.
-- 
John F. Haugh II                 +--------- Cute Chocolate Quote ---------
HASA, Division "S"               | "USENET should not be confused with
UUCP:   killer!rpp386!jfh        |  something that matters, like CHOCOLATE"
DOMAIN: jfh@rpp386.uucp          |             -- with my apologizes

swonk@ccicpg.UUCP (Glen Swonk) (05/12/89)

Can anyone point me to a source for generating function protypes?
I know some compilers can do this but I would like a portable
mechanism for doing this. The real reason for doing this
is to generate a database for the Norton Guides.

Also, would this be an easier task to do with lex and yacc or
with a C program?

thanks, glenn
-- 
Glenn L. Swonk		CCI Computers 
(714)458-7282		9801 Muirlands Boulevard
			Irvine, CA 92718
uunet!ccicpg!swonk

josh@concept.viewlogic.com (Josh Marantz) (01/15/91)

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