1

Earlier I was fixing a lexer for my parser; now I have to create a validater for it. My idea was to convert the enumeration constants to strings by using the following preprocessor macro: #define MACRO_STRINGIFY(x) #x.

Then I made a function to compare various token values lactually, I made three, but they are all the same with a few minor changes):

unsigned int compare_keyword( enum script_keywords keyword, char *token ) {
  char *temporary = MACRO_STRINGIFY( keyword );
  unsigned int i = 0;
  for (; i < (strlen( "KEYWORD_" ) + 1); i++) {
    ++temporary;
  }
  // 0 on match, 1 on no match
  return strcmp( temporary, token ) ? 1 : 0;
}

Now, this function works absolutely fine... when keyword is the enumeration constant:

void test() {
  printf( "\nIF is " );
  // Finish the sentence based on the return value
  compare_keyword( KEYWORD_IF, "IF" ) ? printf( "not a keyword.\n" ) : printf( "a keyword.\n" );
}
test(); //--> Outputs 'IF is a keyword' like expected.

On the other hand, the function does not work as intended if I pass a value like 1 (the value which the symbolic enumeration constant KEYWORD_IF resolves to.):

// Same as last time with one edit:
void test() {
  /* See above code with following change on line 4 */
  compare_keyword( 1, "IF" ) /* etc... */
  /* Rest of code from previous test */
}
test(); //--> Outputs 'IF is not a keyword' even if KEYWORD_IF resolves to the value 1.

The point I'm getting across here is that the preproccessor is very literal, and I would much prefer using a for loop to loop through the constants efficiently without bloating the code size (which is what would happen if I end up using enumeration constants). Therefore, the question is how can I convert plain integer values to their symbolic names without using switch…case… or if…else…?

Edit: Enumeration Details:

enum script_keywords {
  KEYWORD_IF = 1,
  KEYWORD_THEN = 2,
  KEYWORD_ELSEIF = 3,
  KEYWORD_ELSE = 4,
  KEYWORD_ENDIF = 5,
  KEYWORD_FOR = 6,
  KEYWORD_TO = 7,
  KEYWORD_STEP = 8,
  KEYWORD_EXITFOR = 9,
  KEYWORD_NEXT = 10,
  KEYWORD_LOOP = 11,
  KEYWORD_WHILE = 12,
  KEYWORD_EXITLOOP = 13,
  KEYWORD_ENDLOOP = 14,
  KEYWORD_DO = 15,
  KEYWORD_EXITDO = 16,
  KEYWORD_UNTIL = 17,
  KEYWORD_ON = 18,
  KEYWORD_GOTO = 19,
  KEYWORD_CALL = 20,
  KEYWORD_LET = 21,
  KEYWORD_DIM = 22,
  KEYWORD_AS = 23
};
  • Why do you actually need this behaviour? Normally you would compare the enumeration *value*. – sapi Jan 05 '15 at 22:54
  • Not if I'm comparing strings; the integer value is useless for string comparisons. –  Jan 05 '15 at 23:01
  • 1
    What function works absolutely fine? `MACRO_STRINGIFY( keyword );` will return the string `"keyword"`. How does it help you? – n. m. could be an AI Jan 05 '15 at 23:04
  • No, it returns a string of the literal parameter value of keyword. –  Jan 05 '15 at 23:11
  • 1
    You probably want to look up x-macros. There are some questions on the subject on SO. – Jonathan Leffler Jan 05 '15 at 23:17
  • 1
    Enumeration constants don't exist as such in compiled programs; they are a source-only artifact. You cannot generate their string representations directly from their `int` values. You could, however, create a lookup table by which to obtain string versions of the constants. Note that this will be lossy when different constants correspond to the same value (which can indeed happen, but will not happen by default). – John Bollinger Jan 05 '15 at 23:19
  • @Isaiah: if `MACRO_STRINGIFY(keyword)` does not return the string `"keyword"`, you need to show how it is defined and the definition of the data type `enum script_keywords`. But you mention `#define MACRO_STRINGIFY(x) #x` which can't do other than generate `"keyword"` in the context shown. – Jonathan Leffler Jan 05 '15 at 23:20
  • Maybe I wrote my test case wrong in my actual code; I'll test it again to make sure. –  Jan 05 '15 at 23:35
  • I am very sorry, I must've gotten the ternary operators mixed or something, oh well, I'll fallback to switch case. –  Jan 05 '15 at 23:37
  • Three questions to look at: (1) stringification in macros [How to make a char string from a C macro's value](http://stackoverflow.com/qs/195975/); (2) X-Macros [Is it possible to modify this X-macro to build a struct which includes arrays?](http://stackoverflow.com/q/13561509/is/13562004#13562004); (3) X-Macros again [Easy way to use variables of `enum` types as string in C?](http://stackoverflow.com/q/147267/easy/147492#147492). It isn't particularly trivial to use X-Macros, but it does get the job done. Just make sure you document it carefully -- very carefully. – Jonathan Leffler Jan 05 '15 at 23:47
  • I'm using a lookup table, but thanks anyway... I still have one problem though: in my test case at the end of my loop in my main program, it outputs the correct results in the debugger yet the wrong ones in a normal run... –  Jan 06 '15 at 00:22

1 Answers1

0

The Macro "MACRO_STRINGIFY" is evaluated at compile time by the preprocessor. It will return the actual name of the argument, so

MACRO_STRINGIFY(keyword) -> "keyword"
MACRO_STRINGIFY(KEYWORD_IF) -> "KEYWORD_IF"
MACRO_STRINGIFY(1) -> "1"

Apparently this will not lead to any solution.

Instead one could use a compile-time generated key-value mapping to implement such functionality:

struct map
{
  int key;
  const char* value;
};

struct map mappings[] =
{
  { KEYWORD_IF, "IF" },
  { KEYWORD_ELSE, "ELSE" }
};

and then simply iterate those mapping entries at runtime to find out what you need:

static int is_keyword(const char* str)
{
  int i;
  const int count = sizeof(mappings) / sizeof(mappings[0]);

  for(i = 0; i < count; i++)
  {
    struct map* m = &mappings[i];

    if(strcmp(str, m->value) == 0)
    {
      return 1;
    }
  }

  return 0;
}

void test()
{
  const char* str = "IF";
  const char* what;

  if(is_keyword(str))
  {
    what = "a keyword";
  }
  else
  {
    what = "not a keyword";
  }

  printf("%s is %s.\n", str, what);
}

This is about as minimal as it may get. The executable binary does not normally include names of enumeration values as known from e.g. Java.

To extend this even further, you could do some preprocessor voodoo to generate the mapping array (semi-) automatically. But as I am not a big friend of preprocessor voodoo I will skip that ;)

s1kam4n
  • 144
  • 8
  • Thanks anyway, but I decided simple lookup table using an array of strings. –  Jan 06 '15 at 00:23
  • 1
    No problem ;) This is a more generic approach and nice if you are going to reuse the keyword type. For a simple lookup weather something is a keyword or not (not minding which), then a LUT is clearly sufficient. – s1kam4n Jan 06 '15 at 00:27