[comp.sources.misc] v03i045: ANSI prototypes builder for C.

dcw@doc.ic.ac.uk (Duncan White) (06/13/88)

comp.sources.misc: Volume 3, Issue 45
Submitted-By: "Duncan White" <dcw@doc.ic.ac.uk>
Archive-Name: proto

	A couple of months ago, I mentioned on the news that I had
	written a prototype builder for C.  Several people mailed
	me requesting it, but I never got around to sending it to
	them.

	A couple of days ago, I modified the source to allow the user
	to print out a pretty printed form of the functions recognised,
	which seems like a useful option to have!

	Anyway, I wondered if you'd like to post the source into
	comp.sources.misc ....  the code should run on almost any
	machine... it certainly compiles and runs fine on an Atari ST
	with Lattice C 3.04, on a PC with Microsoft 4.0, and BSD4.2
	and 4.3 Unix with standard Unix C.

	[Of course, on standard Unix compilers, it is a rather
	pointless : but I develop on Unix and the ST, so appreciate
	the ability to prototype on either machine...]

		Duncan White

----------------- Cut here ----------------------- Cut here -------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	Makefile
#	dcw.h
#	dcwos.h
#	proto.1
#	proto.c
#	testproto
# This archive created: Tue Jun  7 20:26:55 1988
export PATH; PATH=/bin:$PATH
echo shar: extracting "'Makefile'" '(62 characters)'
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
proto:	proto.c
	cc -o proto proto.c

clean:
	/bin/rm -f proto
SHAR_EOF
if test 62 -ne "`wc -c < 'Makefile'`"
then
	echo shar: error transmitting "'Makefile'" '(should have been 62 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'dcw.h'" '(1306 characters)'
if test -f 'dcw.h'
then
	echo shar: will not over-write existing file "'dcw.h'"
else
cat << \SHAR_EOF > 'dcw.h'
/*
 *	My standard include file
 *
 ********* History:
 *
 *	Version	Who	Date		Comments
 *	=======	===	====		========
 *	1.3.3	dcw	6th June 1988	Made BOOL & BOOLEAN char, not int!!
 *	1.3.2	dcw	26th Apr 1988	Added NOC_ALLOC, NOC_NEW, made
 *					COPYOF call NOC_ALLOC !!!
 *	1.3.1	dcw	2nd Mar 1988	Changed name to dcw.h and dcwos.h
 *					included strings.h and ctype.h
 *					added extern calloc() decln
 *	1.3	dcw	9th Sep 1987	Included os.h
 *	1.2	dcw	19th July 1987	Made external 'extern' [was null]
 *					and added brackets in streq
 *	1.1	dcw	2nd July 1987	Added BOOLEAN as well as BOOL !
 *	1.0	dcw	10th June 1987	Wrote it.
 */


#include <stdio.h>
#include <strings.h>
#include <ctype.h>
#include "dcwos.h"

extern char *calloc(), malloc();

#define ASSERT(x,y) if(!(x)){printf y;exit(1);}
#define ABORT(y)    ASSERT(FALSE,y)
#define FORWARD
#define EXTERNAL    extern

#define ALLOC(n,t) (t *)calloc((n),sizeof(t))
#define NEW(t)     ALLOC(1,t)

/* make copy of old string into new: was ALLOC not NOC_ALLOC */
#define COPYOF(new,old)  {new=NOC_ALLOC(1+strlen(old),char);strcpy(new,old);}

#define NOC_ALLOC(n,t) (t *)malloc((n)*sizeof(t))
#define NOC_NEW(t)     NOC_ALLOC(1,t)

#define streq(x,y)  (strcmp((x),(y))==0)

#define BOOL    char
#define BOOLEAN char
#define TRUE    1
#define FALSE   0
SHAR_EOF
if test 1306 -ne "`wc -c < 'dcw.h'`"
then
	echo shar: error transmitting "'dcw.h'" '(should have been 1306 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'dcwos.h'" '(279 characters)'
if test -f 'dcwos.h'
then
	echo shar: will not over-write existing file "'dcwos.h'"
else
cat << \SHAR_EOF > 'dcwos.h'
/*
 * Alter this to show which o/s you have
 */

#define UNIX
#undef ATARIST
#undef MSDOS

/*
 * Alter this to show which compiler you have:
 */

#define UNIXC
#undef LATTICE304
#undef MICROSOFT4

/*
 * Define this only if your compiler supports prototypes
 */

#undef HASPROTOS
SHAR_EOF
if test 279 -ne "`wc -c < 'dcwos.h'`"
then
	echo shar: error transmitting "'dcwos.h'" '(should have been 279 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'proto.1'" '(1489 characters)'
if test -f 'proto.1'
then
	echo shar: will not over-write existing file "'proto.1'"
else
cat << \SHAR_EOF > 'proto.1'
.EV
.T1 proto 1 "2/6/88"
.SH NAME
proto \- build ANSI style prototypes from C source code.
.SH SYNTAX
.B proto
[\fB\-f\fP]  \fIinputfile\fP [\fIoutputfile\fp]
.SH DESCRIPTION
.I Proto
builds a list of ANSI-style external declarations [\fIprototypes\fP] for all
the functions in a given C source file.
It produces this list in two forms:
.nf
	#ifdef HASPROTOS
	<nice external prototype declarations>
	#else
	<old ikky external declarations for UNIX C compilers>
	#endif
.fi
.PP
You can then incorporate the file generated into your .h files.
Make sure you #define HASPROTOS iff your compiler supports
prototypes: otherwise you'll lose all the advantages.
.PP
If you omit the output file from the command line,
the standard output will be used.
.SH OPTIONS
The only option this program accepts is \fB-f\fP,
which causes it to place a pretty-printed form of the
original function declarations in the output file,
rather than the prototypes.
.SH PORTABILITY
This program should work fine on any system with a C compiler.
If it doesn't, please let me know!
.SH AUTHOR
Duncan White,
Dept Of Computing,
Imperial College,
London,
England.
.SH LIMITATIONS
The grammar used for parsing is incomplete.
In particular, array parameters, function parameters, and structure parameters
not disguised by \fI#define's\fP or \fItypedef's\fP will not be recognised.
.PP
In addition, the entire function declaration, including the types of the
parameters, must reside on a single line for it to be recognised.
SHAR_EOF
if test 1489 -ne "`wc -c < 'proto.1'`"
then
	echo shar: error transmitting "'proto.1'" '(should have been 1489 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'proto.c'" '(10332 characters)'
if test -f 'proto.c'
then
	echo shar: will not over-write existing file "'proto.c'"
else
cat << \SHAR_EOF > 'proto.c'
/*
 *			Prototypes builder...
 *			=====================
 *
 *		Copyright (C) Duncan White, 1987 & 1988.
 *
 ****** Restrictions on use
 *
 *	Anyone may use, adapt and extend this program in any way they like,
 *	subject to the following:
 *
 *	1).	my name is not removed.
 *	2).	your name is appended if you change it, detailing the changes
 *		you made.
 *	3).	you don't make any money out of it without consulting me.
 *	4).	you don't attempt to alter this 'restrictions on use' section.
 *
 *	In other words, share & enjoy...
 *
 ****** Purpose
 *
 *	This program attempts to build a file of external declarations
 *	[prototypes] for all the functions declared in a given C file.
 *	It produces this list in two forms:
 *
 *	#ifdef HASPROTOS
 *	<nice external prototype declarations>
 *	#else
 *	<old ikky external declarations for UNIX C compilers>
 *	#endif
 *
 *	You can then incorporate the file generated into your .h files.
 *	Make sure you #define HASPROTOS iff your compiler supports
 *	prototypes: otherwise you'll lose all the advantages.
 *
 *	Alternatively, if you give the -f [function] option, the program will
 *	instead produce a pretty printed text form of the function
 *	declarations it parses.
 *
 ****** How does it do it:
 *
 *	The program parses EACH LINE of a C source file to see whether it
 *	satisfies the grammar shown below. If it does, the appropriate
 *	declarations is bunged out onto the output stream.
 *
 *	Unfortunately, this implies that an entire function declaration
 *	must be on a single line.... sorry to all those who don't use this
 *	convention: I didn't feel up to writing a full C parser, or a
 *	backtracking parser!
 *
 ****** The Grammar
 *
 *	The grammar is restricted; in particular, it omits:
 *
 *	1).	function parameters (use typedef!),
 *	2).	array parameters (I use pointers instead),
 *	3).	and struct parameters not concealed by #define's or typedef's.
 *
 *	function_declaration	= [ starred_id ] id '(' idlist ')' tslist
 *	idlist			= empty | id list{ ',' id }
 *	tslist			= list{ typespec } ( EOL | '{' )
 *	typespec		= id starred_id list{ ',' starred_id } ';'
 *	starred_id		= {'*'} id
 *
 ******* History
 *
 *	Version	Who	Date		Comments
 *	=======	===	====		========
 *	1.0	dcw	16th Aug 1987	Wrote it
 *	1.1	dcw	30th Aug 1987	Corrected spaces bug &
 *					added #ifdef LATTICE #else
 *	1.2	dcw	9th Sep 1987	Used dcwos.h to determine HASPROTOS
 *	1.3	dcw	25th Feb 1988	Added starred id for return type
 *	XNX1.4	dcw	5th April 1988	Ported to Xenix
 *					made it accept declns eg. thingy() {..
 *					altered arg{c,v} interface.
 *	BSD1.4	dcw	7th April 1988	Reverse ported to BSD
 *	1.5	dcw	7th April 1988	Made if and while special cases in
 *					get_token
 *	2.0	dcw	2nd July 1988	Made ready for releasing to news...
 */


#include "dcw.h"


/* ----------------------- Data types ------------------------- */


#define ID_LEN		50
#define LINE_LEN	1024


#define FDECL		struct anon1
#define EL_IDLIST	struct anon2
#define EL_TSLIST	struct anon3

#define IDLIST	EL_IDLIST *
#define TSLIST	EL_TSLIST *

FDECL {
	char	ftype[ID_LEN];
	char	fname[ID_LEN];
	IDLIST	formals;
	TSLIST	ftypes;
};

EL_IDLIST {
	char	id[ID_LEN];
	IDLIST	next;
};

EL_TSLIST {
	char	type[ID_LEN];
	char	var[ID_LEN];
	TSLIST	next;
};


/* --------------------- Forward declns ----------------------- */
/*             Guess what generated these then ???              */


#ifdef HASPROTOS
extern void main( int , char ** );
extern void usage( BOOL );
extern void out_extern( FILE * , FDECL * , BOOL );
extern void print_fdecl( FILE * , FDECL * );
extern void prototype( FILE * , FDECL * , char * );
extern int readline( FILE * , char * );
extern void get_token( void );
extern BOOL parse_function_decln( char * , FDECL * );
extern BOOL parse_list_id( IDLIST * );
extern BOOL parse_restof_ids( IDLIST * );
extern BOOL parse_list_ts( TSLIST * );
extern BOOL parse_typespec( TSLIST * );
extern BOOL parse_starred_id( char * , char * );
#else
extern void main();
extern void usage();
extern void out_extern();
extern void print_fdecl();
extern void prototype();
extern int readline();
extern void get_token();
extern BOOL parse_function_decln();
extern BOOL parse_list_id();
extern BOOL parse_restof_ids();
extern BOOL parse_list_ts();
extern BOOL parse_typespec();
extern BOOL parse_starred_id();
#endif


/* ---------------------- Main program ------------------------ */


void main( argc, argv ) int argc; char **argv;
{
	FILE	*in,*out;
	FDECL	f;
	char	line[LINE_LEN];
	BOOL	origonly;

	usage( argc>1 && argc<=4 );

	origonly = ( strcmp( argv[1], "-f" ) == 0 );
	if( origonly) {
		int i;

		for( i=2; i<argc; i++ ) argv[i-1] = argv[i];
		argc--;
	}

	usage( argc > 1 );	/* still at least one argument left */

	in = fopen( argv[1], "r" );
	ASSERT( in != NULL, ("Can't open '%s'\n",argv[1]) );

	if( argc == 2 ) {
		out = stdout;
	} else {
		out = fopen( argv[2], "w" );
		ASSERT( out != NULL, ("Can't create '%s'\n",argv[2]) );
	}

	if( origonly ) {
		while( readline( in, line ) != EOF ) {
			if( parse_function_decln( line, &f ) ) {
				print_fdecl( out, &f );
			}
		}
	} else {
		fprintf( out, "#ifdef HASPROTOS\n" );
		while( readline( in, line ) != EOF ) {
			if( parse_function_decln( line, &f ) ) {
				out_extern( out, &f, TRUE );
			}
		}
		fprintf( out, "#else\n" );
		fseek( in, 0L, 0 );
		while( readline( in, line ) != EOF ) {
			if( parse_function_decln( line, &f ) ) {
				out_extern( out, &f, FALSE );
			}
		}
		fprintf( out, "#endif\n" );
	}
	fclose( in );
	fclose( out );
	exit( 0 );
}


void usage(b) BOOL b;
{
	ASSERT( b, ("Usage: proto [-f] infile [outfile]\n") );
}


void out_extern( out, f, b ) FILE *out; FDECL *f; BOOL b;
{
	IDLIST  i;

	fprintf( out, "extern %s %s(", f->ftype, f->fname );
	if( b ) {
		if( f->formals ) {
			prototype( out, f, f->formals->id );
			for( i=f->formals->next; i; i=i->next ) {
				fprintf( out, "," );
				prototype( out, f, i->id );
			}
		} else {
			fprintf( out, " void " );
		}
	}
	fprintf( out, ");\n" );
}


/* print the original form of the function declaration */

void print_fdecl( out, f ) FILE *out; FDECL *f;
{
	IDLIST i;
	TSLIST t;

	fprintf( out, "%s %s(", f->ftype, f->fname );
	if( f->formals ) {
		fprintf( out, "%s", f->formals->id );
		for( i=f->formals->next; i; i=i->next ) {
			fprintf( out, ", %s", i->id );
		}
	}
	fprintf( out, ")" );
	for( t=f->ftypes; t; t=t->next ) {
		fprintf( out, " %s %s;", t->type, t->var );
	}
	fprintf( out, " \n" );
}


void prototype( out, f, name ) FILE *out; FDECL *f; char *name;
{
	TSLIST t;

	for( t=f->ftypes; t; t=t->next ) {
		if( streq( name, t->var ) ) {
			fprintf( out, " %s ", t->type );
			return;
		}
	}
	fprintf( stderr, "warning: %s has undeclared formal '%s' => integer\n",
		f->fname, name );
fprintf( out, " int" );
}


int readline( f, line ) FILE *f; char *line;
{
	int c;
	char *p = line;

	c=getc(f);
	if( c==EOF ) return c;
	ungetc( c, f );
	while( (c=getc(f)) != '\n' ) *p++ = c;
	*p = '\0';
	return p-line;
}


/* ----------------- Lexer routines ---------------------- */


char *lex_line;
char tokstr[100];
enum token_type {
	tID, tSTAR, tSEMI, tCOMMA, tOPEN, tCLOSE, tEOL, tBRACE, tERR
} token;


void get_token()
{
	char *s = tokstr;
	char c;

	while( *lex_line == ' ' || *lex_line == '\t' ) lex_line++;
	c = *lex_line++;
	*s++ = c;
	*s = '\0';

	switch( c ) {
	case '\0' : token = tEOL      ; break;
	case ','  : token = tCOMMA    ; break;
	case ';'  : token = tSEMI     ; break;
	case '*'  : token = tSTAR     ; break;
	case '('  : token = tOPEN     ; break;
	case '{'  : token = tBRACE; break;
	case ')'  : token = tCLOSE    ; break;
	default   :
		if( !isalpha( c ) ) {
			token = tERR;
		} else {
			while( (c = *lex_line)=='_'||isalnum(c) ) {
				*s++ = c; lex_line++;
			}
			*s = '\0';
			if( strcmp(tokstr,"if") == 0
			||  strcmp(tokstr,"while") == 0
			||  strcmp(tokstr,"switch") == 0
			) {
				token = tERR;
			} else {
				token = tID;
			}
		}
	}
}


/* ------------------- Parser routines ------------------- */


BOOL parse_function_decln( line, f ) char *line; FDECL *f;
{
	char stars   [ID_LEN];
	char basetype[ID_LEN];

	lex_line = line;
	get_token();
	if( token != tID ) return FALSE;

	strcpy( basetype, tokstr );
	get_token();
	if( token == tOPEN ) {
		strcpy( f->fname, basetype );
		strcpy( f->ftype, "int" );
	} else {
		if( ! parse_starred_id( stars, f->fname ) ) return FALSE;

		if( *stars == '\0' ) {
			strcpy( f->ftype, basetype );
		} else {
			sprintf( f->ftype, "%s %s", basetype, stars );
		}
		if( token != tOPEN ) return FALSE;
	}
	get_token();
	if( ! parse_list_id( &(f->formals) ) || token != tCLOSE )
		return FALSE;
	get_token();
	return parse_list_ts( &(f->ftypes) );
}


/* attempt to parse an idLIST [possibly empty] */

BOOL parse_list_id( ip ) IDLIST *ip;
{
	*ip = NULL;
	if( token != tID ) return TRUE;
	*ip = NEW( EL_IDLIST );
	strcpy( (*ip)->id, tokstr );
	get_token();
	return parse_restof_ids( &((*ip)->next) );
}


BOOL parse_restof_ids( ip ) IDLIST *ip;
{
	*ip = NULL;
	if( token != tCOMMA ) return TRUE;

	get_token();
	if( token != tID ) return FALSE;

	*ip = NEW( EL_IDLIST );
	strcpy( (*ip)->id, tokstr );
	get_token();
	return parse_restof_ids( &((*ip)->next) );
}


/*
 * attempt to parse a list of typespecs [possibly empty]
 * followed by tEOL or tBRACE
 */

BOOL parse_list_ts( tp ) TSLIST *tp;
{
	TSLIST t;

	for(;;) {
		*tp = NULL;
		if( token==tEOL || token==tBRACE ) return TRUE;

		if( ! parse_typespec( tp ) ) return FALSE;
		for( t = *tp; t->next; t=t->next );
		tp = &(t->next);
	}
	/*NOTREACHED*/
}


BOOL parse_typespec( tp ) TSLIST *tp;
{
	char basetype[ID_LEN];
	char stars   [ID_LEN];

	if( token != tID ) return FALSE;
	strcpy( basetype, tokstr );

	for(;;) {
		get_token();
		*tp = NEW( EL_TSLIST );
		if( ! parse_starred_id( stars, (*tp)->var ) ) return FALSE;

		if( *stars == '\0' ) {
			strcpy( (*tp)->type, basetype );
		} else {
			sprintf( (*tp)->type, "%s %s", basetype, stars );
		}

		if( token == tSEMI ) {
			get_token();
			return TRUE;
		}
		if( token != tCOMMA ) return FALSE;
		tp = &((*tp)->next);
	}
	/*NOTREACHED*/
}


BOOL parse_starred_id( stars, id ) char *stars, *id;
{
	BOOL ok;

	while( token == tSTAR ) {
		*stars++ = '*';
		get_token();
	}
	*stars = '\0';
	ok = token == tID;
	if( ok ) {
		strcpy( id, tokstr );
		get_token();
	}
	return ok;
}
SHAR_EOF
if test 10332 -ne "`wc -c < 'proto.c'`"
then
	echo shar: error transmitting "'proto.c'" '(should have been 10332 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'testproto'" '(134 characters)'
if test -f 'testproto'
then
	echo shar: will not over-write existing file "'testproto'"
else
cat << \SHAR_EOF > 'testproto'
int x()
char *x(zz) int zz;
char *x2((zz) int zz;
char *x____32y(zz) int zz;
helllo();
wibblewobble ****eek(x,y,z) char **z; int x; {
SHAR_EOF
if test 134 -ne "`wc -c < 'testproto'`"
then
	echo shar: error transmitting "'testproto'" '(should have been 134 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0

----------------------------------------------------------------------------
Duncan White,           |       Flying is the art of aiming oneself
Dept. Of Computing,     |       at the ground and missing.
Imperial College,       |               -- Douglas Adams, So Long and Thanks
London SW7, England     |                  for all the fish.