Exercise 6.1 from ANSI C book - getword function I have a following exercise from ANSI C book:


  Exercise 6.1. Our version of getword does not properly handle
  underscores, string constants, comments, or preprocessor control
  lines. Write a better version.


Here's my improved version of getword function and code that test if it works:

#include &lt;ctype.h&gt;
#include &lt;stdbool.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;

#define BUFSIZE 100
#define MAXWORD 100
#define NKEYS (sizeof keytab / sizeof(keytab[0]))

int buf[BUFSIZE];
int bufp = 0;

struct key {
    char * word;
    int count;
} keytab[] = {
        { "#define", 0 },
        { "#elif", 0 },
        { "#else", 0 },
        { "#endif", 0 },
        { "#error", 0 },
        { "#if", 0 },
        { "#ifdef", 0 },
        { "#ifndef", 0 },
        { "#include", 0 },
        { "#line", 0 },
        { "#pragma", 0 },
        { "auto", 0 },
        { "break", 0 },
        { "case", 0 },
        { "char", 0 },
        { "const", 0 },
        { "continue", 0 },
        { "default", 0 },
        { "do", 0 },
        { "double", 0 },
        { "else", 0 },
        { "enum", 0 },
        { "extern", 0 },
        { "float", 0 },
        { "for", 0 },
        { "goto", 0 },
        { "if", 0 },
        { "int", 0 },
        { "long", 0 },
        { "register", 0 },
        { "return", 0 },
        { "short", 0 },
        { "signed", 0 },
        { "sizeof", 0 },
        { "static", 0 },
        { "struct", 0 },
        { "switch", 0 },
        { "typedef", 0 },
        { "union", 0 },
        { "unsigned", 0 },
        { "void", 0 },
        { "volatile", 0 },
        { "while", 0 },
};

int getword(char *, int);
int binsearch(char *, struct key *, int);
int getch(void);
void ungetch(int);

int main(void) {
    int n,  c;
    char word[MAXWORD];

    while ((c = getword(word, MAXWORD)) != EOF) {
//        printf("getword(word, MAXWORD) = %c %d\n", c, c);
//        printf("word = %s\n", word);
        if (isalpha(word[0]) || word[0] == '_' || word[0] == '#') {
            if ((n = binsearch(word, keytab, NKEYS)) &gt;= 0) {
                keytab[n].count++;
            }
        }
    }
    for (n = 0; n &lt; NKEYS; n++)
        if (keytab[n].count &gt; 0)
            printf("%4d %s\n",
                   keytab[n].count, keytab[n].word);
    return 0;
}

int getch(void) {
    return (bufp &gt; 0) ? buf[--bufp] : getchar();
}

void ungetch(int c) {
    if (bufp &gt;= BUFSIZE)
        printf("ungetch: too many characters\n");
    else
        buf[bufp++] = c;
}

int getword(char * word, int lim) {
    int c;
    char * w = word;
    static int last;
    while (isblank(c = getch()))
        ;
    if (c != EOF)
        *w++ = c;
    if (last == '/' &amp;&amp; c == '/') {
        while (c = getch() != '\n')
            ;
        return c;
    }
    if (last == '/' &amp;&amp; c == '*') {
        x:
        while (c = getch() != '*')
            ;
        if (c = getch() == '/')
            return c;
        else
            goto x;
    }
    if (c == '\'') {
        while (c = getch() != '\'')
            ;
        return c;
    }
    if (c == '\"') {
        while (c = getch() != '\"')
            ;
        return c;
    }
    if (!isalpha(c) &amp;&amp; c != '_' &amp;&amp; c != '#') {
        *w = '\0';
        last = c;
        return c;
    }
    for ( ; --lim &gt; 0; w++) {
        if (!isalnum(*w = getch()) &amp;&amp; *w != '_') {
            ungetch(*w);
            break;
        }
    }
    *w = '\0';
    last = word[0];
    return word[0];
}

int binsearch(char * word, struct key tab[], int n) {
    int cond;
    int low, high, mid;

    low = 0;
    high = n - 1;
    while (low &lt;= high) {
        mid = (low + high) / 2;
        if ((cond = strcmp(word, tab[mid].word)) &lt; 0)
            high = mid - 1;
        else if (cond &gt; 0)
            low = mid + 1;
        else
            return mid;
    }
    return -1;
}


What are your opinions about my solution? Is it a correct approach? Is this code enough fast?
The last logic is quite hard to trace, and it is prone to bugs. Consider /* ... */*. Once the code reached the closing /, it returns, but last is not updated, and remains /, so the next * is still treated as a beginning of a comment.
Handling of string constants has a definite problem: it doesn't account for an escaped quote char in the middle of the string (e.g. "aaa\"bbb").


Combining the two above observations, I strongly recommend to revisit the design, and realize few more functions, such as get_line_comment, get_c_comment, get_quoted_string etc.


No word in keytab starts with an underscore. You don't need to go into binsearch in that case.
The preprocessor directive may not appear in the middle of the line. I am not sure if a directive in such case should be counted.