Last active
June 15, 2026 20:15
-
-
Save Eczbek/534991ec341eb048e791ff44c1953a9f to your computer and use it in GitHub Desktop.
type-safe print
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdarg.h> | |
| #include <stddef.h> | |
| #include <stdio.h> | |
| #define DETAIL_EVAL(...) DETAIL_EVAL0(DETAIL_EVAL0(DETAIL_EVAL0(DETAIL_EVAL0(__VA_ARGS__)))) | |
| #define DETAIL_EVAL0(...) DETAIL_EVAL1(DETAIL_EVAL1(DETAIL_EVAL1(DETAIL_EVAL1(__VA_ARGS__)))) | |
| #define DETAIL_EVAL1(...) DETAIL_EVAL2(DETAIL_EVAL2(DETAIL_EVAL2(DETAIL_EVAL2(__VA_ARGS__)))) | |
| #define DETAIL_EVAL2(...) DETAIL_EVAL3(DETAIL_EVAL3(DETAIL_EVAL3(DETAIL_EVAL3(__VA_ARGS__)))) | |
| #define DETAIL_EVAL3(...) __VA_ARGS__ | |
| #define DETAIL_PARENS () | |
| #define DETAIL_EACH(F, ...) __VA_OPT__(DETAIL_EVAL(DETAIL_EACH_(F, __VA_ARGS__))) | |
| #define DETAIL_EACH_(F, X, ...) F(X) __VA_OPT__(DETAIL_EACH_NEXT DETAIL_PARENS (F, __VA_ARGS__)) | |
| #define DETAIL_EACH_NEXT() DETAIL_EACH_ | |
| static inline const char* detail_escape(char c) { | |
| switch (c) { | |
| case '\a': return "\\a"; | |
| case '\b': return "\\b"; | |
| case '\t': return "\\t"; | |
| case '\n': return "\\n"; | |
| case '\v': return "\\v"; | |
| case '\f': return "\\f"; | |
| case '\r': return "\\r"; | |
| case ' ': return " "; | |
| case '!': return "!"; | |
| case '"': return "\\\""; | |
| case '#': return "#"; | |
| case '$': return "$"; | |
| case '%': return "%"; | |
| case '&': return "&"; | |
| case '\'': return "\\'"; | |
| case '(': return "("; | |
| case ')': return ")"; | |
| case '*': return "*"; | |
| case '+': return "+"; | |
| case ',': return ","; | |
| case '-': return "-"; | |
| case '.': return "."; | |
| case '/': return "/"; | |
| case '0': return "0"; | |
| case '1': return "1"; | |
| case '2': return "2"; | |
| case '3': return "3"; | |
| case '4': return "4"; | |
| case '5': return "5"; | |
| case '6': return "6"; | |
| case '7': return "7"; | |
| case '8': return "8"; | |
| case '9': return "9"; | |
| case ':': return ":"; | |
| case ';': return ";"; | |
| case '<': return "<"; | |
| case '=': return "="; | |
| case '>': return ">"; | |
| case '?': return "?"; | |
| case '@': return "@"; | |
| case 'A': return "A"; | |
| case 'B': return "B"; | |
| case 'C': return "C"; | |
| case 'D': return "D"; | |
| case 'E': return "E"; | |
| case 'F': return "F"; | |
| case 'G': return "G"; | |
| case 'H': return "H"; | |
| case 'I': return "I"; | |
| case 'J': return "J"; | |
| case 'K': return "K"; | |
| case 'L': return "L"; | |
| case 'M': return "M"; | |
| case 'N': return "N"; | |
| case 'O': return "O"; | |
| case 'P': return "P"; | |
| case 'Q': return "Q"; | |
| case 'R': return "R"; | |
| case 'S': return "S"; | |
| case 'T': return "T"; | |
| case 'U': return "U"; | |
| case 'V': return "V"; | |
| case 'W': return "W"; | |
| case 'X': return "X"; | |
| case 'Y': return "Y"; | |
| case 'Z': return "Z"; | |
| case '[': return "["; | |
| case '\\': return "\\\\"; | |
| case ']': return "]"; | |
| case '^': return "^"; | |
| case '_': return "_"; | |
| case '`': return "`"; | |
| case 'a': return "a"; | |
| case 'b': return "b"; | |
| case 'c': return "c"; | |
| case 'd': return "d"; | |
| case 'e': return "e"; | |
| case 'f': return "f"; | |
| case 'g': return "g"; | |
| case 'h': return "h"; | |
| case 'i': return "i"; | |
| case 'j': return "j"; | |
| case 'k': return "k"; | |
| case 'l': return "l"; | |
| case 'm': return "m"; | |
| case 'n': return "n"; | |
| case 'o': return "o"; | |
| case 'p': return "p"; | |
| case 'q': return "q"; | |
| case 'r': return "r"; | |
| case 's': return "s"; | |
| case 't': return "t"; | |
| case 'u': return "u"; | |
| case 'v': return "v"; | |
| case 'w': return "w"; | |
| case 'x': return "x"; | |
| case 'y': return "y"; | |
| case 'z': return "z"; | |
| case '{': return "{"; | |
| case '|': return "|"; | |
| case '}': return "}"; | |
| case '~': return "~"; | |
| } | |
| switch (c) { | |
| case '\x0': return "\x0"; | |
| case '\x1': return "\x1"; | |
| case '\x2': return "\x2"; | |
| case '\x3': return "\x3"; | |
| case '\x4': return "\x4"; | |
| case '\x5': return "\x5"; | |
| case '\x6': return "\x6"; | |
| case '\x7': return "\x7"; | |
| case '\x8': return "\x8"; | |
| case '\x9': return "\x9"; | |
| case '\xA': return "\xA"; | |
| case '\xB': return "\xB"; | |
| case '\xC': return "\xC"; | |
| case '\xD': return "\xD"; | |
| case '\xE': return "\xE"; | |
| case '\xF': return "\xF"; | |
| case '\x10': return "\x10"; | |
| case '\x11': return "\x11"; | |
| case '\x12': return "\x12"; | |
| case '\x13': return "\x13"; | |
| case '\x14': return "\x14"; | |
| case '\x15': return "\x15"; | |
| case '\x16': return "\x16"; | |
| case '\x17': return "\x17"; | |
| case '\x18': return "\x18"; | |
| case '\x19': return "\x19"; | |
| case '\x1A': return "\x1A"; | |
| case '\x1B': return "\x1B"; | |
| case '\x1C': return "\x1C"; | |
| case '\x1D': return "\x1D"; | |
| case '\x1E': return "\x1E"; | |
| case '\x1F': return "\x1F"; | |
| case '\x20': return "\x20"; | |
| case '\x21': return "\x21"; | |
| case '\x22': return "\x22"; | |
| case '\x23': return "\x23"; | |
| case '\x24': return "\x24"; | |
| case '\x25': return "\x25"; | |
| case '\x26': return "\x26"; | |
| case '\x27': return "\x27"; | |
| case '\x28': return "\x28"; | |
| case '\x29': return "\x29"; | |
| case '\x2A': return "\x2A"; | |
| case '\x2B': return "\x2B"; | |
| case '\x2C': return "\x2C"; | |
| case '\x2D': return "\x2D"; | |
| case '\x2E': return "\x2E"; | |
| case '\x2F': return "\x2F"; | |
| case '\x30': return "\x30"; | |
| case '\x31': return "\x31"; | |
| case '\x32': return "\x32"; | |
| case '\x33': return "\x33"; | |
| case '\x34': return "\x34"; | |
| case '\x35': return "\x35"; | |
| case '\x36': return "\x36"; | |
| case '\x37': return "\x37"; | |
| case '\x38': return "\x38"; | |
| case '\x39': return "\x39"; | |
| case '\x3A': return "\x3A"; | |
| case '\x3B': return "\x3B"; | |
| case '\x3C': return "\x3C"; | |
| case '\x3D': return "\x3D"; | |
| case '\x3E': return "\x3E"; | |
| case '\x3F': return "\x3F"; | |
| case '\x40': return "\x40"; | |
| case '\x41': return "\x41"; | |
| case '\x42': return "\x42"; | |
| case '\x43': return "\x43"; | |
| case '\x44': return "\x44"; | |
| case '\x45': return "\x45"; | |
| case '\x46': return "\x46"; | |
| case '\x47': return "\x47"; | |
| case '\x48': return "\x48"; | |
| case '\x49': return "\x49"; | |
| case '\x4A': return "\x4A"; | |
| case '\x4B': return "\x4B"; | |
| case '\x4C': return "\x4C"; | |
| case '\x4D': return "\x4D"; | |
| case '\x4E': return "\x4E"; | |
| case '\x4F': return "\x4F"; | |
| case '\x50': return "\x50"; | |
| case '\x51': return "\x51"; | |
| case '\x52': return "\x52"; | |
| case '\x53': return "\x53"; | |
| case '\x54': return "\x54"; | |
| case '\x55': return "\x55"; | |
| case '\x56': return "\x56"; | |
| case '\x57': return "\x57"; | |
| case '\x58': return "\x58"; | |
| case '\x59': return "\x59"; | |
| case '\x5A': return "\x5A"; | |
| case '\x5B': return "\x5B"; | |
| case '\x5C': return "\x5C"; | |
| case '\x5D': return "\x5D"; | |
| case '\x5E': return "\x5E"; | |
| case '\x5F': return "\x5F"; | |
| case '\x60': return "\x60"; | |
| case '\x61': return "\x61"; | |
| case '\x62': return "\x62"; | |
| case '\x63': return "\x63"; | |
| case '\x64': return "\x64"; | |
| case '\x65': return "\x65"; | |
| case '\x66': return "\x66"; | |
| case '\x67': return "\x67"; | |
| case '\x68': return "\x68"; | |
| case '\x69': return "\x69"; | |
| case '\x6A': return "\x6A"; | |
| case '\x6B': return "\x6B"; | |
| case '\x6C': return "\x6C"; | |
| case '\x6D': return "\x6D"; | |
| case '\x6E': return "\x6E"; | |
| case '\x6F': return "\x6F"; | |
| case '\x70': return "\x70"; | |
| case '\x71': return "\x71"; | |
| case '\x72': return "\x72"; | |
| case '\x73': return "\x73"; | |
| case '\x74': return "\x74"; | |
| case '\x75': return "\x75"; | |
| case '\x76': return "\x76"; | |
| case '\x77': return "\x77"; | |
| case '\x78': return "\x78"; | |
| case '\x79': return "\x79"; | |
| case '\x7A': return "\x7A"; | |
| case '\x7B': return "\x7B"; | |
| case '\x7C': return "\x7C"; | |
| case '\x7D': return "\x7D"; | |
| case '\x7E': return "\x7E"; | |
| case '\x7F': return "\x7F"; | |
| case '\x80': return "\x80"; | |
| case '\x81': return "\x81"; | |
| case '\x82': return "\x82"; | |
| case '\x83': return "\x83"; | |
| case '\x84': return "\x84"; | |
| case '\x85': return "\x85"; | |
| case '\x86': return "\x86"; | |
| case '\x87': return "\x87"; | |
| case '\x88': return "\x88"; | |
| case '\x89': return "\x89"; | |
| case '\x8A': return "\x8A"; | |
| case '\x8B': return "\x8B"; | |
| case '\x8C': return "\x8C"; | |
| case '\x8D': return "\x8D"; | |
| case '\x8E': return "\x8E"; | |
| case '\x8F': return "\x8F"; | |
| case '\x90': return "\x90"; | |
| case '\x91': return "\x91"; | |
| case '\x92': return "\x92"; | |
| case '\x93': return "\x93"; | |
| case '\x94': return "\x94"; | |
| case '\x95': return "\x95"; | |
| case '\x96': return "\x96"; | |
| case '\x97': return "\x97"; | |
| case '\x98': return "\x98"; | |
| case '\x99': return "\x99"; | |
| case '\x9A': return "\x9A"; | |
| case '\x9B': return "\x9B"; | |
| case '\x9C': return "\x9C"; | |
| case '\x9D': return "\x9D"; | |
| case '\x9E': return "\x9E"; | |
| case '\x9F': return "\x9F"; | |
| case '\xA0': return "\xA0"; | |
| case '\xA1': return "\xA1"; | |
| case '\xA2': return "\xA2"; | |
| case '\xA3': return "\xA3"; | |
| case '\xA4': return "\xA4"; | |
| case '\xA5': return "\xA5"; | |
| case '\xA6': return "\xA6"; | |
| case '\xA7': return "\xA7"; | |
| case '\xA8': return "\xA8"; | |
| case '\xA9': return "\xA9"; | |
| case '\xAA': return "\xAA"; | |
| case '\xAB': return "\xAB"; | |
| case '\xAC': return "\xAC"; | |
| case '\xAD': return "\xAD"; | |
| case '\xAE': return "\xAE"; | |
| case '\xAF': return "\xAF"; | |
| case '\xB0': return "\xB0"; | |
| case '\xB1': return "\xB1"; | |
| case '\xB2': return "\xB2"; | |
| case '\xB3': return "\xB3"; | |
| case '\xB4': return "\xB4"; | |
| case '\xB5': return "\xB5"; | |
| case '\xB6': return "\xB6"; | |
| case '\xB7': return "\xB7"; | |
| case '\xB8': return "\xB8"; | |
| case '\xB9': return "\xB9"; | |
| case '\xBA': return "\xBA"; | |
| case '\xBB': return "\xBB"; | |
| case '\xBC': return "\xBC"; | |
| case '\xBD': return "\xBD"; | |
| case '\xBE': return "\xBE"; | |
| case '\xBF': return "\xBF"; | |
| case '\xC0': return "\xC0"; | |
| case '\xC1': return "\xC1"; | |
| case '\xC2': return "\xC2"; | |
| case '\xC3': return "\xC3"; | |
| case '\xC4': return "\xC4"; | |
| case '\xC5': return "\xC5"; | |
| case '\xC6': return "\xC6"; | |
| case '\xC7': return "\xC7"; | |
| case '\xC8': return "\xC8"; | |
| case '\xC9': return "\xC9"; | |
| case '\xCA': return "\xCA"; | |
| case '\xCB': return "\xCB"; | |
| case '\xCC': return "\xCC"; | |
| case '\xCD': return "\xCD"; | |
| case '\xCE': return "\xCE"; | |
| case '\xCF': return "\xCF"; | |
| case '\xD0': return "\xD0"; | |
| case '\xD1': return "\xD1"; | |
| case '\xD2': return "\xD2"; | |
| case '\xD3': return "\xD3"; | |
| case '\xD4': return "\xD4"; | |
| case '\xD5': return "\xD5"; | |
| case '\xD6': return "\xD6"; | |
| case '\xD7': return "\xD7"; | |
| case '\xD8': return "\xD8"; | |
| case '\xD9': return "\xD9"; | |
| case '\xDA': return "\xDA"; | |
| case '\xDB': return "\xDB"; | |
| case '\xDC': return "\xDC"; | |
| case '\xDD': return "\xDD"; | |
| case '\xDE': return "\xDE"; | |
| case '\xDF': return "\xDF"; | |
| case '\xE0': return "\xE0"; | |
| case '\xE1': return "\xE1"; | |
| case '\xE2': return "\xE2"; | |
| case '\xE3': return "\xE3"; | |
| case '\xE4': return "\xE4"; | |
| case '\xE5': return "\xE5"; | |
| case '\xE6': return "\xE6"; | |
| case '\xE7': return "\xE7"; | |
| case '\xE8': return "\xE8"; | |
| case '\xE9': return "\xE9"; | |
| case '\xEA': return "\xEA"; | |
| case '\xEB': return "\xEB"; | |
| case '\xEC': return "\xEC"; | |
| case '\xED': return "\xED"; | |
| case '\xEE': return "\xEE"; | |
| case '\xEF': return "\xEF"; | |
| case '\xF0': return "\xF0"; | |
| case '\xF1': return "\xF1"; | |
| case '\xF2': return "\xF2"; | |
| case '\xF3': return "\xF3"; | |
| case '\xF4': return "\xF4"; | |
| case '\xF5': return "\xF5"; | |
| case '\xF6': return "\xF6"; | |
| case '\xF7': return "\xF7"; | |
| case '\xF8': return "\xF8"; | |
| case '\xF9': return "\xF9"; | |
| case '\xFA': return "\xFA"; | |
| case '\xFB': return "\xFB"; | |
| case '\xFC': return "\xFC"; | |
| case '\xFD': return "\xFD"; | |
| case '\xFE': return "\xFE"; | |
| case '\xFF': return "\xFF"; | |
| } | |
| return ""; | |
| } | |
| enum { | |
| detail_type_id_unsigned_char, | |
| detail_type_id_unsigned_short, | |
| detail_type_id_unsigned_int, | |
| detail_type_id_unsigned_long, | |
| detail_type_id_unsigned_long_long, | |
| detail_type_id_signed_char, | |
| detail_type_id_short, | |
| detail_type_id_int, | |
| detail_type_id_long, | |
| detail_type_id_long_long, | |
| detail_type_id_float, | |
| detail_type_id_double, | |
| detail_type_id_long_double, | |
| detail_type_id_char, | |
| detail_type_id_bool, | |
| detail_type_id_string, | |
| detail_type_id_address, | |
| detail_type_id_unknown, | |
| detail_type_id_sentinel | |
| }; | |
| #define detail_type_id(X) \ | |
| _Generic((X), \ | |
| unsigned char: detail_type_id_unsigned_char, \ | |
| unsigned short: detail_type_id_unsigned_short, \ | |
| unsigned int: detail_type_id_unsigned_int, \ | |
| unsigned long: detail_type_id_unsigned_long, \ | |
| unsigned long long: detail_type_id_unsigned_long_long, \ | |
| signed char: detail_type_id_signed_char, \ | |
| short: detail_type_id_short, \ | |
| int: detail_type_id_int, \ | |
| long: detail_type_id_long, \ | |
| long long: detail_type_id_long_long, \ | |
| float: detail_type_id_float, \ | |
| double: detail_type_id_double, \ | |
| long double: detail_type_id_long_double, \ | |
| char: detail_type_id_char, \ | |
| bool: detail_type_id_bool, \ | |
| char*: detail_type_id_string, \ | |
| const char*: detail_type_id_string, \ | |
| void*: detail_type_id_address, \ | |
| const void*: detail_type_id_address, \ | |
| default: detail_type_id_unknown \ | |
| ), (X), | |
| static inline bool meta_print(const char* fmt, ...) { | |
| va_list args; | |
| va_start(args, fmt); | |
| for (size_t i = 0; fmt[i]; ++i) { | |
| if ((!i || (fmt[i - 1] != '\\')) && (fmt[i] == '$')) { | |
| switch (va_arg(args, int)) { | |
| case detail_type_id_unsigned_char: | |
| case detail_type_id_unsigned_short: | |
| case detail_type_id_signed_char: | |
| case detail_type_id_short: | |
| case detail_type_id_int: | |
| case detail_type_id_char: | |
| case detail_type_id_bool: | |
| va_arg(args, int); | |
| continue; | |
| case detail_type_id_unsigned_int: | |
| va_arg(args, unsigned int); | |
| continue; | |
| case detail_type_id_unsigned_long: | |
| va_arg(args, unsigned long); | |
| continue; | |
| case detail_type_id_unsigned_long_long: | |
| va_arg(args, unsigned long long); | |
| continue; | |
| case detail_type_id_long: | |
| va_arg(args, long long); | |
| continue; | |
| case detail_type_id_long_long: | |
| va_arg(args, unsigned long long); | |
| continue; | |
| case detail_type_id_float: | |
| case detail_type_id_double: | |
| va_arg(args, double); | |
| continue; | |
| case detail_type_id_long_double: | |
| va_arg(args, long double); | |
| continue; | |
| case detail_type_id_string: | |
| va_arg(args, const char*); | |
| continue; | |
| case detail_type_id_address: | |
| va_arg(args, void*); | |
| continue; | |
| case detail_type_id_unknown: | |
| fprintf(stderr, "unprintable argument for placeholder at position %zu in format string ", i); | |
| break; | |
| case detail_type_id_sentinel: | |
| fprintf(stderr, "no argument for placeholder at position %zu in format string ", i); | |
| break; | |
| } | |
| fprintf(stderr, "\""); | |
| while (*fmt) { | |
| fputs(detail_escape(*fmt++), stderr); | |
| } | |
| fprintf(stderr, "\"\n"); | |
| va_end(args); | |
| return false; | |
| } | |
| } | |
| va_start(args, fmt); | |
| bool escaped = false; | |
| for (; *fmt; escaped = *fmt++ == '\\') { | |
| if (escaped) { | |
| if ((*fmt != '$') && (*fmt != '\\')) { | |
| putc('\\', stdout); | |
| } | |
| putc(*fmt, stdout); | |
| } else if (*fmt == '$') { | |
| switch (va_arg(args, int)) { | |
| case detail_type_id_unsigned_char: | |
| case detail_type_id_unsigned_short: | |
| fprintf(stdout, "%u", (unsigned int)va_arg(args, int)); | |
| continue; | |
| case detail_type_id_unsigned_int: | |
| fprintf(stdout, "%u", va_arg(args, unsigned int)); | |
| continue; | |
| case detail_type_id_unsigned_long: | |
| fprintf(stdout, "%lu", va_arg(args, unsigned long)); | |
| continue; | |
| case detail_type_id_unsigned_long_long: | |
| fprintf(stdout, "%llu", va_arg(args, unsigned long long)); | |
| continue; | |
| case detail_type_id_signed_char: | |
| case detail_type_id_short: | |
| case detail_type_id_int: | |
| fprintf(stdout, "%i", va_arg(args, int)); | |
| continue; | |
| case detail_type_id_long: | |
| fprintf(stdout, "%li", va_arg(args, long)); | |
| continue; | |
| case detail_type_id_long_long: | |
| fprintf(stdout, "%lli", va_arg(args, long long)); | |
| continue; | |
| case detail_type_id_float: | |
| case detail_type_id_double: | |
| fprintf(stdout, "%lf", va_arg(args, double)); | |
| continue; | |
| case detail_type_id_long_double: | |
| fprintf(stdout, "%Lf", va_arg(args, long double)); | |
| continue; | |
| case detail_type_id_char: | |
| fprintf(stdout, "%c", va_arg(args, int)); | |
| continue; | |
| case detail_type_id_bool: | |
| fprintf(stdout, "%s", va_arg(args, int) ? "true" : "false"); | |
| continue; | |
| case detail_type_id_string: | |
| fprintf(stdout, "%s", va_arg(args, const char*)); | |
| continue; | |
| case detail_type_id_address: | |
| fprintf(stdout, "%p", va_arg(args, void*)); | |
| continue; | |
| } | |
| } else if (*fmt != '\\') { | |
| putc(*fmt, stdout); | |
| } | |
| } | |
| if (escaped) { | |
| putc('\\', stdout); | |
| } | |
| va_end(args); | |
| return true; | |
| } | |
| #define meta_print(FMT, ...) meta_print((FMT), DETAIL_EACH(detail_type_id, __VA_ARGS__) detail_type_id_sentinel) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment