From 4987ab3c59e04d01db7ea5e4220b0479901861a2 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 8 Feb 2026 01:36:55 +0530 Subject: [PATCH 01/14] Add C API wrapper (c) with tests and CMake integration --- CMakeLists.txt | 65 ++++++++ include/fmt/c.h | 259 ++++++++++++++++++++++++++++++ src/c.cc | 242 ++++++++++++++++++++++++++++ test/test_c.c | 410 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 976 insertions(+) create mode 100644 include/fmt/c.h create mode 100644 src/c.cc create mode 100644 test/test_c.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c0551323f00..d977c6d3e536 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -532,3 +532,68 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) include(CPack) endif () + +# C API Wrapper + +option(FMT_C_API "Build C API wrapper" OFF) + +if(FMT_C_API) + message(STATUS "Building C API wrapper (fmt::fmt_c)") + + enable_language(C) + + add_library(fmt_c STATIC src/c.cc) + + target_compile_features(fmt_c PUBLIC cxx_std_11) + target_compile_definitions(fmt_c PUBLIC FMT_C_STATIC) + target_link_libraries(fmt_c PUBLIC fmt::fmt) + + target_include_directories(fmt_c PUBLIC + $ + $ + ) + + set_target_properties(fmt_c PROPERTIES + VERSION ${FMT_VERSION} + SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} + DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}" + C_VISIBILITY_PRESET default + CXX_VISIBILITY_PRESET hidden + ) + + add_library(fmt::fmt_c ALIAS fmt_c) + if(FMT_INSTALL) + install(TARGETS fmt_c + EXPORT fmt-targets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) + install(FILES include/fmt/c.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/fmt + ) + endif() + + if(FMT_TEST AND EXISTS ${PROJECT_SOURCE_DIR}/test/test_c.c) + message(STATUS "Adding C API test executable") + + add_executable(test-c-api test/test_c.c) + + set_source_files_properties(test/test_c.c PROPERTIES LANGUAGE C) + + target_link_libraries(test-c-api PRIVATE fmt::fmt_c) +#needed for c11(_generic) + if(MSVC) + target_compile_options(test-c-api PRIVATE /std:c11 /Zc:preprocessor) + else() + set_target_properties(test-c-api PROPERTIES + C_STANDARD 11 + C_STANDARD_REQUIRED ON + C_EXTENSIONS OFF + ) + endif() + + add_test(NAME c-api-test COMMAND test-c-api) + endif() + +endif(FMT_C_API) \ No newline at end of file diff --git a/include/fmt/c.h b/include/fmt/c.h new file mode 100644 index 000000000000..58f2288cd25a --- /dev/null +++ b/include/fmt/c.h @@ -0,0 +1,259 @@ +#ifndef FMT_C_API_H +#define FMT_C_API_H + +#include + +#define FMT_C_ABI_VERSION 1 +#define FMT_C_MAX_ARGS 16 + +#define FMT_OK 0 +#define FMT_ERR_NULL_FORMAT -1 +#define FMT_ERR_EXCEPTION -2 +#define FMT_ERR_MEMORY -3 + +#ifdef __cplusplus + #include + #include + extern "C" { +#else + #include + #include +#endif + +#if defined(_WIN32) && !defined(FMT_C_STATIC) + #ifdef FMT_C_EXPORT + #define FMT_C_API __declspec(dllexport) + #else + #define FMT_C_API __declspec(dllimport) + #endif +#else + #define FMT_C_API +#endif + +// Custom formatter callback +// Returns number of bytes written (excluding null terminator), or -1 on error +typedef int (*FmtCustomFn)(char* buf, size_t cap, const void* data); + +typedef enum { + FMT_INT, + FMT_UINT, + FMT_FLOAT, + FMT_DOUBLE, + FMT_LONG_DOUBLE, + FMT_STRING, + FMT_PTR, + FMT_BOOL, + FMT_CHAR, + FMT_CUSTOM +} FmtType; + +typedef struct { + FmtType type; + + // Explicit padding for ABI stability + // - type: 4 bytes (enum) + // - _padding: 4 bytes (explicit alignment) + // - value: 16 bytes (union, sized by long double) + // - custom_fn: 8 bytes (function pointer) + // Ensures consistent struct size across compilers (..* 24 bytes in MSVC) + int32_t _padding; + union { + int64_t i64; + uint64_t u64; + float f32; + double f64; + long double f128; + const char* str; + const void* ptr; // Used for FMT_PTR and custom data + int bool_val; + int char_val; + } value; + + //FMT_CUSTOM type only + FmtCustomFn custom_fn; +} FmtArg; + +FMT_C_API int fmt_c_format(char* buffer, size_t capacity, const char* format_str, + const FmtArg* args, size_t arg_count); +FMT_C_API void fmt_c_print(FILE* f, const char* format_str, + const FmtArg* args, size_t arg_count); + +FMT_C_API const char* fmt_c_get_error(void); + +FMT_C_API int fmt_c_get_version(void); + +static inline FmtArg fmt_from_int(int64_t x) { + FmtArg a; + a.type = FMT_INT; + a._padding = 0; + a.value.i64 = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_uint(uint64_t x) { + FmtArg a; + a.type = FMT_UINT; + a._padding = 0; + a.value.u64 = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_float(float x) { + FmtArg a; + a.type = FMT_FLOAT; + a._padding = 0; + a.value.f32 = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_double(double x) { + FmtArg a; + a.type = FMT_DOUBLE; + a._padding = 0; + a.value.f64 = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_long_double(long double x) { + FmtArg a; + a.type = FMT_LONG_DOUBLE; + a._padding = 0; + a.value.f128 = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_str(const char* x) { + FmtArg a; + a.type = FMT_STRING; + a._padding = 0; + a.value.str = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_ptr(const void* x) { + FmtArg a; + a.type = FMT_PTR; + a._padding = 0; + a.value.ptr = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_bool(bool x) { + FmtArg a; + a.type = FMT_BOOL; + a._padding = 0; + a.value.bool_val = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_char(int x) { + FmtArg a; + a.type = FMT_CHAR; + a._padding = 0; + a.value.char_val = x; + a.custom_fn = NULL; + return a; +} + +static inline FmtArg fmt_from_custom(const void* data, FmtCustomFn func) { + FmtArg a; + a.type = FMT_CUSTOM; + a._padding = 0; + a.value.ptr = data; + a.custom_fn = func; + return a; +} + +static inline FmtArg fmt_identity(FmtArg x) { + return x; +} + +#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. Add /Zc:preprocessor to your compiler flags." +#endif + +#define FMT_MAKE_ARG(x) _Generic((x), \ + FmtArg: fmt_identity, \ + _Bool: fmt_from_bool, \ + char: fmt_from_char, \ + signed char: fmt_from_int, \ + 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_from_ptr \ +)(x) + +#define FMT_MAKE_CUSTOM(data_ptr, func_ptr) \ + fmt_from_custom((const void*)(data_ptr), func_ptr) + +#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_snprintf(buf, cap, fmt, ...) \ + fmt_c_format(buf, cap, fmt, \ + (FmtArg[]){ {FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__) } + 1, \ + FMT_NARG(__VA_ARGS__)) + +#define fmt_fprintf(f, fmt, ...) \ + fmt_c_print(f, fmt, \ + (FmtArg[]){ {FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__) } + 1, \ + FMT_NARG(__VA_ARGS__)) + +#define fmt_printf(fmt, ...) \ + fmt_fprintf(stdout, fmt, ##__VA_ARGS__) + +#endif // !__cplusplus + +#endif // FMT_C_API_H \ No newline at end of file diff --git a/src/c.cc b/src/c.cc new file mode 100644 index 000000000000..c5c0e0650a27 --- /dev/null +++ b/src/c.cc @@ -0,0 +1,242 @@ +#undef FMT_C_EXPORT +#define FMT_C_EXPORT +#include "fmt/c.h" + +#include +#include +#include +#include +#include + +static const size_t MAX_PACKED_ARGS = 16; + +extern "C" { +static thread_local std::string g_last_error; + +const char* fmt_c_get_error(void) { + return g_last_error.empty() ? "" : g_last_error.c_str(); +} + +static void set_error(const char* msg) { + try { + g_last_error = msg; + } catch(...) { + } +} + +static void clear_error() { + g_last_error.clear(); +} + +int fmt_c_get_version(void) { + return FMT_C_ABI_VERSION; +} + +using Context = fmt::format_context; + +// Fixed-size array for type-erased format arguments +static thread_local std::array, MAX_PACKED_ARGS> g_fixed_store; + +static bool populate_store(const FmtArg* c_args, size_t arg_count, + std::vector& custom_buffers) { + + if (arg_count > MAX_PACKED_ARGS) { + set_error("Argument count exceeds maximum (FMT_C_MAX_ARGS)"); + return false; + } + + for (size_t i = 0; i < arg_count; ++i) { + switch (c_args[i].type) { + case FMT_INT: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.i64); + break; + + case FMT_UINT: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.u64); + break; + + case FMT_FLOAT: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f32); + break; + + case FMT_DOUBLE: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f64); + break; + + case FMT_LONG_DOUBLE: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f128); + break; + + case FMT_PTR: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.ptr); + break; + + case FMT_CHAR: + g_fixed_store[i] = fmt::basic_format_arg(static_cast(c_args[i].value.char_val)); + break; + + case FMT_BOOL: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.bool_val != 0); + break; + + case FMT_STRING: { + const char* s = c_args[i].value.str ? c_args[i].value.str : "(null)"; + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view(s)); + break; + } + + case FMT_CUSTOM: { + if (!c_args[i].custom_fn) { + set_error("Custom formatter function is NULL"); + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + return false; + } + + if (!c_args[i].value.ptr) { + set_error("Custom formatter data pointer is NULL"); + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + return false; + } + + try { + std::string buf; + buf.resize(64);// intial bufffer size .... + int len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); + + if (len < 0) { + set_error("Custom formatter returned error code"); + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + return false; + } + if (static_cast(len) >= buf.size()) { + buf.resize(len + 1); + len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); + + if (len < 0) { + set_error("Custom formatter failed on second call"); + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + return false; + } + } + + buf.resize(len); + custom_buffers.push_back(std::move(buf)); + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view(custom_buffers.back())); + + } catch (const std::bad_alloc&) { + set_error("Memory allocation failed in custom formatter"); + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + return false; + } catch (...) { + set_error("Unknown exception in custom formatter"); + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + return false; + } + break; + } + + default: + set_error("Unknown FmtType enum value"); + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + return false; + } + } + + return true; +} +int fmt_c_format(char* buffer, size_t capacity, const char* format_str, + const FmtArg* args, size_t arg_count) { + clear_error(); + if (!format_str) { + set_error("Format string is NULL"); + return FMT_ERR_NULL_FORMAT; + } + + if (arg_count > MAX_PACKED_ARGS) { + set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); + return FMT_ERR_MEMORY; + } + + try { + std::vector custom_buffers; + if (arg_count > 0) { + if (!args) { + set_error("Argument array is NULL but arg_count > 0"); + return FMT_ERR_NULL_FORMAT; + } + if (!populate_store(args, arg_count, custom_buffers)) { + return FMT_ERR_EXCEPTION; + } + } + + auto format_args_view = fmt::basic_format_args( + g_fixed_store.data(), + static_cast(arg_count) + ); + + if (!buffer || capacity == 0) { + char tmp[1]; + auto result = fmt::vformat_to_n(tmp, 0, format_str, format_args_view); + return static_cast(result.size); + } + auto result = fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); + + *result.out = '\0'; + return static_cast(result.size); + + } catch (const fmt::format_error& e) { + set_error(e.what()); + return FMT_ERR_EXCEPTION; + } catch (const std::exception& e) { + set_error(e.what()); + return FMT_ERR_EXCEPTION; + } catch (...) { + set_error("Unknown C++ exception"); + return FMT_ERR_EXCEPTION; + } +} + +void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, size_t arg_count) { + clear_error(); + + if (!f) { + set_error("File stream is NULL"); + return; + } + + if (!format_str) { + set_error("Format string is NULL"); + return; + } + if (arg_count > MAX_PACKED_ARGS) { + set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); + return; + } + + try { + std::vector custom_buffers; + if (arg_count > 0) { + if (!args) { + set_error("Argument array is NULL but arg_count > 0"); + return; + } + if (!populate_store(args, arg_count, custom_buffers)) { + return; + } + } + auto format_args_view = fmt::basic_format_args( + g_fixed_store.data(), + static_cast(arg_count) + ); + fmt::vprint(f, format_str, format_args_view); + + } catch (const fmt::format_error& e) { + set_error(e.what()); + } catch (const std::exception& e) { + set_error(e.what()); + } catch (...) { + set_error("Unknown C++ exception"); + } +} + +} // extern "C" \ No newline at end of file diff --git a/test/test_c.c b/test/test_c.c new file mode 100644 index 000000000000..8c1281833ee8 --- /dev/null +++ b/test/test_c.c @@ -0,0 +1,410 @@ +/* Test suite for fmt C API */ +#include "fmt/c.h" +#include +#include +#include + +#define TEST(name) \ + static void test_##name(void); \ + static void run_test_##name(void) { \ + printf("Running test: %s ... ", #name); \ + test_##name(); \ + printf("PASSED\n"); \ + } \ + static void test_##name(void) + +#define ASSERT_STR_EQ(actual, expected) \ + do { \ + if (strcmp(actual, expected) != 0) { \ + fprintf(stderr, "\nAssertion failed:\n Expected: \"%s\"\n Got: \"%s\"\n", expected, actual); \ + exit(1); \ + } \ + } while(0) + +#define ASSERT_INT_EQ(actual, expected) \ + do { \ + if ((actual) != (expected)) { \ + fprintf(stderr, "\nAssertion failed:\n Expected: %d\n Got: %d\n", expected, actual); \ + exit(1); \ + } \ + } while(0) + +#define ASSERT_TRUE(cond) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "\nAssertion failed: %s\n", #cond); \ + exit(1); \ + } \ + } while(0) + +TEST(basic_integer) { + char buf[100]; + int ret = fmt_snprintf(buf, sizeof(buf), "Number: {}", 42); + ASSERT_STR_EQ(buf, "Number: 42"); + ASSERT_INT_EQ(ret, 10); +} + +TEST(multiple_integers) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); + ASSERT_STR_EQ(buf, "1 + 2 = 3"); +} + +TEST(unsigned_integers) { + char buf[100]; + unsigned int x = 4294967295U; + fmt_snprintf(buf, sizeof(buf), "{}", x); + ASSERT_STR_EQ(buf, "4294967295"); +} + +TEST(floating_point) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "Pi = {}", 3.14159); + ASSERT_TRUE(strncmp(buf, "Pi = 3.14159", 12) == 0); +} + +TEST(float_type) { + char buf[100]; + float f = 1.234f; + fmt_snprintf(buf, sizeof(buf), "Float: {:.3f}", f); + ASSERT_STR_EQ(buf, "Float: 1.234"); +} + +TEST(long_double_type) { + char buf[100]; + long double ld = 12345.6789L; + fmt_snprintf(buf, sizeof(buf), "{:.4f}", ld); + ASSERT_STR_EQ(buf, "12345.6789"); +} + +TEST(mixed_floating_types) { + char buf[200]; + float f = 1.5f; + double d = 2.5; + long double ld = 3.5L; + + fmt_snprintf(buf, sizeof(buf), "{} {} {}", f, d, ld); + ASSERT_STR_EQ(buf, "1.5 2.5 3.5"); +} + +TEST(strings) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "Hello, {}!", "from fmt!"); + ASSERT_STR_EQ(buf, "Hello, from fmt!!"); +} + +TEST(null_string) { + char buf[100]; + const char* null_str = NULL; + fmt_snprintf(buf, sizeof(buf), "{}", null_str); + ASSERT_STR_EQ(buf, "(null)"); +} + +TEST(pointers) { + char buf[100]; + void* ptr = (void*)0x12345678; + fmt_snprintf(buf, sizeof(buf), "{}", ptr); + ASSERT_TRUE(strstr(buf, "12345678") != NULL); +} + +TEST(booleans) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); + ASSERT_STR_EQ(buf, "true false"); +} + +TEST(characters) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "Char: {}", (char)'A'); + ASSERT_STR_EQ(buf, "Char: A"); +} + +TEST(mixed_types) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); + ASSERT_TRUE(strstr(buf, "42") != NULL); + ASSERT_TRUE(strstr(buf, "3.14") != NULL); + ASSERT_TRUE(strstr(buf, "text") != NULL); + ASSERT_TRUE(strstr(buf, "true") != NULL); +} + +TEST(format_zero_padding) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:05d}", 42); + ASSERT_STR_EQ(buf, "00042"); +} + +TEST(format_precision) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:.2f}", 3.14159); + ASSERT_STR_EQ(buf, "3.14"); +} + +TEST(format_hex) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:x}", 255); + ASSERT_STR_EQ(buf, "ff"); +} + +TEST(format_hex_upper) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:X}", 255); + ASSERT_STR_EQ(buf, "FF"); +} + +TEST(positional_arguments) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{1} {0}", "from fmt!", "Hello"); + ASSERT_STR_EQ(buf, "Hello from fmt!"); +} + +TEST(zero_arguments) { + char buf[100]; + // fmt_snprintf(buf, sizeof(buf), "No arguments"); + fmt_c_format(buf, sizeof(buf), "No arguments", NULL, 0); // strict compiler check bypass - either turn on cextension in cmake or this + ASSERT_STR_EQ(buf, "No arguments"); +} + +TEST(buffer_size_query) { + int size = fmt_snprintf(NULL, 0, "Test string: {}", 42); + ASSERT_INT_EQ(size, 15); +} + +TEST(buffer_overflow) { + char buf[10]; + int ret = fmt_snprintf(buf, sizeof(buf), "Very long string: {}", 12345); + ASSERT_INT_EQ(buf[9], '\0'); + ASSERT_TRUE(ret > 9); +} + +static int custom_point_formatter(char* buf, size_t cap, const void* data) { + const int* point = (const int*)data; + if (!buf || cap == 0) { + return snprintf(NULL, 0, "Point(%d, %d)", point[0], point[1]); + } + return snprintf(buf, cap, "Point(%d, %d)", point[0], point[1]); +} + +TEST(custom_formatter) { + char buf[100]; + int point[2] = {10, 20}; + FmtArg args[] = { + FMT_MAKE_CUSTOM(point, custom_point_formatter) + }; + fmt_c_format(buf, sizeof(buf), "Location: {}", args, 1); + ASSERT_STR_EQ(buf, "Location: Point(10, 20)"); +} + +TEST(custom_formatter_null_function) { + char buf[100]; + int data = 42; + FmtArg args[] = { + fmt_from_custom(&data, NULL) + }; + int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "NULL") != NULL); +} + +TEST(custom_formatter_null_data) { + char buf[100]; + FmtArg args[] = { + fmt_from_custom(NULL, custom_point_formatter) + }; + int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "NULL") != NULL); +} + +static int failing_formatter(char* buf, size_t cap, const void* data) { + (void)buf; (void)cap; (void)data; + return -1; +} + +TEST(custom_formatter_error_return) { + char buf[100]; + int data = 42; + FmtArg args[] = { + FMT_MAKE_CUSTOM(&data, failing_formatter) + }; + int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "error") != NULL); +} + +TEST(error_null_format) { + char buf[100]; + int ret = fmt_c_format(buf, sizeof(buf), NULL, NULL, 0); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strlen(err) > 0); +} + +TEST(error_too_many_args) { + char buf[100]; + FmtArg args[20]; // More than MAX_PACKED_ARGS (16) + for (int i = 0; i < 20; i++) { + args[i] = fmt_from_int(i); + } + int ret = fmt_c_format(buf, sizeof(buf), "{}", args, 20); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "maximum") != NULL || strstr(err, "many") != NULL); +} + +// NEW: Test NULL args with non-zero count +TEST(error_null_args_nonzero_count) { + char buf[100]; + int ret = fmt_c_format(buf, sizeof(buf), "{}", NULL, 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strlen(err) > 0); +} + +TEST(error_invalid_format) { + char buf[100]; + int ret = fmt_snprintf(buf, sizeof(buf), "{:invalid}", 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strlen(err) > 0); +} + +TEST(printf_to_stdout) { + printf("\n Output from fmt_printf: "); + fmt_printf("Test {} {} {}", 1, 2.5, "string"); + printf("\n"); +} +TEST(print_null_file) { + FmtArg args[] = { fmt_from_int(42) }; + fmt_c_print(NULL, "{}", args, 1); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "NULL") != NULL); +} + +TEST(print_null_format) { + fmt_c_print(stdout, NULL, NULL, 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "NULL") != NULL); +} + +TEST(long_strings) { + char buf[1000]; + const char* long_str = "This is a very long string that contains a lot of text " + "to test the buffer handling capabilities of the formatter"; + fmt_snprintf(buf, sizeof(buf), "Message: {}", long_str); + ASSERT_TRUE(strstr(buf, long_str) != NULL); +} + +TEST(multiple_calls) { + char buf[100]; + + fmt_snprintf(buf, sizeof(buf), "{} {}", 1, 2); + ASSERT_STR_EQ(buf, "1 2"); + + fmt_snprintf(buf, sizeof(buf), "{} {}", "hello", 3.14); + ASSERT_TRUE(strstr(buf, "hello") != NULL); + + fmt_snprintf(buf, sizeof(buf), "{}", (bool)true); + ASSERT_STR_EQ(buf, "true"); +} + +TEST(escaped_braces) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{{}} {}", 42); + ASSERT_STR_EQ(buf, "{} 42"); +} + +TEST(all_integer_types) { + char buf[200]; + short s = 100; + int i = 200; + long l = 300L; + long long ll = 400LL; + unsigned short us = 500; + unsigned int ui = 600; + unsigned long ul = 700UL; + unsigned long long ull = 800ULL; + + fmt_snprintf(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, ul, ull); + ASSERT_TRUE(strstr(buf, "100") != NULL); + ASSERT_TRUE(strstr(buf, "800") != NULL); +} + +TEST(version_check) { + int version = fmt_c_get_version(); + ASSERT_INT_EQ(version, FMT_C_ABI_VERSION); +} + +TEST(alignment) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:>10}", 42); + ASSERT_STR_EQ(buf, " 42"); +} + +TEST(center_alignment) { + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:^10}", "Hi"); + ASSERT_STR_EQ(buf, " Hi "); +} + +TEST(struct_size_and_alignment) { + // Verify that FmtArg has expected size with explicit padding + // On 64-bit: 4 (type) + 4 (padding) + 16 (union) + 8 (ptr) = 32 bytes .... 24 bytes in MSVC becuz of it's internal optimization + // This may vary on 32-bit or with different compilers + printf("\n FmtArg size: %zu bytes (alignment: %zu)\n", + sizeof(FmtArg), _Alignof(FmtArg)); + + FmtArg arg = fmt_from_int(42); + ASSERT_INT_EQ(arg._padding, 0); +} + +int main(void) { + printf("=== Running fmt C API Tests ===\n\n"); + + run_test_basic_integer(); + run_test_multiple_integers(); + run_test_unsigned_integers(); + run_test_floating_point(); + run_test_float_type(); + run_test_long_double_type(); + run_test_mixed_floating_types(); + run_test_strings(); + run_test_null_string(); + run_test_pointers(); + run_test_booleans(); + run_test_characters(); + run_test_mixed_types(); + run_test_format_zero_padding(); + run_test_format_precision(); + run_test_format_hex(); + run_test_format_hex_upper(); + run_test_positional_arguments(); + run_test_zero_arguments(); + run_test_buffer_size_query(); + run_test_buffer_overflow(); + run_test_custom_formatter(); + run_test_custom_formatter_null_function(); + run_test_custom_formatter_null_data(); + run_test_custom_formatter_error_return(); + run_test_error_null_args_nonzero_count(); + run_test_error_null_format(); + run_test_error_too_many_args(); + run_test_error_invalid_format(); + run_test_printf_to_stdout(); + run_test_print_null_file(); + run_test_print_null_format(); + run_test_long_strings(); + run_test_multiple_calls(); + run_test_escaped_braces(); + run_test_all_integer_types(); + run_test_version_check(); + run_test_alignment(); + run_test_center_alignment(); + run_test_struct_size_and_alignment(); + + printf("\n=== All tests passed! ===\n"); + return 0; +} \ No newline at end of file From acdb302bb050ced30f60aa82040eb5eafbdd3af6 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 8 Feb 2026 01:45:55 +0530 Subject: [PATCH 02/14] minor ..consistency fixed --- src/c.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/c.cc b/src/c.cc index c5c0e0650a27..aacf204a0f87 100644 --- a/src/c.cc +++ b/src/c.cc @@ -8,7 +8,7 @@ #include #include -static const size_t MAX_PACKED_ARGS = 16; +static const size_t MAX_PACKED_ARGS = FMT_C_MAX_ARGS; extern "C" { static thread_local std::string g_last_error; From 078663b718b1b720601a7f2794b7f41c12496c30 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 8 Feb 2026 02:19:05 +0530 Subject: [PATCH 03/14] NFC: formatting fixes --- include/fmt/c.h | 378 +++++++++++++++++---------------- src/c.cc | 391 +++++++++++++++++----------------- test/test_c.c | 547 ++++++++++++++++++++++++------------------------ 3 files changed, 671 insertions(+), 645 deletions(-) diff --git a/include/fmt/c.h b/include/fmt/c.h index 58f2288cd25a..aaf60fa48c8f 100644 --- a/include/fmt/c.h +++ b/include/fmt/c.h @@ -6,28 +6,28 @@ #define FMT_C_ABI_VERSION 1 #define FMT_C_MAX_ARGS 16 -#define FMT_OK 0 +#define FMT_OK 0 #define FMT_ERR_NULL_FORMAT -1 -#define FMT_ERR_EXCEPTION -2 -#define FMT_ERR_MEMORY -3 +#define FMT_ERR_EXCEPTION -2 +#define FMT_ERR_MEMORY -3 #ifdef __cplusplus - #include - #include - extern "C" { +# include +# include +extern "C" { #else - #include - #include +# include +# include #endif #if defined(_WIN32) && !defined(FMT_C_STATIC) - #ifdef FMT_C_EXPORT - #define FMT_C_API __declspec(dllexport) - #else - #define FMT_C_API __declspec(dllimport) - #endif +# ifdef FMT_C_EXPORT +# define FMT_C_API __declspec(dllexport) +# else +# define FMT_C_API __declspec(dllimport) +# endif #else - #define FMT_C_API +# define FMT_C_API #endif // Custom formatter callback @@ -35,146 +35,145 @@ typedef int (*FmtCustomFn)(char* buf, size_t cap, const void* data); typedef enum { - FMT_INT, - FMT_UINT, - FMT_FLOAT, - FMT_DOUBLE, - FMT_LONG_DOUBLE, - FMT_STRING, - FMT_PTR, - FMT_BOOL, - FMT_CHAR, - FMT_CUSTOM + FMT_INT, + FMT_UINT, + FMT_FLOAT, + FMT_DOUBLE, + FMT_LONG_DOUBLE, + FMT_STRING, + FMT_PTR, + FMT_BOOL, + FMT_CHAR, + FMT_CUSTOM } FmtType; typedef struct { - FmtType type; - - // Explicit padding for ABI stability - // - type: 4 bytes (enum) - // - _padding: 4 bytes (explicit alignment) - // - value: 16 bytes (union, sized by long double) - // - custom_fn: 8 bytes (function pointer) - // Ensures consistent struct size across compilers (..* 24 bytes in MSVC) - int32_t _padding; - union { - int64_t i64; - uint64_t u64; - float f32; - double f64; - long double f128; - const char* str; - const void* ptr; // Used for FMT_PTR and custom data - int bool_val; - int char_val; - } value; - - //FMT_CUSTOM type only - FmtCustomFn custom_fn; + FmtType type; + + // Explicit padding for ABI stability + // - type: 4 bytes (enum) + // - _padding: 4 bytes (explicit alignment) + // - value: 16 bytes (union, sized by long double) + // - custom_fn: 8 bytes (function pointer) + // Ensures consistent struct size across compilers (..* 24 bytes in MSVC) + int32_t _padding; + union { + int64_t i64; + uint64_t u64; + float f32; + double f64; + long double f128; + const char* str; + const void* ptr; // Used for FMT_PTR and custom data + int bool_val; + int char_val; + } value; + + // FMT_CUSTOM type only + FmtCustomFn custom_fn; } FmtArg; -FMT_C_API int fmt_c_format(char* buffer, size_t capacity, const char* format_str, - const FmtArg* args, size_t arg_count); -FMT_C_API void fmt_c_print(FILE* f, const char* format_str, - const FmtArg* args, size_t arg_count); +FMT_C_API int fmt_c_format(char* buffer, size_t capacity, + const char* format_str, const FmtArg* args, + size_t arg_count); +FMT_C_API void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, + size_t arg_count); FMT_C_API const char* fmt_c_get_error(void); FMT_C_API int fmt_c_get_version(void); static inline FmtArg fmt_from_int(int64_t x) { - FmtArg a; - a.type = FMT_INT; - a._padding = 0; - a.value.i64 = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_INT; + a._padding = 0; + a.value.i64 = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_uint(uint64_t x) { - FmtArg a; - a.type = FMT_UINT; - a._padding = 0; - a.value.u64 = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_UINT; + a._padding = 0; + a.value.u64 = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_float(float x) { - FmtArg a; - a.type = FMT_FLOAT; - a._padding = 0; - a.value.f32 = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_FLOAT; + a._padding = 0; + a.value.f32 = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_double(double x) { - FmtArg a; - a.type = FMT_DOUBLE; - a._padding = 0; - a.value.f64 = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_DOUBLE; + a._padding = 0; + a.value.f64 = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_long_double(long double x) { - FmtArg a; - a.type = FMT_LONG_DOUBLE; - a._padding = 0; - a.value.f128 = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_LONG_DOUBLE; + a._padding = 0; + a.value.f128 = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_str(const char* x) { - FmtArg a; - a.type = FMT_STRING; - a._padding = 0; - a.value.str = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_STRING; + a._padding = 0; + a.value.str = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_ptr(const void* x) { - FmtArg a; - a.type = FMT_PTR; - a._padding = 0; - a.value.ptr = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_PTR; + a._padding = 0; + a.value.ptr = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_bool(bool x) { - FmtArg a; - a.type = FMT_BOOL; - a._padding = 0; - a.value.bool_val = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_BOOL; + a._padding = 0; + a.value.bool_val = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_char(int x) { - FmtArg a; - a.type = FMT_CHAR; - a._padding = 0; - a.value.char_val = x; - a.custom_fn = NULL; - return a; + FmtArg a; + a.type = FMT_CHAR; + a._padding = 0; + a.value.char_val = x; + a.custom_fn = NULL; + return a; } static inline FmtArg fmt_from_custom(const void* data, FmtCustomFn func) { - FmtArg a; - a.type = FMT_CUSTOM; - a._padding = 0; - a.value.ptr = data; - a.custom_fn = func; - return a; + FmtArg a; + a.type = FMT_CUSTOM; + a._padding = 0; + a.value.ptr = data; + a.custom_fn = func; + return a; } -static inline FmtArg fmt_identity(FmtArg x) { - return x; -} +static inline FmtArg fmt_identity(FmtArg x) { return x; } #ifdef __cplusplus } @@ -183,77 +182,96 @@ static inline FmtArg fmt_identity(FmtArg x) { #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. Add /Zc:preprocessor to your compiler flags." -#endif - -#define FMT_MAKE_ARG(x) _Generic((x), \ - FmtArg: fmt_identity, \ - _Bool: fmt_from_bool, \ - char: fmt_from_char, \ - signed char: fmt_from_int, \ - 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_from_ptr \ -)(x) - -#define FMT_MAKE_CUSTOM(data_ptr, func_ptr) \ +# if defined(_MSC_VER) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) +# error \ + "C API requires MSVC 2019+ with /Zc:preprocessor flag. Add /Zc:preprocessor to your compiler flags." +# endif + +# define FMT_MAKE_ARG(x) \ + _Generic((x), \ + FmtArg: fmt_identity, \ + _Bool: fmt_from_bool, \ + char: fmt_from_char, \ + signed char: fmt_from_int, \ + 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_from_ptr)(x) + +# define FMT_MAKE_CUSTOM(data_ptr, func_ptr) \ fmt_from_custom((const void*)(data_ptr), func_ptr) -#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_snprintf(buf, cap, fmt, ...) \ - fmt_c_format(buf, cap, fmt, \ - (FmtArg[]){ {FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__) } + 1, \ +# 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_snprintf(buf, cap, fmt, ...) \ + fmt_c_format( \ + buf, cap, fmt, \ + (FmtArg[]){{FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ FMT_NARG(__VA_ARGS__)) -#define fmt_fprintf(f, fmt, ...) \ - fmt_c_print(f, fmt, \ - (FmtArg[]){ {FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__) } + 1, \ +# define fmt_fprintf(f, fmt, ...) \ + fmt_c_print( \ + f, fmt, \ + (FmtArg[]){{FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ FMT_NARG(__VA_ARGS__)) -#define fmt_printf(fmt, ...) \ - fmt_fprintf(stdout, fmt, ##__VA_ARGS__) +# define fmt_printf(fmt, ...) fmt_fprintf(stdout, fmt, ##__VA_ARGS__) -#endif // !__cplusplus +#endif // !__cplusplus -#endif // FMT_C_API_H \ No newline at end of file +#endif // FMT_C_API_H \ No newline at end of file diff --git a/src/c.cc b/src/c.cc index aacf204a0f87..048e3bb9e961 100644 --- a/src/c.cc +++ b/src/c.cc @@ -3,10 +3,11 @@ #include "fmt/c.h" #include + +#include #include #include #include -#include static const size_t MAX_PACKED_ARGS = FMT_C_MAX_ARGS; @@ -14,229 +15,233 @@ extern "C" { static thread_local std::string g_last_error; const char* fmt_c_get_error(void) { - return g_last_error.empty() ? "" : g_last_error.c_str(); + return g_last_error.empty() ? "" : g_last_error.c_str(); } static void set_error(const char* msg) { - try { - g_last_error = msg; - } catch(...) { - } + try { + g_last_error = msg; + } catch (...) { + } } -static void clear_error() { - g_last_error.clear(); -} +static void clear_error() { g_last_error.clear(); } -int fmt_c_get_version(void) { - return FMT_C_ABI_VERSION; -} +int fmt_c_get_version(void) { return FMT_C_ABI_VERSION; } using Context = fmt::format_context; // Fixed-size array for type-erased format arguments -static thread_local std::array, MAX_PACKED_ARGS> g_fixed_store; +static thread_local std::array, MAX_PACKED_ARGS> + g_fixed_store; static bool populate_store(const FmtArg* c_args, size_t arg_count, std::vector& custom_buffers) { + if (arg_count > MAX_PACKED_ARGS) { + set_error("Argument count exceeds maximum (FMT_C_MAX_ARGS)"); + return false; + } + + for (size_t i = 0; i < arg_count; ++i) { + switch (c_args[i].type) { + case FMT_INT: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.i64); + break; + + case FMT_UINT: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.u64); + break; + + case FMT_FLOAT: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f32); + break; + + case FMT_DOUBLE: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f64); + break; + + case FMT_LONG_DOUBLE: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f128); + break; + + case FMT_PTR: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.ptr); + break; + + case FMT_CHAR: + g_fixed_store[i] = fmt::basic_format_arg( + static_cast(c_args[i].value.char_val)); + break; + + case FMT_BOOL: + g_fixed_store[i] = + fmt::basic_format_arg(c_args[i].value.bool_val != 0); + break; + + case FMT_STRING: { + const char* s = c_args[i].value.str ? c_args[i].value.str : "(null)"; + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view(s)); + break; + } - if (arg_count > MAX_PACKED_ARGS) { - set_error("Argument count exceeds maximum (FMT_C_MAX_ARGS)"); + case FMT_CUSTOM: { + if (!c_args[i].custom_fn) { + set_error("Custom formatter function is NULL"); + g_fixed_store[i] = + fmt::basic_format_arg(fmt::string_view("")); return false; - } + } - for (size_t i = 0; i < arg_count; ++i) { - switch (c_args[i].type) { - case FMT_INT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.i64); - break; - - case FMT_UINT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.u64); - break; - - case FMT_FLOAT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f32); - break; - - case FMT_DOUBLE: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f64); - break; - - case FMT_LONG_DOUBLE: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f128); - break; - - case FMT_PTR: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.ptr); - break; - - case FMT_CHAR: - g_fixed_store[i] = fmt::basic_format_arg(static_cast(c_args[i].value.char_val)); - break; - - case FMT_BOOL: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.bool_val != 0); - break; - - case FMT_STRING: { - const char* s = c_args[i].value.str ? c_args[i].value.str : "(null)"; - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view(s)); - break; - } - - case FMT_CUSTOM: { - if (!c_args[i].custom_fn) { - set_error("Custom formatter function is NULL"); - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); - return false; - } - - if (!c_args[i].value.ptr) { - set_error("Custom formatter data pointer is NULL"); - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); - return false; - } - - try { - std::string buf; - buf.resize(64);// intial bufffer size .... - int len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); - - if (len < 0) { - set_error("Custom formatter returned error code"); - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); - return false; - } - if (static_cast(len) >= buf.size()) { - buf.resize(len + 1); - len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); - - if (len < 0) { - set_error("Custom formatter failed on second call"); - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); - return false; - } - } - - buf.resize(len); - custom_buffers.push_back(std::move(buf)); - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view(custom_buffers.back())); - - } catch (const std::bad_alloc&) { - set_error("Memory allocation failed in custom formatter"); - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); - return false; - } catch (...) { - set_error("Unknown exception in custom formatter"); - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); - return false; - } - break; - } - - default: - set_error("Unknown FmtType enum value"); - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); - return false; + if (!c_args[i].value.ptr) { + set_error("Custom formatter data pointer is NULL"); + g_fixed_store[i] = + fmt::basic_format_arg(fmt::string_view("")); + return false; + } + + try { + std::string buf; + buf.resize(64); // intial bufffer size .... + int len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); + + if (len < 0) { + set_error("Custom formatter returned error code"); + g_fixed_store[i] = + fmt::basic_format_arg(fmt::string_view("")); + return false; } + if (static_cast(len) >= buf.size()) { + buf.resize(len + 1); + len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); + + if (len < 0) { + set_error("Custom formatter failed on second call"); + g_fixed_store[i] = + fmt::basic_format_arg(fmt::string_view("")); + return false; + } + } + + buf.resize(len); + custom_buffers.push_back(std::move(buf)); + g_fixed_store[i] = fmt::basic_format_arg( + fmt::string_view(custom_buffers.back())); + + } catch (const std::bad_alloc&) { + set_error("Memory allocation failed in custom formatter"); + g_fixed_store[i] = + fmt::basic_format_arg(fmt::string_view("")); + return false; + } catch (...) { + set_error("Unknown exception in custom formatter"); + g_fixed_store[i] = + fmt::basic_format_arg(fmt::string_view("")); + return false; + } + break; } - return true; + default: + set_error("Unknown FmtType enum value"); + g_fixed_store[i] = + fmt::basic_format_arg(fmt::string_view("")); + return false; + } + } + + return true; } int fmt_c_format(char* buffer, size_t capacity, const char* format_str, const FmtArg* args, size_t arg_count) { - clear_error(); - if (!format_str) { - set_error("Format string is NULL"); + clear_error(); + if (!format_str) { + set_error("Format string is NULL"); + return FMT_ERR_NULL_FORMAT; + } + + if (arg_count > MAX_PACKED_ARGS) { + set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); + return FMT_ERR_MEMORY; + } + + try { + std::vector custom_buffers; + if (arg_count > 0) { + if (!args) { + set_error("Argument array is NULL but arg_count > 0"); return FMT_ERR_NULL_FORMAT; - } - - if (arg_count > MAX_PACKED_ARGS) { - set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); - return FMT_ERR_MEMORY; - } - - try { - std::vector custom_buffers; - if (arg_count > 0) { - if (!args) { - set_error("Argument array is NULL but arg_count > 0"); - return FMT_ERR_NULL_FORMAT; - } - if (!populate_store(args, arg_count, custom_buffers)) { - return FMT_ERR_EXCEPTION; - } - } - - auto format_args_view = fmt::basic_format_args( - g_fixed_store.data(), - static_cast(arg_count) - ); - - if (!buffer || capacity == 0) { - char tmp[1]; - auto result = fmt::vformat_to_n(tmp, 0, format_str, format_args_view); - return static_cast(result.size); - } - auto result = fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); - - *result.out = '\0'; - return static_cast(result.size); - - } catch (const fmt::format_error& e) { - set_error(e.what()); - return FMT_ERR_EXCEPTION; - } catch (const std::exception& e) { - set_error(e.what()); - return FMT_ERR_EXCEPTION; - } catch (...) { - set_error("Unknown C++ exception"); + } + if (!populate_store(args, arg_count, custom_buffers)) { return FMT_ERR_EXCEPTION; + } } -} -void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, size_t arg_count) { - clear_error(); + auto format_args_view = fmt::basic_format_args( + g_fixed_store.data(), static_cast(arg_count)); - if (!f) { - set_error("File stream is NULL"); - return; + if (!buffer || capacity == 0) { + char tmp[1]; + auto result = fmt::vformat_to_n(tmp, 0, format_str, format_args_view); + return static_cast(result.size); } + auto result = + fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); + + *result.out = '\0'; + return static_cast(result.size); + + } catch (const fmt::format_error& e) { + set_error(e.what()); + return FMT_ERR_EXCEPTION; + } catch (const std::exception& e) { + set_error(e.what()); + return FMT_ERR_EXCEPTION; + } catch (...) { + set_error("Unknown C++ exception"); + return FMT_ERR_EXCEPTION; + } +} - if (!format_str) { - set_error("Format string is NULL"); +void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, + size_t arg_count) { + clear_error(); + + if (!f) { + set_error("File stream is NULL"); + return; + } + + if (!format_str) { + set_error("Format string is NULL"); + return; + } + if (arg_count > MAX_PACKED_ARGS) { + set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); + return; + } + + try { + std::vector custom_buffers; + if (arg_count > 0) { + if (!args) { + set_error("Argument array is NULL but arg_count > 0"); return; - } - if (arg_count > MAX_PACKED_ARGS) { - set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); + } + if (!populate_store(args, arg_count, custom_buffers)) { return; + } } - - try { - std::vector custom_buffers; - if (arg_count > 0) { - if (!args) { - set_error("Argument array is NULL but arg_count > 0"); - return; - } - if (!populate_store(args, arg_count, custom_buffers)) { - return; - } - } - auto format_args_view = fmt::basic_format_args( - g_fixed_store.data(), - static_cast(arg_count) - ); - fmt::vprint(f, format_str, format_args_view); - - } catch (const fmt::format_error& e) { - set_error(e.what()); - } catch (const std::exception& e) { - set_error(e.what()); - } catch (...) { - set_error("Unknown C++ exception"); - } + auto format_args_view = fmt::basic_format_args( + g_fixed_store.data(), static_cast(arg_count)); + fmt::vprint(f, format_str, format_args_view); + + } catch (const fmt::format_error& e) { + set_error(e.what()); + } catch (const std::exception& e) { + set_error(e.what()); + } catch (...) { + set_error("Unknown C++ exception"); + } } -} // extern "C" \ No newline at end of file +} // extern "C" \ No newline at end of file diff --git a/test/test_c.c b/test/test_c.c index 8c1281833ee8..697026ef596a 100644 --- a/test/test_c.c +++ b/test/test_c.c @@ -1,410 +1,413 @@ /* Test suite for fmt C API */ -#include "fmt/c.h" #include -#include #include +#include -#define TEST(name) \ - static void test_##name(void); \ - static void run_test_##name(void) { \ - printf("Running test: %s ... ", #name); \ - test_##name(); \ - printf("PASSED\n"); \ - } \ - static void test_##name(void) - -#define ASSERT_STR_EQ(actual, expected) \ - do { \ - if (strcmp(actual, expected) != 0) { \ - fprintf(stderr, "\nAssertion failed:\n Expected: \"%s\"\n Got: \"%s\"\n", expected, actual); \ - exit(1); \ - } \ - } while(0) - -#define ASSERT_INT_EQ(actual, expected) \ - do { \ - if ((actual) != (expected)) { \ - fprintf(stderr, "\nAssertion failed:\n Expected: %d\n Got: %d\n", expected, actual); \ - exit(1); \ - } \ - } while(0) - -#define ASSERT_TRUE(cond) \ - do { \ - if (!(cond)) { \ - fprintf(stderr, "\nAssertion failed: %s\n", #cond); \ - exit(1); \ - } \ - } while(0) +#include "fmt/c.h" + +#define TEST(name) \ + static void test_##name(void); \ + static void run_test_##name(void) { \ + printf("Running test: %s ... ", #name); \ + test_##name(); \ + printf("PASSED\n"); \ + } \ + static void test_##name(void) + +#define ASSERT_STR_EQ(actual, expected) \ + do { \ + if (strcmp(actual, expected) != 0) { \ + fprintf(stderr, \ + "\nAssertion failed:\n Expected: \"%s\"\n Got: \"%s\"\n", \ + expected, actual); \ + exit(1); \ + } \ + } while (0) + +#define ASSERT_INT_EQ(actual, expected) \ + do { \ + if ((actual) != (expected)) { \ + fprintf(stderr, "\nAssertion failed:\n Expected: %d\n Got: %d\n", \ + expected, actual); \ + exit(1); \ + } \ + } while (0) + +#define ASSERT_TRUE(cond) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "\nAssertion failed: %s\n", #cond); \ + exit(1); \ + } \ + } while (0) TEST(basic_integer) { - char buf[100]; - int ret = fmt_snprintf(buf, sizeof(buf), "Number: {}", 42); - ASSERT_STR_EQ(buf, "Number: 42"); - ASSERT_INT_EQ(ret, 10); + char buf[100]; + int ret = fmt_snprintf(buf, sizeof(buf), "Number: {}", 42); + ASSERT_STR_EQ(buf, "Number: 42"); + ASSERT_INT_EQ(ret, 10); } TEST(multiple_integers) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); - ASSERT_STR_EQ(buf, "1 + 2 = 3"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); + ASSERT_STR_EQ(buf, "1 + 2 = 3"); } TEST(unsigned_integers) { - char buf[100]; - unsigned int x = 4294967295U; - fmt_snprintf(buf, sizeof(buf), "{}", x); - ASSERT_STR_EQ(buf, "4294967295"); + char buf[100]; + unsigned int x = 4294967295U; + fmt_snprintf(buf, sizeof(buf), "{}", x); + ASSERT_STR_EQ(buf, "4294967295"); } TEST(floating_point) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Pi = {}", 3.14159); - ASSERT_TRUE(strncmp(buf, "Pi = 3.14159", 12) == 0); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "Pi = {}", 3.14159); + ASSERT_TRUE(strncmp(buf, "Pi = 3.14159", 12) == 0); } TEST(float_type) { - char buf[100]; - float f = 1.234f; - fmt_snprintf(buf, sizeof(buf), "Float: {:.3f}", f); - ASSERT_STR_EQ(buf, "Float: 1.234"); + char buf[100]; + float f = 1.234f; + fmt_snprintf(buf, sizeof(buf), "Float: {:.3f}", f); + ASSERT_STR_EQ(buf, "Float: 1.234"); } TEST(long_double_type) { - char buf[100]; - long double ld = 12345.6789L; - fmt_snprintf(buf, sizeof(buf), "{:.4f}", ld); - ASSERT_STR_EQ(buf, "12345.6789"); + char buf[100]; + long double ld = 12345.6789L; + fmt_snprintf(buf, sizeof(buf), "{:.4f}", ld); + ASSERT_STR_EQ(buf, "12345.6789"); } TEST(mixed_floating_types) { - char buf[200]; - float f = 1.5f; - double d = 2.5; - long double ld = 3.5L; + char buf[200]; + float f = 1.5f; + double d = 2.5; + long double ld = 3.5L; - fmt_snprintf(buf, sizeof(buf), "{} {} {}", f, d, ld); - ASSERT_STR_EQ(buf, "1.5 2.5 3.5"); + fmt_snprintf(buf, sizeof(buf), "{} {} {}", f, d, ld); + ASSERT_STR_EQ(buf, "1.5 2.5 3.5"); } TEST(strings) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Hello, {}!", "from fmt!"); - ASSERT_STR_EQ(buf, "Hello, from fmt!!"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "Hello, {}!", "from fmt!"); + ASSERT_STR_EQ(buf, "Hello, from fmt!!"); } TEST(null_string) { - char buf[100]; - const char* null_str = NULL; - fmt_snprintf(buf, sizeof(buf), "{}", null_str); - ASSERT_STR_EQ(buf, "(null)"); + char buf[100]; + const char* null_str = NULL; + fmt_snprintf(buf, sizeof(buf), "{}", null_str); + ASSERT_STR_EQ(buf, "(null)"); } TEST(pointers) { - char buf[100]; - void* ptr = (void*)0x12345678; - fmt_snprintf(buf, sizeof(buf), "{}", ptr); - ASSERT_TRUE(strstr(buf, "12345678") != NULL); + char buf[100]; + void* ptr = (void*)0x12345678; + fmt_snprintf(buf, sizeof(buf), "{}", ptr); + ASSERT_TRUE(strstr(buf, "12345678") != NULL); } TEST(booleans) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); - ASSERT_STR_EQ(buf, "true false"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); + ASSERT_STR_EQ(buf, "true false"); } TEST(characters) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Char: {}", (char)'A'); - ASSERT_STR_EQ(buf, "Char: A"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "Char: {}", (char)'A'); + ASSERT_STR_EQ(buf, "Char: A"); } TEST(mixed_types) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); - ASSERT_TRUE(strstr(buf, "42") != NULL); - ASSERT_TRUE(strstr(buf, "3.14") != NULL); - ASSERT_TRUE(strstr(buf, "text") != NULL); - ASSERT_TRUE(strstr(buf, "true") != NULL); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); + ASSERT_TRUE(strstr(buf, "42") != NULL); + ASSERT_TRUE(strstr(buf, "3.14") != NULL); + ASSERT_TRUE(strstr(buf, "text") != NULL); + ASSERT_TRUE(strstr(buf, "true") != NULL); } TEST(format_zero_padding) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:05d}", 42); - ASSERT_STR_EQ(buf, "00042"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:05d}", 42); + ASSERT_STR_EQ(buf, "00042"); } TEST(format_precision) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:.2f}", 3.14159); - ASSERT_STR_EQ(buf, "3.14"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:.2f}", 3.14159); + ASSERT_STR_EQ(buf, "3.14"); } TEST(format_hex) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:x}", 255); - ASSERT_STR_EQ(buf, "ff"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:x}", 255); + ASSERT_STR_EQ(buf, "ff"); } TEST(format_hex_upper) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:X}", 255); - ASSERT_STR_EQ(buf, "FF"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:X}", 255); + ASSERT_STR_EQ(buf, "FF"); } TEST(positional_arguments) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{1} {0}", "from fmt!", "Hello"); - ASSERT_STR_EQ(buf, "Hello from fmt!"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{1} {0}", "from fmt!", "Hello"); + ASSERT_STR_EQ(buf, "Hello from fmt!"); } TEST(zero_arguments) { - char buf[100]; - // fmt_snprintf(buf, sizeof(buf), "No arguments"); - fmt_c_format(buf, sizeof(buf), "No arguments", NULL, 0); // strict compiler check bypass - either turn on cextension in cmake or this - ASSERT_STR_EQ(buf, "No arguments"); + char buf[100]; + // fmt_snprintf(buf, sizeof(buf), "No arguments"); + fmt_c_format(buf, sizeof(buf), "No arguments", NULL, + 0); // strict compiler check bypass - either turn on cextension + // in cmake or this + ASSERT_STR_EQ(buf, "No arguments"); } TEST(buffer_size_query) { - int size = fmt_snprintf(NULL, 0, "Test string: {}", 42); - ASSERT_INT_EQ(size, 15); + int size = fmt_snprintf(NULL, 0, "Test string: {}", 42); + ASSERT_INT_EQ(size, 15); } TEST(buffer_overflow) { - char buf[10]; - int ret = fmt_snprintf(buf, sizeof(buf), "Very long string: {}", 12345); - ASSERT_INT_EQ(buf[9], '\0'); - ASSERT_TRUE(ret > 9); + char buf[10]; + int ret = fmt_snprintf(buf, sizeof(buf), "Very long string: {}", 12345); + ASSERT_INT_EQ(buf[9], '\0'); + ASSERT_TRUE(ret > 9); } static int custom_point_formatter(char* buf, size_t cap, const void* data) { - const int* point = (const int*)data; - if (!buf || cap == 0) { - return snprintf(NULL, 0, "Point(%d, %d)", point[0], point[1]); - } - return snprintf(buf, cap, "Point(%d, %d)", point[0], point[1]); + const int* point = (const int*)data; + if (!buf || cap == 0) { + return snprintf(NULL, 0, "Point(%d, %d)", point[0], point[1]); + } + return snprintf(buf, cap, "Point(%d, %d)", point[0], point[1]); } TEST(custom_formatter) { - char buf[100]; - int point[2] = {10, 20}; - FmtArg args[] = { - FMT_MAKE_CUSTOM(point, custom_point_formatter) - }; - fmt_c_format(buf, sizeof(buf), "Location: {}", args, 1); - ASSERT_STR_EQ(buf, "Location: Point(10, 20)"); + char buf[100]; + int point[2] = {10, 20}; + FmtArg args[] = {FMT_MAKE_CUSTOM(point, custom_point_formatter)}; + fmt_c_format(buf, sizeof(buf), "Location: {}", args, 1); + ASSERT_STR_EQ(buf, "Location: Point(10, 20)"); } TEST(custom_formatter_null_function) { - char buf[100]; - int data = 42; - FmtArg args[] = { - fmt_from_custom(&data, NULL) - }; - int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); + char buf[100]; + int data = 42; + FmtArg args[] = {fmt_from_custom(&data, NULL)}; + int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "NULL") != NULL); } TEST(custom_formatter_null_data) { - char buf[100]; - FmtArg args[] = { - fmt_from_custom(NULL, custom_point_formatter) - }; - int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); + char buf[100]; + FmtArg args[] = {fmt_from_custom(NULL, custom_point_formatter)}; + int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "NULL") != NULL); } static int failing_formatter(char* buf, size_t cap, const void* data) { - (void)buf; (void)cap; (void)data; - return -1; + (void)buf; + (void)cap; + (void)data; + return -1; } TEST(custom_formatter_error_return) { - char buf[100]; - int data = 42; - FmtArg args[] = { - FMT_MAKE_CUSTOM(&data, failing_formatter) - }; - int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "error") != NULL); + char buf[100]; + int data = 42; + FmtArg args[] = {FMT_MAKE_CUSTOM(&data, failing_formatter)}; + int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "error") != NULL); } TEST(error_null_format) { - char buf[100]; - int ret = fmt_c_format(buf, sizeof(buf), NULL, NULL, 0); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); + char buf[100]; + int ret = fmt_c_format(buf, sizeof(buf), NULL, NULL, 0); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strlen(err) > 0); } TEST(error_too_many_args) { - char buf[100]; - FmtArg args[20]; // More than MAX_PACKED_ARGS (16) - for (int i = 0; i < 20; i++) { - args[i] = fmt_from_int(i); - } - int ret = fmt_c_format(buf, sizeof(buf), "{}", args, 20); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "maximum") != NULL || strstr(err, "many") != NULL); + char buf[100]; + FmtArg args[20]; // More than MAX_PACKED_ARGS (16) + for (int i = 0; i < 20; i++) { + args[i] = fmt_from_int(i); + } + int ret = fmt_c_format(buf, sizeof(buf), "{}", args, 20); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "maximum") != NULL || strstr(err, "many") != NULL); } // NEW: Test NULL args with non-zero count TEST(error_null_args_nonzero_count) { - char buf[100]; - int ret = fmt_c_format(buf, sizeof(buf), "{}", NULL, 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); + char buf[100]; + int ret = fmt_c_format(buf, sizeof(buf), "{}", NULL, 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strlen(err) > 0); } TEST(error_invalid_format) { - char buf[100]; - int ret = fmt_snprintf(buf, sizeof(buf), "{:invalid}", 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); + char buf[100]; + int ret = fmt_snprintf(buf, sizeof(buf), "{:invalid}", 1); + ASSERT_TRUE(ret < 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strlen(err) > 0); } TEST(printf_to_stdout) { - printf("\n Output from fmt_printf: "); - fmt_printf("Test {} {} {}", 1, 2.5, "string"); - printf("\n"); + printf("\n Output from fmt_printf: "); + fmt_printf("Test {} {} {}", 1, 2.5, "string"); + printf("\n"); } TEST(print_null_file) { - FmtArg args[] = { fmt_from_int(42) }; - fmt_c_print(NULL, "{}", args, 1); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); + FmtArg args[] = {fmt_from_int(42)}; + fmt_c_print(NULL, "{}", args, 1); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "NULL") != NULL); } TEST(print_null_format) { - fmt_c_print(stdout, NULL, NULL, 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); + fmt_c_print(stdout, NULL, NULL, 0); + const char* err = fmt_c_get_error(); + ASSERT_TRUE(strstr(err, "NULL") != NULL); } TEST(long_strings) { - char buf[1000]; - const char* long_str = "This is a very long string that contains a lot of text " - "to test the buffer handling capabilities of the formatter"; - fmt_snprintf(buf, sizeof(buf), "Message: {}", long_str); - ASSERT_TRUE(strstr(buf, long_str) != NULL); + char buf[1000]; + const char* long_str = + "This is a very long string that contains a lot of text " + "to test the buffer handling capabilities of the formatter"; + fmt_snprintf(buf, sizeof(buf), "Message: {}", long_str); + ASSERT_TRUE(strstr(buf, long_str) != NULL); } TEST(multiple_calls) { - char buf[100]; + char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {}", 1, 2); - ASSERT_STR_EQ(buf, "1 2"); + fmt_snprintf(buf, sizeof(buf), "{} {}", 1, 2); + ASSERT_STR_EQ(buf, "1 2"); - fmt_snprintf(buf, sizeof(buf), "{} {}", "hello", 3.14); - ASSERT_TRUE(strstr(buf, "hello") != NULL); + fmt_snprintf(buf, sizeof(buf), "{} {}", "hello", 3.14); + ASSERT_TRUE(strstr(buf, "hello") != NULL); - fmt_snprintf(buf, sizeof(buf), "{}", (bool)true); - ASSERT_STR_EQ(buf, "true"); + fmt_snprintf(buf, sizeof(buf), "{}", (bool)true); + ASSERT_STR_EQ(buf, "true"); } TEST(escaped_braces) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{{}} {}", 42); - ASSERT_STR_EQ(buf, "{} 42"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{{}} {}", 42); + ASSERT_STR_EQ(buf, "{} 42"); } TEST(all_integer_types) { - char buf[200]; - short s = 100; - int i = 200; - long l = 300L; - long long ll = 400LL; - unsigned short us = 500; - unsigned int ui = 600; - unsigned long ul = 700UL; - unsigned long long ull = 800ULL; - - fmt_snprintf(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, ul, ull); - ASSERT_TRUE(strstr(buf, "100") != NULL); - ASSERT_TRUE(strstr(buf, "800") != NULL); + char buf[200]; + short s = 100; + int i = 200; + long l = 300L; + long long ll = 400LL; + unsigned short us = 500; + unsigned int ui = 600; + unsigned long ul = 700UL; + unsigned long long ull = 800ULL; + + fmt_snprintf(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, + ul, ull); + ASSERT_TRUE(strstr(buf, "100") != NULL); + ASSERT_TRUE(strstr(buf, "800") != NULL); } TEST(version_check) { - int version = fmt_c_get_version(); - ASSERT_INT_EQ(version, FMT_C_ABI_VERSION); + int version = fmt_c_get_version(); + ASSERT_INT_EQ(version, FMT_C_ABI_VERSION); } TEST(alignment) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:>10}", 42); - ASSERT_STR_EQ(buf, " 42"); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:>10}", 42); + ASSERT_STR_EQ(buf, " 42"); } TEST(center_alignment) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:^10}", "Hi"); - ASSERT_STR_EQ(buf, " Hi "); + char buf[100]; + fmt_snprintf(buf, sizeof(buf), "{:^10}", "Hi"); + ASSERT_STR_EQ(buf, " Hi "); } TEST(struct_size_and_alignment) { - // Verify that FmtArg has expected size with explicit padding - // On 64-bit: 4 (type) + 4 (padding) + 16 (union) + 8 (ptr) = 32 bytes .... 24 bytes in MSVC becuz of it's internal optimization - // This may vary on 32-bit or with different compilers - printf("\n FmtArg size: %zu bytes (alignment: %zu)\n", - sizeof(FmtArg), _Alignof(FmtArg)); + // Verify that FmtArg has expected size with explicit padding + // On 64-bit: 4 (type) + 4 (padding) + 16 (union) + 8 (ptr) = 32 bytes .... 24 + // bytes in MSVC becuz of it's internal optimization This may vary on 32-bit + // or with different compilers + printf("\n FmtArg size: %zu bytes (alignment: %zu)\n", sizeof(FmtArg), + _Alignof(FmtArg)); - FmtArg arg = fmt_from_int(42); - ASSERT_INT_EQ(arg._padding, 0); + FmtArg arg = fmt_from_int(42); + ASSERT_INT_EQ(arg._padding, 0); } int main(void) { - printf("=== Running fmt C API Tests ===\n\n"); - - run_test_basic_integer(); - run_test_multiple_integers(); - run_test_unsigned_integers(); - run_test_floating_point(); - run_test_float_type(); - run_test_long_double_type(); - run_test_mixed_floating_types(); - run_test_strings(); - run_test_null_string(); - run_test_pointers(); - run_test_booleans(); - run_test_characters(); - run_test_mixed_types(); - run_test_format_zero_padding(); - run_test_format_precision(); - run_test_format_hex(); - run_test_format_hex_upper(); - run_test_positional_arguments(); - run_test_zero_arguments(); - run_test_buffer_size_query(); - run_test_buffer_overflow(); - run_test_custom_formatter(); - run_test_custom_formatter_null_function(); - run_test_custom_formatter_null_data(); - run_test_custom_formatter_error_return(); - run_test_error_null_args_nonzero_count(); - run_test_error_null_format(); - run_test_error_too_many_args(); - run_test_error_invalid_format(); - run_test_printf_to_stdout(); - run_test_print_null_file(); - run_test_print_null_format(); - run_test_long_strings(); - run_test_multiple_calls(); - run_test_escaped_braces(); - run_test_all_integer_types(); - run_test_version_check(); - run_test_alignment(); - run_test_center_alignment(); - run_test_struct_size_and_alignment(); - - printf("\n=== All tests passed! ===\n"); - return 0; + printf("=== Running fmt C API Tests ===\n\n"); + + run_test_basic_integer(); + run_test_multiple_integers(); + run_test_unsigned_integers(); + run_test_floating_point(); + run_test_float_type(); + run_test_long_double_type(); + run_test_mixed_floating_types(); + run_test_strings(); + run_test_null_string(); + run_test_pointers(); + run_test_booleans(); + run_test_characters(); + run_test_mixed_types(); + run_test_format_zero_padding(); + run_test_format_precision(); + run_test_format_hex(); + run_test_format_hex_upper(); + run_test_positional_arguments(); + run_test_zero_arguments(); + run_test_buffer_size_query(); + run_test_buffer_overflow(); + run_test_custom_formatter(); + run_test_custom_formatter_null_function(); + run_test_custom_formatter_null_data(); + run_test_custom_formatter_error_return(); + run_test_error_null_args_nonzero_count(); + run_test_error_null_format(); + run_test_error_too_many_args(); + run_test_error_invalid_format(); + run_test_printf_to_stdout(); + run_test_print_null_file(); + run_test_print_null_format(); + run_test_long_strings(); + run_test_multiple_calls(); + run_test_escaped_braces(); + run_test_all_integer_types(); + run_test_version_check(); + run_test_alignment(); + run_test_center_alignment(); + run_test_struct_size_and_alignment(); + + printf("\n=== All tests passed! ===\n"); + return 0; } \ No newline at end of file From 0caff00eefbbc7e41b738ada4b9b697be17c1e24 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 8 Feb 2026 02:42:49 +0530 Subject: [PATCH 04/14] NFC: testing ci build tests --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d977c6d3e536..b0cc64c7d59f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -535,7 +535,7 @@ endif () # C API Wrapper -option(FMT_C_API "Build C API wrapper" OFF) +option(FMT_C_API "Build C API wrapper" ON) if(FMT_C_API) message(STATUS "Building C API wrapper (fmt::fmt_c)") From 33b4de12b75d6a3aca284add3c842d45c46ee37d Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 8 Feb 2026 02:58:56 +0530 Subject: [PATCH 05/14] CI build fixes --- src/c.cc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/c.cc b/src/c.cc index 048e3bb9e961..bdeef2107804 100644 --- a/src/c.cc +++ b/src/c.cc @@ -189,10 +189,6 @@ int fmt_c_format(char* buffer, size_t capacity, const char* format_str, *result.out = '\0'; return static_cast(result.size); - - } catch (const fmt::format_error& e) { - set_error(e.what()); - return FMT_ERR_EXCEPTION; } catch (const std::exception& e) { set_error(e.what()); return FMT_ERR_EXCEPTION; @@ -235,8 +231,6 @@ void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, g_fixed_store.data(), static_cast(arg_count)); fmt::vprint(f, format_str, format_args_view); - } catch (const fmt::format_error& e) { - set_error(e.what()); } catch (const std::exception& e) { set_error(e.what()); } catch (...) { From 09c75f64ad39c5953b22a5f12d1c9995a3d7ae18 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 8 Feb 2026 03:15:54 +0530 Subject: [PATCH 06/14] MSVC compiler ambiguity addressed between 'char' & 'signed char' --- include/fmt/c.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/fmt/c.h b/include/fmt/c.h index aaf60fa48c8f..16ddecc1ffc7 100644 --- a/include/fmt/c.h +++ b/include/fmt/c.h @@ -192,7 +192,6 @@ static inline FmtArg fmt_identity(FmtArg x) { return x; } FmtArg: fmt_identity, \ _Bool: fmt_from_bool, \ char: fmt_from_char, \ - signed char: fmt_from_int, \ unsigned char: fmt_from_uint, \ short: fmt_from_int, \ unsigned short: fmt_from_uint, \ From 181ef323a5b663f6cbf5e81df337913217363a22 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Mon, 9 Feb 2026 00:05:46 +0530 Subject: [PATCH 07/14] api simplification & minor changes --- CMakeLists.txt | 4 +- include/fmt/{c.h => fmt-c.h} | 22 +--- src/c.cc | 241 ----------------------------------- src/fmt-c.cc | 134 +++++++++++++++++++ test/test_c.c | 112 ++++++---------- 5 files changed, 178 insertions(+), 335 deletions(-) rename include/fmt/{c.h => fmt-c.h} (90%) delete mode 100644 src/c.cc create mode 100644 src/fmt-c.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b0cc64c7d59f..1c81eee460d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -542,7 +542,7 @@ if(FMT_C_API) enable_language(C) - add_library(fmt_c STATIC src/c.cc) + add_library(fmt_c STATIC src/fmt-c.cc) target_compile_features(fmt_c PUBLIC cxx_std_11) target_compile_definitions(fmt_c PUBLIC FMT_C_STATIC) @@ -569,7 +569,7 @@ if(FMT_C_API) ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) - install(FILES include/fmt/c.h + install(FILES include/fmt/fmt-c.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/fmt ) endif() diff --git a/include/fmt/c.h b/include/fmt/fmt-c.h similarity index 90% rename from include/fmt/c.h rename to include/fmt/fmt-c.h index 16ddecc1ffc7..50720c5fec38 100644 --- a/include/fmt/c.h +++ b/include/fmt/fmt-c.h @@ -1,6 +1,9 @@ #ifndef FMT_C_API_H #define FMT_C_API_H +#include +#include +#include #include #define FMT_C_ABI_VERSION 1 @@ -10,14 +13,10 @@ #define FMT_ERR_NULL_FORMAT -1 #define FMT_ERR_EXCEPTION -2 #define FMT_ERR_MEMORY -3 +#define FMT_ERR_INVALID_ARG -4 #ifdef __cplusplus -# include -# include extern "C" { -#else -# include -# include #endif #if defined(_WIN32) && !defined(FMT_C_STATIC) @@ -76,10 +75,6 @@ typedef struct { FMT_C_API int fmt_c_format(char* buffer, size_t capacity, const char* format_str, const FmtArg* args, size_t arg_count); -FMT_C_API void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, - size_t arg_count); - -FMT_C_API const char* fmt_c_get_error(void); FMT_C_API int fmt_c_get_version(void); @@ -257,19 +252,12 @@ static inline FmtArg fmt_identity(FmtArg x) { return x; } # define FMT_MAP(f, ...) \ FMT_CAT(FMT_MAP_, FMT_NARG(__VA_ARGS__))(f, ##__VA_ARGS__) -# define fmt_snprintf(buf, cap, fmt, ...) \ +# define fmt_format(buf, cap, fmt, ...) \ fmt_c_format( \ buf, cap, fmt, \ (FmtArg[]){{FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ FMT_NARG(__VA_ARGS__)) -# define fmt_fprintf(f, fmt, ...) \ - fmt_c_print( \ - f, fmt, \ - (FmtArg[]){{FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ - FMT_NARG(__VA_ARGS__)) - -# define fmt_printf(fmt, ...) fmt_fprintf(stdout, fmt, ##__VA_ARGS__) #endif // !__cplusplus diff --git a/src/c.cc b/src/c.cc deleted file mode 100644 index bdeef2107804..000000000000 --- a/src/c.cc +++ /dev/null @@ -1,241 +0,0 @@ -#undef FMT_C_EXPORT -#define FMT_C_EXPORT -#include "fmt/c.h" - -#include - -#include -#include -#include -#include - -static const size_t MAX_PACKED_ARGS = FMT_C_MAX_ARGS; - -extern "C" { -static thread_local std::string g_last_error; - -const char* fmt_c_get_error(void) { - return g_last_error.empty() ? "" : g_last_error.c_str(); -} - -static void set_error(const char* msg) { - try { - g_last_error = msg; - } catch (...) { - } -} - -static void clear_error() { g_last_error.clear(); } - -int fmt_c_get_version(void) { return FMT_C_ABI_VERSION; } - -using Context = fmt::format_context; - -// Fixed-size array for type-erased format arguments -static thread_local std::array, MAX_PACKED_ARGS> - g_fixed_store; - -static bool populate_store(const FmtArg* c_args, size_t arg_count, - std::vector& custom_buffers) { - if (arg_count > MAX_PACKED_ARGS) { - set_error("Argument count exceeds maximum (FMT_C_MAX_ARGS)"); - return false; - } - - for (size_t i = 0; i < arg_count; ++i) { - switch (c_args[i].type) { - case FMT_INT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.i64); - break; - - case FMT_UINT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.u64); - break; - - case FMT_FLOAT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f32); - break; - - case FMT_DOUBLE: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f64); - break; - - case FMT_LONG_DOUBLE: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f128); - break; - - case FMT_PTR: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.ptr); - break; - - case FMT_CHAR: - g_fixed_store[i] = fmt::basic_format_arg( - static_cast(c_args[i].value.char_val)); - break; - - case FMT_BOOL: - g_fixed_store[i] = - fmt::basic_format_arg(c_args[i].value.bool_val != 0); - break; - - case FMT_STRING: { - const char* s = c_args[i].value.str ? c_args[i].value.str : "(null)"; - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view(s)); - break; - } - - case FMT_CUSTOM: { - if (!c_args[i].custom_fn) { - set_error("Custom formatter function is NULL"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - - if (!c_args[i].value.ptr) { - set_error("Custom formatter data pointer is NULL"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - - try { - std::string buf; - buf.resize(64); // intial bufffer size .... - int len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); - - if (len < 0) { - set_error("Custom formatter returned error code"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - if (static_cast(len) >= buf.size()) { - buf.resize(len + 1); - len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); - - if (len < 0) { - set_error("Custom formatter failed on second call"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - } - - buf.resize(len); - custom_buffers.push_back(std::move(buf)); - g_fixed_store[i] = fmt::basic_format_arg( - fmt::string_view(custom_buffers.back())); - - } catch (const std::bad_alloc&) { - set_error("Memory allocation failed in custom formatter"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } catch (...) { - set_error("Unknown exception in custom formatter"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - break; - } - - default: - set_error("Unknown FmtType enum value"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - } - - return true; -} -int fmt_c_format(char* buffer, size_t capacity, const char* format_str, - const FmtArg* args, size_t arg_count) { - clear_error(); - if (!format_str) { - set_error("Format string is NULL"); - return FMT_ERR_NULL_FORMAT; - } - - if (arg_count > MAX_PACKED_ARGS) { - set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); - return FMT_ERR_MEMORY; - } - - try { - std::vector custom_buffers; - if (arg_count > 0) { - if (!args) { - set_error("Argument array is NULL but arg_count > 0"); - return FMT_ERR_NULL_FORMAT; - } - if (!populate_store(args, arg_count, custom_buffers)) { - return FMT_ERR_EXCEPTION; - } - } - - auto format_args_view = fmt::basic_format_args( - g_fixed_store.data(), static_cast(arg_count)); - - if (!buffer || capacity == 0) { - char tmp[1]; - auto result = fmt::vformat_to_n(tmp, 0, format_str, format_args_view); - return static_cast(result.size); - } - auto result = - fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); - - *result.out = '\0'; - return static_cast(result.size); - } catch (const std::exception& e) { - set_error(e.what()); - return FMT_ERR_EXCEPTION; - } catch (...) { - set_error("Unknown C++ exception"); - return FMT_ERR_EXCEPTION; - } -} - -void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, - size_t arg_count) { - clear_error(); - - if (!f) { - set_error("File stream is NULL"); - return; - } - - if (!format_str) { - set_error("Format string is NULL"); - return; - } - if (arg_count > MAX_PACKED_ARGS) { - set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); - return; - } - - try { - std::vector custom_buffers; - if (arg_count > 0) { - if (!args) { - set_error("Argument array is NULL but arg_count > 0"); - return; - } - if (!populate_store(args, arg_count, custom_buffers)) { - return; - } - } - auto format_args_view = fmt::basic_format_args( - g_fixed_store.data(), static_cast(arg_count)); - fmt::vprint(f, format_str, format_args_view); - - } catch (const std::exception& e) { - set_error(e.what()); - } catch (...) { - set_error("Unknown C++ exception"); - } -} - -} // extern "C" \ No newline at end of file diff --git a/src/fmt-c.cc b/src/fmt-c.cc new file mode 100644 index 000000000000..4b7dd0315d99 --- /dev/null +++ b/src/fmt-c.cc @@ -0,0 +1,134 @@ +#undef FMT_C_EXPORT +#define FMT_C_EXPORT +#include "fmt/fmt-c.h" + +#include + +#include +#include +#include +#include + + +extern "C" { + +int fmt_c_get_version(void) { return FMT_C_ABI_VERSION; } + +using Context = fmt::format_context; + +// Fixed-size array for type-erased format arguments +static thread_local std::array, FMT_C_MAX_ARGS> + g_fixed_store; + +static bool populate_store(const FmtArg* c_args, size_t arg_count, + std::vector& custom_buffers) { + 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: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.i64); + break; + case FMT_UINT: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.u64); + break; + case FMT_FLOAT: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f32); + break; + case FMT_DOUBLE: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f64); + break; + case FMT_LONG_DOUBLE: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f128); + break; + case FMT_PTR: + g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.ptr); + break; + case FMT_CHAR: + g_fixed_store[i] = fmt::basic_format_arg( + static_cast(c_args[i].value.char_val)); + break; + case FMT_BOOL: + g_fixed_store[i] = + fmt::basic_format_arg(c_args[i].value.bool_val != 0); + break; + case FMT_STRING: { + const char* s = c_args[i].value.str ? c_args[i].value.str : "(null)"; + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view(s)); + break; + } + + case FMT_CUSTOM: { + if (!c_args[i].custom_fn || !c_args[i].value.ptr) { + g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + return false; + } + + try { + std::string buf; + buf.resize(64); + int len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); + + if (len < 0) return false; + + if (static_cast(len) >= buf.size()) { + buf.resize(len + 1); + len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); + if (len < 0) return false; + } + + buf.resize(len); + custom_buffers.push_back(std::move(buf)); + g_fixed_store[i] = fmt::basic_format_arg( + fmt::string_view(custom_buffers.back())); + + } catch (...) { + return false; + } + break; + } + + default: + return false; + } + } + return true; +} +int fmt_c_format(char* buffer, size_t capacity, const char* format_str, + const FmtArg* args, size_t arg_count) { + if (!format_str) return FMT_ERR_NULL_FORMAT; + if (arg_count > FMT_C_MAX_ARGS) return FMT_ERR_INVALID_ARG; + + try { + std::vector custom_buffers; + if (arg_count > 0) { + if (!args) return FMT_ERR_INVALID_ARG; + if (!populate_store(args, arg_count, custom_buffers)) { + return FMT_ERR_EXCEPTION; + } + } + + auto format_args_view = fmt::basic_format_args( + g_fixed_store.data(), static_cast(arg_count)); + + if (!buffer || capacity == 0) { + char tmp[1]; + auto result = fmt::vformat_to_n(tmp, 0, format_str, format_args_view); + return static_cast(result.size); + } + + auto result = fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); + *result.out = '\0'; + return static_cast(result.size); + + } catch (const std::bad_alloc&) { + return FMT_ERR_MEMORY; + } catch (...) { + return FMT_ERR_EXCEPTION; + } +} + + +} // extern "C" \ No newline at end of file diff --git a/test/test_c.c b/test/test_c.c index 697026ef596a..51f8fd32a74d 100644 --- a/test/test_c.c +++ b/test/test_c.c @@ -3,7 +3,7 @@ #include #include -#include "fmt/c.h" +#include "fmt/fmt-c.h" #define TEST(name) \ static void test_##name(void); \ @@ -43,41 +43,41 @@ TEST(basic_integer) { char buf[100]; - int ret = fmt_snprintf(buf, sizeof(buf), "Number: {}", 42); + int ret = fmt_format(buf, sizeof(buf), "Number: {}", 42); ASSERT_STR_EQ(buf, "Number: 42"); ASSERT_INT_EQ(ret, 10); } TEST(multiple_integers) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); + fmt_format(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); ASSERT_STR_EQ(buf, "1 + 2 = 3"); } TEST(unsigned_integers) { char buf[100]; unsigned int x = 4294967295U; - fmt_snprintf(buf, sizeof(buf), "{}", x); + fmt_format(buf, sizeof(buf), "{}", x); ASSERT_STR_EQ(buf, "4294967295"); } TEST(floating_point) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Pi = {}", 3.14159); + fmt_format(buf, sizeof(buf), "Pi = {}", 3.14159); ASSERT_TRUE(strncmp(buf, "Pi = 3.14159", 12) == 0); } TEST(float_type) { char buf[100]; float f = 1.234f; - fmt_snprintf(buf, sizeof(buf), "Float: {:.3f}", f); + fmt_format(buf, sizeof(buf), "Float: {:.3f}", f); ASSERT_STR_EQ(buf, "Float: 1.234"); } TEST(long_double_type) { char buf[100]; long double ld = 12345.6789L; - fmt_snprintf(buf, sizeof(buf), "{:.4f}", ld); + fmt_format(buf, sizeof(buf), "{:.4f}", ld); ASSERT_STR_EQ(buf, "12345.6789"); } @@ -87,45 +87,45 @@ TEST(mixed_floating_types) { double d = 2.5; long double ld = 3.5L; - fmt_snprintf(buf, sizeof(buf), "{} {} {}", f, d, ld); + fmt_format(buf, sizeof(buf), "{} {} {}", f, d, ld); ASSERT_STR_EQ(buf, "1.5 2.5 3.5"); } TEST(strings) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Hello, {}!", "from fmt!"); + fmt_format(buf, sizeof(buf), "Hello, {}!", "from fmt!"); ASSERT_STR_EQ(buf, "Hello, from fmt!!"); } TEST(null_string) { char buf[100]; const char* null_str = NULL; - fmt_snprintf(buf, sizeof(buf), "{}", null_str); + fmt_format(buf, sizeof(buf), "{}", null_str); ASSERT_STR_EQ(buf, "(null)"); } TEST(pointers) { char buf[100]; void* ptr = (void*)0x12345678; - fmt_snprintf(buf, sizeof(buf), "{}", ptr); + fmt_format(buf, sizeof(buf), "{}", ptr); ASSERT_TRUE(strstr(buf, "12345678") != NULL); } TEST(booleans) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); + fmt_format(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); ASSERT_STR_EQ(buf, "true false"); } TEST(characters) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Char: {}", (char)'A'); + fmt_format(buf, sizeof(buf), "Char: {}", (char)'A'); ASSERT_STR_EQ(buf, "Char: A"); } TEST(mixed_types) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); + fmt_format(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); ASSERT_TRUE(strstr(buf, "42") != NULL); ASSERT_TRUE(strstr(buf, "3.14") != NULL); ASSERT_TRUE(strstr(buf, "text") != NULL); @@ -134,51 +134,50 @@ TEST(mixed_types) { TEST(format_zero_padding) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:05d}", 42); + fmt_format(buf, sizeof(buf), "{:05d}", 42); ASSERT_STR_EQ(buf, "00042"); } TEST(format_precision) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:.2f}", 3.14159); + fmt_format(buf, sizeof(buf), "{:.2f}", 3.14159); ASSERT_STR_EQ(buf, "3.14"); } TEST(format_hex) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:x}", 255); + fmt_format(buf, sizeof(buf), "{:x}", 255); ASSERT_STR_EQ(buf, "ff"); } TEST(format_hex_upper) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:X}", 255); + fmt_format(buf, sizeof(buf), "{:X}", 255); ASSERT_STR_EQ(buf, "FF"); } TEST(positional_arguments) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{1} {0}", "from fmt!", "Hello"); + fmt_format(buf, sizeof(buf), "{1} {0}", "from fmt!", "Hello"); ASSERT_STR_EQ(buf, "Hello from fmt!"); } TEST(zero_arguments) { char buf[100]; - // fmt_snprintf(buf, sizeof(buf), "No arguments"); + // fmt_format(buf, sizeof(buf), "No arguments"); fmt_c_format(buf, sizeof(buf), "No arguments", NULL, - 0); // strict compiler check bypass - either turn on cextension - // in cmake or this + 0); // strict compiler check bypass ASSERT_STR_EQ(buf, "No arguments"); } TEST(buffer_size_query) { - int size = fmt_snprintf(NULL, 0, "Test string: {}", 42); + int size = fmt_format(NULL, 0, "Test string: {}", 42); ASSERT_INT_EQ(size, 15); } TEST(buffer_overflow) { char buf[10]; - int ret = fmt_snprintf(buf, sizeof(buf), "Very long string: {}", 12345); + int ret = fmt_format(buf, sizeof(buf), "Very long string: {}", 12345); ASSERT_INT_EQ(buf[9], '\0'); ASSERT_TRUE(ret > 9); } @@ -205,8 +204,7 @@ TEST(custom_formatter_null_function) { FmtArg args[] = {fmt_from_custom(&data, NULL)}; int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); + // ASSERT_INT_EQ(ret, FMT_ERR_EXCEPTION); // Optional specific check } TEST(custom_formatter_null_data) { @@ -214,8 +212,6 @@ TEST(custom_formatter_null_data) { FmtArg args[] = {fmt_from_custom(NULL, custom_point_formatter)}; int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); } static int failing_formatter(char* buf, size_t cap, const void* data) { @@ -231,16 +227,12 @@ TEST(custom_formatter_error_return) { FmtArg args[] = {FMT_MAKE_CUSTOM(&data, failing_formatter)}; int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "error") != NULL); } TEST(error_null_format) { char buf[100]; int ret = fmt_c_format(buf, sizeof(buf), NULL, NULL, 0); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); + ASSERT_INT_EQ(ret, FMT_ERR_NULL_FORMAT); } TEST(error_too_many_args) { @@ -250,44 +242,19 @@ TEST(error_too_many_args) { args[i] = fmt_from_int(i); } int ret = fmt_c_format(buf, sizeof(buf), "{}", args, 20); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "maximum") != NULL || strstr(err, "many") != NULL); + ASSERT_INT_EQ(ret, FMT_ERR_INVALID_ARG); } -// NEW: Test NULL args with non-zero count TEST(error_null_args_nonzero_count) { char buf[100]; int ret = fmt_c_format(buf, sizeof(buf), "{}", NULL, 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); + ASSERT_INT_EQ(ret, FMT_ERR_INVALID_ARG); } TEST(error_invalid_format) { char buf[100]; - int ret = fmt_snprintf(buf, sizeof(buf), "{:invalid}", 1); + int ret = fmt_format(buf, sizeof(buf), "{:invalid}", 1); ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); -} - -TEST(printf_to_stdout) { - printf("\n Output from fmt_printf: "); - fmt_printf("Test {} {} {}", 1, 2.5, "string"); - printf("\n"); -} -TEST(print_null_file) { - FmtArg args[] = {fmt_from_int(42)}; - fmt_c_print(NULL, "{}", args, 1); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); -} - -TEST(print_null_format) { - fmt_c_print(stdout, NULL, NULL, 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); } TEST(long_strings) { @@ -295,26 +262,26 @@ TEST(long_strings) { const char* long_str = "This is a very long string that contains a lot of text " "to test the buffer handling capabilities of the formatter"; - fmt_snprintf(buf, sizeof(buf), "Message: {}", long_str); + fmt_format(buf, sizeof(buf), "Message: {}", long_str); ASSERT_TRUE(strstr(buf, long_str) != NULL); } TEST(multiple_calls) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {}", 1, 2); + fmt_format(buf, sizeof(buf), "{} {}", 1, 2); ASSERT_STR_EQ(buf, "1 2"); - fmt_snprintf(buf, sizeof(buf), "{} {}", "hello", 3.14); + fmt_format(buf, sizeof(buf), "{} {}", "hello", 3.14); ASSERT_TRUE(strstr(buf, "hello") != NULL); - fmt_snprintf(buf, sizeof(buf), "{}", (bool)true); + fmt_format(buf, sizeof(buf), "{}", (bool)true); ASSERT_STR_EQ(buf, "true"); } TEST(escaped_braces) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{{}} {}", 42); + fmt_format(buf, sizeof(buf), "{{}} {}", 42); ASSERT_STR_EQ(buf, "{} 42"); } @@ -329,7 +296,7 @@ TEST(all_integer_types) { unsigned long ul = 700UL; unsigned long long ull = 800ULL; - fmt_snprintf(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, + fmt_format(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, ul, ull); ASSERT_TRUE(strstr(buf, "100") != NULL); ASSERT_TRUE(strstr(buf, "800") != NULL); @@ -342,21 +309,19 @@ TEST(version_check) { TEST(alignment) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:>10}", 42); + fmt_format(buf, sizeof(buf), "{:>10}", 42); ASSERT_STR_EQ(buf, " 42"); } TEST(center_alignment) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:^10}", "Hi"); + fmt_format(buf, sizeof(buf), "{:^10}", "Hi"); ASSERT_STR_EQ(buf, " Hi "); } TEST(struct_size_and_alignment) { // Verify that FmtArg has expected size with explicit padding - // On 64-bit: 4 (type) + 4 (padding) + 16 (union) + 8 (ptr) = 32 bytes .... 24 - // bytes in MSVC becuz of it's internal optimization This may vary on 32-bit - // or with different compilers + // On 64-bit: 4 (type) + 4 (padding) + 16 (union) + 8 (ptr) = 32 bytes .... printf("\n FmtArg size: %zu bytes (alignment: %zu)\n", sizeof(FmtArg), _Alignof(FmtArg)); @@ -396,9 +361,6 @@ int main(void) { run_test_error_null_format(); run_test_error_too_many_args(); run_test_error_invalid_format(); - run_test_printf_to_stdout(); - run_test_print_null_file(); - run_test_print_null_format(); run_test_long_strings(); run_test_multiple_calls(); run_test_escaped_braces(); From 3a9c8dcdff2d56b8ac663703f2027d8521e6cf4b Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Mon, 9 Feb 2026 00:07:31 +0530 Subject: [PATCH 08/14] NFC: linting fixes --- include/fmt/fmt-c.h | 5 ++--- src/fmt-c.cc | 13 ++++++------- test/test_c.c | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index 50720c5fec38..a3ca0fcf971c 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -1,9 +1,9 @@ #ifndef FMT_C_API_H #define FMT_C_API_H +#include #include #include -#include #include #define FMT_C_ABI_VERSION 1 @@ -252,13 +252,12 @@ static inline FmtArg fmt_identity(FmtArg x) { return x; } # define FMT_MAP(f, ...) \ FMT_CAT(FMT_MAP_, FMT_NARG(__VA_ARGS__))(f, ##__VA_ARGS__) -# define fmt_format(buf, cap, fmt, ...) \ +# define fmt_format(buf, cap, fmt, ...) \ fmt_c_format( \ buf, cap, fmt, \ (FmtArg[]){{FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ FMT_NARG(__VA_ARGS__)) - #endif // !__cplusplus #endif // FMT_C_API_H \ No newline at end of file diff --git a/src/fmt-c.cc b/src/fmt-c.cc index 4b7dd0315d99..87c6a156b6dc 100644 --- a/src/fmt-c.cc +++ b/src/fmt-c.cc @@ -9,7 +9,6 @@ #include #include - extern "C" { int fmt_c_get_version(void) { return FMT_C_ABI_VERSION; } @@ -62,7 +61,8 @@ static bool populate_store(const FmtArg* c_args, size_t arg_count, case FMT_CUSTOM: { if (!c_args[i].custom_fn || !c_args[i].value.ptr) { - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view("")); + g_fixed_store[i] = + fmt::basic_format_arg(fmt::string_view("")); return false; } @@ -90,14 +90,13 @@ static bool populate_store(const FmtArg* c_args, size_t arg_count, break; } - default: - return false; + default: return false; } } return true; } int fmt_c_format(char* buffer, size_t capacity, const char* format_str, - const FmtArg* args, size_t arg_count) { + const FmtArg* args, size_t arg_count) { if (!format_str) return FMT_ERR_NULL_FORMAT; if (arg_count > FMT_C_MAX_ARGS) return FMT_ERR_INVALID_ARG; @@ -119,7 +118,8 @@ int fmt_c_format(char* buffer, size_t capacity, const char* format_str, return static_cast(result.size); } - auto result = fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); + auto result = + fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); *result.out = '\0'; return static_cast(result.size); @@ -130,5 +130,4 @@ int fmt_c_format(char* buffer, size_t capacity, const char* format_str, } } - } // extern "C" \ No newline at end of file diff --git a/test/test_c.c b/test/test_c.c index 51f8fd32a74d..726206c19f91 100644 --- a/test/test_c.c +++ b/test/test_c.c @@ -297,7 +297,7 @@ TEST(all_integer_types) { unsigned long long ull = 800ULL; fmt_format(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, - ul, ull); + ul, ull); ASSERT_TRUE(strstr(buf, "100") != NULL); ASSERT_TRUE(strstr(buf, "800") != NULL); } From 43ce6be47b8b0f76c9439d9f33fcdb8067786cca Mon Sep 17 00:00:00 2001 From: Soumik Date: Sun, 15 Feb 2026 04:55:02 +0530 Subject: [PATCH 09/14] Refactor C API: Support C11, clean build system, fix null-termination --- CMakeLists.txt | 100 ++++++------- include/fmt/c.h | 277 ------------------------------------ include/fmt/fmt-c.h | 200 ++++++++++++++++++++++++++ src/c.cc | 247 -------------------------------- src/fmt-c.cc | 92 ++++++++++++ test/test_c.c | 334 +++++++++----------------------------------- 6 files changed, 399 insertions(+), 851 deletions(-) delete mode 100644 include/fmt/c.h create mode 100644 include/fmt/fmt-c.h delete mode 100644 src/c.cc create mode 100644 src/fmt-c.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index d977c6d3e536..99a0ff7420a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -534,66 +534,52 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) endif () # C API Wrapper - -option(FMT_C_API "Build C API wrapper" OFF) - -if(FMT_C_API) - message(STATUS "Building C API wrapper (fmt::fmt_c)") - +add_library(fmt_c STATIC src/fmt-c.cc) + +target_compile_features(fmt_c PUBLIC cxx_std_11) +target_compile_definitions(fmt_c PUBLIC FMT_C_STATIC) +target_link_libraries(fmt_c PUBLIC fmt::fmt) + +target_include_directories(fmt_c PUBLIC + $ + $ +) + +set_target_properties(fmt_c PROPERTIES + VERSION ${FMT_VERSION} + SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} + DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}" + C_VISIBILITY_PRESET default + CXX_VISIBILITY_PRESET hidden +) + +add_library(fmt::fmt_c ALIAS fmt_c) +if(FMT_INSTALL) + install(TARGETS fmt_c + EXPORT fmt-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + install(FILES include/fmt/fmt-c.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/fmt +) +endif() + +if(FMT_TEST) enable_language(C) + message(STATUS "Adding C API test executable") - add_library(fmt_c STATIC src/c.cc) - - target_compile_features(fmt_c PUBLIC cxx_std_11) - target_compile_definitions(fmt_c PUBLIC FMT_C_STATIC) - target_link_libraries(fmt_c PUBLIC fmt::fmt) - - target_include_directories(fmt_c PUBLIC - $ - $ - ) - - set_target_properties(fmt_c PROPERTIES - VERSION ${FMT_VERSION} - SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} - DEBUG_POSTFIX "${CMAKE_DEBUG_POSTFIX}" - C_VISIBILITY_PRESET default - CXX_VISIBILITY_PRESET hidden - ) - - add_library(fmt::fmt_c ALIAS fmt_c) - if(FMT_INSTALL) - install(TARGETS fmt_c - EXPORT fmt-targets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ) - install(FILES include/fmt/c.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/fmt + add_executable(test-c-api test/test_c.c) + target_link_libraries(test-c-api PRIVATE fmt::fmt_c) + #needed for c11(_generic) + if(MSVC) + target_compile_options(test-c-api PRIVATE /std:c11 /Zc:preprocessor) + else() + set_target_properties(test-c-api PROPERTIES + C_STANDARD 11 + C_STANDARD_REQUIRED ON ) endif() - if(FMT_TEST AND EXISTS ${PROJECT_SOURCE_DIR}/test/test_c.c) - message(STATUS "Adding C API test executable") - - add_executable(test-c-api test/test_c.c) - - set_source_files_properties(test/test_c.c PROPERTIES LANGUAGE C) - - target_link_libraries(test-c-api PRIVATE fmt::fmt_c) -#needed for c11(_generic) - if(MSVC) - target_compile_options(test-c-api PRIVATE /std:c11 /Zc:preprocessor) - else() - set_target_properties(test-c-api PROPERTIES - C_STANDARD 11 - C_STANDARD_REQUIRED ON - C_EXTENSIONS OFF - ) - endif() - - add_test(NAME c-api-test COMMAND test-c-api) - endif() + add_test(NAME c-api-test COMMAND test-c-api) +endif() -endif(FMT_C_API) \ No newline at end of file diff --git a/include/fmt/c.h b/include/fmt/c.h deleted file mode 100644 index aaf60fa48c8f..000000000000 --- a/include/fmt/c.h +++ /dev/null @@ -1,277 +0,0 @@ -#ifndef FMT_C_API_H -#define FMT_C_API_H - -#include - -#define FMT_C_ABI_VERSION 1 -#define FMT_C_MAX_ARGS 16 - -#define FMT_OK 0 -#define FMT_ERR_NULL_FORMAT -1 -#define FMT_ERR_EXCEPTION -2 -#define FMT_ERR_MEMORY -3 - -#ifdef __cplusplus -# include -# include -extern "C" { -#else -# include -# include -#endif - -#if defined(_WIN32) && !defined(FMT_C_STATIC) -# ifdef FMT_C_EXPORT -# define FMT_C_API __declspec(dllexport) -# else -# define FMT_C_API __declspec(dllimport) -# endif -#else -# define FMT_C_API -#endif - -// Custom formatter callback -// Returns number of bytes written (excluding null terminator), or -1 on error -typedef int (*FmtCustomFn)(char* buf, size_t cap, const void* data); - -typedef enum { - FMT_INT, - FMT_UINT, - FMT_FLOAT, - FMT_DOUBLE, - FMT_LONG_DOUBLE, - FMT_STRING, - FMT_PTR, - FMT_BOOL, - FMT_CHAR, - FMT_CUSTOM -} FmtType; - -typedef struct { - FmtType type; - - // Explicit padding for ABI stability - // - type: 4 bytes (enum) - // - _padding: 4 bytes (explicit alignment) - // - value: 16 bytes (union, sized by long double) - // - custom_fn: 8 bytes (function pointer) - // Ensures consistent struct size across compilers (..* 24 bytes in MSVC) - int32_t _padding; - union { - int64_t i64; - uint64_t u64; - float f32; - double f64; - long double f128; - const char* str; - const void* ptr; // Used for FMT_PTR and custom data - int bool_val; - int char_val; - } value; - - // FMT_CUSTOM type only - FmtCustomFn custom_fn; -} FmtArg; - -FMT_C_API int fmt_c_format(char* buffer, size_t capacity, - const char* format_str, const FmtArg* args, - size_t arg_count); -FMT_C_API void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, - size_t arg_count); - -FMT_C_API const char* fmt_c_get_error(void); - -FMT_C_API int fmt_c_get_version(void); - -static inline FmtArg fmt_from_int(int64_t x) { - FmtArg a; - a.type = FMT_INT; - a._padding = 0; - a.value.i64 = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_uint(uint64_t x) { - FmtArg a; - a.type = FMT_UINT; - a._padding = 0; - a.value.u64 = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_float(float x) { - FmtArg a; - a.type = FMT_FLOAT; - a._padding = 0; - a.value.f32 = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_double(double x) { - FmtArg a; - a.type = FMT_DOUBLE; - a._padding = 0; - a.value.f64 = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_long_double(long double x) { - FmtArg a; - a.type = FMT_LONG_DOUBLE; - a._padding = 0; - a.value.f128 = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_str(const char* x) { - FmtArg a; - a.type = FMT_STRING; - a._padding = 0; - a.value.str = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_ptr(const void* x) { - FmtArg a; - a.type = FMT_PTR; - a._padding = 0; - a.value.ptr = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_bool(bool x) { - FmtArg a; - a.type = FMT_BOOL; - a._padding = 0; - a.value.bool_val = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_char(int x) { - FmtArg a; - a.type = FMT_CHAR; - a._padding = 0; - a.value.char_val = x; - a.custom_fn = NULL; - return a; -} - -static inline FmtArg fmt_from_custom(const void* data, FmtCustomFn func) { - FmtArg a; - a.type = FMT_CUSTOM; - a._padding = 0; - a.value.ptr = data; - a.custom_fn = func; - return a; -} - -static inline FmtArg fmt_identity(FmtArg x) { return x; } - -#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. Add /Zc:preprocessor to your compiler flags." -# endif - -# define FMT_MAKE_ARG(x) \ - _Generic((x), \ - FmtArg: fmt_identity, \ - _Bool: fmt_from_bool, \ - char: fmt_from_char, \ - signed char: fmt_from_int, \ - 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_from_ptr)(x) - -# define FMT_MAKE_CUSTOM(data_ptr, func_ptr) \ - fmt_from_custom((const void*)(data_ptr), func_ptr) - -# 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_snprintf(buf, cap, fmt, ...) \ - fmt_c_format( \ - buf, cap, fmt, \ - (FmtArg[]){{FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ - FMT_NARG(__VA_ARGS__)) - -# define fmt_fprintf(f, fmt, ...) \ - fmt_c_print( \ - f, fmt, \ - (FmtArg[]){{FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ - FMT_NARG(__VA_ARGS__)) - -# define fmt_printf(fmt, ...) fmt_fprintf(stdout, fmt, ##__VA_ARGS__) - -#endif // !__cplusplus - -#endif // FMT_C_API_H \ No newline at end of file diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h new file mode 100644 index 000000000000..d3b2d8af5599 --- /dev/null +++ b/include/fmt/fmt-c.h @@ -0,0 +1,200 @@ +#ifndef FMT_C_H +#define FMT_C_H + +#include +#include +#include +#include + +#define FMT_C_MAX_ARGS 16 + +typedef enum { + fmt_ok = 0, + 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 { + int64_t i64; + uint64_t u64; + float f32; + double f64; + long double f128; + const char* str; + const void* ptr; // Used for FMT_PTR and custom data + int bool_val; + int 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(int64_t x) { + fmt_arg arg; + arg.type = fmt_int; + arg.value.i64 = x; + return arg; +} + +static inline fmt_arg fmt_from_uint(uint64_t 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 + +# 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_from_ptr)(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, \ + FMT_NARG(__VA_ARGS__)) + +#endif // !__cplusplus + +#endif // FMT_C_H diff --git a/src/c.cc b/src/c.cc deleted file mode 100644 index 048e3bb9e961..000000000000 --- a/src/c.cc +++ /dev/null @@ -1,247 +0,0 @@ -#undef FMT_C_EXPORT -#define FMT_C_EXPORT -#include "fmt/c.h" - -#include - -#include -#include -#include -#include - -static const size_t MAX_PACKED_ARGS = FMT_C_MAX_ARGS; - -extern "C" { -static thread_local std::string g_last_error; - -const char* fmt_c_get_error(void) { - return g_last_error.empty() ? "" : g_last_error.c_str(); -} - -static void set_error(const char* msg) { - try { - g_last_error = msg; - } catch (...) { - } -} - -static void clear_error() { g_last_error.clear(); } - -int fmt_c_get_version(void) { return FMT_C_ABI_VERSION; } - -using Context = fmt::format_context; - -// Fixed-size array for type-erased format arguments -static thread_local std::array, MAX_PACKED_ARGS> - g_fixed_store; - -static bool populate_store(const FmtArg* c_args, size_t arg_count, - std::vector& custom_buffers) { - if (arg_count > MAX_PACKED_ARGS) { - set_error("Argument count exceeds maximum (FMT_C_MAX_ARGS)"); - return false; - } - - for (size_t i = 0; i < arg_count; ++i) { - switch (c_args[i].type) { - case FMT_INT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.i64); - break; - - case FMT_UINT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.u64); - break; - - case FMT_FLOAT: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f32); - break; - - case FMT_DOUBLE: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f64); - break; - - case FMT_LONG_DOUBLE: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.f128); - break; - - case FMT_PTR: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.ptr); - break; - - case FMT_CHAR: - g_fixed_store[i] = fmt::basic_format_arg( - static_cast(c_args[i].value.char_val)); - break; - - case FMT_BOOL: - g_fixed_store[i] = - fmt::basic_format_arg(c_args[i].value.bool_val != 0); - break; - - case FMT_STRING: { - const char* s = c_args[i].value.str ? c_args[i].value.str : "(null)"; - g_fixed_store[i] = fmt::basic_format_arg(fmt::string_view(s)); - break; - } - - case FMT_CUSTOM: { - if (!c_args[i].custom_fn) { - set_error("Custom formatter function is NULL"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - - if (!c_args[i].value.ptr) { - set_error("Custom formatter data pointer is NULL"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - - try { - std::string buf; - buf.resize(64); // intial bufffer size .... - int len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); - - if (len < 0) { - set_error("Custom formatter returned error code"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - if (static_cast(len) >= buf.size()) { - buf.resize(len + 1); - len = c_args[i].custom_fn(&buf[0], buf.size(), c_args[i].value.ptr); - - if (len < 0) { - set_error("Custom formatter failed on second call"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - } - - buf.resize(len); - custom_buffers.push_back(std::move(buf)); - g_fixed_store[i] = fmt::basic_format_arg( - fmt::string_view(custom_buffers.back())); - - } catch (const std::bad_alloc&) { - set_error("Memory allocation failed in custom formatter"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } catch (...) { - set_error("Unknown exception in custom formatter"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - break; - } - - default: - set_error("Unknown FmtType enum value"); - g_fixed_store[i] = - fmt::basic_format_arg(fmt::string_view("")); - return false; - } - } - - return true; -} -int fmt_c_format(char* buffer, size_t capacity, const char* format_str, - const FmtArg* args, size_t arg_count) { - clear_error(); - if (!format_str) { - set_error("Format string is NULL"); - return FMT_ERR_NULL_FORMAT; - } - - if (arg_count > MAX_PACKED_ARGS) { - set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); - return FMT_ERR_MEMORY; - } - - try { - std::vector custom_buffers; - if (arg_count > 0) { - if (!args) { - set_error("Argument array is NULL but arg_count > 0"); - return FMT_ERR_NULL_FORMAT; - } - if (!populate_store(args, arg_count, custom_buffers)) { - return FMT_ERR_EXCEPTION; - } - } - - auto format_args_view = fmt::basic_format_args( - g_fixed_store.data(), static_cast(arg_count)); - - if (!buffer || capacity == 0) { - char tmp[1]; - auto result = fmt::vformat_to_n(tmp, 0, format_str, format_args_view); - return static_cast(result.size); - } - auto result = - fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); - - *result.out = '\0'; - return static_cast(result.size); - - } catch (const fmt::format_error& e) { - set_error(e.what()); - return FMT_ERR_EXCEPTION; - } catch (const std::exception& e) { - set_error(e.what()); - return FMT_ERR_EXCEPTION; - } catch (...) { - set_error("Unknown C++ exception"); - return FMT_ERR_EXCEPTION; - } -} - -void fmt_c_print(FILE* f, const char* format_str, const FmtArg* args, - size_t arg_count) { - clear_error(); - - if (!f) { - set_error("File stream is NULL"); - return; - } - - if (!format_str) { - set_error("Format string is NULL"); - return; - } - if (arg_count > MAX_PACKED_ARGS) { - set_error("Too many arguments (maximum is FMT_C_MAX_ARGS)"); - return; - } - - try { - std::vector custom_buffers; - if (arg_count > 0) { - if (!args) { - set_error("Argument array is NULL but arg_count > 0"); - return; - } - if (!populate_store(args, arg_count, custom_buffers)) { - return; - } - } - auto format_args_view = fmt::basic_format_args( - g_fixed_store.data(), static_cast(arg_count)); - fmt::vprint(f, format_str, format_args_view); - - } catch (const fmt::format_error& e) { - set_error(e.what()); - } catch (const std::exception& e) { - set_error(e.what()); - } catch (...) { - set_error("Unknown C++ exception"); - } -} - -} // extern "C" \ No newline at end of file diff --git a/src/fmt-c.cc b/src/fmt-c.cc new file mode 100644 index 000000000000..b3e5cbf02b12 --- /dev/null +++ b/src/fmt-c.cc @@ -0,0 +1,92 @@ +#include "fmt/fmt-c.h" + +#include + +#include +#include +#include +#include +#include + +extern "C" { + +using Context = fmt::format_context; + +static bool populate_store(fmt::basic_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] = fmt::basic_format_arg(c_args[i].value.i64); + break; + case fmt_uint: + out[i] = fmt::basic_format_arg(c_args[i].value.u64); + break; + case fmt_float: + out[i] = fmt::basic_format_arg(c_args[i].value.f32); + break; + case fmt_double: + out[i] = fmt::basic_format_arg(c_args[i].value.f64); + break; + case fmt_long_double: + out[i] = fmt::basic_format_arg(c_args[i].value.f128); + break; + case fmt_ptr: + out[i] = fmt::basic_format_arg(c_args[i].value.ptr); + break; + case fmt_char: + out[i] = fmt::basic_format_arg( + static_cast(c_args[i].value.char_val)); + break; + case fmt_bool: + out[i] = fmt::basic_format_arg(c_args[i].value.bool_val != 0); + break; + case fmt_string: { + const char* s = c_args[i].value.str ? c_args[i].value.str : "(null)"; + out[i] = fmt::basic_format_arg(fmt::string_view(s)); + 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); + + fmt::basic_format_arg format_args[FMT_C_MAX_ARGS]; + + try { + if (arg_count > 0) { + assert(args); + if (!populate_store(format_args, args, arg_count)) { + return fmt_err_exception; + } + } + + auto format_args_view = fmt::basic_format_args( + format_args, static_cast(arg_count)); + + size_t write_capacity = (capacity > 0) ? capacity - 1 : 0; + auto result = + fmt::vformat_to_n(buffer, write_capacity, format_str, format_args_view); + + if (capacity > 0) { + buffer[result.size] = '\0'; + } + return static_cast(result.size); + + } catch (const std::bad_alloc&) { + return fmt_err_memory; + } catch (...) { + return fmt_err_exception; + } +} + +} // extern "C" diff --git a/test/test_c.c b/test/test_c.c index 697026ef596a..99c3a4ae7931 100644 --- a/test/test_c.c +++ b/test/test_c.c @@ -3,17 +3,7 @@ #include #include -#include "fmt/c.h" - -#define TEST(name) \ - static void test_##name(void); \ - static void run_test_##name(void) { \ - printf("Running test: %s ... ", #name); \ - test_##name(); \ - printf("PASSED\n"); \ - } \ - static void test_##name(void) - +#include "fmt/fmt-c.h" #define ASSERT_STR_EQ(actual, expected) \ do { \ if (strcmp(actual, expected) != 0) { \ @@ -41,284 +31,138 @@ } \ } while (0) -TEST(basic_integer) { +void test_basic_integer(void) { char buf[100]; - int ret = fmt_snprintf(buf, sizeof(buf), "Number: {}", 42); + int ret = fmt_format(buf, sizeof(buf), "Number: {}", 42); ASSERT_STR_EQ(buf, "Number: 42"); ASSERT_INT_EQ(ret, 10); } -TEST(multiple_integers) { +void test_multiple_integers(void) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); + fmt_format(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); ASSERT_STR_EQ(buf, "1 + 2 = 3"); } -TEST(unsigned_integers) { +void test_unsigned_integers(void) { char buf[100]; unsigned int x = 4294967295U; - fmt_snprintf(buf, sizeof(buf), "{}", x); + fmt_format(buf, sizeof(buf), "{}", x); ASSERT_STR_EQ(buf, "4294967295"); } -TEST(floating_point) { +void test_floating_point(void) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Pi = {}", 3.14159); + fmt_format(buf, sizeof(buf), "Pi = {}", 3.14159); ASSERT_TRUE(strncmp(buf, "Pi = 3.14159", 12) == 0); } -TEST(float_type) { +void test_float_type(void) { char buf[100]; float f = 1.234f; - fmt_snprintf(buf, sizeof(buf), "Float: {:.3f}", f); + fmt_format(buf, sizeof(buf), "Float: {:.3f}", f); ASSERT_STR_EQ(buf, "Float: 1.234"); } -TEST(long_double_type) { +void test_long_double_type(void) { char buf[100]; long double ld = 12345.6789L; - fmt_snprintf(buf, sizeof(buf), "{:.4f}", ld); + fmt_format(buf, sizeof(buf), "{:.4f}", ld); ASSERT_STR_EQ(buf, "12345.6789"); } -TEST(mixed_floating_types) { +void test_mixed_floating_types(void) { char buf[200]; float f = 1.5f; double d = 2.5; long double ld = 3.5L; - fmt_snprintf(buf, sizeof(buf), "{} {} {}", f, d, ld); + fmt_format(buf, sizeof(buf), "{} {} {}", f, d, ld); ASSERT_STR_EQ(buf, "1.5 2.5 3.5"); } -TEST(strings) { +void test_strings(void) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Hello, {}!", "from fmt!"); + fmt_format(buf, sizeof(buf), "Hello, {}!", "from fmt!"); ASSERT_STR_EQ(buf, "Hello, from fmt!!"); } -TEST(null_string) { +void test_null_string(void) { char buf[100]; const char* null_str = NULL; - fmt_snprintf(buf, sizeof(buf), "{}", null_str); + fmt_format(buf, sizeof(buf), "{}", null_str); ASSERT_STR_EQ(buf, "(null)"); } -TEST(pointers) { +void test_pointers(void) { char buf[100]; void* ptr = (void*)0x12345678; - fmt_snprintf(buf, sizeof(buf), "{}", ptr); + fmt_format(buf, sizeof(buf), "{}", ptr); ASSERT_TRUE(strstr(buf, "12345678") != NULL); } -TEST(booleans) { +void test_booleans(void) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); + fmt_format(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); ASSERT_STR_EQ(buf, "true false"); } -TEST(characters) { +void test_characters(void) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "Char: {}", (char)'A'); + fmt_format(buf, sizeof(buf), "Char: {}", (char)'A'); ASSERT_STR_EQ(buf, "Char: A"); } -TEST(mixed_types) { +void test_mixed_types(void) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); + fmt_format(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); ASSERT_TRUE(strstr(buf, "42") != NULL); ASSERT_TRUE(strstr(buf, "3.14") != NULL); ASSERT_TRUE(strstr(buf, "text") != NULL); ASSERT_TRUE(strstr(buf, "true") != NULL); } - -TEST(format_zero_padding) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:05d}", 42); - ASSERT_STR_EQ(buf, "00042"); -} - -TEST(format_precision) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:.2f}", 3.14159); - ASSERT_STR_EQ(buf, "3.14"); -} - -TEST(format_hex) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:x}", 255); - ASSERT_STR_EQ(buf, "ff"); -} - -TEST(format_hex_upper) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:X}", 255); - ASSERT_STR_EQ(buf, "FF"); -} - -TEST(positional_arguments) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{1} {0}", "from fmt!", "Hello"); - ASSERT_STR_EQ(buf, "Hello from fmt!"); -} - -TEST(zero_arguments) { +void test_zero_arguments(void) { char buf[100]; - // fmt_snprintf(buf, sizeof(buf), "No arguments"); - fmt_c_format(buf, sizeof(buf), "No arguments", NULL, - 0); // strict compiler check bypass - either turn on cextension - // in cmake or this + // fmt_format(buf, sizeof(buf), "No arguments"); + fmt_vformat(buf, sizeof(buf), "No arguments", NULL, + 0); // strict compiler check bypass ASSERT_STR_EQ(buf, "No arguments"); } -TEST(buffer_size_query) { - int size = fmt_snprintf(NULL, 0, "Test string: {}", 42); +void test_buffer_size_query(void) { + int size = fmt_format(NULL, 0, "Test string: {}", 42); ASSERT_INT_EQ(size, 15); } -TEST(buffer_overflow) { - char buf[10]; - int ret = fmt_snprintf(buf, sizeof(buf), "Very long string: {}", 12345); - ASSERT_INT_EQ(buf[9], '\0'); - ASSERT_TRUE(ret > 9); -} - -static int custom_point_formatter(char* buf, size_t cap, const void* data) { - const int* point = (const int*)data; - if (!buf || cap == 0) { - return snprintf(NULL, 0, "Point(%d, %d)", point[0], point[1]); - } - return snprintf(buf, cap, "Point(%d, %d)", point[0], point[1]); -} - -TEST(custom_formatter) { - char buf[100]; - int point[2] = {10, 20}; - FmtArg args[] = {FMT_MAKE_CUSTOM(point, custom_point_formatter)}; - fmt_c_format(buf, sizeof(buf), "Location: {}", args, 1); - ASSERT_STR_EQ(buf, "Location: Point(10, 20)"); -} - -TEST(custom_formatter_null_function) { - char buf[100]; - int data = 42; - FmtArg args[] = {fmt_from_custom(&data, NULL)}; - int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); -} - -TEST(custom_formatter_null_data) { +void test_error_invalid_format(void) { char buf[100]; - FmtArg args[] = {fmt_from_custom(NULL, custom_point_formatter)}; - int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); + int ret = fmt_format(buf, sizeof(buf), "{:invalid}", 1); ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); } -static int failing_formatter(char* buf, size_t cap, const void* data) { - (void)buf; - (void)cap; - (void)data; - return -1; -} - -TEST(custom_formatter_error_return) { - char buf[100]; - int data = 42; - FmtArg args[] = {FMT_MAKE_CUSTOM(&data, failing_formatter)}; - int ret = fmt_c_format(buf, sizeof(buf), "Value: {}", args, 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "error") != NULL); -} - -TEST(error_null_format) { - char buf[100]; - int ret = fmt_c_format(buf, sizeof(buf), NULL, NULL, 0); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); -} - -TEST(error_too_many_args) { - char buf[100]; - FmtArg args[20]; // More than MAX_PACKED_ARGS (16) - for (int i = 0; i < 20; i++) { - args[i] = fmt_from_int(i); - } - int ret = fmt_c_format(buf, sizeof(buf), "{}", args, 20); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "maximum") != NULL || strstr(err, "many") != NULL); -} - -// NEW: Test NULL args with non-zero count -TEST(error_null_args_nonzero_count) { - char buf[100]; - int ret = fmt_c_format(buf, sizeof(buf), "{}", NULL, 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); -} - -TEST(error_invalid_format) { - char buf[100]; - int ret = fmt_snprintf(buf, sizeof(buf), "{:invalid}", 1); - ASSERT_TRUE(ret < 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strlen(err) > 0); -} - -TEST(printf_to_stdout) { - printf("\n Output from fmt_printf: "); - fmt_printf("Test {} {} {}", 1, 2.5, "string"); - printf("\n"); -} -TEST(print_null_file) { - FmtArg args[] = {fmt_from_int(42)}; - fmt_c_print(NULL, "{}", args, 1); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); -} - -TEST(print_null_format) { - fmt_c_print(stdout, NULL, NULL, 0); - const char* err = fmt_c_get_error(); - ASSERT_TRUE(strstr(err, "NULL") != NULL); -} - -TEST(long_strings) { +void test_long_strings(void) { char buf[1000]; const char* long_str = "This is a very long string that contains a lot of text " "to test the buffer handling capabilities of the formatter"; - fmt_snprintf(buf, sizeof(buf), "Message: {}", long_str); + fmt_format(buf, sizeof(buf), "Message: {}", long_str); ASSERT_TRUE(strstr(buf, long_str) != NULL); } -TEST(multiple_calls) { +void test_multiple_calls(void) { char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{} {}", 1, 2); + fmt_format(buf, sizeof(buf), "{} {}", 1, 2); ASSERT_STR_EQ(buf, "1 2"); - fmt_snprintf(buf, sizeof(buf), "{} {}", "hello", 3.14); + fmt_format(buf, sizeof(buf), "{} {}", "hello", 3.14); ASSERT_TRUE(strstr(buf, "hello") != NULL); - fmt_snprintf(buf, sizeof(buf), "{}", (bool)true); + fmt_format(buf, sizeof(buf), "{}", (bool)true); ASSERT_STR_EQ(buf, "true"); } -TEST(escaped_braces) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{{}} {}", 42); - ASSERT_STR_EQ(buf, "{} 42"); -} - -TEST(all_integer_types) { +void test_all_integer_types(void) { char buf[200]; short s = 100; int i = 200; @@ -329,85 +173,35 @@ TEST(all_integer_types) { unsigned long ul = 700UL; unsigned long long ull = 800ULL; - fmt_snprintf(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, - ul, ull); + fmt_format(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, + ul, ull); ASSERT_TRUE(strstr(buf, "100") != NULL); ASSERT_TRUE(strstr(buf, "800") != NULL); } -TEST(version_check) { - int version = fmt_c_get_version(); - ASSERT_INT_EQ(version, FMT_C_ABI_VERSION); -} - -TEST(alignment) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:>10}", 42); - ASSERT_STR_EQ(buf, " 42"); -} - -TEST(center_alignment) { - char buf[100]; - fmt_snprintf(buf, sizeof(buf), "{:^10}", "Hi"); - ASSERT_STR_EQ(buf, " Hi "); -} - -TEST(struct_size_and_alignment) { - // Verify that FmtArg has expected size with explicit padding - // On 64-bit: 4 (type) + 4 (padding) + 16 (union) + 8 (ptr) = 32 bytes .... 24 - // bytes in MSVC becuz of it's internal optimization This may vary on 32-bit - // or with different compilers - printf("\n FmtArg size: %zu bytes (alignment: %zu)\n", sizeof(FmtArg), - _Alignof(FmtArg)); - - FmtArg arg = fmt_from_int(42); - ASSERT_INT_EQ(arg._padding, 0); -} - int main(void) { printf("=== Running fmt C API Tests ===\n\n"); - run_test_basic_integer(); - run_test_multiple_integers(); - run_test_unsigned_integers(); - run_test_floating_point(); - run_test_float_type(); - run_test_long_double_type(); - run_test_mixed_floating_types(); - run_test_strings(); - run_test_null_string(); - run_test_pointers(); - run_test_booleans(); - run_test_characters(); - run_test_mixed_types(); - run_test_format_zero_padding(); - run_test_format_precision(); - run_test_format_hex(); - run_test_format_hex_upper(); - run_test_positional_arguments(); - run_test_zero_arguments(); - run_test_buffer_size_query(); - run_test_buffer_overflow(); - run_test_custom_formatter(); - run_test_custom_formatter_null_function(); - run_test_custom_formatter_null_data(); - run_test_custom_formatter_error_return(); - run_test_error_null_args_nonzero_count(); - run_test_error_null_format(); - run_test_error_too_many_args(); - run_test_error_invalid_format(); - run_test_printf_to_stdout(); - run_test_print_null_file(); - run_test_print_null_format(); - run_test_long_strings(); - run_test_multiple_calls(); - run_test_escaped_braces(); - run_test_all_integer_types(); - run_test_version_check(); - run_test_alignment(); - run_test_center_alignment(); - run_test_struct_size_and_alignment(); + test_basic_integer(); + test_multiple_integers(); + test_unsigned_integers(); + test_floating_point(); + test_float_type(); + test_long_double_type(); + test_mixed_floating_types(); + test_strings(); + test_null_string(); + test_pointers(); + test_booleans(); + test_characters(); + test_mixed_types(); + test_zero_arguments(); + test_buffer_size_query(); + test_error_invalid_format(); + test_long_strings(); + test_multiple_calls(); + test_all_integer_types(); printf("\n=== All tests passed! ===\n"); return 0; -} \ No newline at end of file +} From 1335192387e3978562c093d504f1f69f8679533b Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 15 Feb 2026 23:33:11 +0530 Subject: [PATCH 10/14] Revisioned changes --- CMakeLists.txt | 14 +++---- include/fmt/fmt-c.h | 27 +++++++------ src/fmt-c.cc | 91 +++++++++++++---------------------------- test/test_c.c | 99 +++++++++++++++++++++------------------------ 4 files changed, 93 insertions(+), 138 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99a0ff7420a6..aabe66cdaa9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -537,7 +537,6 @@ endif () add_library(fmt_c STATIC src/fmt-c.cc) target_compile_features(fmt_c PUBLIC cxx_std_11) -target_compile_definitions(fmt_c PUBLIC FMT_C_STATIC) target_link_libraries(fmt_c PUBLIC fmt::fmt) target_include_directories(fmt_c PUBLIC @@ -570,16 +569,13 @@ if(FMT_TEST) add_executable(test-c-api test/test_c.c) target_link_libraries(test-c-api PRIVATE fmt::fmt_c) - #needed for c11(_generic) + set_target_properties(test-c-api PROPERTIES + C_STANDARD 11 + C_STANDARD_REQUIRED ON + ) if(MSVC) - target_compile_options(test-c-api PRIVATE /std:c11 /Zc:preprocessor) - else() - set_target_properties(test-c-api PROPERTIES - C_STANDARD 11 - C_STANDARD_REQUIRED ON - ) + target_compile_options(test-c-api PRIVATE /Zc:preprocessor) endif() add_test(NAME c-api-test COMMAND test-c-api) endif() - diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index d3b2d8af5599..34927bf1626f 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -1,15 +1,16 @@ #ifndef FMT_C_H #define FMT_C_H -#include -#include -#include +#ifdef __cplusplus +# define _Bool bool +#endif #include -#define FMT_C_MAX_ARGS 16 +void fmt_error_unsupported_type_detected(void); + +enum { fmt_c_max_args = 16 }; typedef enum { - fmt_ok = 0, fmt_err_exception = -1, fmt_err_memory = -2, fmt_err_invalid_arg = -3 @@ -34,29 +35,29 @@ typedef enum { typedef struct { fmt_type type; union { - int64_t i64; - uint64_t u64; + 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 - int bool_val; - int char_val; + _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(int64_t x) { +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(uint64_t x) { +static inline fmt_arg fmt_from_uint(unsigned long long x) { fmt_arg arg; arg.type = fmt_uint; arg.value.u64 = x; @@ -98,7 +99,7 @@ static inline fmt_arg fmt_from_ptr(const void* x) { return arg; } -static inline fmt_arg fmt_from_bool(bool x) { +static inline fmt_arg fmt_from_bool(_Bool x) { fmt_arg arg; arg.type = fmt_bool; arg.value.bool_val = x; @@ -143,7 +144,7 @@ static inline fmt_arg fmt_from_char(int x) { const char*: fmt_from_str, \ void*: fmt_from_ptr, \ const void*: fmt_from_ptr, \ - default: fmt_from_ptr)(x) + default: fmt_error_unsupported_type_detected)(x) # define FMT_CAT(a, b) FMT_CAT_(a, b) # define FMT_CAT_(a, b) a##b diff --git a/src/fmt-c.cc b/src/fmt-c.cc index b3e5cbf02b12..dd5130b6fccd 100644 --- a/src/fmt-c.cc +++ b/src/fmt-c.cc @@ -2,91 +2,54 @@ #include -#include #include -#include -#include -#include extern "C" { -using Context = fmt::format_context; +using format_arg = fmt::basic_format_arg; -static bool populate_store(fmt::basic_format_arg* out, - const fmt_arg* c_args, size_t arg_count) { - if (arg_count > FMT_C_MAX_ARGS) { +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] = fmt::basic_format_arg(c_args[i].value.i64); - break; - case fmt_uint: - out[i] = fmt::basic_format_arg(c_args[i].value.u64); - break; - case fmt_float: - out[i] = fmt::basic_format_arg(c_args[i].value.f32); - break; - case fmt_double: - out[i] = fmt::basic_format_arg(c_args[i].value.f64); - break; - case fmt_long_double: - out[i] = fmt::basic_format_arg(c_args[i].value.f128); - break; - case fmt_ptr: - out[i] = fmt::basic_format_arg(c_args[i].value.ptr); - break; - case fmt_char: - out[i] = fmt::basic_format_arg( - static_cast(c_args[i].value.char_val)); - break; - case fmt_bool: - out[i] = fmt::basic_format_arg(c_args[i].value.bool_val != 0); - break; - case fmt_string: { - const char* s = c_args[i].value.str ? c_args[i].value.str : "(null)"; - out[i] = fmt::basic_format_arg(fmt::string_view(s)); - break; - } - - default: return false; + 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); - fmt::basic_format_arg format_args[FMT_C_MAX_ARGS]; + format_arg format_args[fmt_c_max_args]; - try { - if (arg_count > 0) { - assert(args); - if (!populate_store(format_args, args, arg_count)) { - return fmt_err_exception; - } + 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( - format_args, static_cast(arg_count)); - - size_t write_capacity = (capacity > 0) ? capacity - 1 : 0; - auto result = - fmt::vformat_to_n(buffer, write_capacity, format_str, format_args_view); - - if (capacity > 0) { - buffer[result.size] = '\0'; - } - return static_cast(result.size); + auto format_args_view = fmt::basic_format_args( + format_args, static_cast(arg_count)); - } catch (const std::bad_alloc&) { - return fmt_err_memory; - } catch (...) { - return fmt_err_exception; - } + auto result = + fmt::vformat_to_n(buffer, capacity, format_str, format_args_view); + return static_cast(result.size); } -} // extern "C" +} // extern "C" \ No newline at end of file diff --git a/test/test_c.c b/test/test_c.c index 99c3a4ae7931..7924bc23cb6f 100644 --- a/test/test_c.c +++ b/test/test_c.c @@ -1,9 +1,11 @@ /* Test suite for fmt C API */ +#include #include #include #include #include "fmt/fmt-c.h" + #define ASSERT_STR_EQ(actual, expected) \ do { \ if (strcmp(actual, expected) != 0) { \ @@ -31,43 +33,58 @@ } \ } while (0) +// Helper to manually null-terminate buffer after formatting +void terminate(char* buf, int size, size_t capacity) { + if (size >= 0 && (size_t)size < capacity) { + buf[size] = '\0'; + } else if (capacity > 0) { + buf[capacity - 1] = '\0'; + } +} + void test_basic_integer(void) { char buf[100]; int ret = fmt_format(buf, sizeof(buf), "Number: {}", 42); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "Number: 42"); ASSERT_INT_EQ(ret, 10); } void test_multiple_integers(void) { char buf[100]; - fmt_format(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); + int ret = fmt_format(buf, sizeof(buf), "{} + {} = {}", 1, 2, 3); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "1 + 2 = 3"); } void test_unsigned_integers(void) { char buf[100]; unsigned int x = 4294967295U; - fmt_format(buf, sizeof(buf), "{}", x); + int ret = fmt_format(buf, sizeof(buf), "{}", x); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "4294967295"); } void test_floating_point(void) { char buf[100]; - fmt_format(buf, sizeof(buf), "Pi = {}", 3.14159); + int ret = fmt_format(buf, sizeof(buf), "Pi = {}", 3.14159); + terminate(buf, ret, sizeof(buf)); ASSERT_TRUE(strncmp(buf, "Pi = 3.14159", 12) == 0); } void test_float_type(void) { char buf[100]; float f = 1.234f; - fmt_format(buf, sizeof(buf), "Float: {:.3f}", f); + int ret = fmt_format(buf, sizeof(buf), "Float: {:.3f}", f); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "Float: 1.234"); } void test_long_double_type(void) { char buf[100]; long double ld = 12345.6789L; - fmt_format(buf, sizeof(buf), "{:.4f}", ld); + int ret = fmt_format(buf, sizeof(buf), "{:.4f}", ld); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "12345.6789"); } @@ -77,55 +94,55 @@ void test_mixed_floating_types(void) { double d = 2.5; long double ld = 3.5L; - fmt_format(buf, sizeof(buf), "{} {} {}", f, d, ld); + int ret = fmt_format(buf, sizeof(buf), "{} {} {}", f, d, ld); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "1.5 2.5 3.5"); } void test_strings(void) { char buf[100]; - fmt_format(buf, sizeof(buf), "Hello, {}!", "from fmt!"); + int ret = fmt_format(buf, sizeof(buf), "Hello, {}!", "from fmt!"); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "Hello, from fmt!!"); } -void test_null_string(void) { - char buf[100]; - const char* null_str = NULL; - fmt_format(buf, sizeof(buf), "{}", null_str); - ASSERT_STR_EQ(buf, "(null)"); -} - void test_pointers(void) { char buf[100]; void* ptr = (void*)0x12345678; - fmt_format(buf, sizeof(buf), "{}", ptr); + int ret = fmt_format(buf, sizeof(buf), "{}", ptr); + terminate(buf, ret, sizeof(buf)); ASSERT_TRUE(strstr(buf, "12345678") != NULL); } void test_booleans(void) { char buf[100]; - fmt_format(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); + int ret = fmt_format(buf, sizeof(buf), "{} {}", (bool)true, (bool)false); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "true false"); } void test_characters(void) { char buf[100]; - fmt_format(buf, sizeof(buf), "Char: {}", (char)'A'); + int ret = fmt_format(buf, sizeof(buf), "Char: {}", (char)'A'); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "Char: A"); } void test_mixed_types(void) { char buf[100]; - fmt_format(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); + int ret = + fmt_format(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); + terminate(buf, ret, sizeof(buf)); ASSERT_TRUE(strstr(buf, "42") != NULL); ASSERT_TRUE(strstr(buf, "3.14") != NULL); ASSERT_TRUE(strstr(buf, "text") != NULL); ASSERT_TRUE(strstr(buf, "true") != NULL); } + void test_zero_arguments(void) { char buf[100]; - // fmt_format(buf, sizeof(buf), "No arguments"); - fmt_vformat(buf, sizeof(buf), "No arguments", NULL, - 0); // strict compiler check bypass + int ret = fmt_vformat(buf, sizeof(buf), "No arguments", NULL, 0); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "No arguments"); } @@ -134,51 +151,32 @@ void test_buffer_size_query(void) { ASSERT_INT_EQ(size, 15); } -void test_error_invalid_format(void) { - char buf[100]; - int ret = fmt_format(buf, sizeof(buf), "{:invalid}", 1); - ASSERT_TRUE(ret < 0); -} - void test_long_strings(void) { char buf[1000]; const char* long_str = "This is a very long string that contains a lot of text " "to test the buffer handling capabilities of the formatter"; - fmt_format(buf, sizeof(buf), "Message: {}", long_str); + int ret = fmt_format(buf, sizeof(buf), "Message: {}", long_str); + terminate(buf, ret, sizeof(buf)); ASSERT_TRUE(strstr(buf, long_str) != NULL); } void test_multiple_calls(void) { char buf[100]; - fmt_format(buf, sizeof(buf), "{} {}", 1, 2); + int ret = fmt_format(buf, sizeof(buf), "{} {}", 1, 2); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "1 2"); - fmt_format(buf, sizeof(buf), "{} {}", "hello", 3.14); + ret = fmt_format(buf, sizeof(buf), "{} {}", "hello", 3.14); + terminate(buf, ret, sizeof(buf)); ASSERT_TRUE(strstr(buf, "hello") != NULL); - fmt_format(buf, sizeof(buf), "{}", (bool)true); + ret = fmt_format(buf, sizeof(buf), "{}", (bool)true); + terminate(buf, ret, sizeof(buf)); ASSERT_STR_EQ(buf, "true"); } -void test_all_integer_types(void) { - char buf[200]; - short s = 100; - int i = 200; - long l = 300L; - long long ll = 400LL; - unsigned short us = 500; - unsigned int ui = 600; - unsigned long ul = 700UL; - unsigned long long ull = 800ULL; - - fmt_format(buf, sizeof(buf), "{} {} {} {} {} {} {} {}", s, i, l, ll, us, ui, - ul, ull); - ASSERT_TRUE(strstr(buf, "100") != NULL); - ASSERT_TRUE(strstr(buf, "800") != NULL); -} - int main(void) { printf("=== Running fmt C API Tests ===\n\n"); @@ -190,18 +188,15 @@ int main(void) { test_long_double_type(); test_mixed_floating_types(); test_strings(); - test_null_string(); test_pointers(); test_booleans(); test_characters(); test_mixed_types(); test_zero_arguments(); test_buffer_size_query(); - test_error_invalid_format(); test_long_strings(); test_multiple_calls(); - test_all_integer_types(); printf("\n=== All tests passed! ===\n"); return 0; -} +} \ No newline at end of file From 79b61baec6d6dbc97deb6e798c02d84c9bfa2d5e Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 15 Feb 2026 23:36:13 +0530 Subject: [PATCH 11/14] removed unused header --- include/fmt/fmt-c.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index 34927bf1626f..b27803f77dc1 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -4,7 +4,6 @@ #ifdef __cplusplus # define _Bool bool #endif -#include void fmt_error_unsupported_type_detected(void); From f5dd4a7f74527c2b7d5938a17b830b30f8e0ccc9 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Sun, 15 Feb 2026 23:39:07 +0530 Subject: [PATCH 12/14] removed unused header --- include/fmt/fmt-c.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index b27803f77dc1..f75b130e4ef0 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -1,6 +1,6 @@ #ifndef FMT_C_H #define FMT_C_H - +#include #ifdef __cplusplus # define _Bool bool #endif From af06ccf754d23ed097713272e1302140dd84d1e6 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Mon, 16 Feb 2026 19:13:45 +0530 Subject: [PATCH 13/14] NFC: Added newline --- CMakeLists.txt | 1 + include/fmt/fmt-c.h | 3 ++- src/fmt-c.cc | 3 ++- test/test_c.c | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aabe66cdaa9b..7d5f30cb297e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -579,3 +579,4 @@ if(FMT_TEST) add_test(NAME c-api-test COMMAND test-c-api) endif() + diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index f75b130e4ef0..11c89fef1af2 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -195,6 +195,7 @@ static inline fmt_arg fmt_from_char(int x) { (fmt_arg[]){{fmt_int}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ FMT_NARG(__VA_ARGS__)) -#endif // !__cplusplus +#endif // __cplusplus #endif // FMT_C_H + diff --git a/src/fmt-c.cc b/src/fmt-c.cc index dd5130b6fccd..5a746b40bb56 100644 --- a/src/fmt-c.cc +++ b/src/fmt-c.cc @@ -52,4 +52,5 @@ int fmt_vformat(char* buffer, size_t capacity, const char* format_str, return static_cast(result.size); } -} // extern "C" \ No newline at end of file +} // extern "C" + diff --git a/test/test_c.c b/test/test_c.c index 7924bc23cb6f..a59dd0e2d113 100644 --- a/test/test_c.c +++ b/test/test_c.c @@ -199,4 +199,5 @@ int main(void) { printf("\n=== All tests passed! ===\n"); return 0; -} \ No newline at end of file +} + From 87a41ef943e2d7712c3a681e2090b7160be0e9b1 Mon Sep 17 00:00:00 2001 From: Soumik15630m Date: Mon, 16 Feb 2026 19:15:46 +0530 Subject: [PATCH 14/14] NFC: linting fixes --- include/fmt/fmt-c.h | 1 - src/fmt-c.cc | 1 - test/test_c.c | 1 - 3 files changed, 3 deletions(-) diff --git a/include/fmt/fmt-c.h b/include/fmt/fmt-c.h index 11c89fef1af2..74ee42fafcc6 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -198,4 +198,3 @@ static inline fmt_arg fmt_from_char(int x) { #endif // __cplusplus #endif // FMT_C_H - diff --git a/src/fmt-c.cc b/src/fmt-c.cc index 5a746b40bb56..b7e857da1fcf 100644 --- a/src/fmt-c.cc +++ b/src/fmt-c.cc @@ -53,4 +53,3 @@ int fmt_vformat(char* buffer, size_t capacity, const char* format_str, } } // extern "C" - diff --git a/test/test_c.c b/test/test_c.c index a59dd0e2d113..3ee8304718af 100644 --- a/test/test_c.c +++ b/test/test_c.c @@ -200,4 +200,3 @@ int main(void) { printf("\n=== All tests passed! ===\n"); return 0; } -