rapidyaml 0.15.2
parse and emit YAML, and do it fast
Loading...
Searching...
No Matches
scalar_charconv.hpp
Go to the documentation of this file.
1#ifndef C4_YML_SCALAR_CHARCONV_HPP_
2#define C4_YML_SCALAR_CHARCONV_HPP_
3
4/** @file scalar_charconv.hpp */
5
6#ifndef C4_YML_COMMON_HPP_
7#include "c4/yml/common.hpp"
8#endif
9#ifndef C4_YML_ERROR_HPP_
10#include "c4/yml/error.hpp"
11#endif
12#ifndef C4_CHARCONV_HPP_
13#include <c4/charconv.hpp>
14#endif
15
16namespace c4 {
17namespace yml {
18
19
20/** @addtogroup doc_scalar_charconv
21 *
22 * @{
23 */
24
25/** YAML-sense query of nullity. returns true if the scalar points
26 * to `nullptr` or is otherwise equal to one of the strings
27 * `"~"`,`"null"`,`"Null"`,`"NULL"` */
28inline bool scalar_is_null(csubstr s) noexcept
29{
30 return s.str == nullptr ||
31 (s.len == 1 && (s.str[0] == '~')) ||
32 (s.len == 4 && ((0 == memcmp("null", s.str, 4))
33 || (0 == memcmp("Null", s.str, 4))
34 || (0 == memcmp("NULL", s.str, 4))));
35}
36
37
38/** JSON-sense query of plain number */
39inline bool scalar_is_plain_number_json(csubstr s) noexcept
40{
41 return s.is_number()
42 &&
43 (
44 // quote integral numbers if they have a leading 0
45 // https://github.com/biojppm/rapidyaml/issues/291
46 (!(s.len > 1 && s.begins_with('0')))
47 // do not quote reals with leading 0
48 // https://github.com/biojppm/rapidyaml/issues/313
49 || (s.find('.') != csubstr::npos)
50 );
51}
52
53
54/** Query if a scalar is inf (inf, Inf, INF)
55 * @warning length must be 3
56 */
57inline bool scalar_is_inf3(const char *C4_RESTRICT s) noexcept
58{
59 switch(s[0])
60 {
61 case 'i': return ((s[1] == 'n') && (s[2] == 'f'));
62 case 'I': return ((s[1] == 'n') && (s[2] == 'f')) ||
63 ((s[1] == 'N') && (s[2] == 'F'));
64 }
65 return false;
66}
67
68
69/** Query if a scalar is nan (nan, NaN, Nan, NAN)
70 * @warning length must be 3
71 */
72inline bool scalar_is_nan3(const char *C4_RESTRICT s) noexcept
73{
74 switch(s[0])
75 {
76 case 'n': return ((s[1] == 'a') && (s[2] == 'n'));
77 case 'N': return ((s[1] == 'a') && (s[2] == 'N')) ||
78 ((s[1] == 'a') && (s[2] == 'n')) ||
79 ((s[1] == 'A') && (s[2] == 'N'));
80 }
81 return false;
82}
83
84
85/** Same as scalar_is_inf3() || scalar_is_nan3()
86 * @warning length must be 3
87 */
88inline bool scalar_is_inf_or_nan3(const char *C4_RESTRICT s) noexcept
89{
90 switch(s[0])
91 {
92 case 'i': return ((s[1] == 'n') && (s[2] == 'f'));
93 case 'I': return ((s[1] == 'n') && (s[2] == 'f')) ||
94 ((s[1] == 'N') && (s[2] == 'F'));
95 case 'n': return ((s[1] == 'a') && (s[2] == 'n'));
96 case 'N': return ((s[1] == 'a') && (s[2] == 'N')) ||
97 ((s[1] == 'a') && (s[2] == 'n')) ||
98 ((s[1] == 'A') && (s[2] == 'N'));
99 }
100 return false;
101}
102
103
104/** Query if a scalar is plain, eg, true, false, null, +-.inf or .nan */
105inline bool scalar_is_special_json(csubstr s) noexcept
106{
107 if(s.len == 4)
108 return 0 == memcmp("true", s.str, 4)
109 || 0 == memcmp("null", s.str, 4)
110 || ((s[0] == '.') && scalar_is_inf_or_nan3(s.str + 1))
111 || ((s[0] == '-' || s[0] == '+') && scalar_is_inf3(s.str + 1));
112 else if(s.len == 5)
113 return 0 == memcmp("false", s.str, 5)
114 || ((s[0] == '-' || s[0] == '+') && s[1] == '.' && scalar_is_inf3(s.str + 2));
115 else if(s.len == 3)
116 return scalar_is_inf_or_nan3(s.str);
117 return false;
118}
119
120
121//-----------------------------------------------------------------------------
122
123/** @cond dev */
124namespace detail {
125template<class T>
126C4_NODISCARD C4_NO_INLINE bool from_chars_float_yaml_special(csubstr buf, T *C4_RESTRICT val) RYML_NOEXCEPT
127{
128 static_assert(std::is_floating_point<T>::value, "must be floating point");
129 RYML_ASSERT_BASIC_(buf.len);
130 RYML_ASSERT_BASIC_(!buf.begins_with('+'));
131 switch(buf.str[0])
132 {
133 case '.':
134 if(buf.len == 4)
135 {
136 if(scalar_is_nan3(buf.str + 1))
137 {
138 *val = std::numeric_limits<T>::quiet_NaN();
139 goto ok; // NOLINT
140 }
141 else if(scalar_is_inf3(buf.str + 1))
142 {
143 *val = std::numeric_limits<T>::infinity();
144 goto ok; // NOLINT
145 }
146 }
147 break;
148 case '-':
149 if(buf.len == 5 && buf.str[1] == '.' && scalar_is_inf3(buf.str + 2))
150 {
151 *val = -std::numeric_limits<T>::infinity();
152 goto ok; // NOLINT
153 }
154 }
155 return false;
156ok:
157 return true;
158}
159} // namespace detail
160/** @endcond */
161
162
163/** Deserialize a floating point from string. Accepts special values:
164 * .nan, .inf, -.inf, with upper-case variations.
165 *
166 * Unlike non-floating types, only the leading part of the string that
167 * may constitute a number is processed. This happens because the
168 * float parsing is delegated to fast_float, which is implemented that
169 * way. Consequently, for example, all of `"34"`, `"34 "` `"34hg"`
170 * `"34 gh"` will be read as 34. If you are not sure about the
171 * contents of the data, you can use @ref csubstr::first_real_span() to
172 * check before deserializing, for example like this:
173 *
174 * @code{c++}
175 * csubstr val = node.val();
176 * if(val.first_real_span() == val)
177 * node >> v; // or call from_chars_float(val, &v);
178 * else
179 * ERROR("not a real")
180 * @endcode
181 */
182template<class T>
183C4_NODISCARD bool from_chars_float(csubstr scalar, T *C4_RESTRICT val) RYML_NOEXCEPT
184{
185 static_assert(std::is_floating_point<T>::value, "must be floating point");
186 if C4_LIKELY(scalar.len > 0)
187 {
188 if(scalar.str[0] == '+')
189 scalar = scalar.sub(1);
190 if C4_LIKELY(from_chars(scalar, val))
191 return true;
192 else if(scalar.len == 4 || scalar.len == 5)
193 return detail::from_chars_float_yaml_special(scalar, val);
194 }
195 return false;
196}
197
198
199/** Serialize a floating point value to a string.
200 */
201template<class T>
203{
204 static_assert(std::is_floating_point<T>::value, "must be floating point");
205 C4_SUPPRESS_WARNING_GCC_CLANG_WITH_PUSH("-Wfloat-equal");
206 if C4_UNLIKELY(std::isnan(val))
207 return to_chars(buf, csubstr(".nan"));
208 else if C4_UNLIKELY(val == std::numeric_limits<T>::infinity())
209 return to_chars(buf, csubstr(".inf"));
210 else if C4_UNLIKELY(val == -std::numeric_limits<T>::infinity())
211 return to_chars(buf, csubstr("-.inf"));
212 return to_chars(buf, val);
213 C4_SUPPRESS_WARNING_GCC_CLANG_POP
214}
215
216
217//-----------------------------------------------------------------------------
218
219/** Deserialize an integral scalar. Trims leading `+`, and then
220 * dispatches to @ref from_chars<T>(). The full string is used.
221 *
222 * @note Unlike with the floating case (see @ref to_chars_float()),
223 * there is no need for an analogous `to_chars_integral()`, because
224 * the lower-level @ref atoi() and @ref atou() functions used by @ref
225 * to_chars() do not produce leading `+` characters. */
226template<class T>
227C4_NODISCARD C4_ALWAYS_INLINE bool from_chars_integral(csubstr scalar, T *val) RYML_NOEXCEPT
228{
229 if C4_LIKELY(scalar.len > 0)
230 {
231 if(scalar.str[0] == '+')
232 scalar = scalar.sub(1);
233 return from_chars(scalar, val);
234 }
235 return false;
236}
237
238
239//-----------------------------------------------------------------------------
240// scalar_deserialize
241
242#if (C4_CPP >= 17) || defined(__DOXYGEN__)
243
244/** Deserialize a scalar from its string representation, dispatching
245 * to one of @ref from_chars(), @ref from_chars_float() or @ref
246 * from_chars_integral() as appropriate.
247 *
248 * @note When using a standard older than C++17, `if constexpr` is not
249 * available, and the implementation reverts to SFINAE to achieve the
250 * compile-time dispatch.
251 *
252 * @return true if the deserialization succeeded */
253template<class T>
254C4_NODISCARD C4_ALWAYS_INLINE bool scalar_deserialize(csubstr str, T *val)
255{
256 if constexpr (std::is_floating_point<T>::value)
257 return from_chars_float(str, val);
258 else if constexpr (std::is_arithmetic<T>::value)
259 return from_chars_integral(str, val);
260 else
261 return from_chars(str, val);
262}
263
264#else // pre-C++17 implementation: need to use SFINAE
265
266// float
267template<class T>
268C4_NODISCARD C4_ALWAYS_INLINE auto scalar_deserialize(csubstr str, T *val) RYML_NOEXCEPT
269 -> typename std::enable_if<std::is_floating_point<T>::value, bool>::type
270{
271 return from_chars_float(str, val);
272}
273
274// integral
275template<class T>
276C4_NODISCARD C4_ALWAYS_INLINE auto scalar_deserialize(csubstr str, T *val) RYML_NOEXCEPT
277 -> typename std::enable_if<std::is_arithmetic<T>::value && !std::is_floating_point<T>::value, bool>::type
278{
279 return from_chars_integral(str, val);
280}
281
282// other types
283template<class T>
284C4_NODISCARD C4_ALWAYS_INLINE auto scalar_deserialize(csubstr str, T *val)
285 -> typename std::enable_if<!std::is_arithmetic<T>::value && !std::is_floating_point<T>::value, bool>::type
286{
287 return from_chars(str, val);
288}
289
290#endif // pre-C++17 implementation
291
292
293//-----------------------------------------------------------------------------
294// scalar_serialize
295
296#if (C4_CPP >= 17) || defined(__DOXYGEN__)
297
298/** Serialize a scalar to the buffer, dispatching to @ref to_chars() or
299 * @ref to_chars_float() as appropriate.
300 *
301 * @note When using a standard older than C++17, `if constexpr` is not
302 * available, and the implementation reverts to SFINAE to achieve the
303 * compile-time dispatch.
304 *
305 * @return the size of the serialization, (the buffer size is strictly
306 * respected and never overflowed) */
307template<class T>
308C4_ALWAYS_INLINE size_t scalar_serialize(substr buf, T const& C4_RESTRICT a)
309{
310 if constexpr (std::is_floating_point<T>::value)
311 return to_chars_float(buf, a);
312 else
313 return to_chars(buf, a);
314}
315
316#else // pre-C++17 implementation: need to use SFINAE
317
318// float
319template<class T>
320C4_ALWAYS_INLINE auto scalar_serialize(substr buf, T const& C4_RESTRICT a) RYML_NOEXCEPT
321 -> typename std::enable_if<std::is_floating_point<T>::value, size_t>::type
322{
323 return to_chars_float(buf, a);
324}
325
326// other scalar types
327template<class T>
328C4_ALWAYS_INLINE auto scalar_serialize(substr buf, T const& C4_RESTRICT a)
329 -> typename std::enable_if< ! std::is_floating_point<T>::value, size_t>::type
330{
331 return to_chars(buf, a);
332}
333
334#endif // pre-C++17 implementation
335
336
337/** @} */
338
339
340} // namespace yml
341} // namespace c4
342
343
344#endif /* C4_YML_SCALAR_CHARCONV_HPP_ */
Lightweight generic type-safe wrappers for converting individual values to/from strings.
Common utilities and infrastructure used by ryml.
#define RYML_NOEXCEPT
Conditionally expands to noexcept when RYML_USE_ASSERT is 0 and is empty otherwise.
Error utilities used by ryml.
bool from_chars(csubstr buf, uint8_t *v) noexcept
size_t scalar_serialize(substr buf, T const &a)
Serialize a scalar to the buffer, dispatching to to_chars() or to_chars_float() as appropriate.
bool scalar_is_inf_or_nan3(const char *s) noexcept
Same as scalar_is_inf3() || scalar_is_nan3().
bool scalar_is_null(csubstr s) noexcept
YAML-sense query of nullity.
bool scalar_is_special_json(csubstr s) noexcept
Query if a scalar is plain, eg, true, false, null, +-.inf or .nan.
bool scalar_deserialize(csubstr str, T *val)
Deserialize a scalar from its string representation, dispatching to one of from_chars(),...
bool scalar_is_inf3(const char *s) noexcept
Query if a scalar is inf (inf, Inf, INF).
bool scalar_is_plain_number_json(csubstr s) noexcept
JSON-sense query of plain number.
bool from_chars_integral(csubstr scalar, T *val) RYML_NOEXCEPT
Deserialize an integral scalar.
size_t to_chars_float(substr buf, T val) RYML_NOEXCEPT
Serialize a floating point value to a string.
bool scalar_is_nan3(const char *s) noexcept
Query if a scalar is nan (nan, NaN, Nan, NAN).
bool from_chars_float(csubstr scalar, T *val) RYML_NOEXCEPT
Deserialize a floating point from string.
basic_substring< char > substr
a mutable string view
Definition substr.hpp:2355
basic_substring< const char > csubstr
an immutable string view
Definition substr.hpp:2356
size_t to_chars(substr buf, escaped_scalar e)
formatting implementation to escape a scalar with escape_scalar()