exercise 5-18

2026-10-01

#include <stdio.h>
#include <string.h>     //for strcpy(), strcat(), strlen()
#include <ctype.h>      //for isalpha(), isalnum()
//compile with: gcc -Wall -Wextra cc-c.c -o cc-c
//run with:     printf 'int x)\nchar **argv\n' | ./cc-c          # error: syntax error | argv:  pointer to pointer to char
//         or:  printf 'int (*pf\nint *daytab[13]\n' | ./cc-c    # error: missing ) | daytab:  array[13] of pointer to int
//         or:  printf 'int *\nvoid (*comp)()\n' | ./cc-c       # error: expected name or (dcl) | comp:  pointer to function returning void
//         or:  printf 'int x[5\nint (*daytab)[13]\n' | ./cc-c  # error: missing ] | daytab:  pointer to array[13] of int
//         or:  printf '*x\n\nchar c' | ./cc-c                  # error: expected a data type | c:  char

//K&R2 exercise 5-18: make dcl recover from input errors.
//
//Builds on section 5.12's dcl (archived two blocks down, below undcl).
//The book's dcl, given the bad line "int x)", printed:
//
//    syntax error
//    x:  int                         <- a result for a line that failed
//    error: expected name or (dcl)   <- the leftover \n read as a new
//    syntax error                       line, then EOF read as a name
//    x:  x
//
//Two problems: one mistake produces a CASCADE of messages, and the
//parse keeps reading past the end of the bad line into the next one.
//Recovery here means: report the FIRST error on a line, print no
//result for that line, skip whatever is left of it, and start fresh on
//the next line. Now "int x)" gives just:
//
//    error: syntax error
//
//How:
//1. A flag, err, set by errmsg(). errmsg prints only if err is still
//   0, so the first error on a line is reported and the echoes of it
//   are not. main clears err at the start of every line.
//2. dirdcl RETURNS as soon as it finds an error. The book's dirdcl
//   carried on into its while loop, calling gettoken -- which, if the
//   error was at the end of the line, read the NEXT line's first token.
//3. After the parse, if err is set, main throws away tokens up to the
//   '\n' (or EOF), then prints nothing for the line.
//
//Other input that broke the book's version, also fixed:
//4. "int x[5" -- no ']'. The book's loop
//       for (*p++ = c; (*p++ = getch()) != ']'; )
//   kept reading until it found a ']' -- across lines, past EOF, past
//   the end of token[100] into other memory. Now it stops at '\n' or
//   EOF and reports "missing ]".
//5. A name or [size] longer than token[] holds -- the book wrote past
//   the end of token. Now the extra characters are dropped.
//6. Too many *'s, ()'s or []'s -- each adds a phrase to out[1000] by
//   strcat, which never checks the size. append() checks first and
//   reports "declaration too long".
//7. A line not starting with a name ("*x"): the book copied whatever
//   was left in token as the data type. Now: "expected a data type".
//8. Blank lines are skipped, and a last line without '\n' is accepted
//   (EOF counts as the end of the line).
//9. buf in ungetch is now int, not char, so a pushed-back byte >= 128
//   (part of a UTF-8 character) comes back as itself, not as a negative
//   number -- which isalpha() would choke on.
#define MAXTOKEN 100
#define MAXOUT   1000

enum { NAME, PARENS, BRACKETS };

void dcl(void);
void dirdcl(void);
int gettoken(void);
void errmsg(char *msg);
void append(char *s);

int tokentype;              //type of last token
char token[MAXTOKEN];       //last token string
char name[MAXTOKEN];        //identifier name
char datatype[MAXTOKEN];    //data type = char, int, etc.
char out[MAXOUT];
int err = 0;                //1 once the current line has an error

//convert declaration to words
int main(void)
{
    while (gettoken() != EOF) {
        if (tokentype == '\n')              //blank line
            continue;
        err = 0;
        out[0] = '\0';
        if (tokentype != NAME)
            errmsg("expected a data type");
        else {
            strcpy(datatype, token);        //1st token is the datatype
            dcl();                          //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;
}

//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 out, if there's room
void append(char *s)
{
    if (strlen(out) + strlen(s) < MAXOUT)
        strcat(out, s);
    else
        errmsg("declaration too long");
}

//dcl:  parse a declarator
void dcl(void)
{
    int ns;

    for (ns = 0; gettoken() == '*'; )   //count *'s
        ns++;
    dirdcl();
    while (ns-- > 0)
        append(" pointer to");
}

//dirdcl:  parse a direct declarator
void dirdcl(void)
{
    int type;

    if (tokentype == '(') {             //( dcl )
        dcl();
        if (tokentype != ')') {
            errmsg("missing )");
            return;                     //don't read past the error
        }
    } else if (tokentype == NAME)       //variable name
        strcpy(name, token);
    else {
        errmsg("expected name or (dcl)");
        return;                         //don't read past the error
    }
    while ((type=gettoken()) == PARENS || type == BRACKETS)
        if (type == PARENS)
            append(" function returning");
        else {
            append(" array");
            append(token);
            append(" of");
        }
}

//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; (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)) {
        for (*p++ = c; isalnum(c = getch()); )
            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 'int x)\nchar **argv\n' | ./cc-c
error: syntax error
argv:  pointer to pointer to char
win@DESKTOP-MEIH88T:~/webdev-projects$  printf 'int (*pf\nint *daytab[13]\n' | ./cc-c
error: missing )
daytab:  array[13] of pointer to int
win@DESKTOP-MEIH88T:~/webdev-projects$ printf 'int *\nvoid (*comp)()\n' | ./cc-c
error: expected name or (dcl)
comp:  pointer to function returning void
win@DESKTOP-MEIH88T:~/webdev-projects$  printf 'int x[5\nint (*daytab)[13]\n' | ./cc-c
error: missing ]
daytab:  pointer to array[13] of int
win@DESKTOP-MEIH88T:~/webdev-projects$ printf '*x\n\nchar c' | ./cc-c
error: expected a data type
c:  char