Skip to content

Commit c5d87f4

Browse files
committed
Modernize the Beast lexical cast framework
1 parent ee92191 commit c5d87f4

File tree

1 file changed

+116
-207
lines changed

1 file changed

+116
-207
lines changed

src/ripple/beast/core/LexicalCast.h

Lines changed: 116 additions & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -20,255 +20,163 @@
2020
#ifndef BEAST_MODULE_CORE_TEXT_LEXICALCAST_H_INCLUDED
2121
#define BEAST_MODULE_CORE_TEXT_LEXICALCAST_H_INCLUDED
2222

23+
#include <ripple/beast/type_name.h>
24+
25+
#include <boost/beast/core/string_type.hpp>
26+
#include <boost/utility/string_view.hpp>
27+
2328
#include <algorithm>
24-
#include <cassert>
25-
#include <cerrno>
26-
#include <cstdlib>
27-
#include <iostream>
28-
#include <iterator>
29+
#include <array>
30+
#include <cctype>
31+
#include <charconv>
2932
#include <limits>
3033
#include <string>
34+
#include <string_view>
35+
#include <system_error>
3136
#include <type_traits>
32-
#include <typeinfo>
33-
#include <utility>
34-
35-
#include <boost/predef.h>
3637

3738
namespace beast {
3839

39-
namespace detail {
40-
41-
#if BOOST_COMP_MSVC
42-
#pragma warning(push)
43-
#pragma warning(disable : 4800)
44-
#pragma warning(disable : 4804)
45-
#endif
40+
//------------------------------------------------------------------------------
4641

47-
template <class Int, class FwdIt, class Accumulator>
48-
bool
49-
parse_integral(Int& num, FwdIt first, FwdIt last, Accumulator accumulator)
50-
{
51-
num = 0;
42+
namespace detail {
5243

53-
if (first == last)
44+
template <class T>
45+
inline constexpr bool is_boost_string_view_v = []() {
46+
if constexpr (std::is_same_v<T, std::string_view>)
5447
return false;
48+
else if constexpr (std::is_same_v<T, boost::core::string_view>)
49+
return true;
50+
else
51+
return std::is_same_v<T, boost::beast::string_view>;
52+
}();
5553

56-
while (first != last)
57-
{
58-
auto const c = *first++;
59-
if (c < '0' || c > '9')
60-
return false;
61-
if (!accumulator(num, Int(c - '0')))
62-
return false;
63-
}
64-
65-
return true;
66-
}
67-
68-
template <class Int, class FwdIt>
69-
bool
70-
parse_negative_integral(Int& num, FwdIt first, FwdIt last)
71-
{
72-
Int limit_value = std::numeric_limits<Int>::min() / 10;
73-
Int limit_digit = std::numeric_limits<Int>::min() % 10;
74-
75-
if (limit_digit < 0)
76-
limit_digit = -limit_digit;
77-
78-
return parse_integral<Int>(
79-
num, first, last, [limit_value, limit_digit](Int& value, Int digit) {
80-
assert((digit >= 0) && (digit <= 9));
81-
if (value < limit_value ||
82-
(value == limit_value && digit > limit_digit))
83-
return false;
84-
value = (value * 10) - digit;
85-
return true;
86-
});
87-
}
88-
89-
template <class Int, class FwdIt>
90-
bool
91-
parse_positive_integral(Int& num, FwdIt first, FwdIt last)
92-
{
93-
Int limit_value = std::numeric_limits<Int>::max() / 10;
94-
Int limit_digit = std::numeric_limits<Int>::max() % 10;
95-
96-
return parse_integral<Int>(
97-
num, first, last, [limit_value, limit_digit](Int& value, Int digit) {
98-
assert((digit >= 0) && (digit <= 9));
99-
if (value > limit_value ||
100-
(value == limit_value && digit > limit_digit))
101-
return false;
102-
value = (value * 10) + digit;
103-
return true;
104-
});
105-
}
106-
107-
template <class IntType, class FwdIt>
108-
bool
109-
parseSigned(IntType& result, FwdIt first, FwdIt last)
110-
{
111-
static_assert(
112-
std::is_signed<IntType>::value,
113-
"You may only call parseSigned with a signed integral type.");
114-
115-
if (first != last && *first == '-')
116-
return parse_negative_integral(result, first + 1, last);
117-
118-
if (first != last && *first == '+')
119-
return parse_positive_integral(result, first + 1, last);
120-
121-
return parse_positive_integral(result, first, last);
122-
}
123-
124-
template <class UIntType, class FwdIt>
125-
bool
126-
parseUnsigned(UIntType& result, FwdIt first, FwdIt last)
127-
{
128-
static_assert(
129-
std::is_unsigned<UIntType>::value,
130-
"You may only call parseUnsigned with an unsigned integral type.");
131-
132-
if (first != last && *first == '+')
133-
return parse_positive_integral(result, first + 1, last);
134-
135-
return parse_positive_integral(result, first, last);
136-
}
54+
} // namespace detail
13755

13856
//------------------------------------------------------------------------------
13957

140-
// These specializatons get called by the non-member functions to do the work
141-
template <class Out, class In>
142-
struct LexicalCast;
143-
144-
// conversion to std::string
145-
template <class In>
146-
struct LexicalCast<std::string, In>
58+
/** Thrown when a conversion is not possible with LexicalCast.
59+
Only used in the throw variants of lexicalCast.
60+
*/
61+
struct BadLexicalCast : public std::bad_cast
14762
{
148-
explicit LexicalCast() = default;
63+
private:
64+
std::string msg;
14965

150-
template <class Arithmetic = In>
151-
std::enable_if_t<std::is_arithmetic<Arithmetic>::value, bool>
152-
operator()(std::string& out, Arithmetic in)
66+
public:
67+
explicit BadLexicalCast(std::string m = {})
68+
: msg(std::bad_cast::what())
15369
{
154-
out = std::to_string(in);
155-
return true;
70+
if (!m.empty())
71+
msg += ": " + m;
15672
}
15773

158-
template <class Enumeration = In>
159-
std::enable_if_t<std::is_enum<Enumeration>::value, bool>
160-
operator()(std::string& out, Enumeration in)
74+
[[nodiscard]] char const*
75+
what() const noexcept override
16176
{
162-
out = std::to_string(
163-
static_cast<std::underlying_type_t<Enumeration>>(in));
164-
return true;
77+
return msg.c_str();
16578
}
16679
};
16780

168-
// Parse std::string to number
81+
//------------------------------------------------------------------------------
82+
83+
/** Convert from std::string_view to integral type.
84+
@return `false` if there was a parsing or range error
85+
*/
16986
template <class Out>
170-
struct LexicalCast<Out, std::string>
87+
requires std::is_integral_v<Out> && (!std::is_same_v<Out, bool>)
88+
[[nodiscard]] bool
89+
lexicalCastChecked(Out& out, std::string_view in) noexcept
17190
{
172-
explicit LexicalCast() = default;
173-
174-
static_assert(
175-
std::is_integral<Out>::value,
176-
"beast::LexicalCast can only be used with integral types");
91+
if (in.empty())
92+
return false;
17793

178-
template <class Integral = Out>
179-
std::enable_if_t<std::is_unsigned<Integral>::value, bool>
180-
operator()(Integral& out, std::string const& in) const
94+
if (in.front() == '+')
18195
{
182-
return parseUnsigned(out, in.begin(), in.end());
183-
}
96+
in.remove_prefix(1);
18497

185-
template <class Integral = Out>
186-
std::enable_if_t<std::is_signed<Integral>::value, bool>
187-
operator()(Integral& out, std::string const& in) const
188-
{
189-
return parseSigned(out, in.begin(), in.end());
98+
if (in.empty() || in.front() == '-')
99+
return false;
190100
}
191101

192-
bool
193-
operator()(bool& out, std::string in) const
194-
{
195-
// Convert the input to lowercase
196-
std::transform(in.begin(), in.end(), in.begin(), [](auto c) {
197-
return std::tolower(static_cast<unsigned char>(c));
198-
});
199-
200-
if (in == "1" || in == "true")
201-
{
202-
out = true;
203-
return true;
204-
}
205-
206-
if (in == "0" || in == "false")
207-
{
208-
out = false;
209-
return true;
210-
}
211-
212-
return false;
213-
}
214-
};
102+
auto [ptr, ec] = std::from_chars(in.data(), in.data() + in.size(), out);
215103

216-
//------------------------------------------------------------------------------
104+
return ec == std::errc{} && ptr == in.data() + in.size();
105+
}
217106

218-
// Conversion from null terminated char const*
219-
template <class Out>
220-
struct LexicalCast<Out, char const*>
107+
/** Convert from std::string_view to bool.
108+
@return `false` if there was a parsing error
109+
*/
110+
[[nodiscard]] inline bool
111+
lexicalCastChecked(bool& out, std::string_view in) noexcept
221112
{
222-
explicit LexicalCast() = default;
223-
224-
bool
225-
operator()(Out& out, char const* in) const
113+
auto iequals = [](std::string_view a, std::string_view b) {
114+
return std::equal(
115+
a.begin(), a.end(), b.begin(), b.end(), [](char ca, char cb) {
116+
return std::tolower(static_cast<unsigned char>(ca)) ==
117+
std::tolower(static_cast<unsigned char>(cb));
118+
});
119+
};
120+
121+
if (in == "1" || iequals(in, "true"))
226122
{
227-
return LexicalCast<Out, std::string>()(out, in);
123+
out = true;
124+
return true;
228125
}
229-
};
230126

231-
// Conversion from null terminated char*
232-
// The string is not modified.
233-
template <class Out>
234-
struct LexicalCast<Out, char*>
235-
{
236-
explicit LexicalCast() = default;
237-
238-
bool
239-
operator()(Out& out, char* in) const
127+
if (in == "0" || iequals(in, "false"))
240128
{
241-
return LexicalCast<Out, std::string>()(out, in);
129+
out = false;
130+
return true;
242131
}
243-
};
244132

245-
#if BOOST_COMP_MSVC
246-
#pragma warning(pop)
247-
#endif
133+
return false;
134+
}
248135

249-
} // namespace detail
136+
/** Convert from integral type to std::string.
137+
@return `false` if there was a conversion error
138+
*/
139+
template <class In>
140+
requires std::is_integral_v<In>
141+
[[nodiscard]] bool
142+
lexicalCastChecked(std::string& out, In in) noexcept
143+
{
144+
std::array<char, std::numeric_limits<In>::digits10 + 3> buf;
250145

251-
//------------------------------------------------------------------------------
146+
auto [ptr, ec] = std::to_chars(buf.data(), buf.data() + buf.size(), in);
252147

253-
/** Thrown when a conversion is not possible with LexicalCast.
254-
Only used in the throw variants of lexicalCast.
148+
if (ec != std::errc{})
149+
return false;
150+
151+
out.assign(buf.data(), ptr);
152+
return true;
153+
}
154+
155+
/** Convert from enum type to std::string.
156+
@return `false` if there was a conversion error
255157
*/
256-
struct BadLexicalCast : public std::bad_cast
158+
template <class In>
159+
requires std::is_enum_v<In>
160+
[[nodiscard]] bool
161+
lexicalCastChecked(std::string& out, In in) noexcept
257162
{
258-
explicit BadLexicalCast() = default;
259-
};
163+
return lexicalCastChecked(out, static_cast<std::underlying_type_t<In>>(in));
164+
}
260165

261-
/** Intelligently convert from one type to another.
166+
/** Convert from Boost string_view types to integral types.
262167
@return `false` if there was a parsing or range error
263168
*/
264169
template <class Out, class In>
265-
bool
266-
lexicalCastChecked(Out& out, In in)
170+
requires detail::is_boost_string_view_v<In>
171+
[[nodiscard]] bool
172+
lexicalCastChecked(Out& out, In in) noexcept
267173
{
268-
return detail::LexicalCast<Out, In>()(out, in);
174+
return lexicalCastChecked(out, std::string_view(in.data(), in.size()));
269175
}
270176

271-
/** Convert from one type to another, throw on error
177+
//------------------------------------------------------------------------------
178+
179+
/** Convert from one type to another, throw on error.
272180
273181
An exception of type BadLexicalCast is thrown if the conversion fails.
274182
@@ -278,26 +186,27 @@ template <class Out, class In>
278186
Out
279187
lexicalCastThrow(In in)
280188
{
281-
Out out;
282-
283-
if (lexicalCastChecked(out, in))
189+
if (Out out; lexicalCastChecked(out, in))
284190
return out;
285191

286-
throw BadLexicalCast();
192+
throw BadLexicalCast(
193+
#ifdef DEBUG
194+
beast::type_name<In>() + " -> " + beast::type_name<Out>()
195+
#endif
196+
);
287197
}
288198

289199
/** Convert from one type to another.
290200
291-
@param defaultValue The value returned if parsing fails
201+
@param in The value to convert.
202+
@param defaultValue The value returned if parsing fails.
292203
@return The new type.
293204
*/
294205
template <class Out, class In>
295-
Out
206+
[[nodiscard]] Out
296207
lexicalCast(In in, Out defaultValue = Out())
297208
{
298-
Out out;
299-
300-
if (lexicalCastChecked(out, in))
209+
if (Out out; lexicalCastChecked(out, in))
301210
return out;
302211

303212
return defaultValue;

0 commit comments

Comments
 (0)