#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

//	
//	Prototyping utility - generates function prototypes from C source.  
//	Copyright (c) 1996-2003 Jim Peters <http://uazu.net/>.
//	Released under the GNU GPL version 2.
//	
//	Handles ANSI C, but not old K&R function definitions.  Quite
//	old code, a few bugs present, probably could do with an
//	overhaul.  See usage() for usage info.
//
//	With -s option, attempts to split source into individual
//	files, each containing a single function or variable
//	definition.
//

char lin[256];
FILE *in;
FILE *out;
int level;		// level: 0 outside {}, otherwise number of '{'
int in_comm;		// inside /* */ comments ?
int in_str;		// inside a long quoted string ?
int opt_e;		// -e option ?
int opt_v;		// -v option ?
int opt_s;		// -s option ?
int line;
char *file;		// Pointer to current file name

char *sbuf;		// Section-text buffer
int sbuflen;		// Length of buffer in use
int sbufmax;		// Allocated size of buffer
FILE *discard;		// File for discarded sections, or 0
int unknown= 0;		// Used to label unknown functions
char tmp[1024];


//
//	Error
//

static void 
error(char *fmt, ...) { 
   va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); 
   fprintf(stderr, "\n"); exit(1); 
}

static void 
warn(char *fmt, ...) { 
   va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); 
   fprintf(stderr, "\n");
}

void	
usage() {
   fprintf(stderr, "Usage: proto [-evs] [-o out_file] in_files...\n");
   fprintf(stderr, "Options:  -e  Generate external declarations to non-static functions\n");
   fprintf(stderr, "          -v  Also generate external declarations to non-static variables\n");
   fprintf(stderr, "          -o  Specify output file (default is standard output)\n");
   fprintf(stderr, "          -s  Split into files, one for each defined symbol\n");
   error("");
}

//
//	Output a line to the section file
//

void 
AddSectLine(char *str) {
   int len= strlen(str);
   if (!sbuf) {
      sbuflen= 0;
      sbuf= malloc(sbufmax= 32768);
      if (!sbuf) error("Out of memory");
   }
   if (sbuflen + len + 1 >= sbufmax) {
      char *old= sbuf;
      sbuf= malloc(sbufmax *= 2);
      if (!sbuf) error("Out of memory");
      memcpy(sbuf, old, sbuflen);
   }
   memcpy(sbuf + sbuflen, str, len);
   sbuflen += len;
   sbuf[sbuflen++]= '\n';
}

//
//	Output a section to a named file
//

void 
OutputSect(char *fnam) {
   FILE *wr;
   int a, start= 0;
   
   if (!sbuf || !sbuflen) return;
   
   for (a= 0; a<sbuflen; a++) {
      if (sbuf[a] == '\n') { start= a+1; continue; }
      if (!isspace(sbuf[a])) break;
   }
   
   if (!(wr= fopen(fnam, "w"))) 
      error("Can't create file: %s", fnam);
   if (1 != fwrite(sbuf+start, sbuflen-start, 1, wr))
      error("Can't write to file: %s", fnam);
   if (fclose(wr)) 
      error("Error closing file: %s", fnam);
   sbuflen= 0;
}

void 
DiscardSect() {
   if (!sbuf || !sbuflen) return;
   if (!discard) {
      char *fnam= "_discard.c";
      if (!(discard= fopen(fnam, "w"))) 
	 error("Can't create file: %s", fnam);
   }
   if (1 != fwrite(sbuf, sbuflen, 1, discard))
      error("Can't write to discard file");
   sbuflen= 0;
}  

//
//	GetLine - strips out comments, strings and anything enclosed in {}
//

void
GetLineInit() {
   rewind(in);
   line= 0;
   in_comm= in_str= 0;
   level= 0;
}

//	GetLine
//	Rets: 0 OK, 1 EOF

int
GetLine() {
   char *p, buf[256], *end;
   char *q, c;
   int m_cont= 0;		// Macro continuation ?
   
   while (1) {
      line++;
      if (!fgets(buf, sizeof(buf), in)) return 1;
      for (p= buf; *p; p++) ;
      if (p == &buf[sizeof(buf)-1]) error("Line too long, line %d, file %s", line, file);
      while (p > buf && p[-1] == '\n') p--;
      end= p; *end= 0;
      
      if (opt_s) AddSectLine(buf);
      
      for (p= buf; *p == 32 || *p == '\t'; p++) ;
      
      if (m_cont) { m_cont= (end > buf && end[-1] == '\\'); continue; }
      
      if (in_str) { 
	 while (*p != '"' && *p) if (*p == '\\' && p[1]) p+=2; else p++;
	 if (!*p) continue;
	 in_str= 0; p++;
      }
      
      if (*p == '#')  { m_cont= (end[-1] == '\\'); continue; }
      
      if (in_comm) {
	 for (; *p; p++) if (p[0] == '*' && p[1] == '/') break;
	 if (!*p) continue;			// Next line
	 p+=2;
	 in_comm= 0;
      }
      
      q= lin;
      while (c= *p++) {
	 if (c == '\'' || c == '"') {		// Don't bother copying strings at level 0
	    while (*p != c && *p) if (*p == '\\' && p[1]) p+=2; else p++;
	    if (!level) *q++= *q++= c;	// "" or ''
	    if (!*p++) {
	       if (c == '\'') error("Missing quote in '' string at line %d, file %s", line, file);
	       in_str= 1; break;
	    }
	 }
	 else if (c=='/' && p[0] == '/')
	    break;
	 else if (c=='/' && p[0] == '*') {
	    in_comm= 1;
	    for (p++; *p; p++) if (p[0] == '*' && p[1] == '/') {
	       p+=2;
	       in_comm= 0;
	       break;
	    }
	    break;
	 }
	 else if (c=='{') {
	    if (!level) *q++= c;
	    level++;
	 }
	 else if (c=='}') {
	    level--;
	    if (level < 0) error("Too many closing braces, line %d, file %s", line, file);
	    if (!level) *q++= c;
	 }
	 else if (!level) *q++= c;
      }
      *q++= 0;
      
      for (p=lin; c= *p; p++) if (c != ' ' && c != '\t') return 0;		// Line complete
   }
   
   return 1;
}


void
DontWant(char *dst, char *src, char *dont) {
   register char *p,c;
   int last_sp= 0;
   
   while (c= *src++) {
      for (p= dont; *p; p++) if (c == *p) { c= ' '; break; }
      if (last_sp && c==' ') continue;
      *dst++= c;
      last_sp= (c==' ');
   }
   *dst++= 0;
}

void
DoFuncDef(char *pro, char *prosumm) {
   if (opt_e)	fprintf(out, "extern %s;\n", pro);
   else		fprintf(out, "%s;\n", pro);
   if (!opt_s) return;
   
   // Scan for first identifier before the '(' matching the final ')'.
   // This should be the function name - use it to name the section file.
   {
      char *p= strrchr(pro, ')');
      char *end, *q;
      int lev= 1;
      while (p > pro && lev > 0) {
	 switch (*--p) {
	  case '(': lev--; break;
	  case ')': lev++; break;
	 }
      }
      while (p > pro && (*p != '_' && !isalnum(*p))) p--;
      end= p+1;
      while (p > pro && (p[-1] == '_' || isalnum(p[-1]))) p--;
      
      tmp[0]= '_'; strcpy(tmp+1, file); q= strchr(tmp, 0);
      if (0 == strcmp(q-2, ".c")) q-=2;
      *q++= '-';
      if (*p == '_' || isalpha(*p)) {
	 memcpy(q, p, end-p); 
	 strcpy(q + (end-p), ".c");
      }
      else {
	 sprintf(q, "unknown-%d.c", unknown++);
	 warn("Couldn't work out function name; outputting to `%s'", tmp);
      }
      OutputSect(tmp);
   }      
}

void
DoVariable(char *pro, char *prosumm) {
   char *p, *q, c;
   char *lim= 0, *par;
   int last_sp, lev, del;
   
   if (!(opt_s || (opt_e && opt_v))) return;
   
   // Wipe out contents of first []
   lim= 0;
   for (p=pro; c= *p; p++) if (c=='[') {
      for (q= p+1; (c= *q) && c!=']'; q++) *q= ' ';
      *q= ' '; p[1]= ']';
      lim= p;
      break;
   }
   
   // Delete assignments
   lev= 0; del= 0; par= 0;
   for (p=q=pro; c= *p; p++) switch (c) {
    case '[':
    case '(': 
       lev++; if (!del) *q++= c;
       par= p; break;
    case ']':
    case ')': 
       lev--; if (!del) *q++= c; 
       break;
    case '=': 
       if (!lim || (par && par < lim)) lim= par;
       if (!lim || p < lim) lim= p;
       if (!lev) del= 1;
       if (!del) *q++= c;
       break;
    case ',':
    case ';':	
       if (!lim || (par && par < lim)) lim= par;
       if (!lim || p < lim) lim= p;
       if (!lev) del= 0;
       if (!del) *q++= c;
       break;
    default:	
       if (!del) *q++= c;
       break;
   }
   *q= 0;
   
   if (opt_s) {
      // Hopefully, the first identifier before `lim' should be the
      // first variable name.  This will go wrong in some cases.
      char *end;
      p= lim ? lim : strchr(pro, 0);
      while (p > pro && (*p != '_' && !isalnum(*p))) p--;
      end= p+1;
      while (p > pro && (p[-1] == '_' || isalnum(p[-1]))) p--;
      
      tmp[0]= '_'; strcpy(tmp+1, file); q= strchr(tmp, 0);
      if (0 == strcmp(q-2, ".c")) q-=2;
      *q++= '-';
      if (*p == '_' || isalpha(*p)) {
	 memcpy(q, p, end-p); 
	 strcpy(q + (end-p), ".c");
      }
      else {
	 sprintf(q, "unknown-%d.c", unknown++);
	 warn("Couldn't work out variable name; outputting to `%s'", tmp);
      }
      OutputSect(tmp);
   }
   
   if (opt_v) {
      // Tidy output string
      last_sp= 1;
      for (p= q= pro; c= *p++; last_sp= (c==' ')) {
	 if (c==' ' && last_sp) continue;
	 *q++= c;
      }
      if (q > pro && q[-1] == ' ') q--;
      *q= 0;
      
      if (q != pro)
	 fprintf(out, "extern %s\n", pro);
   }
}

void 
IgnPrototype(char *pro, char *prosumm) {
   if (opt_s) DiscardSect();
}

void 
IgnOldFuncDef(char *pro, char *prosumm) {
   if (opt_s) DiscardSect();
   warn("Can't handle old-style functions:\n %s", pro);  
}

void
DoProto(char *pro, char *prosumm) {
   char sum[128], *p, c;
   
   for (p=prosumm; c= *p; p++) switch (c) {
    case 'E': return;				// Ignore anything with 'extern' on it
    case 'N':
    case 'S':
    case 'U': 
       if (p[1]=='{') return;		// Can't do anything if structure/union/enum has no name
       break;
    case 'Z':
    case 'T':
       if (opt_e) return;		// Ignore statics and typedefs for '-e'
       break;
   }
   
   DontWant(sum, prosumm, "ZETSUN");
   
   if (0==strcmp(" (){}", sum))		// Function definition
      DoFuncDef(pro, prosumm);
   else if (0==strcmp(" ();", sum))	// Function prototype
      IgnPrototype(pro, prosumm);
   else if (0==strcmp(" () ;", sum))	// Old-style function
      IgnOldFuncDef(pro, prosumm);
   else
      DoVariable(pro, prosumm);
}

//	fprintf(out, "--%s--%s--  ", prosumm, sum);
//	if (0==strcmp(" (){}", sum))
//		fprintf(out, "Function definition:\n  %s\n", pro);
//	else if (0==strcmp(" ();", sum))
//		fprintf(out, "Function prototype:\n  %s\n", pro);
//	else if (0==strcmp(" () ;", sum))
//		fprintf(out, "OUCH !! Old-style function:\n  %s\n", pro);
//	else if (0==strcmp("{}", sum))
//		fprintf(out, "OUCH !! Block with no intro:\n  %s\n", pro);
//	else
//		fprintf(out, "VAR\n  %s\n", pro);


void
ExtractProto() {
   char pro[1024];
   char prosumm[128];		// Summary (just level 0 '(){}[];=')
   char *p,*q,*r,c,d;
   int lev, dlev, flg, lst_sp, lst_an, sum_sp, noout;
   
   q= pro; r= prosumm; lev= 0; lst_sp= 1; lst_an= 0; sum_sp= 0;
   while (0==GetLine()) {
      lst_sp= 1; lst_an= 0;
      for (p= lin; c= *p; p++, lst_an= isalnum(c)) {
	 if (c=='\t') c= ' ';
	 dlev= 0; flg= 1; noout= 0;
	 switch (c) {
	  case '(': dlev++; break;
	  case ')': lev--; break;
	  case '[': dlev++; break;
	  case ']': lev--; break;
	  case '{':
	  case '}': noout= 1;
	  case ';':
	  case ',':
	  case '=': break;
	  default: flg= 0;
	 }
	 
	 // Write to pro[]
	 d= noout ? ' ' : c;
	 if (!lst_sp || d != ' ') *q++= d;
	 lst_sp= d==' ';
	 
	 if (!flg && !lev && !lst_an) switch (c) {
	  case 's':	
	     if (0==memcmp("struct", p, 6) && !isalnum(p[6]))
		{ c= 'S'; flg= 1; sum_sp= 0; memcpy(q-1, p, 6); q+=5; p+=5; break; }
	     if (0==memcmp("static", p, 6) && !isalnum(p[6]))
		{ c= 'Z'; flg= 1; sum_sp= 0; memcpy(q-1, p, 6); q+=5; p+=5; break; }
	     break;
	  case 'u':
	     if (0==memcmp("union", p, 5) && !isalnum(p[5]))
		{ c= 'U'; flg= 1; sum_sp= 0; memcpy(q-1, p, 5); q+=4; p+=4; break; }
	     break;
	  case 't':	
	     if (0==memcmp("typedef", p, 7) && !isalnum(p[7]))
		{ c= 'T'; flg= 1; sum_sp= 0; memcpy(q-1, p, 7); q+=6; p+=6; break; }
	     break;
	  case 'e':	
	     if (0==memcmp("extern", p, 6) && !isalnum(p[6]))
		{ c= 'E'; flg= 1; sum_sp= 0; memcpy(q-1, p, 6); q+=5; p+=5; break; }
	     if (0==memcmp("enum", p, 4) && !isalnum(p[4]))
		{ c= 'N'; flg= 1; sum_sp= 0; memcpy(q-1, p, 4); q+=3; p+=3; break; }
	     break;
	 }
	 
	 if (!lev) {
	    if (flg) { *r++= c; sum_sp= 0; }
	    else if (c != ' ' && !sum_sp) { *r++= ' '; sum_sp= 1; }
	 }
	 lev += dlev;
	 
	 if (c=='}' || c==';') {
	    *q= 0; *r= 0;
	    if (c=='}' && r-prosumm >= 3 && r[-2]=='{' && 
		(r[-3]=='S' || r[-3]=='U' || r[-3]=='N')) continue;
	    if (c=='}' && r-prosumm >= 4 && r[-2]=='{' && r[-3]==' ' && 
		(r[-4]=='S' || r[-4]=='U' || r[-4]=='N')) continue;
	    if (c=='}' && r-prosumm >= 3 && r[-2]=='{' && r[-3]=='=') continue;
	    DoProto(pro, prosumm);
	    q= pro; r= prosumm; lev= 0; lst_sp= 1; sum_sp= 0;
	 }
      }
      if (!lst_sp) *q++= ' ';
   }
}		

int 
main(int argc, char **argv) {	
   char *p,c;
   
   argc--; argv++;
   
   opt_e= opt_v= 0;
   out= stdout;
   while (argc > 0 && argv[0][0]=='-') {
      p= &argv[0][1];
      argc--; argv++;
      while (c= *p++) switch (c) {
       case 'e': opt_e= 1; break;
       case 'v': opt_v= 1; break;
       case 's': opt_s= 1; break;
       case 'o':
	  if (argc < 1) usage();
	  out= fopen(argv[0], "w");
	  if (!out) error("Unable to open output file: %s", argv[0]);
	  argc--; argv++; break;
       default: usage();
      }
   }
   if (opt_v && !opt_e) error("Use -v option with -e option");
   
   if (argc < 1) usage();
   
   while (argc > 0) {
      in= fopen(argv[0], "r");
      if (!in) error("Unable to open input file: %s", argv[0]);
      file= argv[0];
      GetLineInit();
      ExtractProto();
      fclose(in);
      if (level) error("Missing closing braces at end of file: %s", argv[0]);
      argc--; argv++;
   }
   fclose(out);
   exit(0);
}

// END //
