#include <stdio.h>
#include <string.h> //for strcpy(), strcat(), strcmp(), strlen()
#include <ctype.h> //for isalpha(), isalnum()
//compile with: gcc -Wall -Wextra cc-c.c -o cc-c
//run with: printf 'const char *p\nchar *const q\n' | ./cc-c # p: pointer to const char | q: const pointer to char
// or: printf 'int main(int argc, char *argv[])\n' | ./cc-c # main: function taking (argc: int, argv: array[] of pointer to char) returning int
// or: printf 'int (*comp)(void *, void *)\n' | ./cc-c # comp: pointer to function taking (pointer to void, pointer to void) returning int
// or: printf 'int printf(const char *fmt, ...)\n' | ./cc-c # printf: function taking (fmt: pointer to const char, ...) returning int
// or: printf 'void (*signal(int sig, void (*handler)(int)))(int)\n' | ./cc-c
// or: printf 'unsigned long n\nstruct tnode *talloc(void)\nFILE *fp\n' | ./cc-c
//K&R2 exercise 5-20: expand dcl to handle declarations with function
//argument types, qualifiers like const, and so on.
//
//Builds on exercise 5-18's dcl (archived just below 5-19), keeping its
//error recovery. New:
//
//1. TYPES OF MORE THAN ONE WORD, and qualifiers in them:
// unsigned long n n: unsigned long
// const char *p p: pointer to const char
// struct tnode *t t: pointer to struct tnode
// FILE *fp fp: pointer to FILE
// typespec() reads type words until it meets the declarator. The
// words come from three lists (arrays of pointers, as in 5.6-5.8):
// basetypes void char short int long float double signed unsigned
// quals const volatile
// tags struct union enum (followed by a tag name)
// A name that's in none of the lists is the declarator's name -- or,
// if no real type has been seen yet, a typedef name like FILE or
// size_t. ("const x" is the one case this guesses wrong.)
//
//2. QUALIFIERS AFTER A *:
// char *const q q: const pointer to char
// char *const *pp pp: pointer to const pointer to char
// dcl now records each * with its qualifiers (as bits: QCONST,
// QVOLATILE) and prints them in REVERSE order -- the * nearest the
// name is the outermost, so it's said first. The book printed every
// "pointer to" alike, so the order never mattered.
//
//3. FUNCTION ARGUMENT TYPES:
// int f(char *s, int) f: function taking (s: pointer to char,
// int) returning int
// A '(' after a name now starts an argument list. Each argument is a
// whole declaration -- a type, then a declarator -- so params() reads
// it with typespec() and dcl() again: the same functions, called
// recursively. "..." is accepted as the last argument.
//
// Arguments may have no name ("abstract declarators"): char *, or
// int (*)(int). So dcl and dirdcl take a flag, abstract. With it, a
// missing name is fine. It also makes '(' ambiguous: in int (*)(int)
// the first '(' groups, the second starts an argument list. dirdcl
// PEEKS at the next token (5-19's prevtoken): '*' or a plain name
// means grouping; anything else means an argument list.
//
//The big change in shape: dcl and dirdcl used to write into the
//globals out and name. An argument has its own out and name, while the
//outer declaration's are only half built. So both now take the buffers
//as arguments -- dcl(out, name, abstract) -- and params() gives each
//argument fresh local buffers. Recursion keeps every level separate.
//
//Still not handled: typedef names in arguments that are followed by
//nothing (f(x) reads x as a type), initializers (int x = 5), several
//declarators in one line (int x, y), bit fields, and K&R-style old
//function definitions.
#define MAXTOKEN 100
#define MAXOUT 1000
#define MAXPTR 20 //max *'s in one dcl
enum { NAME, PARENS, BRACKETS };
enum { NOTTYPE, BASE, QUAL, TAG }; //what istype() says a word is
#define QCONST 1 //qualifier bits on a *
#define QVOLATILE 2
void dcl(char *out, char *name, int abstract);
void dirdcl(char *out, char *name, int abstract);
void params(char *out);
int typespec(char *type);
int istype(char *s);
int gettoken(void);
void errmsg(char *msg);
void append(char *to, char *s);
int tokentype; //type of last token
char token[MAXTOKEN]; //last token string
int prevtoken = 0; //1: next gettoken() repeats the last token
int err = 0; //1 once the current line has an error
//convert declaration to words
int main(void)
{
char name[MAXTOKEN]; //identifier name
char datatype[MAXOUT]; //data type = const char, unsigned long, etc.
char out[MAXOUT];
while (gettoken() != EOF) {
if (tokentype == '\n') //blank line
continue;
err = 0;
out[0] = name[0] = '\0';
prevtoken = 1; //typespec re-reads this token
if (typespec(datatype) == 0)
errmsg("expected a data type");
else {
dcl(out, name, 0); //parse rest of line
if (tokentype != '\n' && tokentype != EOF)
errmsg("syntax error");
}
if (err) //skip the rest of a bad line
while (tokentype != '\n' && tokentype != EOF)
gettoken();
else
printf("%s: %s %s\n", name, out, datatype);
}
return 0;
}
//dcl: parse a declarator into out; its name (if any) goes into name
void dcl(char *out, char *name, int abstract)
{
int ns, i, q[MAXPTR];
for (ns = 0; gettoken() == '*'; ns++) { //each * and its qualifiers
if (ns >= MAXPTR) {
errmsg("too many *'s");
return;
}
q[ns] = 0;
while (gettoken() == NAME && istype(token) == QUAL)
q[ns] |= (strcmp(token, "const") == 0) ? QCONST : QVOLATILE;
prevtoken = 1; //not a qualifier: give it back
}
dirdcl(out, name, abstract);
for (i = ns-1; i >= 0; i--) { //the * nearest the name first
if (q[i] & QCONST)
append(out, " const");
if (q[i] & QVOLATILE)
append(out, " volatile");
append(out, " pointer to");
}
}
//dirdcl: parse a direct declarator into out
void dirdcl(char *out, char *name, int abstract)
{
int type;
if (tokentype == '(') {
type = gettoken(); //peek: grouping, or arguments?
prevtoken = 1;
if (type == '*' || (type == NAME && istype(token) == NOTTYPE)) {
dcl(out, name, abstract); //( dcl )
if (tokentype != ')') {
errmsg("missing )");
return;
}
type = gettoken();
} else if (abstract)
type = '('; //no name, then an argument list
else {
errmsg("expected name or (dcl)");
return;
}
} else if (tokentype == NAME) { //variable name
strcpy(name, token);
type = gettoken();
} else if (abstract)
type = tokentype; //no name: suffixes may follow
else {
errmsg("expected name or (dcl)");
return;
}
while (type == PARENS || type == BRACKETS || type == '(') {
if (type == PARENS)
append(out, " function returning");
else if (type == BRACKETS) {
append(out, " array");
append(out, token);
append(out, " of");
} else {
params(out); //( argument list )
if (err)
return;
}
type = gettoken();
}
}
//params: parse a function's argument list, after its '('
void params(char *out)
{
char ptype[MAXOUT]; //an argument's type
char pout[MAXOUT]; //...its declarator, in words
char pname[MAXTOKEN]; //...and its name, if it has one
int n = 0;
append(out, " function taking (");
do {
if (n++ > 0)
append(out, ", ");
if (gettoken() == '.') { //... : more arguments may follow
if (gettoken() != '.' || gettoken() != '.') {
errmsg("expected ...");
return;
}
append(out, "...");
gettoken(); //should be the ')'
break;
}
prevtoken = 1;
if (typespec(ptype) == 0) {
errmsg("expected an argument type");
return;
}
pout[0] = pname[0] = '\0';
dcl(pout, pname, 1); //name optional
if (err)
return;
if (pname[0] != '\0') {
append(out, pname);
append(out, ": ");
}
if (pout[0] != '\0') {
append(out, pout + 1); //skip pout's leading blank
append(out, " ");
}
append(out, ptype);
} while (tokentype == ',');
if (tokentype != ')') {
errmsg("missing ) after arguments");
return;
}
append(out, ") returning");
}
//typespec: read a type like "const unsigned long" into type;
//return the number of words read (0: no type there)
int typespec(char *type)
{
int n = 0; //words in type
int base = 0; //1 once a real type (not a qualifier) is seen
int kind;
type[0] = '\0';
while (gettoken() == NAME) {
kind = istype(token);
if (kind == NOTTYPE) {
if (base)
break; //the declarator's name
base = 1; //a typedef name: FILE, size_t
} else if (kind == BASE)
base = 1;
if (n++ > 0)
append(type, " ");
append(type, token);
if (kind == TAG) { //struct, union, enum: + a tag
if (gettoken() != NAME) {
errmsg("expected a struct/union/enum tag");
return 0;
}
append(type, " ");
append(type, token);
base = 1;
}
}
prevtoken = 1; //that token isn't part of the type
return n;
}
char *basetypes[] = { "void", "char", "short", "int", "long", "float",
"double", "signed", "unsigned", NULL };
char *quals[] = { "const", "volatile", NULL };
char *tags[] = { "struct", "union", "enum", NULL };
//inlist: 1 if s is one of the words in list (which ends with NULL)
int inlist(char *list[], char *s)
{
for ( ; *list != NULL; list++)
if (strcmp(*list, s) == 0)
return 1;
return 0;
}
//istype: BASE, QUAL or TAG if s is that kind of type word, else NOTTYPE
int istype(char *s)
{
if (inlist(basetypes, s))
return BASE;
else if (inlist(quals, s))
return QUAL;
else if (inlist(tags, s))
return TAG;
else
return NOTTYPE;
}
//errmsg: report the first error on a line; later ones are echoes of it
void errmsg(char *msg)
{
if (!err)
printf("error: %s\n", msg);
err = 1;
}
//append: add s to the end of to (a MAXOUT buffer), if there's room
void append(char *to, char *s)
{
if (strlen(to) + strlen(s) < MAXOUT)
strcat(to, s);
else
errmsg("declaration too long");
}
//gettoken: return next token
int gettoken(void)
{
int c, getch(void);
void ungetch(int);
char *p = token;
if (prevtoken) { //a token given back: repeat it
prevtoken = 0;
return tokentype;
}
while ((c = getch()) == ' ' || c == '\t')
;
if (c == '(') {
if ((c = getch()) == ')') {
strcpy(token, "()");
return tokentype = PARENS;
} else {
ungetch(c);
return tokentype = '(';
}
} else if (c == '[') {
for (*p++ = c; (c = getch()) != ']' && c != '\n' && c != EOF; )
if (p < token + MAXTOKEN - 2) //leave room for ']' and '\0'
*p++ = c;
if (c != ']') { //line ended inside [ ]
*p = '\0';
errmsg("missing ]");
return tokentype = c; //the '\n' (or EOF) ends the line
}
*p++ = ']';
*p = '\0';
return tokentype = BRACKETS;
} else if (isalpha(c) || c == '_') {
for (*p++ = c; isalnum(c = getch()) || c == '_'; )
if (p < token + MAXTOKEN - 1) //leave room for '\0'
*p++ = c;
*p = '\0';
ungetch(c);
return tokentype = NAME;
} else
return tokentype = c;
}
//--- from section 4.3 ---
#define BUFSIZE 100
int buf[BUFSIZE]; //buffer for ungetch (int: holds any byte)
int bufp = 0; //next free position in buf
//getch: get a (possibly pushed back) character
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
//ungetch: push character back on input
void ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}
win@DESKTOP-MEIH88T:~/webdev-projects$ gcc -Wall -Wextra cc-c.c -o cc-c
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'const char *p\nchar *const q\n' | ./cc-c
p: pointer to const char
q: const pointer to char
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'int main(int argc, char *argv[])\n' | ./cc-c
main: function taking (argc: int, argv: array[] of pointer to char) returning int
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'int (*comp)(void *, void *)\n' | ./cc-c
comp: pointer to function taking (pointer to void, pointer to void) returning int
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'int printf(const char *fmt, ...)\n' | ./cc-c
printf: function taking (fmt: pointer to const char, ...) returning int
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'void (*signal(int sig, void (*handler)(int)))(int)\n' | ./cc-c
signal: function taking (sig: int, handler: pointer to function taking (int) returning void) returning pointer to function taking (int) returning void
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'unsigned long n\nstruct tnode *talloc(void)\nFILE *fp\n' | ./cc-c
n: unsigned long
talloc: function taking (void) returning pointer to struct tnode
fp: pointer to FILE