[net.sources] cdecl sources

grahamr@azure.UUCP (Graham Ross) (12/22/83)

Cdecl is a little program that translates between C declarations and
pseudo-English ones.  The sources are in the form of a "shell archive".
There are no version numbers on this program, but, "right now and for a
limited time," this is guaranteed to be the latest version.

It will compile and run on 2.9, 4.1a, and 4.2 (tested) and probably any
Unix since V7 (untested).  There's a good chance it is portable to other
C environments that have stdio.

I wrote it myself and guarantee it to be free of licensed software.
Moreover, I hereby give it away again.

Directions for installing cdecl:
	make a clean directory.
	make a file "x" in that directory.
	tear off the portion of this file that follows the perforated line
		and put it in "x".
	say "sh x"
	say "make"
	install cdecl in some bin directory
	install cdecl.1 in /usr/man/man1
	the first time you run cdecl, say "help" for instructions

Cheers,
	Graham Ross
	tektronix!tekmdp!grahamr
----------------------------------------------------------------------
echo "x - cdecl.1"
cat >cdecl.1 <<'!!EOF!!'
.de Ex
.LP
.RS 2
..
.de Ee
.RE
.LP
..
.TH cdecl 1
.SH NAME
cdecl \- Compose C declarations
.SH SYNOPSIS
.B cdecl
.SH DESCRIPTION
.I Cdecl
is a program for encoding and decoding C type-declarations.
It reads standard input for statements in the language described below.
A transformation is made from that language to C.
The results of this transformation are written on standard output.
.PP
.I Cdecl's
scope is intentionally small.
It doesn't help you figure out storage classes or initializations.
.SH "COMMAND LANGUAGE"
There are four statements in the language.
The "declare" statement composes a C type-declaration
from a verbose description.
The "cast" statement composes a C type-cast
as might appear in an expression.
The "explain" statement decodes a C type-declaration or cast,
producing a verbose description.
The "help" statement describes the others.
.PP
The following grammar describes the language.
In the grammar, words in "<>" are non-terminals,
bare lower-case words are terminals that stand for themselves.
Bare upper-case words are other lexical tokens:
NOTHING means the empty string;
NAME means a C identifier;
NUMBER means a string of decimal digits; and
NL means the new-line character.
.LP
.nf
<program>    ::= NOTHING
               | <program> <stat> NL
<stat>       ::= NOTHING
               | declare NAME as <decl>
               | cast NAME into <decl>
               | explain <huh>
               | help
<decl>       ::= array of <decl>
               | array of NUMBER <decl>
               | function returning <decl>
               | function ( <namelist> ) returning <decl>
               | pointer to <decl>
               | <type>
<huh>        ::= <type> <cdecl>
               | ( <type> <abstract> ) NAME
<cdecl>      ::= <cdecl1>
               | * <cdecl>
<cdecl1>     ::= <cdecl1> ( )
               | <cdecl1> ( <namelist> )
               | <cdecl1> [ ]
               | <cdecl1> [ NUMBER ]
               | ( <cdecl> )
               | NAME
<abstract>   ::= NOTHING
               | ( )
               | ( <abstract> ) ( )
               | * <abstract>
               | <abstract> [ ]
               | <abstract> [ NUMBER ]
               | ( <abstract> )
<type>       ::= <typename> | <modlist>
               | <modlist> <typename>
               | struct NAME | union NAME | enum NAME
<typename>   ::= int | char | double | float
<modlist>    ::= <modifier> | <modlist> <modifier>
<modifier>   ::= short | long | unsigned
<namelist>   ::= NAME | <namelist> , NAME
.fi
.SH EXAMPLES
To declare an array of pointers to functions like malloc(3), do
.Ex
declare fptab as array of pointer to function returning pointer to char
.Ee
The result of this command is
.Ex
char *(*fptab[])()
.Ee
When you see this declaration in someone else's code, you
can make sense out of it by doing
.Ex
explain char *(*fptab[])()
.Ee
The proper declaration for signal(2) cannot be described in
.IR cdecl 's
language.  (It can't be described in C either!)
An adequate declaration for most purposes is given by
.Ex
declare signal as function returning pointer to function returning int
.Ee
The function declaration that results has two sets of empty parentheses.
The author of such a function might wonder where to put the parameters.
.Ex
declare signal as function (args) returning pointer to function returning int
.Ee
provides the solution:
.Ex
int (*signal(args))()
.Ee
You can use
.I cdecl
as you create a C program with an editor like vi(1).  You simply type
in the "English" version of the declaration and apply
.I cdecl
as a filter to the line.  In vi(1), type "!!cdecl<esc>".
.SH DIAGNOSTICS
The declare and cast statements try to point out constructions that are
not supported in C.  In some cases, a guess is made as to what was really
intended.  In these cases, the C result is a toy declaration whose
semantics will work only in Algol-68.  Also, certain non-portable
constructs are flagged.  For some C processors, these declarations will
work fine.
.PP
Syntax errors cause the parser to play dead until a newline is read.
.SH "SEE ALSO"
Section 8.4 of the C Reference Manual.
.SH BUGS
The pseudo-English syntax is excessively verbose.
.PP
There is a wealth of semantic checking that isn't being done.
.PP
.I Cdecl
thinks all the declarations you utter are going to be used as external
definitions.  Some declaration contexts in C allow more flexibility than
this.  An example of this is:
.Ex
declare argv as array of array of char
.Ee
where
.I cdecl
responds with
.Ex
.nf
Unsupported in C -- Inner array of unspecified size
        (maybe you mean "array of pointer")
char argv[][]
.fi
.Ee
!!EOF!!
echo "x - cdgram.y"
cat >cdgram.y <<'!!EOF!!'
%{
#include <stdio.h>

#define	MB_SHORT	0001
#define	MB_LONG		0002
#define	MB_UNSIGNED	0004
#define MB_INT		0010
#define MB_CHAR		0020
#define MB_FLOAT	0040
#define MB_DOUBLE	0100

int modbits = 0;
int arbdims = 1;
char *savedtype = 0;
char *savedname = 0;
char *ds(), *cat();
char *index(), *malloc();
char prev = 0;
%}

%union {
	char *dynstr;
	struct {
		char *left;
		char *right;
	} halves;
}

%token DECLARE CAST INTO AS HELP EXPLAIN
%token FUNCTION RETURNING POINTER TO ARRAY OF
%token <dynstr> NAME NUMBER STRUCTUNION UNSIGNED LONG SHORT
%token <dynstr> INT CHAR FLOAT DOUBLE
%type <dynstr> mod_list tname type modifier namelist
%type <dynstr> cdecl cdecl1 cast cdims adims c_type
%type <halves> adecl

%start prog

%%
prog		: /* empty */
		| prog stat
		;

stat		: HELP '\n'
			{
			help();
			}
		| DECLARE NAME AS adecl '\n'
			{
			printf("%s %s%s%s",savedtype,$4.left,$2,$4.right);
#ifdef MKPROGRAM
			if (prev == 'f')
				printf("\n{\n}\n");
			else
				printf(";\n");
#else
			printf("\n");
#endif
			free($4.left);
			free($4.right);
			free($2);
			}
		| CAST NAME INTO adecl '\n'
			{
			if (prev == 'f')
				unsupp("Cast into function",
					"cast into pointer to function");
			else if (prev=='A' || prev=='a')
				unsupp("Cast into array","cast into pointer");
			printf("(%s",savedtype);
			if (strlen($4.left)+strlen($4.right))
				printf(" %s%s",$4.left,$4.right);
			printf(")%s\n",$2);
			free($4.left);
			free($4.right);
			free($2);
			}
		| EXPLAIN type cdecl '\n'
			{ printf("declare %s as %s%s\n",savedname,$3,$2); }
		| EXPLAIN '(' type cast ')' NAME '\n'
			{ printf("cast %s into %s%s\n",$6,$4,$3); }
		| '\n'
		| error '\n'
			{
			yyerrok;
			}
		;

cdecl		: cdecl1
		| '*' cdecl
			{ $$ = cat($2,ds("pointer to "),NULL); }
		;

cdecl1		: cdecl1 '(' ')'
			{ $$ = cat($1,ds("function returning "),NULL); }
		| cdecl1 '(' namelist ')'
			{
				$$ = cat($1,ds("function ("),
					cat($3,ds(") returning "),NULL));
			}
		| cdecl1 cdims
			{ $$ = cat($1,ds("array of "),$2); }
		| '(' cdecl ')'
			{ $$ = $2; }
		| NAME
			{
				savename($1);
				$$ = ds("");
			}
		;

namelist	: NAME
		| namelist ',' NAME
			{ $$ = cat($1,ds(","),$3); }
		;

cast		: /* empty */
			{ $$ = ds(""); }
		| '(' ')'
			{ $$ = ds("function returning "); }
		| '(' cast ')' '(' ')'
			{ $$ = cat($2,ds("function returning "),NULL); }
		| '*' cast
			{ $$ = cat($2,ds("pointer to "),NULL); }
		| cast cdims
			{ $$ = cat($1,ds("array of "),$2); }
		| '(' cast ')'
			{ $$ = $2; }
		;

cdims		: '[' ']'
			{ $$ = ds(""); }
		| '[' NUMBER ']'
			{ $$ = cat($2,ds(" "),NULL); }
		;

adecl		: FUNCTION RETURNING adecl
			{
			if (prev == 'f')
				unsupp("Function returning function",
					"function returning pointer to function");
			else if (prev=='A' || prev=='a')
				unsupp("Function returning array",
					"function returning pointer");
			$$.left = $3.left;
			$$.right = cat(ds("()"),$3.right,NULL);
			prev = 'f';
			}
		| FUNCTION '(' namelist ')' RETURNING adecl
			{
			if (prev == 'f')
				unsupp("Function returning function",
					"function returning pointer to function");
			else if (prev=='A' || prev=='a')
				unsupp("Function returning array",
					"function returning pointer");
			$$.left = $6.left;
			$$.right = cat(ds("("),$3,ds(")"));
			$$.right = cat($$.right,$6.right,NULL);
			prev = 'f';
			}
		| ARRAY OF adims adecl
			{
			if (prev == 'f')
				unsupp("Array of function",
					"array of pointer to function");
			else if (prev == 'a')
				unsupp("Inner array of unspecified size",
					"array of pointer");
			if (arbdims)
				prev = 'a';
			else
				prev = 'A';
			$$.left = $4.left;
			$$.right = cat($3,$4.right,NULL);
			}
		| POINTER TO adecl
			{
			if (prev == 'a')
				unsupp("Pointer to array of unspecified size",
					"pointer to object");
			if (prev=='a' || prev=='A' || prev=='f') {
				$$.left = cat($3.left,ds("(*"),NULL);
				$$.right = cat(ds(")"),$3.right,NULL);
			} else {
				$$.left = cat($3.left,ds("*"),NULL);
				$$.right = $3.right;
			}
			prev = 'p';
			}
		| type
			{
			savetype($1);
			$$.left = ds("");
			$$.right = ds("");
			prev = 't';
			}
		;

adims		: /* empty */
			{
			arbdims = 1;
			$$ = ds("[]");
			}
		| NUMBER
			{
			arbdims = 0;
			$$ = cat(ds("["),$1,ds("]"));
			}
		;

type		: tinit c_type
			{ mbcheck(); $$ = $2; }
		;

tinit		: /* empty */
			{ modbits = 0; }
		;

c_type		: mod_list
			{ $$ = $1; }
		| tname
			{ $$ = $1; }
		| mod_list tname
			{ $$ = cat($1,ds(" "),$2); }
		| STRUCTUNION NAME
			{ $$ = cat($1,ds(" "),$2); }
		;

tname		: INT
			{ modbits |= MB_INT; $$ = $1; }
		| CHAR
			{ modbits |= MB_CHAR; $$ = $1; }
		| FLOAT
			{ modbits |= MB_FLOAT; $$ = $1; }
		| DOUBLE
			{ modbits |= MB_DOUBLE; $$ = $1; }
		;

mod_list: modifier
			{ $$ = $1; }
		| mod_list modifier
			{ $$ = cat($1,ds(" "),$2); }
		;

modifier: UNSIGNED
			{ modbits |= MB_UNSIGNED; $$ = $1; }
		| LONG
			{ modbits |= MB_LONG; $$ = $1; }
		| SHORT
			{ modbits |= MB_SHORT; $$ = $1; }
		;
%%
#include "cdlex.c"

#define LORS	(MB_LONG|MB_SHORT)
#define UORL	(MB_UNSIGNED|MB_LONG)
#define UORS	(MB_UNSIGNED|MB_SHORT)
#define CORL	(MB_CHAR|MB_LONG)
#define CORS	(MB_CHAR|MB_SHORT)
#define CORU	(MB_CHAR|MB_UNSIGNED)

mbcheck()
{
	if ((modbits&LORS) == LORS)
		unsupp("conflicting 'short' and 'long'",NULL);
	if ((modbits&UORL) == UORL)
		unport("unsigned with long (Ritchie)");
	if ((modbits&UORS) == UORS)
		unport("unsigned with short (Ritchie)");
	if ((modbits&CORL) == CORL)
		unsupp("long char",NULL);
	if ((modbits&CORS) == CORS)
		unsupp("short char",NULL);
	if ((modbits&CORU) == CORU)
		unport("unsigned char (Ritchie)");
}

savetype(s)
char *s;
{
	savedtype = s;
}

savename(s)
char *s;
{
	savedname = s;
}
!!EOF!!
echo "x - cdlex.l"
cat >cdlex.l <<'!!EOF!!'
%{
#include <ctype.h>

char *visible();
%}
N	[0-9]
A	[A-Z_a-z]
AN	[0-9A-Z_a-z]
%%
array		return ARRAY;
as			return AS;
cast		return CAST;
declare		return DECLARE;
explain		return EXPLAIN;
function	return FUNCTION;
help		return HELP;
into		return INTO;
of			return OF;
pointer		return POINTER;
returning	return RETURNING;
to			return TO;

char		{ yylval.dynstr = ds(yytext); return CHAR; }
double		{ yylval.dynstr = ds(yytext); return DOUBLE; }
enum		{ yylval.dynstr = ds(yytext); return STRUCTUNION; }
float		{ yylval.dynstr = ds(yytext); return FLOAT; }
int			{ yylval.dynstr = ds(yytext); return INT; }
long		{ yylval.dynstr = ds(yytext); return LONG; }
short		{ yylval.dynstr = ds(yytext); return SHORT; }
struct		{ yylval.dynstr = ds(yytext); return STRUCTUNION; }
union		{ yylval.dynstr = ds(yytext); return STRUCTUNION; }
unsigned	{ yylval.dynstr = ds(yytext); return UNSIGNED; }

{A}{AN}*	{ yylval.dynstr = ds(yytext); return NAME; }
{N}+		{ yylval.dynstr = ds(yytext); return NUMBER; }

[\t ]		;
[,*[\]()\n]		return *yytext;
.			{
				printf("bad character '%s'\n",visible(*yytext));
				return *yytext;
			}
%%
char *
visible(c)
{
	static char buf[5];

	c &= 0377;
	if (isprint(c)) {
		buf[0] = c;
		buf[1] = '\0';
	} else
		sprintf(buf,"\\%02o",c);
	return buf;
}
!!EOF!!
echo "x - cdsupp.c"
cat >cdsupp.c <<'!!EOF!!'
#include <stdio.h>
char *malloc();

main()
{
	yyparse();
}

unsupp(s,hint)
char *s,*hint;
{
	printf("Warning: Unsupported in C -- %s\n",s);
	if (hint != NULL)
		printf("\t(maybe you mean \"%s\")\n",hint);
}

unport(s)
char *s;
{
	printf("Warning: Non-portable construction -- %s\n",s);
}

yyerror(s)
char *s;
{
	printf("%s\n",s);
}

yywrap()
{
	return 1;
}

/*
 * Support for dynamic strings:
 * cat creates a string from three input strings.
 * The input strings are freed by cat (so they better have been malloced).
 * ds makes a malloced string from one that's not.
 */

char *
cat(s1,s2,s3)
char *s1,*s2,*s3;
{
	register char *newstr;
	register unsigned len = 0;

	if (s1 != NULL) len = strlen(s1) + 1;
	if (s2 != NULL) len += strlen(s2);
	if (s3 != NULL) len += strlen(s3);
	newstr = malloc(len);
	if (s1 != NULL) {
		strcpy(newstr,s1);
		free(s1);
	}
	if (s2 != NULL) {
		strcat(newstr,s2);
		free(s2);
	}
	if (s3 != NULL) {
		strcat(newstr,s3);
		free(s3);
	}
	return newstr;
}

char *
ds(s)
char *s;
{
	register char *p;

	p = malloc((unsigned)(strlen(s)+1));
	strcpy(p,s);
	return p;
}

static char *helptext[] = {
	"command:\n",
	"  declare <name> as <english>\n",
	"  cast <name> into <english>\n",
	"  explain <gibberish>\n",
	"english:\n",
	"  function returning <english>\n",
	"  function ( <namelist> ) returning <english>\n",
	"  array of <english>\n",
	"  array of <number> <english>\n",
	"  pointer to <english>\n",
	"  <type>\n",
	"type:\n",
	"  <C-type>\n",
	"  <modlist>\n",
	"  <modlist> <C-type>\n",
	"  <sue> <name>\n",
	"namelist:\n",
	"  <name>\n",
	"  <namelist> , <name>\n",
	"modlist:\n",
	"  <modifier>\n",
	"  <modlist> , <modifier>\n",
	"name is a C identifier\n",
	"gibberish is a C declaration, like \"int *x\" or \"(int *)x\"\n",
	"C-type is int, char, double or float\n",
	"modifier is short, long or unsigned\n",
	"sue is struct, union or enum\n",
	NULL
};

help()
{
	register char **p;

	for (p=helptext; *p!=NULL; p++)
			printf("\t%s",*p);
}
!!EOF!!
echo "x - makefile"
cat >makefile <<'!!EOF!!'
cdecl : cdgram.o cdsupp.o
	cc -o cdecl cdgram.o cdsupp.o

cdgram.o : cdgram.c cdlex.c

cdlex.c : cdlex.l
	lex cdlex.l
	mv lex.yy.c cdlex.c

cdgram.c : cdgram.y
	yacc cdgram.y
	mv y.tab.c cdgram.c
!!EOF!!