-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Add C11 API with type-safe formatting (#4663) #4671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4987ab3
acdb302
078663b
0caff00
33b4de1
09c75f6
181ef32
3a9c8dc
43ce6be
2e471a0
1335192
79b61ba
f5dd4a7
af06ccf
87a41ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| #ifndef FMT_C_H | ||
| #define FMT_C_H | ||
| #include <stddef.h> | ||
| #ifdef __cplusplus | ||
| # define _Bool bool | ||
| #endif | ||
|
|
||
| void fmt_error_unsupported_type_detected(void); | ||
|
|
||
| enum { fmt_c_max_args = 16 }; | ||
|
|
||
| typedef enum { | ||
| fmt_err_exception = -1, | ||
| fmt_err_memory = -2, | ||
| fmt_err_invalid_arg = -3 | ||
| } fmt_error; | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| typedef enum { | ||
| fmt_int, | ||
| fmt_uint, | ||
| fmt_float, | ||
| fmt_double, | ||
| fmt_long_double, | ||
| fmt_string, | ||
| fmt_ptr, | ||
| fmt_bool, | ||
| fmt_char | ||
| } fmt_type; | ||
|
|
||
| typedef struct { | ||
| fmt_type type; | ||
| union { | ||
| long long i64; | ||
| unsigned long long u64; | ||
| float f32; | ||
| double f64; | ||
| long double f128; | ||
| const char* str; | ||
| const void* ptr; // Used for FMT_PTR and custom data | ||
| _Bool bool_val; | ||
| char char_val; | ||
| } value; | ||
| } fmt_arg; | ||
|
|
||
| int fmt_vformat(char* buffer, size_t capacity, const char* format_str, | ||
| const fmt_arg* args, size_t arg_count); | ||
|
|
||
| static inline fmt_arg fmt_from_int(long long x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_int; | ||
| arg.value.i64 = x; | ||
| return arg; | ||
| } | ||
|
|
||
| static inline fmt_arg fmt_from_uint(unsigned long long x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_uint; | ||
| arg.value.u64 = x; | ||
| return arg; | ||
| } | ||
|
|
||
| static inline fmt_arg fmt_from_float(float x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_float; | ||
| arg.value.f32 = x; | ||
| return arg; | ||
| } | ||
|
|
||
| static inline fmt_arg fmt_from_double(double x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_double; | ||
| arg.value.f64 = x; | ||
| return arg; | ||
| } | ||
|
|
||
| static inline fmt_arg fmt_from_long_double(long double x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_long_double; | ||
| arg.value.f128 = x; | ||
| return arg; | ||
| } | ||
|
|
||
| static inline fmt_arg fmt_from_str(const char* x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_string; | ||
| arg.value.str = x; | ||
| return arg; | ||
| } | ||
|
|
||
| static inline fmt_arg fmt_from_ptr(const void* x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_ptr; | ||
| arg.value.ptr = x; | ||
| return arg; | ||
| } | ||
|
|
||
| static inline fmt_arg fmt_from_bool(_Bool x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_bool; | ||
| arg.value.bool_val = x; | ||
| return arg; | ||
| } | ||
|
|
||
| static inline fmt_arg fmt_from_char(int x) { | ||
| fmt_arg arg; | ||
| arg.type = fmt_char; | ||
| arg.value.char_val = x; | ||
| return arg; | ||
| } | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
|
|
||
| #ifndef __cplusplus | ||
|
|
||
| // Require modern MSVC with conformant preprocessor | ||
| # if defined(_MSC_VER) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) | ||
| # error "C API requires MSVC 2019+ with /Zc:preprocessor flag." | ||
| # endif | ||
|
soumik15630m marked this conversation as resolved.
|
||
|
|
||
| # define FMT_MAKE_ARG(x) \ | ||
| _Generic((x), \ | ||
| _Bool: fmt_from_bool, \ | ||
| char: fmt_from_char, \ | ||
| unsigned char: fmt_from_uint, \ | ||
| short: fmt_from_int, \ | ||
| unsigned short: fmt_from_uint, \ | ||
| int: fmt_from_int, \ | ||
| unsigned int: fmt_from_uint, \ | ||
| long: fmt_from_int, \ | ||
| unsigned long: fmt_from_uint, \ | ||
| long long: fmt_from_int, \ | ||
| unsigned long long: fmt_from_uint, \ | ||
| float: fmt_from_float, \ | ||
| double: fmt_from_double, \ | ||
| long double: fmt_from_long_double, \ | ||
| char*: fmt_from_str, \ | ||
| const char*: fmt_from_str, \ | ||
| void*: fmt_from_ptr, \ | ||
| const void*: fmt_from_ptr, \ | ||
| default: fmt_error_unsupported_type_detected)(x) | ||
|
|
||
| # define FMT_CAT(a, b) FMT_CAT_(a, b) | ||
| # define FMT_CAT_(a, b) a##b | ||
|
|
||
| # define FMT_NARG_(_id, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, \ | ||
| _13, _14, _15, _16, N, ...) \ | ||
| N | ||
| # define FMT_NARG(...) \ | ||
| FMT_NARG_(dummy, ##__VA_ARGS__, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, \ | ||
| 4, 3, 2, 1, 0) | ||
|
|
||
| # define FMT_MAP_0(...) | ||
| # define FMT_MAP_1(f, a) f(a) | ||
| # define FMT_MAP_2(f, a, b) f(a), f(b) | ||
| # define FMT_MAP_3(f, a, b, c) f(a), f(b), f(c) | ||
| # define FMT_MAP_4(f, a, b, c, d) f(a), f(b), f(c), f(d) | ||
| # define FMT_MAP_5(f, a, b, c, d, e) f(a), f(b), f(c), f(d), f(e) | ||
| # define FMT_MAP_6(f, a, b, c, d, e, g) f(a), f(b), f(c), f(d), f(e), f(g) | ||
| # define FMT_MAP_7(f, a, b, c, d, e, g, h) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h) | ||
| # define FMT_MAP_8(f, a, b, c, d, e, g, h, i) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i) | ||
| # define FMT_MAP_9(f, a, b, c, d, e, g, h, i, j) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i), f(j) | ||
| # define FMT_MAP_10(f, a, b, c, d, e, g, h, i, j, k) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i), f(j), f(k) | ||
| # define FMT_MAP_11(f, a, b, c, d, e, g, h, i, j, k, l) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i), f(j), f(k), f(l) | ||
| # define FMT_MAP_12(f, a, b, c, d, e, g, h, i, j, k, l, m) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i), f(j), f(k), f(l), f(m) | ||
| # define FMT_MAP_13(f, a, b, c, d, e, g, h, i, j, k, l, m, n) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i), f(j), f(k), f(l), f(m), f(n) | ||
| # define FMT_MAP_14(f, a, b, c, d, e, g, h, i, j, k, l, m, n, o) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i), f(j), f(k), f(l), f(m), \ | ||
| f(n), f(o) | ||
| # define FMT_MAP_15(f, a, b, c, d, e, g, h, i, j, k, l, m, n, o, p) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i), f(j), f(k), f(l), f(m), \ | ||
| f(n), f(o), f(p) | ||
| # define FMT_MAP_16(f, a, b, c, d, e, g, h, i, j, k, l, m, n, o, p, q) \ | ||
| f(a), f(b), f(c), f(d), f(e), f(g), f(h), f(i), f(j), f(k), f(l), f(m), \ | ||
| f(n), f(o), f(p), f(q) | ||
|
|
||
| # define FMT_MAP(f, ...) \ | ||
| FMT_CAT(FMT_MAP_, FMT_NARG(__VA_ARGS__))(f, ##__VA_ARGS__) | ||
|
|
||
| # define fmt_format(buf, cap, fmt, ...) \ | ||
| fmt_vformat( \ | ||
| buf, cap, fmt, \ | ||
| (fmt_arg[]){{fmt_int}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the extra argument (the The extra argument doesn't appear to be needed on GCC/Clang and (at least according to a quick check with godbolt) appears to cause worse codegen since the extra argument has to be written to the stack as well.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, exactly! It's a workaround for the zero-argument case. When
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At least according to cppreference, empty compound literals (and We'd still need the workaround for MSVC of course. (EDIT: nevermind, zero-size arrays are still an extension, but I think there might be a trick with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found an ugly trick that could remove the need for the extra argument, even for MSVC. It uses the same trick used in # define FMT_VA_SELECT(e, n, ...) \
FMT_NARG_(dummy, ##__VA_ARGS__, n, n, n, n, n, n, n, n, n, n, n, n, n, n, \
n, n, e)
# define fmt_format(buffer, size, fmt, ...) \
fmt_vformat((buffer), (size), (fmt), \
FMT_VA_SELECT( \
NULL, ((fmt_arg[]){FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)}), \
##__VA_ARGS__), \
FMT_NARG(__VA_ARGS__))This expands to fmt_vformat((buffer), (size), (fmt), NULL, 0)if fmt_vformat((buffer), (size), (fmt), ((fmt_arg[]){FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)}), FMT_NARG(__VA_ARGS__))otherwise EDIT: removed the need for |
||
| FMT_NARG(__VA_ARGS__)) | ||
|
|
||
| #endif // __cplusplus | ||
|
|
||
| #endif // FMT_C_H | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| #include "fmt/fmt-c.h" | ||
|
|
||
| #include <fmt/core.h> | ||
|
|
||
| #include <cassert> | ||
|
|
||
| extern "C" { | ||
|
|
||
| using format_arg = fmt::basic_format_arg<fmt::format_context>; | ||
|
|
||
| static bool populate_store(format_arg* out, const fmt_arg* c_args, | ||
| size_t arg_count) { | ||
| if (arg_count > fmt_c_max_args) { | ||
| return false; | ||
| } | ||
|
|
||
| for (size_t i = 0; i < arg_count; ++i) { | ||
| switch (c_args[i].type) { | ||
| case fmt_int: out[i] = c_args[i].value.i64; break; | ||
| case fmt_uint: out[i] = c_args[i].value.u64; break; | ||
| case fmt_float: out[i] = c_args[i].value.f32; break; | ||
| case fmt_double: out[i] = c_args[i].value.f64; break; | ||
| case fmt_long_double: out[i] = c_args[i].value.f128; break; | ||
| case fmt_ptr: out[i] = c_args[i].value.ptr; break; | ||
| case fmt_char: out[i] = c_args[i].value.char_val; break; | ||
| case fmt_bool: out[i] = c_args[i].value.bool_val; break; | ||
| case fmt_string: out[i] = c_args[i].value.str; break; | ||
| default: return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| int fmt_vformat(char* buffer, size_t capacity, const char* format_str, | ||
| const fmt_arg* args, size_t arg_count) { | ||
| assert(format_str); | ||
|
|
||
| format_arg format_args[fmt_c_max_args]; | ||
|
|
||
| if (arg_count > 0) { | ||
| assert(args); | ||
| if (!populate_store(format_args, args, arg_count)) { | ||
| return fmt_err_invalid_arg; | ||
| } | ||
| } | ||
|
|
||
| auto format_args_view = fmt::basic_format_args<fmt::format_context>( | ||
| format_args, static_cast<int>(arg_count)); | ||
|
|
||
| auto result = | ||
| fmt::vformat_to_n(buffer, capacity, format_str, format_args_view); | ||
| return static_cast<int>(result.size); | ||
| } | ||
|
|
||
| } // extern "C" |
Uh oh!
There was an error while loading. Please reload this page.