From 7b30a5425eaf7aae1d72d5ba564092e342901fe8 Mon Sep 17 00:00:00 2001 From: Dominick Allen Date: Fri, 25 Oct 2024 17:13:49 -0500 Subject: A lot of work on formatting. --- include/fud_assert.hpp | 4 + include/fud_format.hpp | 448 ++++++++++++++++++++++++++++++++----------------- source/fud_assert.cpp | 63 ++++--- source/fud_format.cpp | 64 +++++++ test/test_assert.cpp | 14 ++ test/test_format.cpp | 108 ++++++++++-- 6 files changed, 507 insertions(+), 194 deletions(-) diff --git a/include/fud_assert.hpp b/include/fud_assert.hpp index 6b21fdc..1b5fe9f 100644 --- a/include/fud_assert.hpp +++ b/include/fud_assert.hpp @@ -31,6 +31,10 @@ namespace fud { #define fudAssert(expr) ((expr) ? static_cast(0) : assertFail(#expr, std::source_location::current())) +namespace impl { +void assertFailMessage(const char* assertion, std::source_location sourceLocation); +} + } // namespace fud #endif diff --git a/include/fud_format.hpp b/include/fud_format.hpp index c42cd03..1f2e0ae 100644 --- a/include/fud_format.hpp +++ b/include/fud_format.hpp @@ -224,6 +224,7 @@ struct FormatHandle { using FormatArgument = std::variant< bool, + char, utf8, int32_t, uint32_t, @@ -233,6 +234,7 @@ using FormatArgument = std::variant< double, long double, const utf8*, + const char*, StringView, const void*, impl::FormatHandle>; @@ -287,6 +289,9 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Arg template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg); +template +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, char arg); + template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg); @@ -314,6 +319,9 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& for template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg); +template +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const char* arg); + template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg); @@ -328,6 +336,8 @@ namespace impl { template FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, const FormatArguments& args); +size_t findSpec(StringView scanView); + } // namespace impl template @@ -347,19 +357,22 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Arg template FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt) { - static_cast(sink); - static_cast(fmt); if (formatMode != FormatCharMode::Unchecked) { return {0, FudStatus::NotImplemented}; } - return {0, FudStatus::NotImplemented}; + auto scanView = fmt.view(); + auto specIndex = impl::findSpec(scanView); + if (specIndex < scanView.length()) { + return {0, FudStatus::FormatInvalid}; + } + + auto drainResult = sink.drain(fmt.view()); + return FormatResult{drainResult.bytesWritten, drainResult.status}; } namespace impl { -size_t findSpec(StringView scanView); - template Result getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments& args); @@ -448,26 +461,6 @@ FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, co return result; } -size_t findSpec(StringView scanView) -{ - size_t index = 0; - bool foundBracket = false; - while (index < scanView.length()) { - auto letter = scanView[index]; - if (letter == FormatSpec::openBracket && not foundBracket) { - foundBracket = true; - } else if (letter == FormatSpec::openBracket && foundBracket) { - foundBracket = false; - } else if (foundBracket) { - index--; - break; - } - index++; - } - - return index; -} - template Result getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments& args) { @@ -653,7 +646,7 @@ FudStatus vFormatPrepareNonPositionalSpec( template requires std::is_integral_v -[[nodiscard]] StringView validateFormatType( +[[nodiscard]] StringView validateIntegralFormatType( FormatResult& result, FormatType formatType, Radix& radix, @@ -710,95 +703,77 @@ template case FormatType::GeneralLower: case FormatType::GeneralUpper: result.status = FudStatus::FormatInvalid; + break; default: result.status = FudStatus::Failure; } return prefix; } -inline FudStatus fillUnsignedBuffer( +FudStatus fillUnsignedBuffer( Array& buffer, uint64_t value, uint8_t& bufferLength, Radix radix, + bool uppercase); + +template +FudStatus fillSignedBuffer( + Array& buffer, + T value, + uint8_t& bufferLength, + Radix radix, bool uppercase) { - static_assert(maxIntCharCount < std::numeric_limits::max()); - bufferLength = 0; - - constexpr Array(Radix::Hexadecimal)> lower{ - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}}; - constexpr Array(Radix::Hexadecimal)> upper{ - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}}; - const auto& lookup = uppercase ? upper : lower; - - if (value == 0) { - buffer[0] = '0'; - bufferLength = 1; - return FudStatus::Success; - } - - static_assert(std::is_same_v, uint8_t>); - auto radixValue = static_cast(radix); - while (value > 0 && bufferLength < maxIntCharCount) { - auto digit = static_cast(value % radixValue); - buffer[bufferLength] = lookup[digit]; - value /= radixValue; - bufferLength++; - } - - if (value > 0 || bufferLength > maxIntCharCount) { - return FudStatus::Failure; + static_assert(sizeof(T) <= sizeof(uint64_t)); + static_assert(std::is_signed_v); + if (value < 0) { + value++; + value = -value; + value++; } - - // TODO: implement fud_algorithm reverse - for (size_t idx = 0; idx < bufferLength / 2; ++idx) { - auto rhsIndex = bufferLength - idx - 1; - std::swap(buffer[idx], buffer[rhsIndex]); + if constexpr (std::is_same_v) { + return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); + } else if constexpr (std::is_same_v) { + return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); + } else if constexpr (std::is_same_v) { + return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); + } else if constexpr (std::is_same_v) { + return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); + } else if constexpr (std::is_same_v) { + return fillUnsignedBuffer(buffer, static_cast(value), bufferLength, radix, uppercase); } - - return FudStatus::Success; } template -[[nodiscard]] FormatResult fillPad(Sink& sink, utf8 fill, size_t count) -{ - FormatResult result{0, FudStatus::Success}; - constexpr size_t BigBlockSize = 256; - constexpr size_t MediumBlockSize = 64; - constexpr size_t SmallBlockSize = 16; - constexpr size_t SmallestBlockSize = 4; - - auto backingBuffer = Array::constFill(fill); - - constexpr auto bufferSizes{Array{{BigBlockSize, MediumBlockSize, SmallBlockSize, SmallestBlockSize}}}; - - for (auto bufferSize : bufferSizes) { - while (count > bufferSize) { - auto drainResult = sink.drain(StringView{bufferSize, backingBuffer.data()}); - result.bytesWritten += drainResult.bytesWritten; - result.status = drainResult.status; - if (result.status != FudStatus::Success) { - return result; - } - count -= result.bytesWritten; - } - } +[[nodiscard]] FormatResult fillPad(Sink& sink, utf8 fill, size_t count); - if (count > 0) { - fudAssert(count < backingBuffer.size()); - auto drainResult = sink.drain(StringView{count, backingBuffer.data()}); - result.bytesWritten += drainResult.bytesWritten; - result.status = drainResult.status; - } +template +FormatResult drainIntegral( + Sink& sink, + StringView buffer, + FormatAlign::Value align, + utf8 fill, + size_t width, + StringView sign, + StringView prefix, + bool padZero); - return result; -} +template +FudStatus fillIntegralBuffer( + FormatType formatType, + Array& buffer, + uint8_t& bufferLength, + T arg, + Radix radix, + bool uppercase); template - requires std::is_integral_v and std::is_scalar_v /* and std::is_unsigned_v */ -[[nodiscard]] FormatResult formatUnsigned(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg) + requires std::is_integral_v and std::is_scalar_v +[[nodiscard]] FormatResult formatIntegral(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg) { + static_cast(formatMode); + FormatResult result{0, FudStatus::Success}; if (formatSpec.takesPrecision || formatSpec.precision != FormatSpec::precisionUnspecified) { result.status = FudStatus::FormatInvalid; @@ -820,32 +795,23 @@ template auto radix = Radix::Decimal; bool uppercase = false; - auto prefix = validateFormatType(result, formatSpec.formatType, radix, uppercase, arg); + auto prefix = validateIntegralFormatType(result, formatSpec.formatType, radix, uppercase, arg); if (result.status != FudStatus::Success) { return result; } - if (formatSpec.formatType == FormatType::Character) { - return format(sink, formatMode, formatSpec, static_cast(arg)); - } - - if (not formatSpec.alternateForm) { - prefix = StringView{""}; - } - - auto calculatedWidth = prefix.length(); - Array buffer{}; uint8_t bufferLength = 0; - // TODO: constexpr if for signed values - result.status = fillUnsignedBuffer(buffer, static_cast(arg), bufferLength, radix, uppercase); + result.status = fillIntegralBuffer(formatSpec.formatType, buffer, bufferLength, arg, radix, uppercase); if (result.status != FudStatus::Success) { return result; } - calculatedWidth += bufferLength; + if (not formatSpec.alternateForm) { + prefix = StringView{""}; + } StringView sign{""}; switch (formatSpec.formatSign) { @@ -865,20 +831,69 @@ template } break; } - calculatedWidth += sign.length(); - if (align == FormatAlign::Value::Right and width > calculatedWidth) { - auto leftPadSize = width - calculatedWidth; - auto padResult = fillPad(sink, fill, leftPadSize); - result.bytesWritten += padResult.bytesWritten; - result.status = padResult.status; - } else if (align == FormatAlign::Value::Center and width > calculatedWidth) { - auto leftPadSize = (width - calculatedWidth) / 2; - auto padResult = fillPad(sink, fill, leftPadSize); - result.bytesWritten += padResult.bytesWritten; - result.status = padResult.status; + return drainIntegral(sink, StringView{bufferLength, buffer.data()}, align, fill, width, sign, prefix, padZero); +} + +template +FudStatus fillIntegralBuffer( + FormatType formatType, + Array& buffer, + uint8_t& bufferLength, + T arg, + Radix radix, + bool uppercase) +{ + if constexpr (std::is_same_v) { + if (formatType == FormatType::Unspecified) { + formatType = FormatType::Character; + } } + if (formatType == FormatType::Character) { + // return format(sink, formatMode, formatSpec, static_cast(arg)); + if constexpr (std::is_signed_v) { + if (arg < 0) { + return FudStatus::FormatInvalid; + } + } + if (arg > std::numeric_limits::max()) { + return FudStatus::FormatInvalid; + } + bufferLength = 1; + buffer[0] = static_cast(arg); + return FudStatus::Success; + } + + // TODO: constexpr if for signed values + static_assert(not std::is_signed_v); + static_assert(std::is_signed_v); + if constexpr (std::is_signed_v) { + return fillSignedBuffer(buffer, arg, bufferLength, radix, uppercase); + } + + return fillUnsignedBuffer(buffer, static_cast(arg), bufferLength, radix, uppercase); +} + +template +FormatResult leftPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 fill, size_t calculatedWidth); + +template +FormatResult rightPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 fill, size_t calculatedWidth); + +template +FormatResult drainIntegral( + Sink& sink, + StringView buffer, + FormatAlign::Value align, + utf8 fill, + size_t width, + StringView sign, + StringView prefix, + bool padZero) +{ + const auto calculatedWidth = buffer.length() + prefix.length() + sign.length(); + auto result = leftPad(sink, align, width, fill, calculatedWidth); if (result.status != FudStatus::Success) { return result; } @@ -894,7 +909,7 @@ template } if (prefix.length() > 0) { - auto drainResult = sink.drain(sign); + auto drainResult = sink.drain(prefix); result.bytesWritten += drainResult.bytesWritten; result.status = drainResult.status; } @@ -914,7 +929,7 @@ template return result; } - auto drainNumberResult = sink.drain(StringView{bufferLength, buffer.data()}); + auto drainNumberResult = sink.drain(buffer); result.bytesWritten += drainNumberResult.bytesWritten; result.status = drainNumberResult.status; @@ -922,20 +937,76 @@ template return result; } + auto rightPadResult = rightPad(sink, align, width, fill, calculatedWidth); + result.bytesWritten += rightPadResult.bytesWritten; + result.status = rightPadResult.status; + + return result; +} + +template +[[nodiscard]] FormatResult fillPad(Sink& sink, utf8 fill, size_t count) +{ + FormatResult result{0, FudStatus::Success}; + constexpr size_t BigBlockSize = 256; + constexpr size_t MediumBlockSize = 64; + constexpr size_t SmallBlockSize = 16; + constexpr size_t SmallestBlockSize = 4; + + auto backingBuffer = Array::constFill(fill); + + constexpr auto bufferSizes{Array{{BigBlockSize, MediumBlockSize, SmallBlockSize, SmallestBlockSize}}}; + + for (auto bufferSize : bufferSizes) { + while (count > bufferSize) { + auto drainResult = sink.drain(StringView{bufferSize, backingBuffer.data()}); + result.bytesWritten += drainResult.bytesWritten; + result.status = drainResult.status; + if (result.status != FudStatus::Success) { + return result; + } + count -= result.bytesWritten; + } + } + + if (count > 0) { + fudAssert(count < backingBuffer.size()); + auto drainResult = sink.drain(StringView{count, backingBuffer.data()}); + result.bytesWritten += drainResult.bytesWritten; + result.status = drainResult.status; + } + + return result; +} + +template +FormatResult leftPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 fill, size_t calculatedWidth) +{ + if (align == FormatAlign::Value::Right and width > calculatedWidth) { + auto leftPadSize = width - calculatedWidth; + return fillPad(sink, fill, leftPadSize); + } + if (align == FormatAlign::Value::Center and width > calculatedWidth) { + auto leftPadSize = (width - calculatedWidth) / 2; + return fillPad(sink, fill, leftPadSize); + } + return FormatResult{0, FudStatus::Success}; +} + +template +FormatResult rightPad(Sink& sink, FormatAlign::Value align, size_t width, utf8 fill, size_t calculatedWidth) +{ + if (align == FormatAlign::Value::Left and width > calculatedWidth) { auto rightPadSize = width - calculatedWidth; - auto padResult = fillPad(sink, fill, rightPadSize); - result.bytesWritten += padResult.bytesWritten; - result.status = padResult.status; - } else if (align == FormatAlign::Value::Center and width > calculatedWidth) { + return fillPad(sink, fill, rightPadSize); + } + if (align == FormatAlign::Value::Center and width > calculatedWidth) { auto leftPadSize = (width - calculatedWidth) / 2; - auto rightPadSize = width - leftPadSize; - auto padResult = fillPad(sink, fill, rightPadSize); - result.bytesWritten += padResult.bytesWritten; - result.status = padResult.status; + auto rightPadSize = width - calculatedWidth - leftPadSize; + return fillPad(sink, fill, rightPadSize); } - - return result; + return FormatResult{0, FudStatus::Success}; } } // namespace impl @@ -943,47 +1014,52 @@ template template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg) { - FormatResult result{0, FudStatus::NotImplemented}; - static_cast(sink); - static_cast(formatMode); - static_cast(formatSpec); - static_cast(arg); - return result; + FormatSpec boolSpec = formatSpec; + if (formatSpec.formatType == FormatType::Unspecified || formatSpec.formatType == FormatType::String) { + StringView view{"true"}; + if (not arg) { + view = StringView{"false"}; + } + return format(sink, formatMode, boolSpec, view); + } + boolSpec.formatType = FormatType::Unspecified; + return impl::formatIntegral(sink, formatMode, boolSpec, static_cast(arg)); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg) { - FormatResult result{0, FudStatus::NotImplemented}; - static_cast(sink); - static_cast(formatMode); - static_cast(formatSpec); - static_cast(arg); - return result; + return impl::formatIntegral(sink, formatMode, formatSpec, arg); +} + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, char arg) +{ + return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int32_t arg) { - return impl::formatUnsigned(sink, formatMode, formatSpec, arg); + return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint32_t arg) { - return impl::formatUnsigned(sink, formatMode, formatSpec, arg); + return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int64_t arg) { - return impl::formatUnsigned(sink, formatMode, formatSpec, arg); + return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint64_t arg) { - return impl::formatUnsigned(sink, formatMode, formatSpec, arg); + return impl::formatIntegral(sink, formatMode, formatSpec, arg); } template @@ -1022,22 +1098,79 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& for template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg) { - FormatResult result{0, FudStatus::NotImplemented}; - static_cast(sink); - static_cast(formatMode); - static_cast(formatSpec); - static_cast(arg); - return result; + return format(sink, formatMode, formatSpec, reinterpret_cast(arg)); +} + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const char* arg) +{ + if (arg == nullptr) { + return {0, FudStatus::NullPointer}; + } + auto stringLengthResult = cStringLength(arg); + if (stringLengthResult < 0 || stringLengthResult >= SSIZE_MAX) { + return {0, FudStatus::StringInvalid}; + } + auto stringLength = static_cast(stringLengthResult); + return format(sink, formatMode, formatSpec, StringView{stringLength, arg}); } template FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg) { - FormatResult result{0, FudStatus::NotImplemented}; - static_cast(sink); - static_cast(formatMode); - static_cast(formatSpec); - static_cast(arg); + /* TODO: implement unicode points, check with formatMode */ + + FormatResult result{0, FudStatus::Success}; + if (formatMode != FormatCharMode::Unchecked) { + result.status = FudStatus::NotSupported; + return result; + } + + if (formatSpec.formatType != FormatType::Unspecified && formatSpec.formatType != FormatType::String && + formatSpec.formatType != FormatType::Escaped) { + result.status = FudStatus::FormatInvalid; + return result; + } + + if (formatSpec.leadingZero) { + result.status = FudStatus::FormatInvalid; + return result; + } + + auto widthSpecified = formatSpec.width != FormatSpec::widthUnspecified; + auto width = widthSpecified ? formatSpec.width : 0; + auto precisionSpecified = formatSpec.precision != FormatSpec::precisionUnspecified; + if (precisionSpecified and formatSpec.precision < arg.length()) { + arg.m_length = formatSpec.precision; + } + auto calculatedWidth = arg.length(); + + auto align = formatSpec.fill.align.value; + auto fill = formatSpec.fill.fill; + if (align == FormatAlign::Value::Default) { + align = FormatAlign::Value::Left; + } + + auto padResult = impl::leftPad(sink, align, width, fill, calculatedWidth); + result.bytesWritten += padResult.bytesWritten; + result.status = padResult.status; + + if (result.status != FudStatus::Success) { + return result; + } + + auto drainViewResult = sink.drain(arg); + result.bytesWritten += drainViewResult.bytesWritten; + result.status = drainViewResult.status; + + if (result.status != FudStatus::Success) { + return result; + } + + padResult = impl::rightPad(sink, align, width, fill, calculatedWidth); + result.bytesWritten += padResult.bytesWritten; + result.status = padResult.status; + return result; } @@ -1064,7 +1197,6 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& for } namespace fudTest { - inline void test() { StringView prefix{0, ""}; diff --git a/source/fud_assert.cpp b/source/fud_assert.cpp index 8811464..0c4d7cf 100644 --- a/source/fud_assert.cpp +++ b/source/fud_assert.cpp @@ -1,39 +1,56 @@ #include "fud_assert.hpp" -// #include "fud_array.hpp" -// #include "fud_format.hpp" +#include "fud_format.hpp" +#include "fud_string.hpp" #include #include #include -// #include namespace fud { -// constexpr std::size_t BITS_PER_OCTAL = 3; -// constexpr auto MAX_LINE_CHARS = BITS_PER_OCTAL * sizeof(decltype(std::source_location{}.line())) + 3; +struct BufferSink { + DrainResult drain(StringView source); +}; -[[noreturn]] void assertFail(const char* assertion, const std::source_location sourceLocation) +DrainResult BufferSink::drain(StringView source) +{ + DrainResult result{0, FudStatus::Success}; + if (source.m_data == nullptr) { + result.status = FudStatus::NullPointer; + return result; + } + if (source.m_length == 0) { + result.status = FudStatus::Success; + return result; + } + result.bytesWritten = fwrite(reinterpret_cast(source.m_data), 1, source.m_length, stderr); + if (result.bytesWritten != source.m_length) { + result.status = FudStatus::Full; + } + return result; +} + +namespace impl { +void assertFailMessage(const char* assertion, const std::source_location sourceLocation) { - const char* file_name = sourceLocation.file_name(); - if (file_name == nullptr) { - fputs("Unknown file", stderr); - } else { - fputs(file_name, stderr); + BufferSink sink; + const char* fileName = sourceLocation.file_name(); + if (fileName == nullptr) { + fileName = "Unknown file"; + } + const char* functionName = sourceLocation.function_name(); + if (functionName == nullptr) { + functionName = "Unknown Function"; } + format(sink, FormatCharMode::Unchecked, "{}:{}:{}: {}\n", fileName, functionName, sourceLocation.line(), assertion); +} - /* - constexpr std::size_t assertMsgSize = MAX_LINE_CHARS + 3; - Array buffer{}; - static_cast(std::format_to_n(buffer.data(), buffer.size() - 1U, ":{}:", sourceLocation.line())); - buffer[buffer.size() - 1] = '\0'; - fputs(buffer.data(), stderr); - */ - - fputs(sourceLocation.function_name(), stderr); - fputs(": ", stderr); - fputs(assertion, stderr); - fputc('\n', stderr); +} // namespace impl + +[[noreturn]] void assertFail(const char* assertion, const std::source_location sourceLocation) +{ + impl::assertFailMessage(assertion, sourceLocation); std::terminate(); } diff --git a/source/fud_format.cpp b/source/fud_format.cpp index 6bd5aae..f3e6894 100644 --- a/source/fud_format.cpp +++ b/source/fud_format.cpp @@ -507,6 +507,70 @@ FudStatus getFormatSign(StringView& formatView, FormatSpec& spec) return FudStatus::Success; } +size_t findSpec(StringView scanView) +{ + size_t index = 0; + bool foundBracket = false; + while (index < scanView.length()) { + auto letter = scanView[index]; + if (letter == FormatSpec::openBracket && not foundBracket) { + foundBracket = true; + } else if (letter == FormatSpec::openBracket && foundBracket) { + foundBracket = false; + } else if (foundBracket) { + index--; + break; + } + index++; + } + + return index; +} + +FudStatus fillUnsignedBuffer( + Array& buffer, + uint64_t value, + uint8_t& bufferLength, + Radix radix, + bool uppercase) +{ + static_assert(maxIntCharCount < std::numeric_limits::max()); + bufferLength = 0; + + constexpr Array(Radix::Hexadecimal)> lower{ + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}}; + constexpr Array(Radix::Hexadecimal)> upper{ + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}}; + const auto& lookup = uppercase ? upper : lower; + + if (value == 0) { + buffer[0] = '0'; + bufferLength = 1; + return FudStatus::Success; + } + + static_assert(std::is_same_v, uint8_t>); + auto radixValue = static_cast(radix); + while (value > 0 && bufferLength < maxIntCharCount) { + auto digit = static_cast(value % radixValue); + buffer[bufferLength] = lookup[digit]; + value /= radixValue; + bufferLength++; + } + + if (value > 0 || bufferLength > maxIntCharCount) { + return FudStatus::Failure; + } + + // TODO: implement fud_algorithm reverse + for (size_t idx = 0; idx < bufferLength / 2; ++idx) { + auto rhsIndex = bufferLength - idx - 1; + std::swap(buffer[idx], buffer[rhsIndex]); + } + + return FudStatus::Success; +} + } // namespace impl } // namespace fud diff --git a/test/test_assert.cpp b/test/test_assert.cpp index 1f95388..5129b97 100644 --- a/test/test_assert.cpp +++ b/test/test_assert.cpp @@ -18,6 +18,7 @@ #include "fud_assert.hpp" #include "gtest/gtest.h" +#include "gmock/gmock.h" namespace fud { @@ -26,4 +27,17 @@ TEST(AssertTest, AssertFud) EXPECT_EXIT(fudAssert(false), ::testing::KilledBySignal(SIGABRT), ".*"); } +TEST(AssertTest, AssertFudMessage) +{ + testing::internal::CaptureStderr(); + auto sourceLoc = std::source_location::current(); + constexpr const char* assertMessage = "Artificial Message"; + impl::assertFailMessage(assertMessage, sourceLoc); + auto message = testing::internal::GetCapturedStderr(); + EXPECT_THAT(message, testing::HasSubstr(sourceLoc.file_name())); + EXPECT_THAT(message, testing::HasSubstr(sourceLoc.function_name())); + EXPECT_THAT(message, testing::HasSubstr(std::to_string(sourceLoc.line()))); + EXPECT_THAT(message, testing::HasSubstr(assertMessage)); +} + } // namespace fud diff --git a/test/test_format.cpp b/test/test_format.cpp index 9c95330..4a93d98 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -411,30 +411,112 @@ TEST(FormatTest, FormatTest) EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); } -TEST(FormatTest, OneArgFormatTest) +TEST(FormatTest, NoArgFormatTest) { String sink{}; - auto formatResult = format(sink, FormatCharMode::Unchecked, "Test {:}", 42U); + auto expected = std::format("Test"); + auto formatResult = format(sink, FormatCharMode::Unchecked, "Test"); EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); ASSERT_EQ(sink.clear(), FudStatus::Success); - ASSERT_EQ(sink.drain(StringView{4, "TEST"}).bytesWritten, 4); - ASSERT_TRUE(sink.valid()); - ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format(""); + formatResult = format(sink, FormatCharMode::Unchecked, ""); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); +} +TEST(FormatTest, OneNumberFormatTest) +{ + String sink{}; + auto formatResult = format(sink, FormatCharMode::Unchecked, "Test {:}", 42U); + EXPECT_TRUE(formatResult.isOkay()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); auto expected = std::format("{:6}", 42U); formatResult = format(sink, FormatCharMode::Unchecked, "{:6}", 42U); EXPECT_TRUE(formatResult.isOkay()); EXPECT_STREQ(sink.c_str(), expected.c_str()); - /* - EXPECT_EQ(std::format("{:6}", 'x') , "x "); - EXPECT_EQ(std::format("{:*<6}", 'x') , "x*****"); - EXPECT_EQ(std::format("{:*>6}", 'x') , "*****x"); - EXPECT_EQ(std::format("{:*^6}", 'x') , "**x***"); - EXPECT_EQ(std::format("{:6d}", c) , " 120"); - EXPECT_EQ(std::format("{:6}", true) , "true "); - */ + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:*<6}", std::numeric_limits::max()); + formatResult = format(sink, FormatCharMode::Unchecked, "{:*<6}", std::numeric_limits::max()); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:*<6}", std::numeric_limits::min()); + formatResult = format(sink, FormatCharMode::Unchecked, "{:*<6}", std::numeric_limits::min()); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:}", std::numeric_limits::min()); + formatResult = format(sink, FormatCharMode::Unchecked, "{:}", std::numeric_limits::min()); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:#X}", std::numeric_limits::min()); + formatResult = format(sink, FormatCharMode::Unchecked, "{:#X}", std::numeric_limits::min()); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:*<6}", 42U); + formatResult = format(sink, FormatCharMode::Unchecked, "{:*<6}", 42U); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:*>6}", 42U); + formatResult = format(sink, FormatCharMode::Unchecked, "{:*>6}", 42U); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:*^6}", 42U); + formatResult = format(sink, FormatCharMode::Unchecked, "{:*^6}", 42U); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:*<6}", 'x'); + formatResult = format(sink, FormatCharMode::Unchecked, "{:*<6}", 'x'); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("{:6d}", 'x'); + formatResult = format(sink, FormatCharMode::Unchecked, "{:6d}", 'x'); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); +} + +TEST(FormatTest, OneBoolFormatTest) +{ + String sink{}; + + ASSERT_EQ(sink.clear(), FudStatus::Success); + auto expected = std::format("{:6}", true); + auto formatResult = format(sink, FormatCharMode::Unchecked, "{:6}", true); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); +} + +TEST(FormatTest, TwoArgFormatTest) +{ + String sink{}; + auto expected = std::format("Test {:} {:}", 1U, false); + auto formatResult = format(sink, FormatCharMode::Unchecked, "Test {:} {:}", 1U, false); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); + + ASSERT_EQ(sink.clear(), FudStatus::Success); + expected = std::format("Test {:} {:}", 1U, "Hello"); + formatResult = format(sink, FormatCharMode::Unchecked, "Test {:} {:}", 1U, "Hello"); + EXPECT_TRUE(formatResult.isOkay()); + EXPECT_STREQ(sink.c_str(), expected.c_str()); } } // namespace fud -- cgit v1.2.3