rapidyaml  0.16.0
parseandemitYAML,anddoitfast
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 
16 namespace c4 {
17 namespace 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"` */
28 inline 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 */
39 inline 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  */
57 inline 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  */
72 inline 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  */
88 inline 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 */
105 inline 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 */
124 namespace detail {
125 template<class T>
126 C4_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;
156 ok:
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  */
182 template<class T>
183 C4_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  */
201 template<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. */
226 template<class T>
227 C4_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 */
253 template<class T>
254 C4_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
267 template<class T>
268 C4_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
275 template<class T>
276 C4_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
283 template<class T>
284 C4_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) */
307 template<class T>
308 C4_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
319 template<class T>
320 C4_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
327 template<class T>
328 C4_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_ */
bool scalar_is_null(csubstr s) noexcept
YAML-sense query of nullity.
size_t to_chars(substr buf, escaped_scalar e)
formatting implementation to escape a scalar with escape_scalar()
bool from_chars_float(csubstr scalar, T *val) RYML_NOEXCEPT
Deserialize a floating point from string.
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_plain_number_json(csubstr s) noexcept
JSON-sense query of plain number.
size_t to_chars_float(substr buf, T val) RYML_NOEXCEPT
Serialize a floating point value to a string.
const char * str
Definition: node_type.cpp:70
bool scalar_deserialize(csubstr str, T *val)
Deserialize a scalar from its string representation, dispatching to one of from_chars(), from_chars_float() or from_chars_integral() as appropriate.
bool from_chars(csubstr buf, uint8_t *v) noexcept
Definition: charconv.hpp:2383
Common utilities and infrastructure used by ryml.
bool scalar_is_inf3(const char *s) noexcept
Query if a scalar is inf (inf, Inf, INF)
bool scalar_is_nan3(const char *s) noexcept
Query if a scalar is nan (nan, NaN, Nan, NAN)
Lightweight generic type-safe wrappers for converting individual values to/from strings.
#define RYML_NOEXCEPT
Conditionally expands to noexcept when RYML_USE_ASSERT is 0 and is empty otherwise.
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
bool scalar_is_inf_or_nan3(const char *s) noexcept
Same as scalar_is_inf3() || scalar_is_nan3()
bool from_chars_integral(csubstr scalar, T *val) RYML_NOEXCEPT
Deserialize an integral scalar.
bool scalar_is_special_json(csubstr s) noexcept
Query if a scalar is plain, eg, true, false, null, +-.inf or .nan.
Error utilities used by ryml.