exercise 5-20

2026-10-01

#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