← Back to Blog
February 20, 2025

Converting Enums to String in C

TIL enums C 2 min read

I found a clever way to convert enums to strings while working on a project involving QuickJS, a lightweight JavaScript engine. The QuickJS VM uses enums to define its bytecodes, and I needed to convert these enums into strings for debugging and decompilation purposes.

Problem

In Quick Js, Enums for bytecodes were defined like this

typedef enum {
  OP_invalid,
  OP_push_i32,
  OP_push_const
} OPCodeEnum;

And I needed to convert this to string. While I could write the switch case statement to print the equivalent opcode name based on the position of the enum.

switch (opcode) {
  case OP_invalid: 
    printf("OP_invalid");
  case OP_push_i32: 
    printf("OP_push_i32")
  ....
}

This is fine for small enums. But when you have a large enum with many values, it becomes cumbersome and error-prone to maintain. QuickJs stores its bytecodes in quickjs-opcode.h file with DEF macro.

The DEF macro defines each opcode with its properties, such as size, number of values popped and pushed, and a format type. The macro is defined like this:

#ifdef DEF

#ifndef def
#define def(id, size, n_pop, n_push, f) 
DEF(id, size, n_pop, n_push, f)
#endif


DEF(push_atom_value, 5, 0, 1, atom)
DEF(private_symbol, 5, 0, 1, atom)

Solution

To convert this into an array of opcodes with string, then you can use the macro but with # to stringify the opcode name. Yes, just a hash to stringify the enum.

typedef struct JSOpCode {
    const char *name;
    uint8_t size; 
    uint8_t n_pop;
    uint8_t n_push;
    uint8_t fmt;
} JSOpCode;



static const JSOpCode opcode_info[] = {
#define FMT(f)
#ifdef ENABLE_DUMPS // JS_DUMP_BYTECODE_*
#define DEF(id, size, n_pop, n_push, f) { #id, size, n_pop, n_push, OP_FMT_ ## f },
#else
#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_ ## f },
#endif
#include "quickjs-opcode.h"
#undef DEF
#undef FMT
};