diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c81eee4..99a0ff74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -534,66 +534,52 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) endif () # C API Wrapper +add_library(fmt_c STATIC src/fmt-c.cc) -option(FMT_C_API "Build C API wrapper" ON) +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) -if(FMT_C_API) - message(STATUS "Building C API wrapper (fmt::fmt_c)") +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/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 - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - ) - install(FILES include/fmt/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_test(NAME c-api-test COMMAND test-c-api) +endif() - 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/fmt-c.h b/include/fmt/fmt-c.h index a3ca0fcf..d3b2d8af 100644 --- a/include/fmt/fmt-c.h +++ b/include/fmt/fmt-c.h @@ -1,61 +1,38 @@ -#ifndef FMT_C_API_H -#define FMT_C_API_H +#ifndef FMT_C_H +#define FMT_C_H #include #include #include #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 -#define FMT_ERR_INVALID_ARG -4 +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 -#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; + fmt_int, + fmt_uint, + fmt_float, + fmt_double, + fmt_long_double, + fmt_string, + fmt_ptr, + fmt_bool, + fmt_char +} fmt_type; 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; + fmt_type type; union { int64_t i64; uint64_t u64; @@ -67,109 +44,74 @@ typedef struct { int bool_val; int char_val; } value; +} fmt_arg; - // FMT_CUSTOM type only - FmtCustomFn custom_fn; -} FmtArg; +int fmt_vformat(char* buffer, size_t capacity, const char* format_str, + const fmt_arg* 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 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 fmt_arg fmt_from_int(int64_t x) { + fmt_arg arg; + arg.type = fmt_int; + arg.value.i64 = x; + return arg; } -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 fmt_arg fmt_from_uint(uint64_t x) { + fmt_arg arg; + arg.type = fmt_uint; + arg.value.u64 = x; + return arg; } -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 fmt_arg fmt_from_float(float x) { + fmt_arg arg; + arg.type = fmt_float; + arg.value.f32 = x; + return arg; } -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 fmt_arg fmt_from_double(double x) { + fmt_arg arg; + arg.type = fmt_double; + arg.value.f64 = x; + return arg; } -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 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 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 fmt_arg fmt_from_str(const char* x) { + fmt_arg arg; + arg.type = fmt_string; + arg.value.str = x; + return arg; } -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 fmt_arg fmt_from_ptr(const void* x) { + fmt_arg arg; + arg.type = fmt_ptr; + arg.value.ptr = x; + return arg; } -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 fmt_arg fmt_from_bool(bool x) { + fmt_arg arg; + arg.type = fmt_bool; + arg.value.bool_val = x; + return arg; } -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 fmt_arg fmt_from_char(int x) { + fmt_arg arg; + arg.type = fmt_char; + arg.value.char_val = x; + return arg; } -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 @@ -178,13 +120,11 @@ static inline FmtArg fmt_identity(FmtArg x) { return x; } // 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." +# error "C API requires MSVC 2019+ with /Zc:preprocessor flag." # endif # define FMT_MAKE_ARG(x) \ _Generic((x), \ - FmtArg: fmt_identity, \ _Bool: fmt_from_bool, \ char: fmt_from_char, \ unsigned char: fmt_from_uint, \ @@ -205,9 +145,6 @@ static inline FmtArg fmt_identity(FmtArg x) { return x; } 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 @@ -252,12 +189,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, ...) \ - fmt_c_format( \ - buf, cap, fmt, \ - (FmtArg[]){{FMT_INT}, FMT_MAP(FMT_MAKE_ARG, ##__VA_ARGS__)} + 1, \ +# 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_API_H \ No newline at end of file +#endif // FMT_C_H diff --git a/src/fmt-c.cc b/src/fmt-c.cc index 87c6a156..b3e5cbf0 100644 --- a/src/fmt-c.cc +++ b/src/fmt-c.cc @@ -1,92 +1,53 @@ -#undef FMT_C_EXPORT -#define FMT_C_EXPORT #include "fmt/fmt-c.h" #include #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) { +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: - g_fixed_store[i] = fmt::basic_format_arg(c_args[i].value.i64); + case fmt_int: + out[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); + case fmt_uint: + out[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); + case fmt_float: + out[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); + case fmt_double: + out[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); + case fmt_long_double: + out[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); + case fmt_ptr: + out[i] = fmt::basic_format_arg(c_args[i].value.ptr); break; - case FMT_CHAR: - g_fixed_store[i] = fmt::basic_format_arg( + case fmt_char: + out[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); + case fmt_bool: + out[i] = fmt::basic_format_arg(c_args[i].value.bool_val != 0); break; - case FMT_STRING: { + 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; - } + out[i] = fmt::basic_format_arg(fmt::string_view(s)); break; } @@ -95,39 +56,37 @@ static bool populate_store(const FmtArg* c_args, size_t arg_count, } 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; +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 { - 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; + assert(args); + if (!populate_store(format_args, args, arg_count)) { + 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); - } + format_args, static_cast(arg_count)); + size_t write_capacity = (capacity > 0) ? capacity - 1 : 0; auto result = - fmt::vformat_to_n(buffer, capacity - 1, format_str, format_args_view); - *result.out = '\0'; + 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; + return fmt_err_memory; } catch (...) { - return FMT_ERR_EXCEPTION; + return fmt_err_exception; } } -} // extern "C" \ No newline at end of file +} // extern "C" diff --git a/test/test_c.c b/test/test_c.c index 726206c1..99c3a4ae 100644 --- a/test/test_c.c +++ b/test/test_c.c @@ -4,16 +4,6 @@ #include #include "fmt/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) { \ @@ -41,47 +31,47 @@ } \ } while (0) -TEST(basic_integer) { +void test_basic_integer(void) { char buf[100]; 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_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_format(buf, sizeof(buf), "{}", x); ASSERT_STR_EQ(buf, "4294967295"); } -TEST(floating_point) { +void test_floating_point(void) { char buf[100]; 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_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_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; @@ -91,39 +81,39 @@ TEST(mixed_floating_types) { ASSERT_STR_EQ(buf, "1.5 2.5 3.5"); } -TEST(strings) { +void test_strings(void) { char buf[100]; 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_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_format(buf, sizeof(buf), "{}", ptr); ASSERT_TRUE(strstr(buf, "12345678") != NULL); } -TEST(booleans) { +void test_booleans(void) { char buf[100]; 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_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_format(buf, sizeof(buf), "{} {} {} {}", 42, 3.14, "text", (bool)true); ASSERT_TRUE(strstr(buf, "42") != NULL); @@ -131,133 +121,26 @@ TEST(mixed_types) { ASSERT_TRUE(strstr(buf, "text") != NULL); ASSERT_TRUE(strstr(buf, "true") != NULL); } - -TEST(format_zero_padding) { - char buf[100]; - fmt_format(buf, sizeof(buf), "{:05d}", 42); - ASSERT_STR_EQ(buf, "00042"); -} - -TEST(format_precision) { - char buf[100]; - fmt_format(buf, sizeof(buf), "{:.2f}", 3.14159); - ASSERT_STR_EQ(buf, "3.14"); -} - -TEST(format_hex) { - char buf[100]; - fmt_format(buf, sizeof(buf), "{:x}", 255); - ASSERT_STR_EQ(buf, "ff"); -} - -TEST(format_hex_upper) { - char buf[100]; - fmt_format(buf, sizeof(buf), "{:X}", 255); - ASSERT_STR_EQ(buf, "FF"); -} - -TEST(positional_arguments) { - char buf[100]; - fmt_format(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_format(buf, sizeof(buf), "No arguments"); - fmt_c_format(buf, sizeof(buf), "No arguments", NULL, - 0); // strict compiler check bypass + fmt_vformat(buf, sizeof(buf), "No arguments", NULL, + 0); // strict compiler check bypass ASSERT_STR_EQ(buf, "No arguments"); } -TEST(buffer_size_query) { +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_format(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); - // ASSERT_INT_EQ(ret, FMT_ERR_EXCEPTION); // Optional specific check -} - -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); -} - -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); -} - -TEST(error_null_format) { - char buf[100]; - int ret = fmt_c_format(buf, sizeof(buf), NULL, NULL, 0); - ASSERT_INT_EQ(ret, FMT_ERR_NULL_FORMAT); -} - -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_INT_EQ(ret, FMT_ERR_INVALID_ARG); -} - -TEST(error_null_args_nonzero_count) { - char buf[100]; - int ret = fmt_c_format(buf, sizeof(buf), "{}", NULL, 1); - ASSERT_INT_EQ(ret, FMT_ERR_INVALID_ARG); -} - -TEST(error_invalid_format) { +void test_error_invalid_format(void) { char buf[100]; int ret = fmt_format(buf, sizeof(buf), "{:invalid}", 1); ASSERT_TRUE(ret < 0); } -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 " @@ -266,7 +149,7 @@ TEST(long_strings) { ASSERT_TRUE(strstr(buf, long_str) != NULL); } -TEST(multiple_calls) { +void test_multiple_calls(void) { char buf[100]; fmt_format(buf, sizeof(buf), "{} {}", 1, 2); @@ -279,13 +162,7 @@ TEST(multiple_calls) { ASSERT_STR_EQ(buf, "true"); } -TEST(escaped_braces) { - char buf[100]; - fmt_format(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; @@ -302,74 +179,29 @@ TEST(all_integer_types) { 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_format(buf, sizeof(buf), "{:>10}", 42); - ASSERT_STR_EQ(buf, " 42"); -} - -TEST(center_alignment) { - char buf[100]; - 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 .... - 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_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 +}