Jump to: navigation, search

The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 5.20 on page 126

Expand dcl to handle declarations with function argument types, qualifiers like const, and so on.



Solution by codybartfast

Category 0

I think this handles all the definitions we've seen so far except for enums. E.g., it supports declarator lists, storage classes, type qualifiers, muliti word types, arguments, unnamed arguments and nested arguments:

int simple;
	simple: int

int dec, *larator, lists[];
	dec: int
	larator: pointer to int
	lists: array[] of int

static char *storage;
	storage: pointer to char in static storage

volatile int qualifier;
	qualifier: volatile int

long unsigned int compound;
	compound: long unsigned int

void arguments(char *name, double time);
	arguments: function taking argument name: pointer to char and argument time:
	double returning void

int nested_args(char *(*read)(void), void (*write)(char *message));
	nested_args: function taking argument read: pointer to function taking no 
	arguments returning pointer to char and argument write: pointer to function 
	taking argument message: pointer to char returning void returning int

void unnamed(char (*)(long));
	unnamed: function taking argument <unnamed>: pointer to function taking 
	argument <unnamed>: long returning char returning void

And putting it all together:

static const long unsigned int (*book2)[13], *book3[13], complex(
	volatile char (*(*book6(void))[])(
		char **book1,
		void *book4(),
		void (*book5)()
	),
	char (*(*book7[3])())[5]
);
book2: const pointer to array[13] of long unsigned int in static storage

book3: const array[13] of pointer to long unsigned int in static storage

complex: const function taking argument book6: volatile function taking no
arguments returning pointer to array[] of pointer to function taking argument
book1: pointer to pointer to char and argument book4: function taking no
arguments returning pointer to void and argument book5: pointer to function
taking no arguments returning void returning char and argument book7: array[3]
of pointer to function taking no arguments returning pointer to array[5] of char
returning long unsigned int in static storage

Manually formatting the last one:

complex: const function taking argument 
	book6: volatile function taking no arguments returning pointer to array[] of
	pointer to function taking argument 
		book1: pointer to pointer to char
	and argument 
		book4: function taking no arguments returning pointer to void
	and argument 
		book5: pointer to function taking no arguments returning void 
	returning char
and argument
	book7: array[3] of pointer to function taking no arguments returning 
	pointer to array[5] of char 
returning long unsigned int in static storage
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAXSYMBL (1 << 7)
#define MAXMSG (1 << 10)
#define MAXCHBUF (1 << 1)
#define MAXTKBUF (1 << 1)
#define MAXBOILER (1 << 4)

enum { STORE, QUAL, TYPE, VAR, BRACKETS };
enum { OK = 0, ERROR };
enum { NO = 0, YES };

int declaration(char *dec, int ispara);
int dcl(char *name, char *out, int allowanon);
int dirdcl(char *name, char *out, int allowanon);
int args(char *out);

int gettoken(void);
void ungettoken(int ttype, char *tk);
int ws(void);
int name(char *p);
int brackets(char *p);
int oparens(char *p);
int contains(char **names, int count, char *name);

void nextdef(void);
int getch(void);
void ungetch(int c);

int tokentype;
char token[MAXSYMBL];

int chbuf[MAXCHBUF];
int chbufp = 0;

int ttbuf[MAXTKBUF];
char tkbuf[MAXTKBUF][MAXSYMBL];
int tkbufp = 0;

char *types[] = { "void",  "char",   "short",  "int",	  "long",
		  "float", "double", "signed", "unsigned" };
int ntypes = 9;
char *stores[] = { "auto", "register", "static", "extern" };
int nstores = 4;
char *quals[] = { "const", "volatile" };
int nquals = 2;

int main(void)
{
	char dec[MAXMSG];
	while (gettoken() != EOF) {
		if (declaration(dec, NO) != OK) {
			nextdef();
		}
	}
	return 0;
}

int declaration(char *dec, int ispara)
{
	char store[MAXSYMBL + MAXBOILER];
	char qual[MAXSYMBL + MAXBOILER];
	char type[MAXMSG];
	char name[MAXSYMBL];
	char out[MAXMSG];

	store[0] = '\0';
	qual[0] = '\0';
	type[0] = '\0';

	if (tokentype == STORE) {
		sprintf(store, " in %s storage", token);
		gettoken();
	}
	if (tokentype == QUAL) {
		sprintf(qual, " %s", token);
		gettoken();
	}
	if (tokentype != TYPE) {
		printf("\nerror: Expected a type\n");
		return ERROR;
	}
	do {
		if (type[0] != '\0')
			strcat(type, " ");
		strcat(type, token);
	} while (gettoken() == TYPE);
	ungettoken(tokentype, token);

	do {
		out[0] = '\0';
		if (dcl(name, out, ispara) != OK) {
			return ERROR;
		} else if (tokentype != ';' && tokentype != ',' &&
			   tokentype != ')') {
			printf("\nsytax error, got %d/%c\n", tokentype,
			       tokentype);
			return ERROR;
		} else {
			sprintf(dec, "%s:%s%s %s%s", name, qual, out, type,
				store);
			if (!ispara)
				printf("\n%s\n", dec);
		}
	} while (!ispara && tokentype == ',');
	return OK;
}

int dcl(char *name, char *out, int allowanon)
{
	int ns, rslt;

	for (ns = 0; gettoken() == '*';)
		ns++;
	if ((rslt = dirdcl(name, out, allowanon)) != OK)
		return rslt;
	while (ns-- > 0)
		strcat(out, " pointer to");
	return OK;
}

int dirdcl(char *name, char *out, int allowanon)
{
	int rslt;

	if (tokentype == VAR)
		strcpy(name, token);
	else if (tokentype == '(') {
		if ((rslt = dcl(name, out, allowanon)) != OK)
			return rslt;
		if (tokentype != ')') {
			printf("\nerror: missing )\n");
			return ERROR;
		}
	} else if (allowanon) {
		strcpy(name, "<unnamed>");
		allowanon = NO;
		ungettoken(tokentype, token);
	} else {
		printf("\nerror: expected variable name or (dcl)\n");
		return ERROR;
	}
	while (gettoken() == '(' || tokentype == BRACKETS) {
		if (tokentype == '(') {
			strcat(out, " function taking");
			if ((rslt = args(out)) != NO) {
				return rslt;
			}
		} else {
			strcat(out, " array");
			strcat(out, token);
			strcat(out, " of");
		}
	}
	return OK;
}

int args(char *out)
{
	char dec[MAXMSG];
	int argcount = 0;
	char *seperator = " argument ";
	int expectarg = YES;

	if (gettoken() == ')') {
		/* fun() */
		expectarg = NO;
	} else if (tokentype == TYPE && strcmp(token, "void") == 0) {
		if (gettoken() == ')')
			/* fun(void) */
			expectarg = NO;
		else {
			ungettoken(tokentype, token);
			tokentype = TYPE;
			strcpy(token, "void");
		}
	}

	if (expectarg) {
		do {
			if (argcount++ > 0)
				gettoken();
			if (declaration(dec, YES) != OK)
				return ERROR;
			strcat(out, seperator);
			strcat(out, dec);
			seperator = " and argument ";
		} while (tokentype == ',');
	}
	if (tokentype == ')') {
		if (argcount == 0)
			strcat(out, " no arguments");
		strcat(out, " returning");
	} else {
		printf("\nerror: expected closing parentheses "
		       "after arguments (got %d/%c)\n",
		       tokentype, tokentype);
		return ERROR;
	}
	return OK;
}

int gettoken(void)
{
	if (tkbufp > 0) {
		--tkbufp;
		tokentype = ttbuf[tkbufp];
		strcpy(token, tkbuf[tkbufp]);
	} else {
		ws();
		if (!(oparens(token) || brackets(token) || name(token))) {
			token[0] = tokentype = getch();
			token[1] = '\0';
		}
	}
	return tokentype;
}

void ungettoken(int ttype, char *tk)
{
	if (tkbufp >= MAXTKBUF) {
		printf("ungettoken: too many tokens\n");
	} else {
		ttbuf[tkbufp] = ttype;
		strcpy(tkbuf[tkbufp], tk);
		tkbufp++;
	}
}

int ws(void)
{
	char c;
	int rslt = NO;

	while (isspace(c = getch()))
		rslt = YES;
	ungetch(c);
	return rslt;
}

int name(char *p)
{
	char c, *tkn;
	int rslt = NO;

	tkn = p;
	if (isalpha(c = getch()) || c == '_') {
		rslt = YES;
		for (*p++ = c; isalnum(c = getch()) || c == '_';)
			*p++ = c;
		*p = '\0';
		if (contains(types, ntypes, tkn))
			tokentype = TYPE;
		else if (contains(stores, nstores, tkn))
			tokentype = STORE;
		else if (contains(quals, nquals, tkn))
			tokentype = QUAL;
		else
			tokentype = VAR;
	}
	ungetch(c);
	return rslt;
}

int brackets(char *p)
{
	char c;

	if ((c = getch()) == '[') {
		for (*p++ = c; (*p++ = (c = getch())) != ']';)
			;
		*p = '\0';
		tokentype = BRACKETS;
		return YES;
	}
	ungetch(c);
	return NO;
}

int oparens(char *p)
{
	char c;

	if ((c = getch()) == '(') {
		*p++ = tokentype = '(';
		*p = '\0';
		return YES;
	}
	ungetch(c);
	return NO;
}

void nextdef(void)
{
	int c;

	while ((c = getch()) != ';' && c != EOF)
		;
	if (c == EOF)
		ungetch(c);
}

int contains(char **names, int count, char *name)
{
	int i;

	for (i = 0; i < count; i++)
		if (strcmp(name, names[i]) == 0)
			return YES;
	return NO;
}

int getch(void)
{
	return (chbufp > 0) ? chbuf[--chbufp] : getchar();
}

void ungetch(int c)
{
	if (chbufp >= MAXCHBUF)
		printf("ungetch: too many characters\n");
	else
		chbuf[chbufp++] = c;
}

Latest code on github

Solution by anonymous

I attempted to make a solution that handled every possible declaration in c, but after studying the C17 standard, I quickly learned that my goal was unreasonable. I then managed to get a working solution that handled almost everything you could throw at it with the exception of the brackets. I quickly learned that I was trying to fight the dir/dirdcl recursive-descent parser and I needed to start over. After testing codybartfast's program I discovered that his algorithm was a huge improvement over my attempts so I merged my code with his to come up with my current solution.

I improved upon codybartfast's program quite a bit and fixed problems along the way. For example, his version outputs "book2: const pointer to array[13] of long unsigned int in static storage" which isn't correct since it actually is "book2: pointer to array[13] of static const long unsigned int." The pointer to the array isn't constant, but rather the long unsigned int is constant. Plus, I removed the "in x storage" part since it didn't make a lot of sense. This is because static actually changes the scope of a variable and if a variable keeps it value between invocations if it is inside of a function. The other storage class specifiers also didn't fit the "in x storage" string either. Finally, I allowed more than one type qualifier and more than one storage class specifier since his version didn't despite being legal according to the C17 standard.

I went out of my way to protect the program against buffer overflows since strcat and strcpy are quite unsafe functions. Even the "n" versions of the functions have a lot of flaws and could easily lead to buffer overflows. I ended up writing my own safer version of strcat and replaced all strcpy with snprintf. This is much better, but still not perfect.

I also made a lot of formatting changes and added some nice to have output formatting that greatly improves readability even at the expense of limiting the buffer size to prevent buffer overflows (albeit I did make the ceiling ridiculously high). I personally liked requiring each declarator to be on a single line and removed the multiline capability that codybartfast had. Finally, I added a bunch of comments and tried to simplify things as much as possible.

For comparison this is my output for the declarations he used as examples:

int simple;
simple: int

int dec, *larator, lists[];
dec: int
larator: pointer to int
lists: array[] of int

static char *storage;
storage: pointer to static char

volatile int qualifier;
qualifier: volatile int

long unsigned int compound;
compound: long unsigned int

void arguments(char *name, double time);
arguments: function expecting 2 parameters: parameter name (pointer to char) parameter time (double) returning nothing

int nested_args(char *(*read)(void), void (*write)(char *message));
nested_args: function expecting 2 parameters: parameter read (pointer to function expecting no parameters returning pointer to char) parameter write (pointer to function expecting 1 parameter: parameter message (pointer to char) returning nothing) returning int

void unnamed(char (*)(long));
unnamed: function expecting 1 parameter: unnamed parameter (pointer to function expecting 1 parameter: unnamed parameter (long) returning char) returning nothing

static const long unsigned int (*book2)[13], *book3[13], complex(volatile char (*(*book6(void))[])(char **book1,void *book4(),void (*book5)()),char (*(*book7[3])())[5]);
book2: pointer to array[13] of static const long unsigned int
book3: array[13] of pointer to static const long unsigned int
complex: function expecting 2 parameters: parameter book6 (function expecting no parameters returning pointer to array[] of pointer to function expecting 3 parameters: parameter book1 (pointer to pointer to char) parameter book4 (obsolescent non-prototype function declaration with unknown parameters returning pointer to void) parameter book5 (pointer to obsolescent non-prototype function declaration with unknown parameters returning nothing) returning volatile char) parameter book7 (array[3] of pointer to obsolescent non-prototype function declaration with unknown parameters returning pointer to array[5] of char) returning static const long unsigned int

Here are three more examples of my own:

_Thread_local static int multipleStorageClassSpecifiers;
multipleStorageClassSpecifiers: _Thread_local static int

const volatile int multipleTypeQualifiers;
multipleTypeQualifiers: const volatile int

void everythingSupported(char a, signed char a, unsigned char a, short a, signed short a, short int a, signed short int a, unsigned short a, unsigned short int a, int a, signed a, signed int a, unsigned a, unsigned int a, long a, signed long a, long int a, signed long int a, unsigned long a, unsigned long int a, long long a, signed long long a, long long int a, signed long long int a, unsigned long long a, unsigned long long int a, float a, double a, long double a, _Bool a, float _Complex a, double _Complex a, long double _Complex a, _Atomic int a, const int a, restrict int a, volatile int a, _Thread_local int a, auto int a, extern int a, register int a, static int a);
everythingSupported: function expecting 42 parameters: parameter a (char) parameter a (signed char) parameter a (unsigned char) parameter a (short) parameter a (signed short) parameter a (short int) parameter a (signed short int) parameter a (unsigned short) parameter a (unsigned short int) parameter a (int) parameter a (signed) parameter a (signed int) parameter a (unsigned) parameter a (unsigned int) parameter a (long) parameter a (signed long) parameter a (long int) parameter a (signed long int) parameter a (unsigned long) parameter a (unsigned long int) parameter a (long long) parameter a (signed long long) parameter a (long long int) parameter a (signed long long int) parameter a (unsigned long long) parameter a (unsigned long long int) parameter a (float) parameter a (double) parameter a (long double) parameter a (_Bool) parameter a (float _Complex) parameter a (double _Complex) parameter a (long double _Complex) parameter a (_Atomic int) parameter a (const int) parameter a (restrict int) parameter a (volatile int) parameter a (_Thread_local int) parameter a (auto int) parameter a (extern int) parameter a (register int) parameter a (static int) returning nothing

Here is my code

#include <stdio.h>
#include <string.h>
#include <ctype.h>

/*
    Exercise 5-20. Expand dcl to handle declarations with function argument types, qualifiers like const, and so on.
*/

#define MAXTOKEN 500
#define MAXOUTPUT 5000 // value must be <= 1.8 million to prevent a buffer overflow attack (when x = 99,999, y = 1,800,032 in y = 18x + 50)
#define BUFSIZE MAXTOKEN * 2 // * 2 because system might need to push back two tokens worth of chars
#define NUMOFSTORAGECLASSES 5
#define NUMOFTYPEQUALIFIERS 4
#define NUMOFTYPESPECIFIERS 11

enum tokentype { VARIABLE, BRACKETS, STORAGECLASS, TYPEQUALIFIER, TYPESPECIFIER };
enum returnstatus { OK, ERROR };
enum boolean { FALSE, TRUE };

int processDeclaration(char *declaration, char expectParameter);
int dcl(char *name, char *out, char expectParameter);
int dirdcl(char *name, char *out, char expectParameter);
int parameters(char *out);
int gettoken(void);
int getch(void);
void ungetch(int c);
int contains(char **strs, char *name, int strCount);
char *saferstrcat(char *dst, const char *str, size_t dstsize);
int error(char *msg);

int tokentype;
char token[MAXTOKEN];
int buf[BUFSIZE];
int bufp = 0;
static char *storageClasses[NUMOFSTORAGECLASSES] = { "_Thread_local", "auto", "extern", "register", "static" }; 
static char *typeQualifiers[NUMOFTYPEQUALIFIERS] = { "_Atomic", "const", "restrict", "volatile" };
static char *typeSpecifiers[NUMOFTYPESPECIFIERS] = { "_Bool", "_Complex", "char", "double", "float", "int", "long", "short", "signed", "unsigned", "void" };

int main(void)
{
    char out[MAXOUTPUT];
    while (gettoken() != EOF)
    {
        processDeclaration(out, FALSE);
        for (int c = tokentype; c != '\n' && c != EOF; ) // discard rest of line since last token could be ';' or an error occurred
            if ((c = getch()) == EOF)
                break;
    }
    return 0;
}

int processDeclaration(char *declaration, char expectParameter)
{
    char datatype[MAXTOKEN]; // stores type qualifier, specifier, and storage class info
    char name[MAXTOKEN]; // stores function/variable name
    char out[MAXOUTPUT]; // used to store information gathered by dcl/dirdcl to be used in the final output
    datatype[0] = '\0'; // ensure null terminated

    if (!(tokentype == STORAGECLASS || tokentype == TYPEQUALIFIER || tokentype == TYPESPECIFIER))
        return error("Error: expected a type");
    while (tokentype == STORAGECLASS || tokentype == TYPEQUALIFIER || tokentype == TYPESPECIFIER)
    {
        if (saferstrcat(datatype, " ", MAXTOKEN) == NULL)
            return error("Error: input too large to fit into buffer");
        if (saferstrcat(datatype, token, MAXTOKEN) == NULL)
            return error("Error: input too large to fit into buffer");
        gettoken(); // get tokens until no longer a token for datatype
    }
    for (int i = strlen(token) - 1; i >= 0; i--) // since the while loop gets an extra unneeded token, push it back in reverse order
        ungetch(token[i]);
    do
    {
        out[0] = '\0'; // ensure new out string each loop iteration
        if (dcl(name, out, expectParameter) == ERROR) // dcl updates out based on input
            return ERROR;
        else if (tokentype != ';' && tokentype != ',' && tokentype != ')' && tokentype != '\n') // if the returned output is not one of these chars, an error occurred
            return error("Syntax error");
        else
        {
            if (strcmp(datatype, " void") == 0 && strncmp(out + (strlen(out) - 10), " returning", 10) == 0)
                snprintf(datatype, MAXTOKEN, "%s", " nothing"); // if is function (has the word returning at the end) and it is returning only void, then improve readability by changing type to "nothing"
            if (strcmp(name, " unnamed") == 0) // if name starts with a space, then it is the special flag set by dirdcl for an unnamed parameter
                snprintf(name, MAXTOKEN, "%s", " unnamed parameter");
            if (expectParameter)
            {
                if (out[0] == ' ') // this improves the output formatting. It finds the first variable that starts with a space and replaces the space with a (
                    out[0] = '(';
                else if (datatype[0] == ' ')
                    datatype[0] = '(';
                snprintf(declaration, MAXOUTPUT, "%s %s%s)", name, out, datatype); // store data in declaration which is used by parameters function
            }
            else // if not expecting parameters, then this was called by main. print out English version of the declaration
                printf("%s:%s%s\n", name, out, datatype);
        }
    } while (!expectParameter && tokentype == ','); // loop is just in case received multiple comma separated declarations that aren't function parameters
    return OK;
}

int dcl(char *name, char *out, char expectParameter)
{
    int numPointers = 0;
    while (gettoken() == '*') // identify the number of back to back asterisks
        numPointers++;
    if (dirdcl(name, out, expectParameter) == ERROR)
        return ERROR;
    while (numPointers-- > 0)
        if (saferstrcat(out, " pointer to", MAXOUTPUT) == NULL)
            return error("Error: input too large to fit into buffer");
    return OK;
}

int dirdcl(char *name, char *out, char expectParameter)
{
    if (tokentype == '(') // ( dcl )
    {
        if (dcl(name, out, expectParameter) == ERROR)
            return ERROR;
        if (tokentype != ')')
            return error("Error: missing )");
    }
    else if (tokentype == VARIABLE)
        snprintf(name, MAXTOKEN, "%s", token);
    else if (expectParameter) // if tokentype is not VARIABLE and expecting a parameter, then it is an unnamed parameter
    {
        snprintf(name, MAXTOKEN, "%s", " unnamed"); // the space added is a flag indicates that this is an unnamed parameter instead of a named parameter called unnamed
        expectParameter = FALSE;
        for (int i = strlen(token) - 1; i >= 0; i--)
            ungetch(token[i]); // push unused token back to input in reverse order
    }
    else
        return error("Error: expected variable name or (dcl)");
    while (gettoken() == '(' || tokentype == BRACKETS)
    {
        if (tokentype == '(')
        {
            if (parameters(out) == ERROR) // found a function so parse its parameters
                return ERROR;
        }
        else
        {
            if (saferstrcat(out, " array", MAXOUTPUT) == NULL)
                return error("Error: input too large to fit into buffer");
            if (saferstrcat(out, token, MAXOUTPUT) == NULL)
                return error("Error: input too large to fit into buffer");
            if (saferstrcat(out, " of", MAXOUTPUT) == NULL)
                return error("Error: input too large to fit into buffer");
        }
    }
    return OK;
}

int parameters(char *out)
{
    char declaration[MAXOUTPUT], expectParameter = TRUE;
    int parameterCount = 0;

    if (gettoken() == ')') // previous token was '(' which means () was found, an old K&R style declaration 
    {
        if (saferstrcat(out, " obsolescent non-prototype function declaration with unknown parameters returning", MAXOUTPUT) == NULL)
            return error("Error: input too large to fit into buffer");
        return OK;
    }
    else if (tokentype == TYPESPECIFIER && strcmp(token, "void") == 0) 
    {
        if (gettoken() == ')') // this is true when the type is void and it is the only parameter
            expectParameter = FALSE;
        else if (tokentype == ',') // very basic check to see if void is used incorrectly. Too much work to do it properly
            return error("Syntax error: functions either can have void * parameters or only a single void parameter");
        else // the parameter is not (void), but rather pointer(s) to void (e.g. void *, void **, etc) or it is (int, void), etc.
        {
            for (int i = strlen(token) - 1; i >= 0; i--)
                ungetch(token[i]); // push unused token back to input in reverse order
            tokentype = TYPESPECIFIER; // reset tokentype since not (void)
            snprintf(token, MAXTOKEN, "%s", "void"); // reset token since not (void)
        }
    }
    // the ##### is used for padding and will be changed later. 5 #'s are to prevent buffer overflows caused by an insanely large input that fits in an oversized buffer
    if (saferstrcat(out, " function expecting ##### parameters:", MAXOUTPUT) == NULL)
        return error("Error: input too large to fit into buffer");
    if (expectParameter)
        do
        {
            if (parameterCount++ > 0) // don't call gettoken the first time, but call it each time thereafter
                gettoken();
            if (processDeclaration(declaration, expectParameter) == ERROR)
                return ERROR;
            if (strncmp(declaration, " unnamed parameter ", 19) != 0) // check if declaration starts with string
                if (saferstrcat(out, " parameter ", MAXOUTPUT) == NULL) // if parameter has a name, prefix out first before adding the declaration part
                    return error("Error: input too large to fit into buffer");
            if (saferstrcat(out, declaration, MAXOUTPUT) == NULL)
                return error("Error: input too large to fit into buffer");
        } while (tokentype == ','); // get all comma separated parameters
    if (tokentype == ')') // after getting parameters, next token should be )
    {
        // this complicated mess is so I can replace the "##### parameters:" string with the parameterMessage in the final output
        char parameterMessage[MAXTOKEN], *p1 = out, *p2 = parameterMessage;
        while (*p1 != '#') // move to first #. this will run before another "##### parameters" is added for the same declaration
            p1++;
        if (parameterCount == 0)
            snprintf(parameterMessage, MAXTOKEN, "no parameters");
        else if (parameterCount == 1)
            snprintf(parameterMessage, MAXTOKEN, "1 parameter:");
        else
            snprintf(parameterMessage, MAXTOKEN, "%d parameters:", parameterCount);
        while (*p2 != '\0') // copy parameterMessage to out until '\0' is reached. Don't copy '\0'
            *p1++ = *p2++;
        for (p2 = p1; *p2++ != ':'; ) // point p2 to p1 and then move p2 to after "##### parameters:"
            ;
        while ((*p1++ = *p2++)) // copy after the : to the end of out to where p1 is pointing (the end of the parameterMessage in out)
            ;
        if (saferstrcat(out, " returning", MAXOUTPUT) == NULL)
            return error("Error: input too large to fit into buffer");
    }
    else
        return error("Error: expected closing parentheses after parameters");
    return OK;
}

int gettoken(void)
{
    int c;
    char *p = token;

    while ((c = getch()) == ' ' || c == '\t') // skip spaces and tabs
        ;
    if (c == '(')
    {
        *(p + 1) = '\0'; // terminate token string
        return *p = tokentype = '(';;
    }
    else if (c == '[') // get [#####] and store in token
    {
        for (*p++ = c; (*p++ = getch()) != ']'; )
            ;
        *p = '\0';
        return tokentype = BRACKETS;
    }
    else if (isalpha(c) || c == '_') // get name
    {
        for (*p++ = c; isalnum(c = getch()) || c == '_'; )
            *p++ = c;
        *p = '\0';
        ungetch(c); // push back the unneeded extra char 
        if (contains(typeSpecifiers, token, NUMOFTYPESPECIFIERS))
            return tokentype = TYPESPECIFIER;
        else if (contains(storageClasses, token, NUMOFSTORAGECLASSES))
            return tokentype = STORAGECLASS;
        else if (contains(typeQualifiers, token, NUMOFTYPEQUALIFIERS))
            return tokentype = TYPEQUALIFIER;
        return tokentype = VARIABLE;
    }

    *(p + 1) = '\0'; // terminate token string
    return *p = tokentype = c; // since not one of the types above, return char as the type and store it's value in the token
}

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

void ungetch(int c)
{
    if (bufp >= BUFSIZE)
        fprintf(stderr, "ungetch: too many characters\n");
    else
        buf[bufp++] = c;
}

int contains(char **strs, char *name, int strCount)
{
    for (int i = 0; i < strCount; i++)
        if (strcmp(strs[i], name) == 0)
            return TRUE;
    return FALSE;
}

// concatenates str to end of dst; requires null terminated strings and dst buffer size. Returns null if provided bad pointers or buffer is too small, otherwise returns pointer to dst
char *saferstrcat(char *dst, const char *str, size_t dstsize)
{
    if (dst == NULL || str == NULL) // if either pointer is NULL, return NULL
        return NULL;
    char *dstStart = dst; // keep track of the base of the string
    size_t dstLen = strlen(dst), strLen = strlen(str); // calcuate the length of both strings once. Prevents the need to do constant checks in the loop
    if (dstLen + strLen >= dstsize) // strlen doesn't count '\0', so if size == dstsize, then not enough space for the '\0' at the end
        return NULL; // concatenating the strings would result in a buffer overflow. So return NULL instead
    dst += dstLen; // move pointer to '\0' at end of string
    while ((*dst++ = *str++)) // copy str to end of dst until str == '\0'. If str == "", the loop copies '\0' and terminates
        ;
    return dstStart;
}

int error(char *msg)
{
    fprintf(stderr, "%s\n", msg);
    return ERROR;
}
Personal tools