undcl

2026-10-01

#include <stdio.h>
#include <string.h>     //for strcpy(), strcat()
#include <ctype.h>      //for isalpha(), isalnum()
//compile with: gcc -Wall -Wextra cc-c.c -o cc-c
//run with:     printf 'x () * [] * () char\n' | ./cc-c       # char (*(*x())[])()
//         or:  printf 'daytab * [13] int\n' | ./cc-c         # int (*daytab)[13]
//         or:  printf 'comp * () void\n' | ./cc-c            # void (*comp)()
//         or:  printf 'argv * * char\n' | ./cc-c             # char (*(*argv))     correct, but with redundant ()
//         or:  printf 'daytab [13] * int\n' | ./cc-c         # int (*daytab[13])   correct, but with redundant ()

//K&R2 section 5.12: undcl, the reverse of dcl (archived just below).
//It reads a declaration written as words -- in a short form, one token
//per word -- and turns it back into C:
//
//    x () * [] * () char    ->    char (*(*x())[])()
//
//The words are the same order dcl prints them in, shortened:
//    x () * [] * () char
//    x: function returning pointer to array[] of pointer to function
//       returning char
//
//How it works: out holds the declaration built so far, and each token
//WRAPS it from the inside out:
//
//    token   rule                    out
//    x       start                   x
//    ()      append                  x()
//    *       wrap as (*...)          (*x())
//    []      append                  (*x())[]
//    *       wrap as (*...)          (*(*x())[])
//    ()      append                  (*(*x())[])()
//    char    put type in front       char (*(*x())[])()
//
//Every * gets parentheses around it, needed or not, because undcl
//doesn't look ahead to see whether a () or [] follows. So "argv * *
//char" gives char (*(*argv)) -- correct C, but char **argv is what a
//person would write. Exercise 5-19 asks you to remove the redundant
//parentheses.
//
//sprintf is printf into a string instead of onto the screen:
//sprintf(temp, "(*%s)", out) writes "(*" + out + ")" into temp. It
//can't write into out directly -- out is also the input being read --
//hence temp, then strcpy back into out.
//
//Fixed from the book's text:
//1. main() -> int main(void), as in dcl (gcc 15 / C23).
//2. Added printf("%s\n", out) after the inner loop -- without it the
//   declaration is built and never printed.
//3. temp was char temp[MAXTOKEN] -- 100 bytes -- but sprintf copies
//   all of out (up to 1000) into it. gcc -Wall caught it:
//       warning: '%s' directive writing up to 999 bytes into a
//       region of size 98 [-Wformat-overflow=]
//   A declaration over ~97 characters would overflow temp and
//   overwrite whatever memory follows. temp is now MAXTOKEN + MAXOUT,
//   big enough for token + ' ' + out.
//
//gettoken, getch and ungetch are unchanged from dcl.
#define MAXTOKEN 100
#define MAXOUT   1000

enum { NAME, PARENS, BRACKETS };

int gettoken(void);

int tokentype;              //type of last token
char token[MAXTOKEN];       //last token string
char out[MAXOUT];

//undcl:  convert word descriptions to declarations
int main(void)
{
    int type;
    char temp[MAXTOKEN + MAXOUT];   //room for token, a blank and out

    while (gettoken() != EOF) {
        strcpy(out, token);
        while ((type = gettoken()) != '\n')
            if (type == PARENS || type == BRACKETS)
                strcat(out, token);
            else if (type == '*') {
                sprintf(temp, "(*%s)", out);
                strcpy(out, temp);
            } else if (type == NAME) {
                sprintf(temp, "%s %s", token, out);
                strcpy(out, temp);
            } else
                printf("invalid input at %s\n", token);
        printf("%s\n", out);
    }
    return 0;
}

//gettoken:  return next token
int gettoken(void)
{
    int c, getch(void);
    void ungetch(int);
    char *p = token;

    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; (*p++ = getch()) != ']'; )
            ;
        *p = '\0';
        return tokentype = BRACKETS;
    } else if (isalpha(c)) {
        for (*p++ = c; isalnum(c = getch()); )
            *p++ = c;
        *p = '\0';
        ungetch(c);
        return tokentype = NAME;
    } else
        return tokentype = c;
}

//--- from section 4.3 ---

#define BUFSIZE 100

char buf[BUFSIZE];          //buffer for ungetch
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 'x () * [] * () char\n' | ./cc-c
char (*(*x())[])()
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'daytab * [13] int\n' | ./cc-c
int (*daytab)[13]
win@DESKTOP-MEIH88T:~/webdev-projects$  printf 'comp * () void\n' | ./cc-c
void (*comp)()
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'argv * * char\n' | ./cc-c
char (*(*argv))
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'daytab [13] * int\n' | ./cc-c
int (*daytab[13])
win@DESKTOP-MEIH88T:~/webdev-projects$ a combination of symbols : code interesting