/* proto - Generate ANSI C prototypes. Author: Eric R. Smith */ /* Program to extract function declarations from C source code * Written by Eric R. Smith and placed in the public domain * Thanks are due to Jwahar R. Bammi for fixing several bugs * And providing the Unix makefiles. */ #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1 #include #include #include #include #define ISCSYM(x) ((x) > 0 && (isalnum(x) || (x) == '_' )) #define ABORTED ( (Word *) -1 ) #define MAXPARAM 20 /* max. number of parameters to a function */ typedef struct word { struct word *next; char string[1]; } Word; int inquote = 0; /* in a quote? */ int newline_seen = 1; /* are we at the start of a line */ long linenum = 1L; /* line number in current file */ long endline = 0L; /* the last line before the { of a f'n */ long symline = 0L; /* Line that symbol was on, set by getsym() */ int dostatic = 0; /* do static functions? */ int donum = 0; /* print line numbers? */ int dohead = 1; /* do file headers? */ int docond = 1; /* conditionalize for non-ANSI compilers? */ int dodiff = 0; /* Output a diff file to prototype original */ int doold = 0; /* do old style: P() */ int glastc = ' '; /* last char. seen by getsym() */ Word *endlist; /* Parentheses after the parameters */ char *progname; /* name of program (for error messages) */ _PROTOTYPE(Word * word_alloc, (char *s)); _PROTOTYPE(void word_free, (Word * w)); _PROTOTYPE(int List_len, (Word * w)); _PROTOTYPE(Word * word_append, (Word * w1, Word * w2)); _PROTOTYPE(int foundin, (Word * w1, Word * w2)); _PROTOTYPE(void addword, (Word * w, char *s)); _PROTOTYPE(void printlist, (Word * p)); _PROTOTYPE(Word * typelist, (Word * p)); _PROTOTYPE(void typefixhack, (Word * w)); _PROTOTYPE(int ngetc, (FILE * f)); _PROTOTYPE(int fnextch, (FILE * f)); _PROTOTYPE(int nextch, (FILE * f)); _PROTOTYPE(int getsym, (char *buf, FILE * f)); _PROTOTYPE(int skipit, (char *buf, FILE * f)); _PROTOTYPE(Word * getparamlist, (FILE * f)); _PROTOTYPE(void emit, (Word * wlist, Word * plist, long startline)); _PROTOTYPE(void getdecl, (FILE * f)); _PROTOTYPE(int main, (int argc, char **argv)); _PROTOTYPE(void Usage, (void)); /* Routines for manipulating lists of words. */ Word *word_alloc(s) char *s; { Word *w; w = (Word *) malloc(sizeof(Word) + strlen(s) + 1); if (w == NULL) { fprintf(stderr, "%s: out of memory\n", progname); exit(1); } (void) strcpy(w->string, s); w->next = NULL; return w; } void word_free(w) Word *w; { Word *oldw; while (w) { oldw = w; w = w->next; free((char *) oldw); } } /* Return the length of a list; empty words are not counted */ int List_len(w) Word *w; { int count = 0; while (w) { if (*w->string) count++; w = w->next; } return count; } /* Append two lists, and return the result */ Word *word_append(w1, w2) Word *w1, *w2; { Word *r, *w; r = w = word_alloc(""); while (w1) { w->next = word_alloc(w1->string); w = w->next; w1 = w1->next; } while (w2) { w->next = word_alloc(w2->string); w = w->next; w2 = w2->next; } return r; } /* See if the last entry in w2 is in w1 */ int foundin(w1, w2) Word *w1, *w2; { while (w2->next) w2 = w2->next; while (w1) { if (!strcmp(w1->string, w2->string)) return 1; w1 = w1->next; } return 0; } /* Add the string s to the given list of words */ void addword(w, s) Word *w; char *s; { while (w->next) w = w->next; w->next = word_alloc(s); } /* Printlist: print out a list */ void printlist(p) Word *p; { Word *w; int i = 0; for (w = p; w; w = w->next) { printf("%s", w->string); if (ISCSYM(w->string[0]) && i > 0 && w->next && w->next->string[0] != ',') printf(" "); i++; } } /* Given a list representing a type and a variable name, extract just * the base type, e.g. "struct word *x" would yield "struct word". * Similarly, "unsigned char x[]" would yield "unsigned char". */ Word *typelist(p) Word *p; { Word *w, *r, *last; last = r = w = word_alloc(""); while (p && p->next) { if (p->string[0] == '[') { word_free(w); last->next = NULL; break; } if (p->string[0] && !ISCSYM(p->string[0])) break; w->next = word_alloc(p->string); last = w; w = w->next; p = p->next; } return r; } /* Typefixhack: promote formal parameters of type "char", "unsigned char", * "short", or "unsigned short" to "int". */ void typefixhack(w) Word *w; { Word *oldw = 0; while (w) { if (*w->string) { if ((!strcmp(w->string, "char") || !strcmp(w->string, "short")) && (List_len(w->next) < 2)) { if (oldw && !strcmp(oldw->string, "unsigned")) { oldw->next = w->next; free((char *) w); w = oldw; } (void) strcpy(w->string, "int"); } } w = w->next; } } /* Read a character: if it's a newline, increment the line count */ int ngetc(f) FILE *f; { int c; c = getc(f); if (c == '\n') linenum++; return c; } /* Read the next character from the file. If the character is '\' then * read and skip the next character. Any comment sequence is converted * to a blank. */ int fnextch(f) FILE *f; { int c, lastc, incomment; c = ngetc(f); while (c == '\\') { c = ngetc(f); /* skip a character */ c = ngetc(f); } if (c == '/' && !inquote) { c = ngetc(f); if (c == '*') { incomment = 1; c = ' '; while (incomment) { lastc = c; c = ngetc(f); if (lastc == '*' && c == '/') incomment = 0; else if (c < 0) return c; } return fnextch(f); } else { if (c == '\n') linenum--; (void) ungetc(c, f); return '/'; } } return c; } /* Get the next "interesting" character. Comments are skipped, and strings * are converted to "0". Also, if a line starts with "#" it is skipped. */ int nextch(f) FILE *f; { int c; c = fnextch(f); if (newline_seen && c == '#') { do { c = fnextch(f); } while (c >= 0 && c != '\n'); if (c < 0) return c; } newline_seen = (c == '\n'); if (c == '\'' || c == '\"') { inquote = c; while ((c = fnextch(f)) >= 0) { if (c == inquote) { inquote = 0; return '0'; } } } return c; } /* Get the next symbol from the file, skipping blanks. * Return 0 if OK, -1 for EOF. * Also collapses everything between { and } */ int getsym(buf, f) char *buf; FILE *f; { register int c; int inbrack = 0; c = glastc; while ((c > 0) && isspace(c)) c = nextch(f); if (c < 0) return -1; if (c == '{') { inbrack = 1; endline = linenum; while (inbrack) { c = nextch(f); if (c < 0) { glastc = c; return c; } if (c == '{') inbrack++; else if (c == '}') inbrack--; } (void) strcpy(buf, "{}"); glastc = nextch(f); return 0; } if (!ISCSYM(c)) { *buf++ = c; glastc = nextch(f); if (c == '(' && glastc == '*') { /* Look for a 'f'n pointer */ *buf++ = glastc; glastc = nextch(f); } *buf = 0; return 0; } symline = linenum; while (ISCSYM(c)) { *buf++ = c; c = nextch(f); } *buf = 0; glastc = c; return 0; } /* Skipit: skip until a ";" or the end of a function declaration is seen */ int skipit(buf, f) char *buf; FILE *f; { int i; do { i = getsym(buf, f); if (i < 0) return i; } while (*buf != ';' && *buf != '{'); return 0; } /* Get a parameter list; when this is called the next symbol in line * should be the first thing in the list. */ Word *getparamlist(f) FILE *f; { static Word *pname[MAXPARAM]; /* parameter names */ Word *tlist, /* type name */ *plist; /* temporary */ int np = 0; /* number of parameters */ int typed[MAXPARAM]; /* parameter has been given a type */ int tlistdone; /* finished finding the type name */ int sawsomething; int i; int inparen = 0; char buf[80]; for (i = 0; i < MAXPARAM; i++) typed[i] = 0; plist = word_alloc(""); endlist = word_alloc(""); /* First, get the stuff inside brackets (if anything) */ sawsomething = 0; /* gets set nonzero when we see an arg */ for (;;) { if (getsym(buf, f) < 0) return(NULL); if (*buf == ')' && (--inparen < 0)) { if (sawsomething) { /* if we've seen an arg */ pname[np] = plist; plist = word_alloc(""); np++; } break; } if (*buf == ';') { /* something weird */ return ABORTED; } sawsomething = 1; /* there's something in the arg. list */ if (*buf == ',' && inparen == 0) { pname[np] = plist; plist = word_alloc(""); np++; } else { addword(plist, buf); if (*buf == '(') inparen++; } } /* Next, get the declarations after the function header */ inparen = 0; tlist = word_alloc(""); plist = word_alloc(""); tlistdone = 0; sawsomething = 0; for (;;) { if (getsym(buf, f) < 0) return(NULL); /* Handle parentheses, which should indicate func pointer rtn values */ if (*buf == '(') { addword(endlist, buf); addword(endlist, " void "); inparen++; } else if (*buf == ')') { if (symline == linenum) { addword(endlist, buf); addword(endlist, buf); } inparen--; } else if (*buf == ',' && !inparen) { /* Handle a list like "int x,y,z" */ if (!sawsomething) return(NULL); for (i = 0; i < np; i++) { if (!typed[i] && foundin(plist, pname[i])) { typed[i] = 1; word_free(pname[i]); pname[i] = word_append(tlist, plist); /* Promote types */ typefixhack(pname[i]); break; } } if (!tlistdone) { tlist = typelist(plist); tlistdone = 1; } word_free(plist); plist = word_alloc(""); } else if (*buf == ';') { /* Handle the end of a list */ if (!sawsomething) return ABORTED; for (i = 0; i < np; i++) { if (!typed[i] && foundin(plist, pname[i])) { typed[i] = 1; word_free(pname[i]); pname[i] = word_append(tlist, plist); typefixhack(pname[i]); break; } } tlistdone = 0; word_free(tlist); word_free(plist); tlist = word_alloc(""); plist = word_alloc(""); } else if (!strcmp(buf, "{}")) break; /* Handle the beginning of the function */ /* Otherwise, throw word into list (except for "register") */ else if (strcmp(buf, "register")) { sawsomething = 1; addword(plist, buf); if (*buf == '(') inparen++; if (*buf == ')') inparen--; } } /* Now take the info we have and build a prototype list */ /* Empty parameter list means "void" */ if (np == 0) return word_alloc("void"); plist = tlist = word_alloc(""); for (i = 0; i < np; i++) { /* If no type provided, make it an "int" */ if (!(pname[i]->next) || (!(pname[i]->next->next)&&strcmp(pname[i]->next->string,"void"))) { addword(tlist, "int"); } while (tlist->next) tlist = tlist->next; tlist->next = pname[i]; if (i < np - 1) addword(tlist, ", "); } return plist; } /* Emit a function declaration. The attributes and name of the function * are in wlist; the parameters are in plist. */ void emit(wlist, plist, startline) Word *wlist, *plist; long startline; { Word *w; int count = 0; if (doold == 0) printf("_PROTOTYPE( "); if (dodiff) { printf("%lda%ld,%ld\n", startline - 1, startline, startline +2); printf("> #ifdef __STDC__\n> "); } if (donum) printf("/*%8ld */ ", startline); for (w = wlist; w; w = w->next) { if (w->string[0]) count++; } if (count < 2) printf("int "); printlist(wlist); if (docond) { if (doold) printf(" P(("); else printf(", ("); } else { printf("("); } printlist(plist); printlist(endlist); if (docond) { if (doold) printf("))"); else printf(") )"); } else { printf(")"); } if (!dodiff) printf(";\n"); else printf("\n"); if (dodiff) { printf("> #else\n"); printf("%lda%ld\n", endline - 1, endline); printf("> #endif\n"); } } /* Get all the function declarations */ void getdecl(f) FILE *f; { Word *plist, *wlist = NULL; char buf[80]; int sawsomething; long startline = 0L; /* line where declaration started */ int oktoprint; again: /* SHAME SHAME */ word_free(wlist); wlist = word_alloc(""); sawsomething = 0; oktoprint = 1; for (;;) { if (getsym(buf, f) < 0) return; /* Guess when a declaration is not an external function definition */ if (!strcmp(buf, ",") || !strcmp(buf, "{}") || !strcmp(buf, "=") || !strcmp(buf, "typedef") || !strcmp(buf, "extern")) { (void) skipit(buf, f); goto again; } if (!dostatic && !strcmp(buf, "static")) oktoprint = 0; /* For the benefit of compilers that allow "inline" declarations */ if (!strcmp(buf, "inline") && !sawsomething) continue; if (!strcmp(buf, ";")) goto again; /* A left parenthesis *might* indicate a function definition */ if (!strcmp(buf, "(")) { if (!sawsomething || !(plist = getparamlist(f))) { (void) skipit(buf, f); goto again; } if (plist == ABORTED) goto again; /* It seems to have been what we wanted */ if (oktoprint) emit(wlist, plist, startline); word_free(plist); goto again; } addword(wlist, buf); if (!sawsomething) startline = symline; sawsomething = 1; } } int main(argc, argv) int argc; char **argv; { FILE *f, *g; char *t; char newname[40]; progname = argv[0]; argv++; argc--; g = stdout; while (*argv && **argv == '-') { t = *argv++; --argc; t++; while (*t) { if (*t == 's') dostatic = 1; else if (*t == 'n') donum = 1; else if (*t == 'p') docond = 0; else if (*t == 'P') doold =1; else if (*t == 'd') { dodiff = 1; doold = 1; docond = 0; donum = 0; dostatic = 1; } else Usage(); t++; } } if (docond && doold) { printf("#ifdef __STDC__\n"); printf("# define P(args)\targs\n"); printf("#else\n"); printf("# define P(args)\t()\n"); printf("#endif\n\n"); } if (argc == 0) getdecl(stdin); else while (argc > 0 && *argv) { if (!(f = fopen(*argv, "r"))) { perror(*argv); exit(EXIT_FAILURE); } #if 0 if (dodiff) { (void) sprintf(newname, "%sdif", *argv); (void) fclose(g); if (!(g = fopen(newname, "w"))) { perror(newname); exit(EXIT_FAILURE); } } #endif if (doold && dohead && !dodiff) printf("\n/* %s */\n", *argv); linenum = 1; newline_seen = 1; glastc = ' '; getdecl(f); argc--; argv++; (void) fclose(f); } if (docond && doold) printf("\n#undef P\n"); /* clean up namespace */ (void) fclose(g); return(EXIT_SUCCESS); } void Usage() { fputs("Usage: ", stderr); fputs(progname, stderr); fputs(" [-d][-n][-p][-s] [files ...]\n", stderr); fputs(" -P: use P() style instead of _PROTOTYPE\n", stderr); fputs(" -d: produce a diff file to prototype original source\n", stderr); fputs(" -n: put line numbers of declarations as comments\n", stderr); fputs(" -p: don't make header files readable by K&R compilers\n", stderr); fputs(" -s: include declarations for static functions\n", stderr); exit(EXIT_FAILURE); }