#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