From 512d026de016f2720060d264adec02e56123851d Mon Sep 17 00:00:00 2001 From: Dominick Allen Date: Thu, 24 Oct 2024 09:04:35 -0500 Subject: As always, formatting is a pain. --- include/fud_format.hpp | 531 +++++++++++++++++++++++++++++++++++++------- include/fud_result.hpp | 32 +++ include/fud_string_view.hpp | 2 + source/fud_assert.cpp | 4 +- source/fud_format.cpp | 7 +- test/test_format.cpp | 173 +++++++++++---- 6 files changed, 617 insertions(+), 132 deletions(-) diff --git a/include/fud_format.hpp b/include/fud_format.hpp index 2576eca..1d09422 100644 --- a/include/fud_format.hpp +++ b/include/fud_format.hpp @@ -19,20 +19,37 @@ #define FUD_FORMAT_HPP // #include "fud_assert.hpp" +#include "fud_option.hpp" #include "fud_result.hpp" #include "fud_status.hpp" +#include "fud_string.hpp" +#include "fud_string_convert.hpp" #include "fud_string_view.hpp" -#include "fud_option.hpp" #include "fud_utf8.hpp" #include -#include #include +#include namespace fud { -struct FormatAlign -{ +struct FormatString { + template + consteval FormatString(const char (&input)[N]) : m_size{N - 1}, m_data{input} + { + static_assert(N > 0); + } + + StringView view() + { + return StringView{m_size, reinterpret_cast(m_data)}; + } + + size_t m_size; + const char* m_data; +}; + +struct FormatAlign { enum class Value : utf8 { Left = '<', @@ -41,7 +58,8 @@ struct FormatAlign Default = std::numeric_limits::max() }; - constexpr static FormatAlign makeDefault() noexcept { + constexpr static FormatAlign makeDefault() noexcept + { return {Value::Default}; } @@ -65,7 +83,8 @@ struct FormatAlign return formatAlign; } - Value operator()() const { + Value operator()() const + { return value; } @@ -76,11 +95,13 @@ struct FormatFill { FormatAlign align; utf8 fill; - constexpr static FormatFill make() noexcept { - return FormatFill {FormatAlign::makeDefault(), ' '}; + constexpr static FormatFill make() noexcept + { + return FormatFill{FormatAlign::makeDefault(), ' '}; } - constexpr static Result, FudStatus> make(StringView formatView, size_t& length) noexcept { + constexpr static Result, FudStatus> make(StringView formatView, size_t& length) noexcept + { using RetType = Result, FudStatus>; if (formatView.length() < 1) { return RetType::okay(NullOpt); @@ -122,74 +143,8 @@ enum class FormatSign : uint8_t Default = std::numeric_limits::max() }; -enum class FormatStringType : uint8_t -{ - String, - Escaped, -}; - -enum class FormatIntegerType : uint8_t -{ - BinaryLower, - BinaryUpper, - Character, - Decimal, - Octal, - HexLower, - HexUpper, -}; - -enum class FormatCharacterType : uint8_t -{ - BinaryLower, - BinaryUpper, - Character, - Decimal, - Octal, - HexLower, - HexUpper, -}; - -enum class FormatBoolType : uint8_t -{ - BinaryLower, - BinaryUpper, - Character, - Decimal, - Octal, - HexLower, - HexUpper, -}; - -enum class FormatFloatingType : uint8_t +enum class FormatType : utf8 { - FloatHexLower, - FloatHexUpper, - ScientificLower, - ScientificUpper, - Fixed, - GeneralLower, - GeneralUpper -}; - -enum class FormatPointerType : uint8_t -{ - HexLower, - HexUpper -}; - -/* -using FormatType = std::variant< // break - std::monostate, - FormatStringType, - FormatIntegerType, - FormatCharacterType, - FormatBoolType, - FormatFloatingType, - FormatPointerType>; -*/ - -enum class FormatType : utf8 { Unspecified = '\0', String = 's', Escaped = '?', @@ -234,19 +189,431 @@ struct FormatSpec { FormatType formatType{}; - bool takesWidth{false}; + bool takesWidth : 1 = false; + + bool takesPrecision : 1 = false; + + bool alternateForm : 1 = false; - bool takesPrecision{false}; + bool leadingZero : 1 = false; - bool alternateForm{false}; + bool hasLocale : 1 = false; - bool leadingZero{false}; + [[nodiscard]] constexpr bool takesPosition() const { + return position == positionUnspecified; + } + + static Result parse(StringView formatView, size_t& specLength); +}; + +namespace impl { + +struct FormatHandle { + void* object; + auto operator<=>(const FormatHandle& rhs) const noexcept = default; +}; + +} // namespace impl - bool hasLocale{false}; +using FormatArgument = std::variant< + std::monostate, + bool, + utf8, + int32_t, + uint32_t, + int64_t, + uint64_t, + float, + double, + long double, + const utf8*, + StringView, + const void*, + impl::FormatHandle>; + +template +struct FormatArguments { + Array arguments; + + constexpr size_t size() const + { + return Size; + } - static Result make(StringView formatView, size_t& specLength); + template + static auto makeFormatArguments(Args&&... args) -> FormatArguments + { + return FormatArguments{Array{{FormatArgument{args}...}}}; + } +}; + +static_assert(sizeof(FormatArguments<1>) > 0); + +enum class FormatCharMode +{ + Unchecked, + AsciiOnly, + ValidUtf8 +}; + +struct FormatResult { + size_t bytesWritten{0}; + FudStatus status{FudStatus::Failure}; + [[nodiscard]] constexpr bool isOkay() + { + return status == FudStatus::Success; + } }; +/* TODO : require concept of a sink that takes pushBack() -> FudStatus */ +/* TODO : sink concept also requires append() -> FudStatus */ +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Args&&... args); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, bool arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, utf8 arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, int32_t arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, uint32_t arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, int64_t arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, uint64_t arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, float arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, double arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, long double arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, const utf8* arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, StringView arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, const void* arg); + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, impl::FormatHandle arg); + +namespace impl { + +Result validateSpecHelper(FormatString fmt); + +struct FormatInfo { + uint16_t numArgs{0}; + uint16_t maxArg{0}; + bool positional{false}; +}; + +Result validateSpecHelperV2(FormatString fmt); + +template +FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, FormatArguments& args); + +} // namespace impl + +template +FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Args&&... args) +{ + static_cast(sink); + if (formatMode != FormatCharMode::Unchecked) { + return {0, FudStatus::NotImplemented}; + } + + constexpr size_t numArgs = sizeof...(args); + auto formatArguments{FormatArguments::makeFormatArguments(std::forward(args)...)}; + static_cast(formatArguments); + + auto numSpecsResult = impl::validateSpecHelper(fmt); + if (numSpecsResult.isError()) { + return {0, numSpecsResult.takeError()}; + } + + if (numSpecsResult.getOkay() < numArgs) { + return {0, FudStatus::FormatInvalid}; + } + + return impl::vFormat(sink, formatMode, fmt, formatArguments); +} + +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}; +} + +namespace impl { + +bool nestingHelper(bool& sawBracket, int& nesting, int increment) +{ + auto inSpec = nesting > 0; + if (not inSpec && not sawBracket) { + sawBracket = true; + return false; + } + if (not inSpec && sawBracket) { + sawBracket = false; + return false; + } + + nesting += increment; + return true; +} + +Result validateSpecHelper(FormatString fmt) +{ + auto scanView{fmt.view()}; + const auto length = scanView.length(); + const auto* data = scanView.data(); + int nesting = 0; + size_t index = 0; + size_t numGroups = 0; + + bool sawOpenBracket = false; + bool sawCloseBracket = false; + while (index < length) { + auto letter = data[index]; + + if (letter == FormatSpec::openBracket) { + auto incremented = nestingHelper(sawOpenBracket, nesting, 1); + if (incremented) { + numGroups++; + } + } else if (letter == FormatSpec::closeBracket) { + nestingHelper(sawCloseBracket, nesting, -1); + if (sawOpenBracket) { + sawOpenBracket = false; + sawCloseBracket = false; + numGroups++; + } + } else { + if (sawOpenBracket) { + nesting++; + numGroups++; + } + sawOpenBracket = false; + sawCloseBracket = false; + } + if (nesting < 0 || nesting > 2) { + return FudStatus::FormatInvalid; + } + ++index; + } + + if (nesting != 0 || sawOpenBracket || sawCloseBracket) { + return FudStatus::FormatInvalid; + } + + return numGroups; +} + +// NOLINTBEGIN(readability-function-cognitive-complexity) +Result validateSpecHelperV2(FormatString fmt) +{ + auto scanView{fmt.view()}; + const auto length = scanView.length(); + const auto* data = scanView.data(); + int nesting = 0; + size_t index = 0; + FormatInfo formatInfo{}; + + bool sawOpenBracket = false; + bool sawCloseBracket = false; + bool enteredGroup = false; + + bool firstGroup = true; + + while (index < length) { + auto inSpec = nesting > 0; + auto letter = data[index]; + + if (letter == FormatSpec::openBracket) { + if (not inSpec) { + sawOpenBracket = not sawOpenBracket; + } else { + nesting++; + formatInfo.numArgs++; + enteredGroup = true; + } + } else if (letter == FormatSpec::closeBracket) { + if (not inSpec) { + sawCloseBracket = not sawCloseBracket; + } else { + nesting--; + } + if (sawOpenBracket) { + sawOpenBracket = false; + sawCloseBracket = false; + enteredGroup = false; + formatInfo.numArgs++; + } + } else if (enteredGroup) { + nesting++; + formatInfo.numArgs++; + enteredGroup = false; + sawOpenBracket = false; + sawCloseBracket = false; + + if (classify::isDigit(letter)) { + if (length <= index) { + return FudStatus::FormatInvalid; + } + StringView specView{length - index, data + index}; + auto positionResult = fromString(specView, Radix::Decimal); + if (positionResult.isError()) + { + return positionResult.takeError(); + } + auto positionValue = positionResult.takeOkay(); + if (not firstGroup && not formatInfo.positional) { + return FudStatus::FormatInvalid; + } + formatInfo.maxArg = std::max(formatInfo.maxArg, positionValue.value); + formatInfo.numArgs++; + firstGroup = false; + + } + } + + if (nesting < 0 || nesting > 2) { + return FudStatus::FormatInvalid; + } + ++index; + } + + if (nesting != 0 || sawOpenBracket || sawCloseBracket) { + return FudStatus::FormatInvalid; + } + + return formatInfo; +} +// NOLINTEND(readability-function-cognitive-complexity) + +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 vFormatPrepareArgs( + StringView& scanView, + FormatArguments& args, + size_t& argIndex, + bool& firstSpec, + bool& takesPosition) +{ + size_t specLength{0}; + auto formatSpecResult{FormatSpec::parse(scanView, specLength)}; + if (formatSpecResult.isError()) { + return formatSpecResult; + } + fudAssert(specLength <= scanView.length()); + auto formatSpec{formatSpecResult.takeOkay()}; + bool specTakesPosition = formatSpec.position != FormatSpec::positionUnspecified; + if (firstSpec) { + takesPosition = specTakesPosition; + firstSpec = false; + } else if (takesPosition != specTakesPosition) { + return FudStatus::FormatInvalid; + } else if (takesPosition && formatSpec.position >= Size) { + return FudStatus::FormatInvalid; + } + if (formatSpec.takesWidth) { + if (takesPosition) { + auto widthPosition = formatSpec.width; + if (widthPosition >= args.size()) { + return FudStatus::FormatInvalid; + } + } + } + // auto formatResult{format(sink, + scanView.advanceUnsafe(specLength); + + return formatSpec; +} + +template +FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, FormatArguments& args) +{ + FormatResult result{0, FudStatus::Failure}; + auto scanView = fmt.view(); + bool firstSpec = false; + bool takesPosition = false; + size_t argIndex = 0; + while (scanView.length() > 0) { + auto specIndex = findSpec(scanView); + fudAssert(specIndex <= scanView.length()); + StringView run{specIndex, scanView.data()}; + auto status = sink.append(run); + if (status != FudStatus::Success) { + result.status = status; + return result; + } + result.bytesWritten += specIndex; + scanView.advanceUnsafe(specIndex); + if (specIndex == scanView.length()) { + return result; + } + + auto specResult{vFormatPrepareArgs(scanView, args, argIndex, firstSpec, takesPosition)}; + } + + static_cast(formatMode); + static_cast(args); + return result; +} + +} // namespace impl + +namespace fudTest { + +inline void test() +{ + String sink{}; + + static_cast(format(sink, FormatCharMode::Unchecked, "HELL YEAH")); + static_cast(format(sink, FormatCharMode::Unchecked, "HELL YEAH", 1, true, "ye")); +} +} // namespace fudTest + } // namespace fud #endif diff --git a/include/fud_result.hpp b/include/fud_result.hpp index 877c49c..95f3e5c 100644 --- a/include/fud_result.hpp +++ b/include/fud_result.hpp @@ -104,21 +104,53 @@ class [[nodiscard]] Result { return std::get(m_value); } + [[nodiscard]] constexpr const T& getOkayOr(const T& alternative) const& + { + if (!isOkay()) { + return alternative; + } + return std::get(m_value); + } + [[nodiscard]] constexpr const E& getError() const& { return std::get(m_value); } + [[nodiscard]] constexpr const E& getErrorOr(const E& alternative) const& + { + if (!isError()) { + return alternative; + } + return std::get(m_value); + } + [[nodiscard]] constexpr T&& takeOkay() { return std::move(std::get(m_value)); } + [[nodiscard]] constexpr T&& takeOkayOr(T&& alternative) + { + if (!isOkay()) { + return std::move(alternative); + } + return std::move(std::get(m_value)); + } + [[nodiscard]] constexpr E&& takeError() { return std::move(std::get(m_value)); } + [[nodiscard]] constexpr E&& takeErrorOr(E&& alternative) + { + if (!isError()) { + return std::move(alternative); + } + return std::move(std::get(m_value)); + } + private: constexpr Result() : m_value() { diff --git a/include/fud_string_view.hpp b/include/fud_string_view.hpp index 0852645..2ced639 100644 --- a/include/fud_string_view.hpp +++ b/include/fud_string_view.hpp @@ -103,6 +103,8 @@ struct StringView { size_t m_length{0}; const utf8* m_data{nullptr}; + + auto operator<=>(const StringView& rhs) const noexcept = default; }; } // namespace fud diff --git a/source/fud_assert.cpp b/source/fud_assert.cpp index ccc90a5..8811464 100644 --- a/source/fud_assert.cpp +++ b/source/fud_assert.cpp @@ -10,8 +10,8 @@ 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; +// constexpr std::size_t BITS_PER_OCTAL = 3; +// constexpr auto MAX_LINE_CHARS = BITS_PER_OCTAL * sizeof(decltype(std::source_location{}.line())) + 3; [[noreturn]] void assertFail(const char* assertion, const std::source_location sourceLocation) { diff --git a/source/fud_format.cpp b/source/fud_format.cpp index 04e611e..6bd5aae 100644 --- a/source/fud_format.cpp +++ b/source/fud_format.cpp @@ -23,12 +23,9 @@ FudStatus getFormatSign(StringView& formatView, FormatSpec& spec); } // namespace impl -Result FormatSpec::make(StringView formatView, size_t& specLength) +Result FormatSpec::parse(StringView formatView, size_t& specLength) { - static_cast(formatView); - static_cast(specLength); - - if (formatView[0] != FormatSpec::openBracket) { + if (formatView.length() < 2 || formatView[0] != FormatSpec::openBracket) { return FudStatus::ArgumentInvalid; } diff --git a/test/test_format.cpp b/test/test_format.cpp index 68c94cd..f0ea93f 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -15,8 +15,8 @@ * limitations under the License. */ -#include "fud_string_view.hpp" #include "fud_format.hpp" +#include "fud_string_view.hpp" #include "gtest/gtest.h" @@ -26,17 +26,20 @@ TEST(FormatTest, BasePositionalTest) { size_t length = 0; - auto formatSpecResult = FormatSpec::make(StringView{" {1:}"}, length); + auto formatSpecResult = FormatSpec::parse(StringView{" {1:}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.takeError(), FudStatus::ArgumentInvalid); - formatSpecResult = FormatSpec::make(StringView{"{1}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); auto formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(formatSpec.position, 1); EXPECT_EQ(length, 3); - formatSpecResult = FormatSpec::make(StringView{"{1:}"}, length); + formatSpecResult = FormatSpec::parse(StringView{""}, length); + ASSERT_EQ(formatSpecResult.takeErrorOr(FudStatus::NotImplemented), FudStatus::ArgumentInvalid); + + formatSpecResult = FormatSpec::parse(StringView{"{1:}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 4); @@ -49,14 +52,14 @@ TEST(FormatTest, BasePositionalTest) EXPECT_FALSE(formatSpec.alternateForm); EXPECT_FALSE(formatSpec.leadingZero); EXPECT_FALSE(formatSpec.hasLocale); - EXPECT_TRUE(std::holds_alternative(formatSpec.formatType)); + EXPECT_EQ(formatSpec.formatType, FormatType::Unspecified); } TEST(FormatTest, AlignTest) { size_t length = 0; - auto formatSpecResult = FormatSpec::make(StringView{"{1:<}"}, length); + auto formatSpecResult = FormatSpec::parse(StringView{"{1:<}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); auto formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(formatSpec.position, 1); @@ -68,16 +71,16 @@ TEST(FormatTest, AlignTest) EXPECT_FALSE(formatSpec.alternateForm); EXPECT_FALSE(formatSpec.leadingZero); EXPECT_FALSE(formatSpec.hasLocale); - EXPECT_TRUE(std::holds_alternative(formatSpec.formatType)); + EXPECT_EQ(formatSpec.formatType, FormatType::Unspecified); - formatSpecResult = FormatSpec::make(StringView{"{1:<<}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:<<}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(formatSpec.position, 1); EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); EXPECT_EQ(formatSpec.fill.fill, '<'); - formatSpecResult = FormatSpec::make(StringView{"{:<<}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{:<<}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 5); @@ -85,7 +88,7 @@ TEST(FormatTest, AlignTest) EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); EXPECT_EQ(formatSpec.fill.fill, '<'); - formatSpecResult = FormatSpec::make(StringView{"{1:_<}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:_<}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 6); @@ -98,7 +101,7 @@ TEST(FormatTest, SpecialTest) { size_t length = 0; - auto formatSpecResult = FormatSpec::make(StringView{"{1:_< }"}, length); + auto formatSpecResult = FormatSpec::parse(StringView{"{1:_< }"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); auto formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 7); @@ -109,7 +112,7 @@ TEST(FormatTest, SpecialTest) EXPECT_FALSE(formatSpec.alternateForm); EXPECT_FALSE(formatSpec.leadingZero); - formatSpecResult = FormatSpec::make(StringView{"{1:+}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:+}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); EXPECT_EQ(length, 5); formatSpec = formatSpecResult.takeOkay(); @@ -118,7 +121,7 @@ TEST(FormatTest, SpecialTest) EXPECT_FALSE(formatSpec.alternateForm); EXPECT_FALSE(formatSpec.leadingZero); - formatSpecResult = FormatSpec::make(StringView{"{1:-}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:-}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 5); @@ -127,7 +130,7 @@ TEST(FormatTest, SpecialTest) EXPECT_FALSE(formatSpec.alternateForm); EXPECT_FALSE(formatSpec.leadingZero); - formatSpecResult = FormatSpec::make(StringView{"{1:_<#}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:_<#}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 7); @@ -138,7 +141,7 @@ TEST(FormatTest, SpecialTest) EXPECT_TRUE(formatSpec.alternateForm); EXPECT_FALSE(formatSpec.leadingZero); - formatSpecResult = FormatSpec::make(StringView{"{1:_<0}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:_<0}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 7); @@ -149,7 +152,7 @@ TEST(FormatTest, SpecialTest) EXPECT_FALSE(formatSpec.alternateForm); EXPECT_TRUE(formatSpec.leadingZero); - formatSpecResult = FormatSpec::make(StringView{"{1: #}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1: #}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 6); @@ -160,15 +163,15 @@ TEST(FormatTest, SpecialTest) EXPECT_TRUE(formatSpec.alternateForm); EXPECT_FALSE(formatSpec.leadingZero); - formatSpecResult = FormatSpec::make(StringView{"{1:# }"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:# }"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); - formatSpecResult = FormatSpec::make(StringView{"{1:##}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:##}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); - formatSpecResult = FormatSpec::make(StringView{"{1: 0}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1: 0}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 6); @@ -179,19 +182,19 @@ TEST(FormatTest, SpecialTest) EXPECT_FALSE(formatSpec.alternateForm); EXPECT_TRUE(formatSpec.leadingZero); - formatSpecResult = FormatSpec::make(StringView{"{1:0 }"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:0 }"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); - formatSpecResult = FormatSpec::make(StringView{"{1:0#}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:0#}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); - formatSpecResult = FormatSpec::make(StringView{"{1:#00}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:#00}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); - formatSpecResult = FormatSpec::make(StringView{"{1: #0}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1: #0}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 7); @@ -207,7 +210,7 @@ TEST(FormatTest, WidthTest) { size_t length = 0; - auto formatSpecResult = FormatSpec::make(StringView{"{1:1}"}, length); + auto formatSpecResult = FormatSpec::parse(StringView{"{1:1}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); auto formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 5); @@ -215,7 +218,7 @@ TEST(FormatTest, WidthTest) EXPECT_FALSE(formatSpec.takesWidth); EXPECT_EQ(formatSpec.width, 1); - formatSpecResult = FormatSpec::make(StringView{"{1:543}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:543}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 7); @@ -223,7 +226,7 @@ TEST(FormatTest, WidthTest) EXPECT_FALSE(formatSpec.takesWidth); EXPECT_EQ(formatSpec.width, 543); - formatSpecResult = FormatSpec::make(StringView{"{:543}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{:543}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 6); @@ -232,16 +235,16 @@ TEST(FormatTest, WidthTest) EXPECT_EQ(formatSpec.width, 543); // leading zero - formatSpecResult = FormatSpec::make(StringView{"{1:00}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:00}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid); // #x100000000 4294967296 - formatSpecResult = FormatSpec::make(StringView{"{1:4294967296}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:4294967296}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError); - formatSpecResult = FormatSpec::make(StringView{"{1:{1}}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:{1}}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 7); @@ -250,15 +253,15 @@ TEST(FormatTest, WidthTest) EXPECT_EQ(formatSpec.width, 1); // #x10000 65536 - formatSpecResult = FormatSpec::make(StringView{"{1:{65536}}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:{65536}}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError); - formatSpecResult = FormatSpec::make(StringView{"{:{1}}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{:{1}}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid); - formatSpecResult = FormatSpec::make(StringView{"{:{}}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{:{}}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 5); @@ -271,7 +274,7 @@ TEST(FormatTest, PrecisionTest) { size_t length = 0; - auto formatSpecResult = FormatSpec::make(StringView{"{1:.1}"}, length); + auto formatSpecResult = FormatSpec::parse(StringView{"{1:.1}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); auto formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 6); @@ -279,7 +282,7 @@ TEST(FormatTest, PrecisionTest) EXPECT_FALSE(formatSpec.takesPrecision); EXPECT_EQ(formatSpec.precision, 1); - formatSpecResult = FormatSpec::make(StringView{"{1:.543}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:.543}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 8); @@ -287,7 +290,7 @@ TEST(FormatTest, PrecisionTest) EXPECT_FALSE(formatSpec.takesPrecision); EXPECT_EQ(formatSpec.precision, 543); - formatSpecResult = FormatSpec::make(StringView{"{:.543}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{:.543}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 7); @@ -296,16 +299,16 @@ TEST(FormatTest, PrecisionTest) EXPECT_EQ(formatSpec.precision, 543); // leading zero - formatSpecResult = FormatSpec::make(StringView{"{1:00}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:00}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid); // #x100000000 4294967296 - formatSpecResult = FormatSpec::make(StringView{"{1:.4294967296}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:.4294967296}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError); - formatSpecResult = FormatSpec::make(StringView{"{1:.{1}}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:.{1}}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 8); @@ -314,15 +317,15 @@ TEST(FormatTest, PrecisionTest) EXPECT_EQ(formatSpec.precision, 1); // #x10000 65536 - formatSpecResult = FormatSpec::make(StringView{"{1:.{65536}}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{1:.{65536}}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError); - formatSpecResult = FormatSpec::make(StringView{"{:.{1}}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{:.{1}}"}, length); ASSERT_TRUE(formatSpecResult.isError()); EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid); - formatSpecResult = FormatSpec::make(StringView{"{:.{}}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{:.{}}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 6); @@ -335,14 +338,14 @@ TEST(FormatTest, LocaleTest) { size_t length = 0; - auto formatSpecResult = FormatSpec::make(StringView{"{1:L}"}, length); + auto formatSpecResult = FormatSpec::parse(StringView{"{1:L}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); auto formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 5); EXPECT_EQ(formatSpec.position, 1); EXPECT_TRUE(formatSpec.hasLocale); - formatSpecResult = FormatSpec::make(StringView{"{:L}"}, length); + formatSpecResult = FormatSpec::parse(StringView{"{:L}"}, length); ASSERT_TRUE(formatSpecResult.isOkay()); formatSpec = formatSpecResult.takeOkay(); EXPECT_EQ(length, 4); @@ -350,4 +353,88 @@ TEST(FormatTest, LocaleTest) EXPECT_TRUE(formatSpec.hasLocale); } +TEST(FormatTest, FormatTest) +{ + auto lengthArray{Array::constFill(0U)}; + const Array formatTypeResults{ + FormatSpec::parse(StringView{"{:s}"}, lengthArray[0x00]), + FormatSpec::parse(StringView{"{:?}"}, lengthArray[0x01]), + FormatSpec::parse(StringView{"{:b}"}, lengthArray[0x02]), + FormatSpec::parse(StringView{"{:B}"}, lengthArray[0x03]), + FormatSpec::parse(StringView{"{:c}"}, lengthArray[0x04]), + FormatSpec::parse(StringView{"{:d}"}, lengthArray[0x05]), + FormatSpec::parse(StringView{"{:o}"}, lengthArray[0x06]), + FormatSpec::parse(StringView{"{:x}"}, lengthArray[0x07]), + FormatSpec::parse(StringView{"{:X}"}, lengthArray[0x08]), + FormatSpec::parse(StringView{"{:a}"}, lengthArray[0x09]), + FormatSpec::parse(StringView{"{:A}"}, lengthArray[0x0A]), + FormatSpec::parse(StringView{"{:e}"}, lengthArray[0x0B]), + FormatSpec::parse(StringView{"{:E}"}, lengthArray[0x0C]), + FormatSpec::parse(StringView{"{:f}"}, lengthArray[0x0D]), + FormatSpec::parse(StringView{"{:F}"}, lengthArray[0x0E]), + FormatSpec::parse(StringView{"{:g}"}, lengthArray[0x0F]), + FormatSpec::parse(StringView{"{:G}"}, lengthArray[0x10])}; + const Array formatTypes{ + FormatType::String, + FormatType::Escaped, + FormatType::BinaryLower, + FormatType::BinaryUpper, + FormatType::Character, + FormatType::Decimal, + FormatType::Octal, + FormatType::HexLower, + FormatType::HexUpper, + FormatType::FloatHexLower, + FormatType::FloatHexUpper, + FormatType::ScientificLower, + FormatType::ScientificUpper, + FormatType::FixedLower, + FormatType::FixedUpper, + FormatType::GeneralLower, + FormatType::GeneralUpper, + }; + for (size_t index = 0; index < lengthArray.size(); ++index) { + const auto& formatSpecResult = formatTypeResults[index]; + const auto& length = lengthArray[index]; + ASSERT_TRUE(formatSpecResult.isOkay()); + auto formatSpec = formatSpecResult.getOkay(); + EXPECT_EQ(length, 4); + EXPECT_EQ(formatSpec.position, FormatSpec::positionUnspecified); + EXPECT_EQ(formatSpec.formatType, formatTypes[index]); + } + + size_t length = 0; + + auto formatSpecResult = FormatSpec::parse(StringView{"{:N}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); +} + +TEST(FormatTest, ValidateSpecHelperTest) +{ + constexpr size_t invalid = 0xFFFF; + EXPECT_EQ(impl::validateSpecHelper("").getOkayOr(invalid), 0); + EXPECT_EQ(impl::validateSpecHelper("{}").getOkayOr(invalid), 1); + EXPECT_EQ(impl::validateSpecHelper("{{{}").getOkayOr(invalid), 1); + EXPECT_EQ(impl::validateSpecHelper("{}}}").getOkayOr(invalid), 1); + EXPECT_EQ(impl::validateSpecHelper("{{{}}}").getOkayOr(invalid), 1); + EXPECT_EQ(impl::validateSpecHelper("}").getErrorOr(FudStatus::Failure), FudStatus::FormatInvalid); + EXPECT_EQ(impl::validateSpecHelper("{}}").getErrorOr(FudStatus::Failure), FudStatus::FormatInvalid); + EXPECT_EQ(impl::validateSpecHelper("{").getErrorOr(FudStatus::Failure), FudStatus::FormatInvalid); + EXPECT_EQ(impl::validateSpecHelper("{{}").getErrorOr(FudStatus::Failure), FudStatus::FormatInvalid); + EXPECT_EQ(impl::validateSpecHelper("{{}").getOkayOr(invalid), invalid); + EXPECT_EQ(impl::validateSpecHelper("{{{}}} {} {{ }} {{{}}}").getOkayOr(invalid), 3); + EXPECT_EQ(impl::validateSpecHelper("{{{}}} {} {{ }} {{ }}}").getOkayOr(invalid), invalid); + EXPECT_EQ( + impl::validateSpecHelper("{{{}}} {} {{ }} {{{ }}").getErrorOr(FudStatus::Failure), + FudStatus::FormatInvalid); +} + +TEST(FormatTest, OneArgFormatTest) +{ + String sink{}; + auto formatResult = format(sink, FormatCharMode::Unchecked, "Test {:X}", 42U); + EXPECT_TRUE(formatResult.isOkay()); +} + } // namespace fud -- cgit v1.2.3