mirror of https://github.com/mamba-org/mamba.git
Constexpr `fmt::formatter::parse` for C++20 with `from_chars` (#3944)
This commit is contained in:
parent
aaae25ff04
commit
e807e690ee
|
@ -15,6 +15,7 @@
|
|||
#include <fmt/format.h>
|
||||
|
||||
#include "mamba/specs/error.hpp"
|
||||
#include "mamba/util/charconv.hpp"
|
||||
|
||||
namespace mamba::specs
|
||||
{
|
||||
|
@ -262,9 +263,43 @@ struct fmt::formatter<mamba::specs::Version>
|
|||
std::optional<std::size_t> m_level;
|
||||
FormatType m_type = FormatType::Normal;
|
||||
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin());
|
||||
constexpr auto parse(format_parse_context& ctx) -> format_parse_context::iterator
|
||||
{
|
||||
const auto end = ctx.end();
|
||||
const auto start = ctx.begin();
|
||||
|
||||
auto format(const ::mamba::specs::Version v, format_context& ctx) const -> decltype(ctx.out());
|
||||
// Make sure that range is not empty
|
||||
if (start == end || *start == '}')
|
||||
{
|
||||
return start;
|
||||
}
|
||||
|
||||
// Check for restricted number of segments at beginning
|
||||
std::size_t val = 0;
|
||||
auto [ptr, ec] = mamba::util::constexpr_from_chars(start, end, val);
|
||||
if (ec == std::errc())
|
||||
{
|
||||
m_level = val;
|
||||
}
|
||||
|
||||
// Check for end of format spec
|
||||
if (ptr == end || *ptr == '}')
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Check the custom format type
|
||||
if (*ptr == 'g')
|
||||
{
|
||||
m_type = FormatType::Glob;
|
||||
++ptr;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
auto format(const ::mamba::specs::Version v, format_context& ctx) const
|
||||
-> format_context::iterator;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2025, Cppreference.com
|
||||
//
|
||||
// Distributed under the terms of the Copyright/CC-BY-SA License.
|
||||
//
|
||||
// The full license can be found at the address
|
||||
// https://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA
|
||||
|
||||
/**
|
||||
* Backport of C++23 ``std::from_chars`` function as constexpr.
|
||||
*/
|
||||
|
||||
#ifndef MAMBA_UTIL_CHARCONV_HPP
|
||||
#define MAMBA_UTIL_CHARCONV_HPP
|
||||
|
||||
#include <charconv>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "mamba/util/deprecation.hpp"
|
||||
|
||||
namespace mamba::util
|
||||
{
|
||||
|
||||
template <typename Int>
|
||||
MAMBA_DEPRECATED_CXX23 constexpr auto
|
||||
constexpr_from_chars(const char* first, const char* last, Int& value) -> std::from_chars_result
|
||||
{
|
||||
static_assert(
|
||||
std::is_integral_v<Int> && std::is_unsigned_v<Int>,
|
||||
"Only unsigned integers supported"
|
||||
);
|
||||
|
||||
constexpr auto is_digit = [](char c) -> bool { return c >= '0' && c <= '9'; };
|
||||
|
||||
if (first == last)
|
||||
{
|
||||
return { first, std::errc::invalid_argument };
|
||||
}
|
||||
|
||||
value = 0;
|
||||
auto it = first;
|
||||
while (it != last && is_digit(*it))
|
||||
{
|
||||
Int digit = static_cast<Int>(static_cast<unsigned char>(*it) - '0');
|
||||
|
||||
if (value > (std::numeric_limits<Int>::max() - digit) / 10)
|
||||
{
|
||||
return { it, std::errc::result_out_of_range };
|
||||
}
|
||||
|
||||
value = value * 10 + digit;
|
||||
++it;
|
||||
}
|
||||
|
||||
if (it == first)
|
||||
{
|
||||
return { first, std::errc::invalid_argument };
|
||||
}
|
||||
|
||||
return { it, std::errc{} };
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -864,45 +864,9 @@ namespace mamba::specs
|
|||
}
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::Version>::parse(format_parse_context& ctx) -> decltype(ctx.begin())
|
||||
{
|
||||
const auto end = ctx.end();
|
||||
const auto start = ctx.begin();
|
||||
|
||||
// Make sure that range is not empty
|
||||
if (start == end || *start == '}')
|
||||
{
|
||||
return start;
|
||||
}
|
||||
|
||||
// Check for restricted number of segments at beginning
|
||||
std::size_t val = 0;
|
||||
auto [ptr, ec] = std::from_chars(start, end, val);
|
||||
if (ec == std::errc())
|
||||
{
|
||||
m_level = val;
|
||||
}
|
||||
|
||||
// Check for end of format spec
|
||||
if (ptr == end || *ptr == '}')
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Check the custom format type
|
||||
if (*ptr == 'g')
|
||||
{
|
||||
m_type = FormatType::Glob;
|
||||
++ptr;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
auto
|
||||
fmt::formatter<mamba::specs::Version>::format(const ::mamba::specs::Version v, format_context& ctx) const
|
||||
-> decltype(ctx.out())
|
||||
-> format_context::iterator
|
||||
{
|
||||
auto out = ctx.out();
|
||||
if (v.epoch() != 0)
|
||||
|
|
|
@ -19,6 +19,7 @@ set(
|
|||
# Utility library
|
||||
src/util/test_cast.cpp
|
||||
src/util/test_compare.cpp
|
||||
src/util/test_charconv.cpp
|
||||
src/util/test_cryptography.cpp
|
||||
src/util/test_encoding.cpp
|
||||
src/util/test_environment.cpp
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) 2025, Cppreference.com
|
||||
//
|
||||
// Distributed under the terms of the Copyright/CC-BY-SA License.
|
||||
//
|
||||
// The full license can be found at the address
|
||||
// https://en.cppreference.com/w/Cppreference:Copyright/CC-BY-SA
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "mamba/util/charconv.hpp"
|
||||
|
||||
using namespace mamba::util;
|
||||
|
||||
namespace
|
||||
{
|
||||
TEST_CASE("constexpr_from_chars works for valid input", "[mamba::util]")
|
||||
{
|
||||
SECTION("Basic parsing")
|
||||
{
|
||||
constexpr const char* input = "12345";
|
||||
unsigned value = 0;
|
||||
auto res = constexpr_from_chars(input, input + 5u, value);
|
||||
REQUIRE(res.ec == std::errc{});
|
||||
REQUIRE(res.ptr == input + 5);
|
||||
REQUIRE(value == 12345u);
|
||||
}
|
||||
|
||||
SECTION("Empty input")
|
||||
{
|
||||
constexpr const char* input = "";
|
||||
std::size_t value = 0;
|
||||
auto res = constexpr_from_chars(input, input, value);
|
||||
REQUIRE(res.ec == std::errc::invalid_argument);
|
||||
REQUIRE(res.ptr == input);
|
||||
}
|
||||
|
||||
SECTION("Non-digit character")
|
||||
{
|
||||
constexpr const char* input = "123a";
|
||||
unsigned value = 0;
|
||||
auto res = constexpr_from_chars(input, input + 4, value);
|
||||
REQUIRE(res.ec == std::errc{});
|
||||
REQUIRE(res.ptr == input + 3);
|
||||
REQUIRE(value == 123u);
|
||||
}
|
||||
|
||||
SECTION("No digits at all")
|
||||
{
|
||||
constexpr const char* input = "abc";
|
||||
unsigned value = 0;
|
||||
auto res = constexpr_from_chars(input, input + 3, value);
|
||||
REQUIRE(res.ec == std::errc::invalid_argument);
|
||||
REQUIRE(res.ptr == input);
|
||||
}
|
||||
|
||||
SECTION("Overflow")
|
||||
{
|
||||
constexpr const char* input = "99999999999999999999";
|
||||
std::size_t value = 0;
|
||||
auto res = constexpr_from_chars(input, input + 20, value);
|
||||
REQUIRE(res.ec == std::errc::result_out_of_range);
|
||||
}
|
||||
|
||||
SECTION("Leading zeroes")
|
||||
{
|
||||
constexpr const char* input = "00042";
|
||||
unsigned value = 0;
|
||||
auto res = constexpr_from_chars(input, input + 5, value);
|
||||
REQUIRE(res.ec == std::errc{});
|
||||
REQUIRE(res.ptr == input + 5);
|
||||
REQUIRE(value == 42u);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue