diff --git a/applications/nucleo-demo/source/Demo.cpp b/applications/nucleo-demo/source/Demo.cpp index 56e4c37..7b95f5a 100644 --- a/applications/nucleo-demo/source/Demo.cpp +++ b/applications/nucleo-demo/source/Demo.cpp @@ -67,7 +67,7 @@ void Demo::OnEntry(DemoState state) { } DemoState Demo::OnCycle(DemoState state) { - jarnax::print("Demo::OnCycle: %u\r\n", static_cast(state)); + // jarnax::print("Demo::OnCycle: %u\r\n", static_cast(state)); if (state == DemoState::StartUp) { state = DemoState::KeyLoop; } else if (state == DemoState::KeyLoop) { diff --git a/modules/core/source/vsnprint.cpp b/modules/core/source/vsnprint.cpp index 8fa67f2..3332e2e 100644 --- a/modules/core/source/vsnprint.cpp +++ b/modules/core/source/vsnprint.cpp @@ -38,7 +38,7 @@ static U clamp_to_range(T value, U min, U max) { /// @param base The base to parse the number in /// @return The index at the exclusive end of the number template -static unsigned long print_number(unsigned long start, char buffer[], T num, unsigned int base) { +static unsigned long print_number(unsigned long start, char buffer[], T num, unsigned int base, unsigned int width = 0, bool leading_zero = false, bool uppercase = false, bool is_negative = false) { // the digits cover base 2, base 8 (don't use) base 10, base 12, base 16 and base 64 static constexpr char const *const digits = "0123456789abcdef" @@ -49,72 +49,72 @@ static unsigned long print_number(unsigned long start, char buffer[], T num, uns unsigned long offset = 0; char backwards_digits[digit_places_limit]; // hold the value temporarily - if (base == base2) { - if (num < 0b10) { - buffer[start] = digits[num]; - return start + 1; - } else { + if (num == 0) { + backwards_digits[offset++] = '0'; + } else { + if (base == base2) { while (num != 0) { - backwards_digits[offset] = digits[num & 1]; - offset++; + backwards_digits[offset++] = digits[num & 1]; num >>= 1; } - } - } - if (base == base8) { - if (num < 8) { - buffer[start] = digits[num]; - return start + 1; - } else { + } else if (base == base8) { while (num != 0) { - backwards_digits[offset] = digits[num & 7]; - offset++; + backwards_digits[offset++] = digits[num & 7]; num >>= 3; } - } - } - if (base == base10) { - if (num < 10) { - buffer[start] = digits[num]; - return start + 1; - } else { + } else if (base == base10) { while (num != 0) { - backwards_digits[offset] = digits[num % 10]; - offset++; + backwards_digits[offset++] = digits[num % 10]; num /= 10; } - } - } - if (base == base16) { - if (num < 0x10) { - buffer[start] = digits[num]; - return start + 1; - } else { + } else if (base == base16) { while (num != 0) { - backwards_digits[offset] = digits[num & 0xF]; - offset++; + char d = digits[num & 0xF]; + if (uppercase && d >= 'a' && d <= 'z') { + d = d - 'a' + 'A'; + } + backwards_digits[offset++] = d; num >>= 4; } - } - } - if (base == base64) { - if (num < 0x40) { - buffer[start] = digits[num]; - return start + 1; - } else { + } else if (base == base64) { while (num != 0) { - backwards_digits[offset] = digits[num & 0x3F]; - offset++; + backwards_digits[offset++] = digits[num & 0x3F]; num >>= 6; } } } - if (offset > 0) { - for (unsigned int i = 0; i < offset; i++) { - buffer[start + i] = backwards_digits[offset - i - 1]; + + // Determine how many characters we will print before padding + unsigned int total_chars = static_cast(offset) + (is_negative ? 1U : 0U); + unsigned long current = start; + + // Space padding (leading_zero = false) goes before the sign + if (!leading_zero && width > total_chars) { + unsigned int padding = width - total_chars; + for (unsigned int i = 0; i < padding; i++) { + buffer[current++] = ' '; + } + } + + // Print the sign if negative + if (is_negative) { + buffer[current++] = '-'; + } + + // Zero padding (leading_zero = true) goes after the sign + if (leading_zero && width > total_chars) { + unsigned int padding = width - total_chars; + for (unsigned int i = 0; i < padding; i++) { + buffer[current++] = '0'; } } - return start + offset; + + // Print the digits in correct order (reverse of backwards_digits) + for (unsigned int i = 0; i < offset; i++) { + buffer[current++] = backwards_digits[offset - i - 1]; + } + + return current; } /// @brief Parses the number and prints it in the specified base in the buffer @@ -125,12 +125,13 @@ static unsigned long print_number(unsigned long start, char buffer[], T num, uns /// @param base The base to parse the number in /// @return The index at the exclusive end of the number template -static unsigned long print_signed_number(unsigned long start, char buffer[], T num, unsigned int base) { +static unsigned long print_signed_number(unsigned long start, char buffer[], T num, unsigned int base, unsigned int width = 0, bool leading_zero = false) { if (num < 0) { - buffer[start] = '-'; - return print_number(start + 1, buffer, -num, base); + // Safe absolute value conversion using uint64_t to avoid signed integer overflow on INT_MIN + uint64_t abs_val = static_cast(-static_cast(num)); + return print_number(start, buffer, abs_val, base, width, leading_zero, false, true); } else { - return print_number(start, buffer, num, base); + return print_number(start, buffer, static_cast(num), base, width, leading_zero, false, false); } } @@ -145,6 +146,25 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va if (*format == '%') { format++; + // Handle leading zero modifier + bool leading_zero = false; + if (*format == '0') { + leading_zero = true; + format++; + } + + // Handle width modifier + unsigned int width = 0; + if (is_digit(*format)) { + while (is_digit(*format)) { + width = width * 10U + static_cast(*format - '0'); + format++; + } + if (width > digit_places_limit) { + width = digit_places_limit; + } + } + // Handle %l modifiers bool longlong_modifier = false; bool long_modifier = false; @@ -177,27 +197,6 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va format++; } - // Handle leading zero modifier - // bool leading_zero = false; - // if (*format == '0') { - // leading_zero = true; - // format++; - // } - // bool specific_digits = false; - // if (is_digit(*format)) { - // specific_digits = true; - // unsigned int digits = 0; - // while (is_digit(*format)) { - // digits = digits * 10 + (*format - '0'); - // format++; - // } - // if (digits > digit_places_limit) { - // digits = digit_places_limit; - // } - // } - // (void)leading_zero; // @TODO unused - // (void)specific_digits; // @TODO unused - switch (*format) { case 's': { const char *str = va_arg(args, const char *); @@ -212,26 +211,22 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va if (longlong_modifier) { int64_t num = va_arg(args, int64_t); num = clamp_to_range(num, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_signed_number(index, buffer, num, base10); + index = print_signed_number(index, buffer, num, base10, width, leading_zero); } else if (long_modifier) { int32_t num = va_arg(args, int32_t); num = clamp_to_range(num, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_signed_number(index, buffer, num, base10); + index = print_signed_number(index, buffer, num, base10, width, leading_zero); } else if (half_modifier) { int value = static_cast(va_arg(args, int)); int16_t num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_signed_number(index, buffer, num, base10); + index = print_signed_number(index, buffer, num, base10, width, leading_zero); } else if (halfhalf_modifier) { int value = va_arg(args, int); int8_t num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_signed_number(index, buffer, num, base10); - // } else if (size_modifier) { - // ssize_t value = va_arg(args, ssize_t); - // num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); - // index = print_signed_number(index, buffer, num, base10); + index = print_signed_number(index, buffer, num, base10, width, leading_zero); } else { auto num = va_arg(args, int); - index = print_signed_number(index, buffer, num, base10); + index = print_signed_number(index, buffer, num, base10, width, leading_zero); } break; } @@ -239,51 +234,76 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va if (longlong_modifier) { uint64_t num = va_arg(args, uint64_t); num = clamp_to_range(num, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_number(index, buffer, num, base10); + index = print_number(index, buffer, num, base10, width, leading_zero); } else if (long_modifier) { uint32_t num = va_arg(args, uint32_t); num = clamp_to_range(num, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_number(index, buffer, num, base10); + index = print_number(index, buffer, num, base10, width, leading_zero); } else if (half_modifier) { unsigned int value = va_arg(args, unsigned int); uint16_t num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_number(index, buffer, num, base10); + index = print_number(index, buffer, num, base10, width, leading_zero); } else if (halfhalf_modifier) { unsigned int value = va_arg(args, unsigned int); uint8_t num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_number(index, buffer, num, base10); + index = print_number(index, buffer, num, base10, width, leading_zero); } else if (size_modifier) { size_t num = va_arg(args, size_t); - index = print_number(index, buffer, num, base10); + index = print_number(index, buffer, num, base10, width, leading_zero); } else { unsigned int num = va_arg(args, unsigned int); - index = print_number(index, buffer, num, base10); + index = print_number(index, buffer, num, base10, width, leading_zero); } break; } case 'x': { buffer[index++] = '0'; buffer[index++] = 'x'; + unsigned int sub_width = (width > 2) ? (width - 2) : 0; if (longlong_modifier) { uint64_t num = va_arg(args, uint64_t); - index = print_number(index, buffer, num, base16); + index = print_number(index, buffer, num, base16, sub_width, leading_zero, false); } else if (long_modifier) { - uint32_t num = va_arg(args, uint32_t); // 32 bits - index = print_number(index, buffer, num, base16); + uint32_t num = va_arg(args, uint32_t); + index = print_number(index, buffer, num, base16, sub_width, leading_zero, false); } else if (half_modifier) { unsigned int value = va_arg(args, unsigned int); uint16_t num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_number(index, buffer, num, base16); + index = print_number(index, buffer, num, base16, sub_width, leading_zero, false); } else if (halfhalf_modifier) { unsigned int value = va_arg(args, unsigned int); uint8_t num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); - index = print_number(index, buffer, num, base16); + index = print_number(index, buffer, num, base16, sub_width, leading_zero, false); } else if (size_modifier) { size_t num = va_arg(args, size_t); - index = print_number(index, buffer, num, base16); + index = print_number(index, buffer, num, base16, sub_width, leading_zero, false); } else { uint32_t num = va_arg(args, unsigned int); - index = print_number(index, buffer, num, base16); + index = print_number(index, buffer, num, base16, sub_width, leading_zero, false); + } + break; + } + case 'X': { + if (longlong_modifier) { + uint64_t num = va_arg(args, uint64_t); + index = print_number(index, buffer, num, base16, width, leading_zero, true); + } else if (long_modifier) { + uint32_t num = va_arg(args, uint32_t); + index = print_number(index, buffer, num, base16, width, leading_zero, true); + } else if (half_modifier) { + unsigned int value = va_arg(args, unsigned int); + uint16_t num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); + index = print_number(index, buffer, num, base16, width, leading_zero, true); + } else if (halfhalf_modifier) { + unsigned int value = va_arg(args, unsigned int); + uint8_t num = clamp_to_range(value, std::numeric_limits::min(), std::numeric_limits::max()); + index = print_number(index, buffer, num, base16, width, leading_zero, true); + } else if (size_modifier) { + size_t num = va_arg(args, size_t); + index = print_number(index, buffer, num, base16, width, leading_zero, true); + } else { + uint32_t num = va_arg(args, unsigned int); + index = print_number(index, buffer, num, base16, width, leading_zero, true); } break; } @@ -321,7 +341,8 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va } buffer[index++] = '0'; buffer[index++] = 'b'; - index = print_number(index, buffer, num, base2); + unsigned int sub_width = (width > 2) ? (width - 2) : 0; + index = print_number(index, buffer, num, base2, sub_width, leading_zero, false); break; } default: @@ -336,8 +357,6 @@ unsigned long vsnprint(char buffer[], size_t buffer_size, const char *format, va format++; } - // FIXME determine if we've run over. - // null terminate the string if (index >= buffer_size) { index = buffer_size - 1; diff --git a/modules/core/tests/CMakeLists.txt b/modules/core/tests/CMakeLists.txt index 1072d3e..2207aa4 100644 --- a/modules/core/tests/CMakeLists.txt +++ b/modules/core/tests/CMakeLists.txt @@ -23,6 +23,7 @@ host_unit_test(NAME core ${CMAKE_CURRENT_SOURCE_DIR}/catch2-tokenizer.cpp # ${CMAKE_CURRENT_SOURCE_DIR}/catch2-units.cpp # requires information from the Configuration and Architecture ${CMAKE_CURRENT_SOURCE_DIR}/catch2-variants.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/catch2-vsnprint.cpp ${CMAKE_CURRENT_SOURCE_DIR}/source/conversions.cpp ${_GENERATED_CORE_TESTS} LIBRARIES diff --git a/modules/core/tests/catch2-vsnprint.cpp b/modules/core/tests/catch2-vsnprint.cpp new file mode 100644 index 0000000..beae3bc --- /dev/null +++ b/modules/core/tests/catch2-vsnprint.cpp @@ -0,0 +1,78 @@ +#include +#include "core/vsnprint.hpp" +#include +#include + +static unsigned long test_vsnprint(char buffer[], size_t buffer_size, const char* format, ...) { + va_list args; + va_start(args, format); + unsigned long result = core::vsnprint(buffer, buffer_size, format, args); + va_end(args); + return result; +} + +TEST_CASE("vsnprint formatting checks") { + char buffer[128]; + + SECTION("Normal string without formatting") { + unsigned long len = test_vsnprint(buffer, sizeof(buffer), "Hello World"); + REQUIRE(std::string(buffer) == "Hello World"); + REQUIRE(len == 11); + } + + SECTION("Uppercase hex with modifiers and padding (%X)") { + unsigned long len = test_vsnprint(buffer, sizeof(buffer), "%02X", 0x0A); + REQUIRE(std::string(buffer) == "0A"); + REQUIRE(len == 2); + + len = test_vsnprint(buffer, sizeof(buffer), "%02X", 0x5); + REQUIRE(std::string(buffer) == "05"); + REQUIRE(len == 2); + + len = test_vsnprint(buffer, sizeof(buffer), "%X", 0xABC); + REQUIRE(std::string(buffer) == "ABC"); + REQUIRE(len == 3); + + len = test_vsnprint(buffer, sizeof(buffer), "%08X", 0x12AB); + REQUIRE(std::string(buffer) == "000012AB"); + REQUIRE(len == 8); + } + + SECTION("Lowercase hex with prepended 0x (%x)") { + unsigned long len = test_vsnprint(buffer, sizeof(buffer), "%x", 0x12AB); + REQUIRE(std::string(buffer) == "0x12ab"); + REQUIRE(len == 6); + + len = test_vsnprint(buffer, sizeof(buffer), "%08x", 0x12AB); + REQUIRE(std::string(buffer) == "0x0012ab"); // 0x + 6 padded hex digits = 8 chars + REQUIRE(len == 8); + } + + SECTION("Decimal signed/unsigned formatting with width and padding") { + unsigned long len = test_vsnprint(buffer, sizeof(buffer), "%d", -123); + REQUIRE(std::string(buffer) == "-123"); + REQUIRE(len == 4); + + len = test_vsnprint(buffer, sizeof(buffer), "%04d", -5); + REQUIRE(std::string(buffer) == "-005"); + REQUIRE(len == 4); + + len = test_vsnprint(buffer, sizeof(buffer), "%4d", -5); + REQUIRE(std::string(buffer) == " -5"); + REQUIRE(len == 4); + + len = test_vsnprint(buffer, sizeof(buffer), "%04u", 7); + REQUIRE(std::string(buffer) == "0007"); + REQUIRE(len == 4); + } + + SECTION("Binary formatting with prepended 0b (%b)") { + unsigned long len = test_vsnprint(buffer, sizeof(buffer), "%b", 5); + REQUIRE(std::string(buffer) == "0b101"); + REQUIRE(len == 5); + + len = test_vsnprint(buffer, sizeof(buffer), "%08b", 5); + REQUIRE(std::string(buffer) == "0b000101"); // 0b + 6 padded binary digits = 8 chars + REQUIRE(len == 8); + } +}