Iterate over preprocessor arguments?

J

Joakim Hove

Hello,

I am writing a C program which among other things need to read and
write textfiles from an external third party application. Typical
content in these files are string fields which can have a limited set
of values, e.g. the three values {"OPEN" , "SHUT", "AUTO"}. The
available strings have been unchanged for ~ 25 years - so I think they
can be safely assumed to not change.

I do not like to carry around strings to hold state information, so in
my own code I check the string content and internalize an enum value.
My implemenentation looks like this:


#define OPEN_STRING "OPEN"
#define SHUT_STRING "SHUT"
#define AUTO_STRING "AUTO"

typedef enum {AUTO , SHUT , OPEN } state_enum;


static state_enum state_from_string(const char * string) {
if (strcmp(string , OPEN_STRING) == 0)
return OPEN;
else if (strcmp(string , SHUT_STRING) == 0)
return SHUT;
else if (strcmp(string , AUTO_STRING) == 0)
return AUTO;
else {
fprintf(stderr , " Failed to interpret:%s as %s|%s|%s - going
home\n",string , OPEN_STRING, SHUT_STRING,AUTO_STRING);
exit(1);
}
}


static const char * string_from_state(state_enum state) {
switch(state) {
case(OPEN):
return OPEN_STRING;
case(SHUT):
return SHUT_STRING;
case(AUTO):
return AUTO_STRING;
default:
fprintf(stderr,"Internal error ??");
exit();
}
}


Now - this is of course very simple code to write; but I have many
such sets, so I end up hand-writing these functions many times - it
does not feel good! It would of course be simple to write e.g. a
Python script to generate these functions, but I am reluctant to
introduce arbitrary different programs in the build process (maybe I
should not be reluctant to that??)

So - is it possible to generate the functions state_from_string() and
string_from_state() automagically by using the preprocessor?


Best Regards

Jaokim
 
G

Guest

I am writing a C program which among other things need to read and
write textfiles from an external third party application. Typical
content in these files are string fields which can have a limited set
of values, e.g. the three values {"OPEN" , "SHUT", "AUTO"}. The
available strings have been unchanged for ~ 25 years - so I think they
can be safely assumed to not change.

I bet you, this week they change
:)
I do not like to carry around strings to hold state information, so in
my own code I check the string content and internalize an enum value.
My implemenentation looks like this:

#define OPEN_STRING "OPEN"
#define SHUT_STRING  "SHUT"
#define AUTO_STRING "AUTO"

typedef enum {AUTO , SHUT , OPEN } state_enum;

static state_enum state_from_string(const char * string) {
    if (strcmp(string , OPEN_STRING) == 0)
       return OPEN;
    else if (strcmp(string , SHUT_STRING) == 0)
       return SHUT;
    else if (strcmp(string , AUTO_STRING) == 0)
       return AUTO;
    else {
       fprintf(stderr , " Failed to interpret:%s as %s|%s|%s - going
home\n",string , OPEN_STRING, SHUT_STRING,AUTO_STRING);
       exit(1);
    }

}

static const char * string_from_state(state_enum state) {
    switch(state) {
      case(OPEN):
         return OPEN_STRING;
      case(SHUT):
         return SHUT_STRING;
     case(AUTO):
         return AUTO_STRING;
     default:
         fprintf(stderr,"Internal error ??");
         exit();
   }

}

Now - this is of course very simple code to write; but I have many
such sets, so I end up hand-writing these functions many times - it
does not feel good! It would of course be simple to write e.g. a
Python script to generate these functions, but I am reluctant to
introduce arbitrary different programs in the build process (maybe I
should not be reluctant to that??)

code generators are cool! Could you write the application in python?
So - is it possible to generate the functions state_from_string() and
string_from_state() automagically by using the preprocessor?

not sure but can you get C to be more dynamic like this:-

char *table [LARGE_ENUFF];
static int table_size = 0;


void load_table (void)
{
char *state_name;

while ((state_name = read_string()) = MORE_DATA)
table[size++] = state_name;
}

static state_enum state_from_string (const char *string)
{
int i;
for (i = 0; i < table_size; i++)
if (strcmp (table string) == 0)
return i;

error ("bad string");
}

static const char * string_from_state(state_enum state)
{
if (state > table_size)
error ("bad state");

return table [state];
}


You have to change your state handling code to search the state
table rather than use case as I assume you do now.

with the usual caveats about codeing-out-loud
I haven't compiled it, it has global data, the error handling is poor
and I'm pretty sure there's an off-by-one error. What does table_size
mean?


--
Nick Keighley

Programming should never be boring, because anything
mundane and repetitive should be done by the computer.
~Alan Turing

I'd rather write programs to write programs than write programs
 
H

Hallvard B Furuseth

Joakim said:
static state_enum state_from_string(const char * string) {
if (strcmp(string , OPEN_STRING) == 0)
return OPEN;
else if (strcmp(string , SHUT_STRING) == 0)
return SHUT;
else if (strcmp(string , AUTO_STRING) == 0)
return AUTO;
else {

If speed is of any importance:

switch (string[0]) {
case 'O': if (!strcmp(string, OPEN_STRING)) return OPEN; break;
...
}

or try to generate a perfect hash, like GNU gperf does. (An array and a
simple hash function so that the first lookup will succeed.)

static const struct { state_enum st; const char *name; } states[4] = {
{ AUTO, AUTO_STRING },
{ SHUT, SHUT_STRING },
{ -1, "" },
{ OPEN, OPEN_STRING }
};
static state_enum state_from_string(const char * string) {
int idx = ((unsigned) string[0] >> 1) & 3;
assert(strcmp(string, states[idx].name) == 0);
return states[idx].st;
}
Now - this is of course very simple code to write; but I have many
such sets, so I end up hand-writing these functions many times - it
does not feel good! It would of course be simple to write e.g. a
Python script to generate these functions, but I am reluctant to
introduce arbitrary different programs in the build process (maybe I
should not be reluctant to that??)

If you've got Python available, there is no reason not to use it.
Just include the file you built in the distribution. Then you
won't have introduced Python in your users' build process.
 
H

Hallvard B Furuseth

Eh. I forgot to answer your original question:-(

Joakim said:
So - is it possible to generate the functions state_from_string() and
string_from_state() automagically by using the preprocessor?

Recursive preprocessor macros do not work - the C standard goes to some
trouble to forbid that. However:

#define STATES S(OPEN), S(SHUT), S(AUTO)

#define S(name) name
typedef enum { STATES, State_count } state_enum;
#undef S

#define S(name) #name
static const char *state_names[State_count] = { STATES };
#undef S

static state_enum state_from_string(const char *string) {
state_enum i;
for (i = 0; i < State_count; i++)
if (strcmp(string, state_names) == 0)
return i;
assert(0);
}
 
N

Nate Eldredge

Joakim Hove said:
Hello,

I am writing a C program which among other things need to read and
write textfiles from an external third party application. Typical
content in these files are string fields which can have a limited set
of values, e.g. the three values {"OPEN" , "SHUT", "AUTO"}. The
available strings have been unchanged for ~ 25 years - so I think they
can be safely assumed to not change.

I do not like to carry around strings to hold state information, so in
my own code I check the string content and internalize an enum value.
My implemenentation looks like this:


#define OPEN_STRING "OPEN"
#define SHUT_STRING "SHUT"
#define AUTO_STRING "AUTO"

typedef enum {AUTO , SHUT , OPEN } state_enum;


static state_enum state_from_string(const char * string) {
if (strcmp(string , OPEN_STRING) == 0)
return OPEN;
else if (strcmp(string , SHUT_STRING) == 0)
return SHUT;
else if (strcmp(string , AUTO_STRING) == 0)
return AUTO;
else {
fprintf(stderr , " Failed to interpret:%s as %s|%s|%s - going
home\n",string , OPEN_STRING, SHUT_STRING,AUTO_STRING);
exit(1);
}
}


static const char * string_from_state(state_enum state) {
switch(state) {
case(OPEN):
return OPEN_STRING;
case(SHUT):
return SHUT_STRING;
case(AUTO):
return AUTO_STRING;
default:
fprintf(stderr,"Internal error ??");
exit();
}
}


Now - this is of course very simple code to write; but I have many
such sets, so I end up hand-writing these functions many times - it
does not feel good! It would of course be simple to write e.g. a
Python script to generate these functions, but I am reluctant to
introduce arbitrary different programs in the build process (maybe I
should not be reluctant to that??)

So - is it possible to generate the functions state_from_string() and
string_from_state() automagically by using the preprocessor?


Best Regards

Jaokim

I might write the following, assuming you have C99:

typedef enum { AUTO, SHUT, OPEN, NUM_STATES } state_enum;
static const char* state_strings[] = {
[AUTO] = "AUTO",
[SHUT] = "SHUT",
[OPEN] = "OPEN",
[NUM_STATES] = NULL,
};

state_enum state_from_string(const char *string) {
int i;
for (i = 0; i < NUM_STATES; i++)
if (strcmp(string, state_strings) == 0)
return i;
fprintf(stderr, "Argh!\n");
exit(1);
}

const char *string_from_state(state_enum state) {
if (state >= 0 && state < NUM_STATES)
return state_strings[state];
else {
fprintf("Noooo!\n");
exit(1);
}
}

This relies on the fact that enum values are assigned consecutively
beginning with 0. If you don't have C99, you can omit the '[AUTO] ='
stuff, but you will have to be careful to keep the array initializers in
the same order as the enums.

There is no need for macros for the strings "AUTO", etc, because they
now only appear in one place.

If you want to do the same for another set of values, you need only make
a new array, and copy the two functions, changing the array name and
NUM_STATES appropriately.

You could also dispense with NUM_STATES while keeping error checking, as
follows:

typedef enum { AUTO, SHUT, OPEN } state_enum;
static const char* state_strings[] = {
[AUTO] = "AUTO",
[SHUT] = "SHUT",
[OPEN] = "OPEN",
};

#define ARRAY_LENGTH(a) (sizeof(a)/sizeof((a)[0]))

state_enum state_from_string(const char *string) {
int i;
for (i = 0; i < ARRAY_LENGTH(state_strings); i++)
if (strcmp(string, state_strings) == 0)
return i;
fprintf(stderr, "Argh!\n");
exit(1);
}

const char *string_from_state(state_enum state) {
if (state >= 0 && state < ARRAY_LENGTH(state_strings))
return state_strings[state];
else {
fprintf("Noooo!\n");
exit(1);
}
}

If you are very lazy, you could make a macro to write the *_from_*
functions. (Compiled, not tested.)

#include <stdio.h>
#include <stdlib.h>

typedef enum { AUTO, SHUT, OPEN } state_enum;
static const char* state_strings[] = {
[AUTO] = "AUTO",
[SHUT] = "SHUT",
[OPEN] = "OPEN",
};

#define MAKE_ENUM_FROM_STRING(name, enum_name, array) \
enum_name name ## _from_string (const char *string) { \
int i; \
for (i = 0; i < ARRAY_LENGTH(array); i++) \
if (strcmp(string, array) == 0) \
return i; \
fprintf(stderr, "Failed to make string %s into %s\n", string, #name); \
exit(1); \
}

#define MAKE_STRING_FROM_ENUM(name, enum_name, array) \
const char *string_from_ ## name (enum_name i) { \
if (i >= 0 && i < ARRAY_LENGTH(array)) \
return array; \
else { \
fprintf(stderr, "Failed to make %s %d into string\n", i, #name); \
exit(1); \
} \
}

/* more laziness */
#define MAKE_FUNCTIONS(name, enum_name, array) \
MAKE_ENUM_FROM_STRING(name, enum_name, array) \
MAKE_STRING_FROM_ENUM(name, enum_name, array)

MAKE_FUNCTIONS(state, state_enum, state_strings)
 
H

Huibert Bol

Nate said:
If you are very lazy, you could make a macro to write the *_from_*
functions. (Compiled, not tested.)

#include <stdio.h>
#include <stdlib.h>

typedef enum { AUTO, SHUT, OPEN } state_enum;
static const char* state_strings[] = {
[AUTO] = "AUTO",
[SHUT] = "SHUT",
[OPEN] = "OPEN",
};

#define MAKE_ENUM_FROM_STRING(name, enum_name, array) \
enum_name name ## _from_string (const char *string) { \
int i; \
for (i = 0; i < ARRAY_LENGTH(array); i++) \
if (strcmp(string, array) == 0) \
return i; \
fprintf(stderr, "Failed to make string %s into %s\n", string, #name); \
exit(1); \
}

#define MAKE_STRING_FROM_ENUM(name, enum_name, array) \
const char *string_from_ ## name (enum_name i) { \
if (i >= 0 && i < ARRAY_LENGTH(array)) \
return array; \
else { \
fprintf(stderr, "Failed to make %s %d into string\n", i, #name); \
exit(1); \
} \
}

/* more laziness */
#define MAKE_FUNCTIONS(name, enum_name, array) \
MAKE_ENUM_FROM_STRING(name, enum_name, array) \
MAKE_STRING_FROM_ENUM(name, enum_name, array)

MAKE_FUNCTIONS(state, state_enum, state_strings)


If you are *extremely* lazy you can even create the enum and strings
array using a macro. One advantage is that you don't have to worry
about keeping the order of the enum and array in sync.

[MAKE_FUNCTIONS as above]

#define MAKE_ENUM_ELEM(X) X
#define MAKE_ENUM(name, S, elems) \
typedef enum { elems(S) } name##_enum;

#define MAKE_STRINGS_ELEM(X) #X
#define MAKE_STRINGS(name, S, elems) \
static const char *name##_strings[] = { elems(S) };

#define MAKE_ALL(name, elements) \
MAKE_ENUM(name, MAKE_ENUM_ELEM, elements) \
MAKE_STRINGS(name, MAKE_STRINGS_ELEM, elements) \
MAKE_FUNCTIONS(name, name##_enum, name##_strings)

#define STATES(S) S(AUTO), S(SHUT), S(OPEN)
MAKE_ALL(state, STATES)
 
J

Joakim Hove

Hello,

thanks for answering :)
If you are *extremely* lazy you can even create the enum and strings
array using a macro.  

Now we are talking - it was something like this I was looking for. I
will try to implement your suggestions.

Joakim
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top