allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (09/07/89)
Posting-number: Volume 8, Issue 30 Submitted-by: ersmith@uwovax.uwo.ca Archive-name: mkproto Here is mkproto, a program for generating prototype declarations for all functions appearing in a C source file. The input C code may be either K&R or ANSI C (i.e. it's OK if the functions are defined using prototypes). Unlike some of the sed-based scripts floating around, it correctly handles prototype promotion (e.g. the prototype for 'int foo() char x;...' is 'int foo(int x)'). Also, it should work OK on just about any computer, not just Unix-based ones (it's been tested under minix, Unix, and TOS). Use: typically, you would type 'mkproto *.c >proto.h' and then add a '#include "proto.h"' line to all the C source files. An ANSI conformant compiler will then be able to do type checking on function calls across module boundaries. As a bonus, proto.h will tell you which source files functions were defined in, and (if you gave the -n function to mkproto) their line numbers. The resulting include file may also be used by non-ANSI compilers; you can disable this feature (for cleaner, strictly ANSI-conforming output) with the -p flag. Please read the description of bugs in mkproto.man; definitely mkproto will not handle all programs correctly, but it does work on the majority of them. A sample of its output is provided in the file "mkproto.h"; this is the result of 'mkproto mkproto.c >mkproto.h'. There is ABSOLUTELY NO WARRANTY for the program; as I said, it doesn't work on all programs (complicated function definitions can make it produce bogus output). It does what I need, though, and it can certainly make porting stuff to ANSI compilers easier. Mkproto is in the public domain. If you find any bugs (other than the ones documented) please let me know. -- Eric R. Smith email: Dept. of Mathematics ersmith@uwovax.uwo.ca University of Western Ontario ersmith@uwovax.bitnet London, Ont. Canada N6A 5B7 ph: (519) 661-3638 # This is a shell archive. # Remove everything above and including the cut line. # Then run the rest of the file through sh. #----cut here-----cut here-----cut here-----cut here----# #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # makefile.min # makefile.tos # makefile.unx # mkproto.c # mkproto.h # mkproto.man # README # This archive created: 05 September 1989 05:51:28 PM EDT # By: eric (at home) echo shar: extracting makefile.min sed 's/^X//' << \SHAR_EOF > makefile.min XCC = mgcc XALL = mkproto X Xmkproto : mkproto.c mkproto.h X $(CC) -O -o mkproto mkproto.c -mshort -s Xclean: X rm -f *.o X Xrealclean: clean X rm -f $(ALL) report core SHAR_EOF if test 159 -ne "`wc -c makefile.min`" then echo shar: error transmitting makefile.min '(should have been 159 characters)' fi echo shar: extracting makefile.tos sed 's/^X//' << \SHAR_EOF > makefile.tos XCC = gcc XALL = mkproto.ttp X Xmkproto.ttp : mkproto.c mkproto.h X $(CC) -O -o mkproto.ttp mkproto.c -mshort -s Xclean: X rm -f *.o X Xrealclean: clean X rm -f $(ALL) report core SHAR_EOF if test 170 -ne "`wc -c makefile.tos`" then echo shar: error transmitting makefile.tos '(should have been 170 characters)' fi echo shar: extracting makefile.unx sed 's/^X//' << \SHAR_EOF > makefile.unx XCC = cc XALL = mkproto X Xmkproto : mkproto.c mkproto.h X $(CC) -O -o mkproto mkproto.c Xclean: X rm -f *.o X Xrealclean: clean X rm -f $(ALL) report core SHAR_EOF if test 147 -ne "`wc -c makefile.unx`" then echo shar: error transmitting makefile.unx '(should have been 147 characters)' fi echo shar: extracting mkproto.c sed 's/^X//' << \SHAR_EOF > mkproto.c X/* Program to extract function declarations from C source code */ X/* Written by Eric R. Smith and placed in the public domain */ X/* Thanks are due to Jwahar R. Bammi for fixing several bugs */ X/* and providing the Unix makefiles. */ X X#if defined(__STDC__) && !defined(minix) X#include <stddef.h> X#include <stdlib.h> X#else X#define EXIT_SUCCESS 0 X#define EXIT_FAILURE 1 Xextern char *malloc(); X#endif X X#include <stdio.h> X#include <ctype.h> X#include <string.h> X X/*#define DEBUG(s) (fputs(s, stderr)) /* */ X#define DEBUG(s) /* */ X X#define ISCSYM(x) ((x) > 0 && (isalnum(x) || (x) == '_')) X#define ABORTED ( (Word *) -1 ) X#define MAXPARAM 20 /* max. number of parameters to a function */ X#define NEWBUFSIZ (20480*sizeof(char)) /* new buffer size */ X Xint inquote = 0; /* in a quote?? */ Xint newline_seen = 1; /* are we at the start of a line */ Xlong linenum = 1L; /* line number in current file */ Xint dostatic = 0; /* do static functions? */ Xint donum = 0; /* print line numbers? */ Xint dohead = 1; /* do file headers? */ Xint docond = 1; /* conditionalize for non-ANSI compilers? */ Xint glastc = ' '; /* last char. seen by getsym() */ X Xtypedef struct word { X struct word *next; X char string[1]; X} Word; X X#include "mkproto.h" X X/* X * Routines for manipulating lists of words. X */ X XWord *word_alloc(s) X char *s; X{ X Word *w; X X w = (Word *) malloc(sizeof(Word) + strlen(s) + 1); /* ++jrb */ X strcpy(w->string, s); X w->next = NULL; X return w; X} X Xvoid word_free(w) X Word *w; X{ X Word *oldw; X while (w) { X oldw = w; X w = w->next; X free(oldw); X } X} X X/* return the length of a list; empty words are not counted */ Xint XList_len(w) X Word *w; X{ X int count = 0; X X while (w) { X if (*w->string) count++; X w = w->next; X } X return count; X} X X/* Append two lists, and return the result */ X XWord *word_append(w1, w2) X Word *w1, *w2; X{ X Word *r, *w; X X r = w = word_alloc(""); X X while (w1) { X w->next = word_alloc(w1->string); X w = w->next; X w1 = w1->next; X } X while (w2) { X w->next = word_alloc(w2->string); X w = w->next; X w2 = w2->next; X } X X return r; X} X X/* see if the last entry in w2 is in w1 */ X Xint Xfoundin(w1, w2) X Word *w1, *w2; X{ X while (w2->next) X w2 = w2->next; X X while (w1) { X if (!strcmp(w1->string, w2->string)) X return 1; X w1 = w1->next; X } X return 0; X} X X/* add the string s to the given list of words */ X Xvoid addword(w, s) X Word *w; char *s; X{ X while (w->next) w = w->next; X w->next = word_alloc(s); X} X X/* given a list representing a type and a variable name, extract just X * the base type, e.g. "struct word *x" would yield "struct word" X */ X XWord *typelist(p) X Word *p; X{ X Word *w, *r; X X r = w = word_alloc(""); X while (p && p->next) { X if (p->string[0] && !ISCSYM(p->string[0])) X break; X w->next = word_alloc(p->string); X w = w->next; X p = p->next; X } X return r; X} X X/* typefixhack: promote formal parameters of type "char", "unsigned char", X "short", or "unsigned short" to "int". X*/ X Xvoid typefixhack(w) X Word *w; X{ X Word *oldw = 0; X X while (w) { X if (*w->string) { X if ( (!strcmp(w->string, "char") || X !strcmp(w->string, "short") ) X && (List_len(w->next) < 2) ) X { X if (oldw && !strcmp(oldw->string, "unsigned")) { X oldw->next = w->next; X free(w); X w = oldw; X } X strcpy(w->string, "int"); X } X } X w = w->next; X } X} X X/* read a character: if it's a newline, increment the line count */ X X#ifdef __GNUC__ /* ++jrb */ Xinline X#endif Xint ngetc(f) X FILE *f; X{ X int c; X X c = getc(f); X if (c == '\n') linenum++; X X return c; X} X X/* read the next character from the file. If the character is '\' then X * read and skip the next character. Any comment sequence is converted X * to a blank. X */ X Xint fnextch(f) X FILE *f; X{ X int c, lastc, incomment; X X c = ngetc(f); X while (c == '\\') { XDEBUG("fnextch: in backslash loop\n"); X c = ngetc(f); /* skip a character */ X c = ngetc(f); X } X if (c == '/' && !inquote) { X c = ngetc(f); X if (c == '*') { X incomment = 1; X c = ' '; XDEBUG("fnextch: comment seen\n"); X while (incomment) { X lastc = c; X c = ngetc(f); X if (lastc == '*' && c == '/') X incomment = 0; X else if (c < 0) X return c; X } X return fnextch(f); X } X else { X if (c == '\n') linenum--; X ungetc(c, f); X return '/'; X } X } X return c; X} X X X/* Get the next "interesting" character. Comments are skipped, and strings X * are converted to "0". Also, if a line starts with "#" it is skipped. X */ X Xint nextch(f) X FILE *f; X{ X int c; X X c = fnextch(f); X if (newline_seen && c == '#') { X do { X c = fnextch(f); X } while (c >= 0 && c != '\n'); X if (c < 0) X return c; X } X newline_seen = (c == '\n'); X X if (c == '\'' || c == '\"') { XDEBUG("nextch: in a quote\n"); X inquote = c; X while ( (c = fnextch(f)) >= 0 ) { X if (c == inquote) { X inquote = 0; XDEBUG("nextch: out of quote\n"); X return '0'; X } X } XDEBUG("nextch: EOF in a quote\n"); X } X return c; X} X X/* X * Get the next symbol from the file, skipping blanks. X * Return 0 if OK, -1 for EOF. X * Also collapses everything between { and } X */ X Xint Xgetsym(buf, f) X char *buf; FILE *f; X{ X register int c; X int inbrack = 0; X XDEBUG("in getsym\n"); X c = glastc; X while ((c > 0) && isspace(c)) { X c = nextch(f); X } XDEBUG("getsym: spaces skipped\n"); X if (c < 0) { XDEBUG("EOF read in getsym\n"); X return -1; X } X if (c == '{') { X inbrack = 1; XDEBUG("getsym: in bracket\n"); X while (inbrack) { X c = nextch(f); X if (c < 0) { XDEBUG("getsym: EOF seen in bracket loop\n"); X glastc = c; X return c; X } X if (c == '{') inbrack++; X else if (c == '}') inbrack--; X } X strcpy(buf, "{}"); X glastc = nextch(f); XDEBUG("getsym: out of in bracket loop\n"); X return 0; X } X if (!ISCSYM(c)) { X *buf++ = c; X *buf = 0; X glastc = nextch(f); XDEBUG("getsym: returning special symbol\n"); X return 0; X } X while (ISCSYM(c)) { X *buf++ = c; X c = nextch(f); X } X *buf = 0; X glastc = c; XDEBUG("getsym: returning word\n"); X return 0; X} X X/* X * skipit: skip until a ";" or the end of a function declaration is seen X */ Xint skipit(buf, f) X char *buf; X FILE *f; X{ X int i; X X do { XDEBUG("in skipit loop\n"); X i = getsym(buf, f); X if (i < 0) return i; X } while (*buf != ';' && *buf != '{'); X X return 0; X} X X/* X * Get a parameter list; when this is called the next symbol in line X * should be the first thing in the list. X */ X XWord *getparamlist(f) X FILE *f; X{ X static Word *pname[MAXPARAM]; /* parameter names */ X Word *tlist, /* type name */ X *plist; /* temporary */ X int np = 0; /* number of parameters */ X int typed[MAXPARAM]; /* parameter has been given a type */ X int tlistdone; /* finished finding the type name */ X int sawsomething; X int i; X int inparen = 0; X char buf[80]; X XDEBUG("in getparamlist\n"); X for (i = 0; i < MAXPARAM; i++) X typed[i] = 0; X X plist = word_alloc(""); X X/* first, get the stuff inside brackets (if anything) */ X X sawsomething = 0; /* gets set nonzero when we see an arg */ X for (;;) { X if (getsym(buf, f) < 0) return NULL; X if (*buf == ')' && (--inparen < 0)) { X if (sawsomething) { /* if we've seen an arg */ X pname[np] = plist; X plist = word_alloc(""); X np++; X } X break; X } X if (*buf == ';') { /* something weird */ X return ABORTED; X } X sawsomething = 1; /* there's something in the arg. list */ X if (*buf == ',' && inparen == 0) { X pname[np] = plist; X plist = word_alloc(""); X np++; X } X else { X addword(plist, buf); X if (*buf == '(') inparen++; X } X } X X/* next, get the declarations after the function header */ X X inparen = 0; X X tlist = word_alloc(""); X plist = word_alloc(""); X tlistdone = 0; X sawsomething = 0; X for(;;) { X if (getsym(buf, f) < 0) return NULL; X X/* handle a list like "int x,y,z" */ X if (*buf == ',' && !inparen) { X if (!sawsomething) X return NULL; X for (i = 0; i < np; i++) { X if (!typed[i] && foundin(plist, pname[i])) { X typed[i] = 1; X word_free(pname[i]); X pname[i] = word_append(tlist, plist); X /* promote types */ X typefixhack(pname[i]); X break; X } X } X if (!tlistdone) { X tlist = typelist(plist); X tlistdone = 1; X } X word_free(plist); X plist = word_alloc(""); X } X/* handle the end of a list */ X else if (*buf == ';') { X if (!sawsomething) X return ABORTED; X for (i = 0; i < np; i++) { X if (!typed[i] && foundin(plist, pname[i])) { X typed[i] = 1; X word_free(pname[i]); X pname[i] = word_append(tlist, plist); X typefixhack(pname[i]); X break; X } X } X tlistdone = 0; X word_free(tlist); word_free(plist); X tlist = word_alloc(""); X plist = word_alloc(""); X } X/* handle the beginning of the function */ X else if (!strcmp(buf, "{}")) break; X/* otherwise, throw the word into the list (except for "register") */ X else if (strcmp(buf, "register")) { X sawsomething = 1; X addword(plist, buf); X if (*buf == '(') inparen++; X if (*buf == ')') inparen--; X } X } X X/* Now take the info we have and build a prototype list */ X X/* empty parameter list means "void" */ X if (np == 0) X return word_alloc("void"); X X plist = tlist = word_alloc(""); X for (i = 0; i < np; i++) { X X/* If no type provided, make it an "int" */ X if ( !(pname[i]->next) || X (!(pname[i]->next->next)&&strcmp(pname[i]->next->string, "void"))) { X addword(tlist, "int"); X } X while (tlist->next) tlist = tlist->next; X tlist->next = pname[i]; X if (i < np - 1) X addword(tlist, ", "); X } X return plist; X} X X/* X * emit a function declaration. The attributes and name of the function X * are in wlist; the parameters are in plist. X */ Xvoid emit(wlist, plist, startline) X Word *wlist, *plist; X long startline; X{ X Word *w; X int count = 0; X XDEBUG("emit called\n"); X if (donum) X printf("/*%8ld */ ", startline); X X for (w = wlist; w; w = w->next) { X if (w->string[0]) X count ++; X } X X if (count < 2) X printf("int "); X X for (w = wlist; w; w = w->next) { X printf("%s", w->string); X if (ISCSYM(w->string[0])) X printf(" "); X } X if (docond) X printf("P(("); X else X printf("( "); X for (w = plist; w; w = w->next) { X printf("%s", w->string); X if (ISCSYM(w->string[0])) X printf(" "); X } X if (docond) X printf("));\n"); X else X printf(");\n"); X} X X/* X * get all the function declarations X */ X Xvoid getdecl(f) X FILE *f; X{ X Word *plist, *wlist = NULL; X char buf[80]; X int sawsomething; X long startline; /* line where declaration started */ X int oktoprint; Xagain: X word_free(wlist); X wlist = word_alloc(""); X sawsomething = 0; X oktoprint = 1; X X for(;;) { XDEBUG("main getdecl loop\n"); X if (getsym(buf,f) < 0) { XDEBUG("EOF in getdecl loop\n"); X return; X } X/* try to guess when a declaration is not an external function definition */ X if (!strcmp(buf, ",") || !strcmp(buf, "{}") || X !strcmp(buf, "=") || !strcmp(buf, "typedef") || X !strcmp(buf, "extern")) { X skipit(buf, f); X goto again; X } X if (!dostatic && !strcmp(buf, "static")) { X oktoprint = 0; X } X/* for the benefit of compilers that allow "inline" declarations */ X if (!strcmp(buf, "inline") && !sawsomething) X continue; X if (!strcmp(buf, ";")) goto again; X X/* A left parenthesis *might* indicate a function definition */ X if (!strcmp(buf, "(")) { X startline = linenum; X if (!sawsomething || !(plist = getparamlist(f))) { X skipit(buf, f); X goto again; X } X if (plist == ABORTED) X goto again; X X/* It seems to have been what we wanted */ X if (oktoprint) X emit(wlist, plist, startline); X word_free(plist); X goto again; X } X addword(wlist, buf); X sawsomething = 1; X } X} X Xvoid Xmain(argc, argv) Xint argc; char **argv; X{ X FILE *f; X char *t, *iobuf; X extern void Usage(); X X argv++; argc--; X X iobuf = malloc(NEWBUFSIZ); X while (*argv && **argv == '-') { X t = *argv++; --argc; t++; X while (*t) { X if (*t == 's') X dostatic = 1; X else if (*t == 'n') X donum = 1; X else if (*t == 'p') X docond = 0; X else X Usage(); X t++; X } X } X X if (docond) { X printf("#ifdef __STDC__\n"); X printf("# define\tP(s) s\n"); X printf("#else\n"); X printf("# define P(s) ()\n"); X printf("#endif\n\n"); X } X if (argc == 0) X getdecl(stdin); X else X while (argc > 0 && *argv) { XDEBUG("trying a new file\n"); X if (!(f = fopen(*argv, "r"))) { X perror(*argv); X exit(EXIT_FAILURE); X } X if (iobuf) X setvbuf(f, iobuf, _IOFBF, NEWBUFSIZ); X if (dohead) X printf("\n/* %s */\n", *argv); X linenum = 1; X newline_seen = 1; X glastc = ' '; XDEBUG("calling getdecl\n"); X getdecl(f); XDEBUG("back from getdecl\n"); X argc--; argv++; X fclose(f); XDEBUG("back from fclose\n"); X } X if (docond) { X printf("\n#undef P\n"); /* clean up namespace */ X } X exit(EXIT_SUCCESS); X} X X Xvoid Usage() X{ X fputs("Usage: mkproto [-n][-s][-p][files ...]\n",stderr); X fputs(" -n: put line numbers of declarations as comments\n",stderr); X fputs(" -s: include declarations for static functions\n", stderr); X fputs(" -p: don't make header files readable by non-ANSI compilers\n", X stderr); X exit(EXIT_FAILURE); X} SHAR_EOF if test 12844 -ne "`wc -c mkproto.c`" then echo shar: error transmitting mkproto.c '(should have been 12844 characters)' fi echo shar: extracting mkproto.h sed 's/^X//' << \SHAR_EOF > mkproto.h X#ifdef __STDC__ X# define P(s) s X#else X# define P(s) () X#endif X X X/* mkproto.c */ XWord *word_alloc P((char *s )); Xvoid word_free P((Word *w )); Xint List_len P((Word *w )); XWord *word_append P((Word *w1 , Word *w2 )); Xint foundin P((Word *w1 , Word *w2 )); Xvoid addword P((Word *w , char *s )); XWord *typelist P((Word *p )); Xvoid typefixhack P((Word *w )); Xint ngetc P((FILE *f )); Xint fnextch P((FILE *f )); Xint nextch P((FILE *f )); Xint getsym P((char *buf , FILE *f )); Xint skipit P((char *buf , FILE *f )); XWord *getparamlist P((FILE *f )); Xvoid emit P((Word *wlist , Word *plist , long startline )); Xvoid getdecl P((FILE *f )); Xvoid main P((int argc , char **argv )); Xvoid Usage P((void )); X X#undef P SHAR_EOF if test 703 -ne "`wc -c mkproto.h`" then echo shar: error transmitting mkproto.h '(should have been 703 characters)' fi echo shar: extracting mkproto.man sed 's/^X//' << \SHAR_EOF > mkproto.man XNAME X mkproto - make prototypes for functions X X XSYNOPSIS X mkproto [-n] [-s] [-p] [ file ] ... X X XDESCRIPTION X Mkproto takes as input one or more C source code files, and Xproduces as output (on the standard output stream) a list of function Xprototypes (a la ANSI) for the external functions defined in the Xgiven source files. This output, redirected to a file, is suitable Xfor #include'ing in a C source file. X The function definitions in the original source Xmay be either "old-style" (in which case appropriate prototypes are Xgenerated for the functions) or "new-style" (in which the definition Xincludes a prototype already). X A -n option causes the line number where each function was defined Xto be prepended to the prototype declaration as a comment. X A -s option causes prototypes to be generated for functions declard X"static" as well as extern functions. X A -p option causes the prototypes emitted to be only readable by ANSI Xcompilers. Normally, the prototypes are "macro-ized" so that compilers Xwith __STDC__ not defined don't see them. X If files are specified on the command line, then a comment specifying Xthe file of origin is emitted before the prototypes constructed from Xthat file. If no files are given, then no comments are emitted and Xthe C source code is taken from the standard input stream. X XBUGS X Mkproto is easily confused by complicated declarations, such as X int ((*signal)())() { ... Xor X struct foo { int x, y; } foofunc() { ... X X Float types are not properly promoted in old style definitions, Xi.e. X int test(f) float f; { ... Xshould (because of the default type conversion rules) have prototype X int test(double f); Xrather than the incorrect X int test(float f); Xgenerated by mkproto. X X Some programs may need to be run through the preprocessor before Xbeing run through mkproto. The -n option is unlikely to work as desired Xon the output of a preprocessor. X X Typedef'd types aren't correctly promoted, e.g. for X typedef schar char; int foo(x) schar x;... Xmkproto incorrectly generates the prototype int foo(schar x) rather than Xthe (correct) int foo(int x). X X Functions named "inline" with no explicit type qualifiers are not Xrecognized. X XSEE ALSO X cc(1), lint(1) X XAUTHOR X Eric R. Smith. X XNOTE X There is no warranty for this program (as noted above, it's guaranteed Xto break sometimes anyways!). Mkproto is in the public domain. SHAR_EOF if test 2433 -ne "`wc -c mkproto.man`" then echo shar: error transmitting mkproto.man '(should have been 2433 characters)' fi echo shar: extracting README sed 's/^X//' << \SHAR_EOF > README XHere is mkproto, a program for generating prototype declarations for all Xfunctions appearing in a C source file. The input C code may be either XK&R or ANSI C (i.e. it's OK if the functions are defined using prototypes). XUnlike some of the sed-based scripts floating around, it correctly Xhandles prototype promotion (e.g. the prototype for 'int foo() char x;...' Xis 'int foo(int x)'). Also, it should work OK on just about any computer, Xnot just Unix-based ones (it's been tested under minix, Unix, and TOS). X XUse: typically, you would type 'mkproto *.c >proto.h' and then add a X'#include "proto.h"' line to all the C source files. An ANSI conformant Xcompiler will then be able to do type checking on function calls across Xmodule boundaries. As a bonus, proto.h will tell you which source files Xfunctions were defined in, and (if you gave the -n function to mkproto) Xtheir line numbers. The resulting include file may also be used by Xnon-ANSI compilers; you can disable this feature (for cleaner, strictly XANSI-conforming output) with the -p flag. X XPlease read the description of bugs in mkproto.man; definitely mkproto Xwill not handle all programs correctly, but it does work on the majority of Xthem. A sample of its output is provided in the file "mkproto.h"; this Xis the result of 'mkproto mkproto.c >mkproto.h'. X XThere is ABSOLUTELY NO WARRANTY for the program; as I said, it doesn't work Xon all programs (complicated function definitions can make it produce bogus Xoutput). It does what I need, though, and it can certainly make porting stuff Xto ANSI compilers easier. X XMkproto is in the public domain. If you find any bugs (other than the ones Xdocumented) please let me know. X-- XEric R. Smith email: XDept. of Mathematics ersmith@uwovax.uwo.ca XUniversity of Western Ontario ersmith@uwovax.bitnet XLondon, Ont. Canada N6A 5B7 Xph: (519) 661-3638 SHAR_EOF if test 1879 -ne "`wc -c README`" then echo shar: error transmitting README '(should have been 1879 characters)' fi # End of shell archive exit 0 -- -- Eric R. Smith email: Dept. of Mathematics ERSMITH@uwovax.uwo.ca University of Western Ontario ERSMITH@uwovax.bitnet London, Ont. Canada N6A 5B7 ph: (519) 661-3638