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
};