1

I have the sturct token_t down below. It has two things in it.

  • type (enum)
  • value (union)

The reason why value is union, cz a token can be either a number or string. The main problem is that I cannot design a proper function that can print this token out for me. Check out my void print_token(token_t* token) function. I know it's terrible. Just becase the printf() prints only either %d (decimal) or %s (string) I cannot print my token out. I want my function to print the token no matter what the value is.

// ===========================================================================
typedef enum token_type_t 
{
    ID,  // Identifier
    STR, // String
    WHS, // Whitespace 
    LPR, // Left Parenthesis
    RPR, // Right Parenthesis
    LCB, // Left Curly Bracket
    RCB, // Right Curly Bracket
    LSB, // Left Square Bracket
    RSB, // Left Square Bracket
    EOF, // End Of File
    EQL, // Equal Sign
    SEM, // Semicolon
} token_type_t;

typedef union token_value_t 
{
    char* str_value;
    int int_value;
} token_value_t;

typedef struct token_t 
{
    token_type_t  type;
    token_value_t value;    
} token_t;
// ===========================================================================


// Here's the problem. I want this function to print the token no matter what type 
// the value is, I want it to be dynamic. Is there any way to do that?
void print_token(token_t* token)
{
    printf("Token { type: %d, value: %s }\n", token->type, token->value);
}
timrau
  • 22,578
  • 4
  • 51
  • 64
  • 3
    The obvious thing? Use a different `printf` call depending on `token->type`? If the type doesn't give you that info then you will need to have some other field that does. – kaylum Jun 18 '21 at 22:10
  • The two elements of `union token_value_t` can't have the same name; this won't compile. – Nate Eldredge Jun 18 '21 at 22:11
  • @NateEldredge my bad, how can I improve that as well? I just want the token value to be either a string or a number –  Jun 18 '21 at 22:13
  • @kaylum is there any way that I can check the type of token in C? Like `typeof` in JavaScript xd –  Jun 18 '21 at 22:14
  • 1
    There is no such thing as that "token" in the C language. That is something you made up yourself so you need to be the one that stores that info. I'm assuming that's what the `token_type_t` field does. But if it doesn't then you need to update the struct to have such a field. That's standard for using `union` fields. Some other field in the same struct as the union needs to record what union field is active is for each struct variable. – kaylum Jun 18 '21 at 22:16
  • @kaylum Thanks. Yes, that makes sense actually. I can identify that using `token_type_t` –  Jun 18 '21 at 22:19
  • 2
    There is a `typeof` in some extensions to C, e.g. https://gcc.gnu.org/onlinedocs/gcc/Typeof.html, but it won't help you here as it's static. There simply is no dynamic typing in C and a `union` doesn't "remember" which member was last assigned to. That is up to your program logic, e.g. make yourself a rule as to which token types use which union member, and then use `if` or `switch` to test `token->type` and print either `token->value.str_value` or `token->value.int_value` as appropriate. – Nate Eldredge Jun 18 '21 at 22:21
  • 1
    You have to ask yourself "how do *I* know if it is a string, then implement that in your code. It could be as simple as `if (token->type == STR) printf("Token is '%s'\n",token->strval);` then `else printf("Token is %d\n",token->intval);`. – SGeorgiades Jun 18 '21 at 22:22
  • @SGeorgiades Thanks! I got it! Yes, that makes perfect sense –  Jun 18 '21 at 22:26
  • 1
    `%d` is the _decimal_ format specifier not "_digit_". – Clifford Jun 18 '21 at 22:36
  • Which token types are stored as strings and which as integers? – Clifford Jun 18 '21 at 22:40
  • 1
    pheianox, Minor `// Idnetifier` --> `// Identifier`. – chux - Reinstate Monica Jun 18 '21 at 22:41

2 Answers2

3

How do I print either string or integer in C?

Use a different printf() depending on type.

void print_token(const token_t* token) {
  printf("Token { type: %d, value: ", token->type);
  switch (token->type) {
    case ID: printf("%d",      token->value.int_value); break;
    case STR: printf("\"%s\"", token->value.str_value); break;
    // TBD code for other cases
  }
  printf(" }\n");
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • That worked perfectly. May I ask why you used `const` ? –  Jun 18 '21 at 22:28
  • 1
    @pheianox `const` allows `print_token(const token_t* token)` to be called with a pointer to `const` data as well as non-const data. `print_token(token_t* token)` cannot be called with a pointer to non-const data. As `print_token()` does not need to modify the data pointed to by `token`, using `const` allows for greater usage. It also conveys to the caller that data is not disturbed. Example: Consider `char *strcpy(char *s1, const char *s2);`: `s1` points to changed data, `s2` does not`. – chux - Reinstate Monica Jun 18 '21 at 22:33
  • 1
    In general, when passing pointer arguments it's good to declare it as a pointer to a `const` to avoid accidentally changing the value. – SGeorgiades Jun 18 '21 at 22:34
  • 1
    This might be a useful application of "fall-through" cases to avoid repetition since there are only two actions - you can group the cases. – Clifford Jun 18 '21 at 22:46
0

One solution is to print only a string, but convert the integer to a string conditionally:

For example:

void print_token( const token_t* token )
{
    char buf[21] ;
    printf( "Token { type: %d, value: %s }\n", 
            token->type, 
            token->type == ID ? itoa( token->value.int_value, buf, 10 ) :
                                token->value.str_value );
}

Noting that itoa() is not a standard function - you might consider that prohibitive if you have to implement your own just for this.

You have not specified which token types are integers and which are strings so if perhaps ID is not the only integer, the condition may be more complex such as:

void print_token( const token_t* token )
{
    char buf[21] ;
    printf( "Token { type: %d, value: %s }\n", 
            token->type, 
            token->type == ID ||
            token->type == EOF ? itoa( token->value.int_value, buf, 10 ) :
                                 token->value.str_value );
}

If you have more more integer types than string types you would do better to swap the test.

If the are just one or two of one token type and the rest are of the other, and you have a suitable integer-to-string function, the above solution is succinct, but might get cumbersome if that are more than a few of both types. In that case consider the solution below.

A switch/case using of case "fall-through" cases and a default may be easier to maintain an does not need an integer-to-string fucntion:

void print_token( const token_t* token ) 
{
    printf( "Token { type: %d, value: ", token->type ) ;
    switch (token->type) 
    {
        // Stack all the integer token cases here
        case ID :
        case EOF : 
            printf("%d", token->value.int_value) ; 
        break ;
    
        // Everything is a string
        default:
            printf( "\"%s\"", token->value.str_value ) ; 
        break;
    }
    printf(" }\n");
}

Again you might swap the explicit cases and default if there are more string tokens than integer.

Clifford
  • 88,407
  • 13
  • 85
  • 165