diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2024-10-25 01:09:10 -0500 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2024-10-25 01:09:10 -0500 |
commit | 11968f674a7de34fb7de744598a8086330cd88a3 (patch) | |
tree | be5b885ef47c8ceab50f558c8aef6632fefc94d1 | |
parent | 512d026de016f2720060d264adec02e56123851d (diff) |
Get working string formatting for unsigned numbers.
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | include/fud_fixed_vector.hpp | 276 | ||||
-rw-r--r-- | include/fud_format.hpp | 873 | ||||
-rw-r--r-- | include/fud_string.hpp | 13 | ||||
-rw-r--r-- | include/fud_string_view.hpp | 6 | ||||
-rw-r--r-- | source/fud_allocator.cpp | 1 | ||||
-rw-r--r-- | source/fud_string.cpp | 99 | ||||
-rw-r--r-- | test/test_format.cpp | 42 |
8 files changed, 1080 insertions, 231 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 12c0727..6a3076b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,7 @@ set(FUD_HEADERS "include/fud_c_file.hpp" "include/fud_c_string.hpp" "include/fud_directory.hpp" + "include/fud_fixed_vector.hpp" "include/fud_fud_type_traits.hpp" "include/fud_memory.hpp" "include/fud_option.hpp" diff --git a/include/fud_fixed_vector.hpp b/include/fud_fixed_vector.hpp new file mode 100644 index 0000000..d0c0f0e --- /dev/null +++ b/include/fud_fixed_vector.hpp @@ -0,0 +1,276 @@ +/* + * libfud + * Copyright 2024 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FUD_FIXED_VECTOR_HPP +#define FUD_FIXED_VECTOR_HPP + +#include "fud_assert.hpp" +#include "fud_config.hpp" +#include "fud_option.hpp" +#include "fud_span.hpp" + +#include <cstddef> + +namespace fud { + +template <typename T, size_t Size> +class FixedVector { + static_assert(Size > 0); + using ValueType = T; + + private: + T m_data[Size]; // NOLINT(cppcoreguidelines-avoid-c-arrays) + size_t m_length; + + public: + [[nodiscard]] constexpr size_t size() const + { + return Size; + } + + constexpr T* data() noexcept + { + return m_data; + } + + constexpr const T* data() const noexcept + { + return m_data; + } + + constexpr T* begin() noexcept + { + return m_data; + } + + constexpr const T* begin() const noexcept + { + return m_data; + } + + constexpr T* end() noexcept + { + return m_data + Size; + } + + constexpr const T* end() const noexcept + { + return m_data + Size; + } + + constexpr T& operator[](size_t index) + { + if constexpr (fudBoundsChecking) { + fudAssert(m_data != nullptr); + fudAssert(index < m_length); + } + return m_data[index]; + } + + constexpr const T& operator[](size_t index) const + { + if constexpr (fudBoundsChecking) { + fudAssert(m_data != nullptr); + fudAssert(index < m_length); + } + return m_data[index]; + } + + constexpr bool operator==(const FixedVector<T, Size>&) const noexcept = default; + + constexpr auto operator<=>(const FixedVector<T, Size>& other) const noexcept = default; + + Span<T, Size> span() + { + return Span<T, Size>{data(), Size}; + } + + FudStatus resize(size_t count) + { + if (count == m_length) { + return FudStatus::Success; + } + + if (count < m_length) { + m_length = count; + return FudStatus::Success; + } + + for (size_t index = m_length; index < count; ++index) { + m_data = std::move(T()); + } + + m_length = count; + return FudStatus::Success; + } + + FudStatus clear() + { + m_length = 0; + return FudStatus::Success; + } + + constexpr Option<T&> front() + { + if (m_length > 0) { + return m_data[0]; + } + return NullOpt; + } + + constexpr Option<const T&> front() const + { + if (m_length > 0) { + return m_data[0]; + } + return NullOpt; + } + + constexpr Option<T&> back() + { + if (m_length > 0) { + return m_data[m_length - 1]; + } + return NullOpt; + } + + constexpr Option<const T&> back() const + { + if (m_length > 0) { + return m_data[m_length - 1]; + } + return NullOpt; + } + + FudStatus pushBack(const T& value) + { + if (m_length == Size) { + return FudStatus::Full; + } + + m_data[m_length] = value; + m_length++; + return FudStatus::Success; + } + + FudStatus pushBack(T&& value) + { + if (m_length == Size) { + return FudStatus::Full; + } + + m_data[m_length] = std::move(value); + m_length++; + return FudStatus::Success; + } + + Result<T, FudStatus> popBack() + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (m_length == 0) { + return FudStatus::Empty; + } + auto result{std::move(m_data[m_length - 1])}; + m_length--; + return result; + } + + FudStatus eraseBack() + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (m_length == 0) { + return FudStatus::Empty; + } + m_length--; + return FudStatus::Success; + } + + FudStatus insert(size_t index, const T& value) + { + if (index > m_length) { + return FudStatus::IndexInvalid; + } + if (index == m_length) { + return pushBack(value); + } + if (m_length == Size) { + return FudStatus::Full; + } + + m_data[m_length] = std::move(m_data[m_length - 1]); + + for (size_t backIndex = m_length - 1; backIndex > index; --backIndex) { + m_data[backIndex] = std::move(m_data[backIndex - 1]); + } + m_data[index] = value; + m_length++; + return FudStatus::Success; + } + + FudStatus insert(size_t index, T&& value) + { + if (index > m_length) { + return FudStatus::IndexInvalid; + } + if (index == m_length) { + return pushBack(std::move(value)); + } + if (m_length == Size) { + return FudStatus::Full; + } + + m_data[m_length] = std::move(m_data[m_length - 1]); + + for (size_t backIndex = m_length - 1; backIndex > index; --backIndex) { + m_data[backIndex] = std::move(m_data[backIndex - 1]); + } + m_data[index] = std::move(value); + m_length++; + return FudStatus::Success; + } + + FudStatus erase(size_t index) + { + if (index >= m_length) { + return FudStatus::IndexInvalid; + } + + for (size_t fwdIndex = index; fwdIndex + 1 < m_length; fwdIndex++) { + m_data[fwdIndex] = std::move(m_data[fwdIndex + 1]); + } + + m_length--; + return FudStatus::Success; + } + + private: + FudStatus cleanup() noexcept + { + auto status = clear(); + + m_length = 0; + return status; + } +}; + +} // namespace fud + +#endif diff --git a/include/fud_format.hpp b/include/fud_format.hpp index 1d09422..c42cd03 100644 --- a/include/fud_format.hpp +++ b/include/fud_format.hpp @@ -19,6 +19,7 @@ #define FUD_FORMAT_HPP // #include "fud_assert.hpp" +#include "fud_array.hpp" #include "fud_option.hpp" #include "fud_result.hpp" #include "fud_status.hpp" @@ -27,12 +28,17 @@ #include "fud_string_view.hpp" #include "fud_utf8.hpp" +#include <concepts> #include <cstdint> #include <limits> #include <variant> namespace fud { +// TODO: constants file? +constexpr size_t bitsPerOctal = 3; +constexpr size_t maxIntCharCount = bitsPerOctal * sizeof(uint64_t) + 4; + struct FormatString { template <size_t N> consteval FormatString(const char (&input)[N]) : m_size{N - 1}, m_data{input} @@ -199,8 +205,9 @@ struct FormatSpec { bool hasLocale : 1 = false; - [[nodiscard]] constexpr bool takesPosition() const { - return position == positionUnspecified; + [[nodiscard]] constexpr bool takesPosition() const + { + return position != positionUnspecified; } static Result<FormatSpec, FudStatus> parse(StringView formatView, size_t& specLength); @@ -216,7 +223,6 @@ struct FormatHandle { } // namespace impl using FormatArgument = std::variant< - std::monostate, bool, utf8, int32_t, @@ -240,6 +246,11 @@ struct FormatArguments { return Size; } + constexpr const FormatArgument& operator[](size_t index) const + { + return arguments[index]; + } + template <typename... Args> static auto makeFormatArguments(Args&&... args) -> FormatArguments { @@ -266,7 +277,7 @@ struct FormatResult { }; /* TODO : require concept of a sink that takes pushBack() -> FudStatus */ -/* TODO : sink concept also requires append() -> FudStatus */ +/* TODO : sink concept also requires drain() -> FudStatus */ template <typename Sink> FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt); @@ -274,58 +285,48 @@ template <typename Sink, typename... Args> FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Args&&... args); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, bool arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, utf8 arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, int32_t arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int32_t arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, uint32_t arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint32_t arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, int64_t arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int64_t arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, uint64_t arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint64_t arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, float arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, float arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, double arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, double arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, long double arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, long double arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, const utf8* arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, StringView arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, const void* arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const void* arg); template <typename Sink> -FormatResult format(Sink& sink, FormatCharMode formatMode, FormatSpec& formatSpec, impl::FormatHandle arg); +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, impl::FormatHandle arg); namespace impl { -Result<size_t, FudStatus> validateSpecHelper(FormatString fmt); - -struct FormatInfo { - uint16_t numArgs{0}; - uint16_t maxArg{0}; - bool positional{false}; -}; - -Result<FormatInfo, FudStatus> validateSpecHelperV2(FormatString fmt); - template <typename Sink, size_t Size> -FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, FormatArguments<Size>& args); +FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, const FormatArguments<Size>& args); } // namespace impl @@ -339,16 +340,6 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt, Arg constexpr size_t numArgs = sizeof...(args); auto formatArguments{FormatArguments<numArgs>::makeFormatArguments(std::forward<Args>(args)...)}; - static_cast<void>(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); } @@ -367,150 +358,95 @@ FormatResult format(Sink& sink, FormatCharMode formatMode, FormatString fmt) 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; -} +size_t findSpec(StringView scanView); -Result<size_t, FudStatus> 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; +template <size_t Size> +Result<uint32_t, FudStatus> getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments<Size>& args); - bool sawOpenBracket = false; - bool sawCloseBracket = false; - while (index < length) { - auto letter = data[index]; +template <size_t Size> +Result<FormatSpec, FudStatus> vFormatPrepareSpec( + StringView& scanView, + const FormatArguments<Size>& args, + const uint32_t& argIndex, + bool& firstSpec, + bool& takesPosition); - 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; - } +template <size_t Size> +Result<FormatSpec, FudStatus> vFormatPrepareFirstSpec( + StringView& scanView, + const FormatArguments<Size>& args, + const uint32_t& argIndex, + bool& takesPosition); - if (nesting != 0 || sawOpenBracket || sawCloseBracket) { - return FudStatus::FormatInvalid; - } +template <size_t Size> +FudStatus vFormatPreparePositionalSpec(FormatSpec& formatSpec, const FormatArguments<Size>& args); - return numGroups; -} +template <size_t Size> +FudStatus vFormatPrepareNonPositionalSpec( + FormatSpec& formatSpec, + const FormatArguments<Size>& args, + const uint32_t& argIndex); -// NOLINTBEGIN(readability-function-cognitive-complexity) -Result<FormatInfo, FudStatus> validateSpecHelperV2(FormatString fmt) +template <typename Sink, size_t Size> +FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, const FormatArguments<Size>& args) { - 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; + FormatResult result{0, FudStatus::Success}; + auto scanView = fmt.view(); + bool firstSpec = true; + bool takesPosition = false; + uint32_t argIndex = 0; + while (scanView.length() > 0) { + if (argIndex > Size) { + result.status = FudStatus::FormatInvalid; + return result; + } - while (index < length) { - auto inSpec = nesting > 0; - auto letter = data[index]; + auto specIndex = findSpec(scanView); + fudAssert(specIndex <= scanView.length()); + StringView run{specIndex, scanView.data()}; + auto drainResult = sink.drain(run); + result.bytesWritten += drainResult.bytesWritten; + if (drainResult.status != FudStatus::Success) { + result.status = drainResult.status; + return result; + } + scanView.advanceUnsafe(specIndex); + if (specIndex == scanView.length()) { + return result; + } - 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<decltype(formatInfo.maxArg)>(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; + auto specResult{vFormatPrepareSpec(scanView, args, argIndex, firstSpec, takesPosition)}; + if (specResult.isError()) { + result.status = specResult.takeError(); + return result; + } + auto formatSpec{specResult.takeOkay()}; + uint32_t formatArgIndex = argIndex; + if (formatSpec.takesPosition()) { + if (formatSpec.position >= Size) { + result.status = specResult.takeError(); + return result; } + formatArgIndex = formatSpec.position; } - - if (nesting < 0 || nesting > 2) { - return FudStatus::FormatInvalid; + auto argResult{std::visit( + [&](const auto& arg) -> FormatResult { return format(sink, formatMode, formatSpec, arg); }, + args[formatArgIndex])}; + if (argResult.status != FudStatus::Success) { + result.status = argResult.status; + return result; + } + result.bytesWritten += argResult.bytesWritten; + if (!formatSpec.takesPosition()) { + argIndex++; + argIndex += static_cast<uint8_t>(formatSpec.takesWidth); + argIndex += static_cast<uint8_t>(formatSpec.takesPrecision); } - ++index; - } - - if (nesting != 0 || sawOpenBracket || sawCloseBracket) { - return FudStatus::FormatInvalid; } - return formatInfo; + return result; } -// NOLINTEND(readability-function-cognitive-complexity) size_t findSpec(StringView scanView) { @@ -533,80 +469,605 @@ size_t findSpec(StringView scanView) } template <size_t Size> -Result<FormatSpec, FudStatus> vFormatPrepareArgs( +Result<uint32_t, FudStatus> getSpecField(FormatSpec& formatSpec, uint32_t index, const FormatArguments<Size>& args) +{ + static_assert(not std::is_signed_v<utf8>); + + if (not formatSpec.takesPosition() || not formatSpec.takesWidth) { + return FudStatus::ArgumentInvalid; + } + + if (index >= Size) { + return FudStatus::FormatInvalid; + } + + if (std::holds_alternative<utf8>(args[index])) { + return std::get<utf8>(args[index]); + } + if (std::holds_alternative<int32_t>(args[index])) { + auto value = std::get<int32_t>(args[index]); + if (value < 0) { + return FudStatus::FormatInvalid; + } + return static_cast<uint32_t>(value); + } + if (std::holds_alternative<int64_t>(args[index])) { + auto value = std::get<int64_t>(args[index]); + if (value < 0 || value > std::numeric_limits<uint32_t>::max()) { + return FudStatus::FormatInvalid; + } + return static_cast<uint32_t>(value); + } + if (std::holds_alternative<uint32_t>(args[index])) { + return std::get<uint32_t>(args[index]); + } + if (std::holds_alternative<uint32_t>(args[index])) { + auto value = std::get<uint64_t>(args[index]); + if (value > std::numeric_limits<uint32_t>::max()) { + return FudStatus::FormatInvalid; + } + return static_cast<uint32_t>(value); + } + + return FudStatus::FormatInvalid; +} + +template <size_t Size> +Result<FormatSpec, FudStatus> vFormatPrepareSpec( StringView& scanView, - FormatArguments<Size>& args, - size_t& argIndex, + const FormatArguments<Size>& args, + const uint32_t& argIndex, bool& firstSpec, bool& takesPosition) { + if (firstSpec) { + firstSpec = false; + return vFormatPrepareFirstSpec(scanView, args, argIndex, 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) { + auto formatSpec{formatSpecResult.getOkay()}; + + if (takesPosition != formatSpec.takesPosition() || (takesPosition && formatSpec.position >= Size)) { return FudStatus::FormatInvalid; } - if (formatSpec.takesWidth) { - if (takesPosition) { - auto widthPosition = formatSpec.width; - if (widthPosition >= args.size()) { - return FudStatus::FormatInvalid; - } + + if (takesPosition) { + auto status = vFormatPreparePositionalSpec(formatSpec, args); + if (status != FudStatus::Success) { + return status; + } + } else { + auto status = vFormatPrepareNonPositionalSpec(formatSpec, args, argIndex); + if (status != FudStatus::Success) { + return status; } } - // auto formatResult{format(sink, - scanView.advanceUnsafe(specLength); + + scanView.advanceUnsafe(specLength); return formatSpec; } -template <typename Sink, size_t Size> -FormatResult vFormat(Sink& sink, FormatCharMode formatMode, FormatString fmt, FormatArguments<Size>& args) +template <size_t Size> +Result<FormatSpec, FudStatus> vFormatPrepareFirstSpec( + StringView& scanView, + const FormatArguments<Size>& args, + const uint32_t& argIndex, + bool& takesPosition) { - 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); + size_t specLength{0}; + auto formatSpecResult{FormatSpec::parse(scanView, specLength)}; + if (formatSpecResult.isError()) { + return formatSpecResult; + } + fudAssert(specLength <= scanView.length()); + auto formatSpec{formatSpecResult.takeOkay()}; + takesPosition = formatSpec.takesPosition(); + + if (takesPosition) { + auto status = vFormatPreparePositionalSpec(formatSpec, args); if (status != FudStatus::Success) { - result.status = status; - return result; + return status; } - result.bytesWritten += specIndex; - scanView.advanceUnsafe(specIndex); - if (specIndex == scanView.length()) { - return result; + } else { + auto status = vFormatPrepareNonPositionalSpec(formatSpec, args, argIndex); + if (status != FudStatus::Success) { + return status; } + } + + scanView.advanceUnsafe(specLength); + + return formatSpec; +} - auto specResult{vFormatPrepareArgs(scanView, args, argIndex, firstSpec, takesPosition)}; +template <size_t Size> +FudStatus vFormatPreparePositionalSpec(FormatSpec& formatSpec, const FormatArguments<Size>& args) +{ + if (not formatSpec.takesPosition()) { + return FudStatus::OperationInvalid; + } + + if (formatSpec.takesWidth) { + auto widthResult = getSpecField(formatSpec, formatSpec.width, args); + if (widthResult.isError()) { + return widthResult.takeError(); + } + formatSpec.width = widthResult.takeOkay(); + } + + if (formatSpec.takesPrecision) { + auto precisionResult = getSpecField(formatSpec, formatSpec.precision, args); + if (precisionResult.isError()) { + return precisionResult.takeError(); + } + formatSpec.precision = precisionResult.takeOkay(); + } + + return FudStatus::Success; +} + +template <size_t Size> +FudStatus vFormatPrepareNonPositionalSpec( + FormatSpec& formatSpec, + const FormatArguments<Size>& args, + const uint32_t& argIndex) +{ + if (formatSpec.takesPosition()) { + return FudStatus::OperationInvalid; + } + + uint32_t fieldIndex = argIndex + 1; + + if (formatSpec.takesWidth) { + if (fieldIndex < argIndex) { + return FudStatus::FormatInvalid; + } + auto widthResult = getSpecField(formatSpec, fieldIndex, args); + if (widthResult.isError()) { + return widthResult.takeError(); + } + formatSpec.width = widthResult.takeOkay(); + fieldIndex++; + } + + if (formatSpec.takesPrecision) { + if (fieldIndex < argIndex) { + return FudStatus::FormatInvalid; + } + auto precisionResult = getSpecField(formatSpec, fieldIndex, args); + if (precisionResult.isError()) { + return precisionResult.takeError(); + } + formatSpec.precision = precisionResult.takeOkay(); + } + + return FudStatus::Success; +} + +template <typename T> + requires std::is_integral_v<T> +[[nodiscard]] StringView validateFormatType( + FormatResult& result, + FormatType formatType, + Radix& radix, + bool& uppercase, + T arg) +{ + StringView prefix{""}; + switch (formatType) { + case FormatType::Unspecified: + break; + case FormatType::BinaryLower: + prefix = StringView{"0b"}; + radix = Radix::Binary; + break; + case FormatType::BinaryUpper: + prefix = StringView{"0B"}; + radix = Radix::Binary; + uppercase = true; + break; + case FormatType::Character: + if constexpr (std::is_signed_v<T>) { + if (arg < 0) { + result.status = FudStatus::FormatInvalid; + } + } + if (arg > std::numeric_limits<utf8>::max()) { + result.status = FudStatus::FormatInvalid; + break; + } + break; + case FormatType::Decimal: + break; + case FormatType::Octal: + prefix = StringView{"o"}; + radix = Radix::Octal; + break; + case FormatType::HexLower: + prefix = StringView{"0x"}; + radix = Radix::Hexadecimal; + break; + case FormatType::HexUpper: + prefix = StringView{"0X"}; + radix = Radix::Hexadecimal; + uppercase = true; + break; + case FormatType::FloatHexLower: + case FormatType::FloatHexUpper: + case FormatType::String: + case FormatType::Escaped: + case FormatType::ScientificLower: + case FormatType::ScientificUpper: + case FormatType::FixedLower: + case FormatType::FixedUpper: + case FormatType::GeneralLower: + case FormatType::GeneralUpper: + result.status = FudStatus::FormatInvalid; + default: + result.status = FudStatus::Failure; + } + return prefix; +} + +inline FudStatus fillUnsignedBuffer( + Array<utf8, maxIntCharCount>& buffer, + uint64_t value, + uint8_t& bufferLength, + Radix radix, + bool uppercase) +{ + static_assert(maxIntCharCount < std::numeric_limits<uint8_t>::max()); + bufferLength = 0; + + constexpr Array<uint8_t, static_cast<size_t>(Radix::Hexadecimal)> lower{ + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}}; + constexpr Array<uint8_t, static_cast<size_t>(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<std::underlying_type_t<Radix>, uint8_t>); + auto radixValue = static_cast<uint8_t>(radix); + while (value > 0 && bufferLength < maxIntCharCount) { + auto digit = static_cast<uint8_t>(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; +} + +template <typename Sink> +[[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<utf8, BigBlockSize>::constFill(fill); + + constexpr auto bufferSizes{Array<size_t, 4>{{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 <typename Sink, typename T> + requires std::is_integral_v<T> and std::is_scalar_v<T> /* and std::is_unsigned_v<T> */ +[[nodiscard]] FormatResult formatUnsigned(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, T arg) +{ + FormatResult result{0, FudStatus::Success}; + if (formatSpec.takesPrecision || formatSpec.precision != FormatSpec::precisionUnspecified) { + result.status = FudStatus::FormatInvalid; + return result; + } + auto fill = formatSpec.fill.fill; + auto align = formatSpec.fill.align.value; + bool padZero = false; + if (align == FormatAlign::Value::Default) { + align = FormatAlign::Value::Right; + fill = formatSpec.leadingZero ? '0' : fill; + padZero = formatSpec.leadingZero; + } + auto width = formatSpec.width; + if (width == FormatSpec::widthUnspecified) { + width = 0; + } + + auto radix = Radix::Decimal; + bool uppercase = false; + + auto prefix = validateFormatType(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<utf8>(arg)); + } + + if (not formatSpec.alternateForm) { + prefix = StringView{""}; + } + + auto calculatedWidth = prefix.length(); + + Array<utf8, maxIntCharCount> buffer{}; + uint8_t bufferLength = 0; + + // TODO: constexpr if for signed values + result.status = fillUnsignedBuffer(buffer, static_cast<uint64_t>(arg), bufferLength, radix, uppercase); + + if (result.status != FudStatus::Success) { + return result; + } + + calculatedWidth += bufferLength; + + StringView sign{""}; + switch (formatSpec.formatSign) { + case FormatSign::Plus: + sign = StringView{"+"}; + break; + case FormatSign::Space: + sign = StringView{" "}; + break; + case FormatSign::Minus: + case FormatSign::Default: + default: + if constexpr (std::is_signed_v<T>) { + if (arg < 0) { + sign = StringView{"-"}; + } + } + 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; + } + + if (result.status != FudStatus::Success) { + return result; + } + + if (sign.length() > 0) { + auto drainResult = sink.drain(sign); + result.bytesWritten += drainResult.bytesWritten; + result.status = drainResult.status; + } + + if (result.status != FudStatus::Success) { + return result; + } + + if (prefix.length() > 0) { + auto drainResult = sink.drain(sign); + result.bytesWritten += drainResult.bytesWritten; + result.status = drainResult.status; + } + + if (result.status != FudStatus::Success) { + return result; + } + + if (padZero) { + auto leftPadSize = width - calculatedWidth; + auto padResult = fillPad(sink, '0', leftPadSize); + result.bytesWritten += padResult.bytesWritten; + result.status = padResult.status; + } + + if (result.status != FudStatus::Success) { + return result; + } + + auto drainNumberResult = sink.drain(StringView{bufferLength, buffer.data()}); + result.bytesWritten += drainNumberResult.bytesWritten; + result.status = drainNumberResult.status; + + if (result.status != FudStatus::Success) { + return result; + } + + 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) { + auto leftPadSize = (width - calculatedWidth) / 2; + auto rightPadSize = width - leftPadSize; + auto padResult = fillPad(sink, fill, rightPadSize); + result.bytesWritten += padResult.bytesWritten; + result.status = padResult.status; } - static_cast<void>(formatMode); - static_cast<void>(args); return result; } } // namespace impl +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, bool arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, utf8 arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int32_t arg) +{ + return impl::formatUnsigned(sink, formatMode, formatSpec, arg); +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint32_t arg) +{ + return impl::formatUnsigned(sink, formatMode, formatSpec, arg); +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, int64_t arg) +{ + return impl::formatUnsigned(sink, formatMode, formatSpec, arg); +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, uint64_t arg) +{ + return impl::formatUnsigned(sink, formatMode, formatSpec, arg); +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, float arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, double arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, long double arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const utf8* arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, StringView arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, const void* arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + +template <typename Sink> +FormatResult format(Sink& sink, FormatCharMode formatMode, const FormatSpec& formatSpec, impl::FormatHandle arg) +{ + FormatResult result{0, FudStatus::NotImplemented}; + static_cast<void>(sink); + static_cast<void>(formatMode); + static_cast<void>(formatSpec); + static_cast<void>(arg); + return result; +} + namespace fudTest { inline void test() { + StringView prefix{0, ""}; String sink{}; static_cast<void>(format(sink, FormatCharMode::Unchecked, "HELL YEAH")); diff --git a/include/fud_string.hpp b/include/fud_string.hpp index 60a328f..0213524 100644 --- a/include/fud_string.hpp +++ b/include/fud_string.hpp @@ -38,6 +38,11 @@ constexpr size_t SSO_BUF_SIZE = SSO_BUF_LENGTH + 1; using StringResult = Result<String, FudStatus>; +struct DrainResult { + size_t bytesWritten; + FudStatus status; +}; + class String { public: static StringResult makeFromCString(const char* cString); @@ -212,12 +217,20 @@ class String { FudStatus append(StringView source); + DrainResult drain(const char* source); + + DrainResult drain(const String& source); + + DrainResult drain(StringView source); + [[nodiscard]] StringResult catenate(const String& rhs) const; [[nodiscard]] StringResult catenate(const char* rhs) const; [[nodiscard]] bool compare(const String& rhs) const; + FudStatus clear(); + const utf8* begin() const; const utf8* end() const; diff --git a/include/fud_string_view.hpp b/include/fud_string_view.hpp index 2ced639..718b3d7 100644 --- a/include/fud_string_view.hpp +++ b/include/fud_string_view.hpp @@ -45,6 +45,12 @@ struct StringView { { } + template <size_t N> + StringView(const char (&input)[N]) : m_length{N - 1}, m_data{reinterpret_cast<const utf8*>(input)} + { + static_assert(N > 0); + } + StringView(size_t strLen, const char* strData) : m_length(strLen), // line break m_data{reinterpret_cast<const utf8*>(strData)} // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) diff --git a/source/fud_allocator.cpp b/source/fud_allocator.cpp index 1e4117a..8daf969 100644 --- a/source/fud_allocator.cpp +++ b/source/fud_allocator.cpp @@ -35,7 +35,6 @@ FudStatus FudAllocator::deallocate(void* pointer, size_t bytes) if (pointer == nullptr || bytes == 0) { return FudStatus::ArgumentInvalid; } - static_cast<void>(bytes); fudFree(pointer); return FudStatus::Success; } diff --git a/source/fud_string.cpp b/source/fud_string.cpp index 6e16741..048cc94 100644 --- a/source/fud_string.cpp +++ b/source/fud_string.cpp @@ -364,14 +364,15 @@ FudStatus String::append(StringView source) return FudStatus::StringInvalid; } - if (source.data() == m_data) { - return FudStatus::Aliased; + if (source.data() == nullptr) { + return FudStatus::NullPointer; } const size_t newLength = length() + source.length(); if (newLength < length()) { return FudStatus::OperationInvalid; } + const size_t newSize = newLength + 1; // cppcheck-suppress knownConditionTrueFalse if (newSize < newLength) { // cppcheck-suppress knownConditionTrueFalse return FudStatus::OperationInvalid; @@ -386,7 +387,7 @@ FudStatus String::append(StringView source) } auto* destPtr = data() + length(); - auto status = copyMem(destPtr, m_capacity, source.data(), source.length()); + auto status = copyMem(destPtr, remainingLength(), source.data(), source.length()); fudAssert(status == FudStatus::Success); m_length += source.length(); @@ -395,6 +396,87 @@ FudStatus String::append(StringView source) return status; } +DrainResult String::drain(const char* source) +{ + auto lenResult = cStringLength(source); + if (lenResult < 0 || lenResult >= SSIZE_MAX) { + return {0, FudStatus::ArgumentInvalid}; + } + + return drain(StringView{static_cast<size_t>(lenResult), source}); +} + +DrainResult String::drain(const String& source) +{ + return drain(source.asView()); +} + +DrainResult String::drain(StringView source) +{ + DrainResult result{0, FudStatus::Success}; + if (!valid()) { + result.status = FudStatus::StringInvalid; + return result; + } + + if (source.data() == nullptr) { + result.status = FudStatus::NullPointer; + return result; + } + + if (source.length() == 0) { + return result; + } + + if (remainingLength() > 0) { + StringView firstPart{source}; + if (source.length() > remainingLength()) { + firstPart.m_length = remainingLength(); + } + auto* destPtr = data() + length(); + auto status = copyMem(destPtr, remainingLength(), firstPart.m_data, firstPart.m_length); + fudAssert(status == FudStatus::Success); + m_length += firstPart.m_length; + result.bytesWritten += firstPart.m_length; + status = nullTerminate(); + source.advanceUnsafe(firstPart.m_length); + fudAssert(status == FudStatus::Success); + } + + if (source.length() == 0) { + return result; + } + + const size_t newLength = length() + source.length(); + if (newLength < length()) { + result.status = FudStatus::OperationInvalid; + return result; + } + const size_t newSize = newLength + 1; // cppcheck-suppress knownConditionTrueFalse + if (newSize < newLength) { // cppcheck-suppress knownConditionTrueFalse + result.status = FudStatus::OperationInvalid; + return result; + } + if (newSize >= m_capacity) { + auto newCapacity = newSize < SIZE_MAX / 2 ? newSize * 2 : SIZE_MAX; + const auto resizeStatus = resize(newCapacity); + if (resizeStatus != FudStatus::Success) { + result.status = resizeStatus; + return result; + } + fudAssert(m_capacity == newCapacity); + } + + auto* destPtr = data() + length(); + auto status = copyMem(destPtr, remainingLength(), source.data(), source.length()); + fudAssert(status == FudStatus::Success); + + m_length += source.length(); + status = nullTerminate(); + + return result; +} + StringResult String::catenate(const char* rhs) const { if (!valid()) { @@ -507,6 +589,17 @@ bool String::compare(const String& rhs) const return diffResult.getOkay() == 0; } +FudStatus String::clear() { + if (!valid()) { + return FudStatus::StringInvalid; + } + + data()[0] = '\0'; + m_length = 0; + + return FudStatus::Success; +} + const utf8* String::begin() const { return data(); diff --git a/test/test_format.cpp b/test/test_format.cpp index f0ea93f..9c95330 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -19,6 +19,7 @@ #include "fud_string_view.hpp" #include "gtest/gtest.h" +#include <format> namespace fud { @@ -410,31 +411,30 @@ TEST(FormatTest, FormatTest) 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); + auto formatResult = format(sink, FormatCharMode::Unchecked, "Test {:}", 42U); + EXPECT_TRUE(formatResult.isOkay()); + + 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); + + 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 "); + */ } } // namespace fud |