If you use this for changing parameters via command strings, I recommend merging the parameter name and value parsing.
Here, perhaps an example program will illustrate the idea better:
Code:
#include <stdio.h> /* Needed only by main() */
#include <limits.h>
static int imin = 0;
static int imax = 0;
static char stralteq(const char *const str1,
const char *const str2upper,
const char *const str2lower,
const size_t n)
{
size_t i;
for (i = 0; i < n; i++)
if (str1[i] != str2upper[i] && str1[i] != str2lower[i])
return 0;
return 1;
}
static const char *strskipws(const char *str)
{
while (*str == '\t' || *str == '\n' || *str == '\v' ||
*str == '\f' || *str == '\r' || *str == ' ')
str++;
return str;
}
typedef enum {
OK = 0,
EMPTY, /* Nothing to parse. */
UNKNOWN, /* Name not recognized. */
NOSEPARATOR,
NOVALUE, /* No value specified. */
TRAILING, /* Trailing garbage. */
OVERFLOW /* Value too large in magnitude. */
} parsing_t;
static parsing_t parse(const char *str)
{
int *ptr = NULL;
const char *endstr;
unsigned int value;
char negative = 0;
if (!str)
return EMPTY;
/* Skip whitespace. */
str = strskipws(str);
/* Nothing to parse? */
if (*str == '\0')
return EMPTY;
/* Compare to known names. */
if (stralteq(str, "IMIN", "imin", 4)) {
str += 4;
ptr = &imin;
} else
if (stralteq(str, "IMAX", "imax", 4)) {
str += 4;
ptr = &imax;
} else
return UNKNOWN;
/* Remember where the name ended. */
endstr = str;
/* Allow whitespace in between. */
str = strskipws(str);
/* Allow = or : in between, too. */
if (*str == '=' || *str == ':')
str = strskipws(str + 1);
/* No separator after name? */
if (str == endstr)
return NOSEPARATOR;
/* Signed? */
while (*str == '-' || *str == '+')
negative ^= (*(str++) == '-');
/* At least one decimal digit is required. */
if (!(*str >= '0' && *str <= '9'))
return NOVALUE;
value = *(str++) - '0';
while (*str >= '0' && *str <= '9') {
const unsigned int oldvalue = value;
value = 10U * value + (*(str++) - '0');
/* Overflow? */
if (value / 10U != oldvalue)
return OVERFLOW;
}
str = strskipws(str);
/* Not end of string? */
if (*str != '\0')
return TRAILING;
/* Assign value. */
if (negative) {
if (value > -(unsigned int)INT_MIN)
return OVERFLOW;
*ptr = -(int)value;
} else {
if (value > (unsigned int)INT_MAX)
return OVERFLOW;
*ptr = (int)value;
}
/* Success. */
return OK;
}
int main(void)
{
char buffer[128];
char *line;
printf("imin = %d, imax = %d. Input strings, or empty line to exit.\n", imin, imax);
fflush(stdout);
while ((line = fgets(buffer, sizeof buffer, stdin)) != NULL) {
switch (parse(line)) {
case OK:
printf("\tOk.\n");
break;
case EMPTY:
printf("\tEmpty line. Exiting.\n");
return 1;
case UNKNOWN:
printf("\tUnknown name.\n");
break;
case NOSEPARATOR:
printf("\tNo separator between name and value.\n");
break;
case NOVALUE:
printf("\tNo value specified.\n");
break;
case TRAILING:
printf("\tIgnoring due to trailing garbage.\n");
break;
case OVERFLOW:
printf("\tIgnoring due to value overflow.\n");
break;
}
printf("\timin = %d, imax = %d\n", imin, imax);
fflush(stdout);
}
printf("No more input.\n");
return 0;
}
stralteq(str1, str2upper, str2lower, n)
This function returns nonzero if the first n characters in str1 match either str2upper or str2lower. I use this kind of a function to allow for alternate versions of the name, for example [Ii][Mm]in . Above, I used it as a case-insensitive comparison. You could use strncmp() instead.
strskipws(str)
Returns a pointer to after the leading ASCII whitespace.
parse(str)
If str contains a known name, followed by whitespace and/or = or :, followed by a (signed) integer, this function updates the internal state (variable) accordingly. See the example code for the return values.
Hope you find this useful.