Replace macro in convert.h with partial template specialization.

Dealing with floating points is also closer to YAML 1.2 core schema.
This commit is contained in:
Lenard Szolnoki 2017-07-03 15:43:13 +02:00
parent 1d26aa7e9f
commit 027f684264
2 changed files with 145 additions and 65 deletions

View File

@ -8,9 +8,11 @@
#endif
#include <array>
#include <cmath>
#include <limits>
#include <list>
#include <map>
#include <regex>
#include <sstream>
#include <vector>
@ -24,12 +26,23 @@
namespace YAML {
class Binary;
struct _Null;
template <typename T>
template <typename T, typename Enable>
struct convert;
} // namespace YAML
namespace YAML {
namespace conversion {
static const std::regex re_true("true|True|TRUE");
static const std::regex re_false("false|False|FALSE");
static const std::regex re_decimal("[-+]?[0-9]+");
static const std::regex re_octal("0o[0-7]+");
static const std::regex re_hex("0x[0-9a-fA-F]+");
static const std::regex re_float(
"[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)?");
static const std::regex re_inf("[-+]?(\\.inf|\\.Inf|\\.INF)");
static const std::regex re_nan("\\.nan|\\.NaN|\\.NAN");
inline bool IsInfinity(const std::string& input) {
return input == ".inf" || input == ".Inf" || input == ".INF" ||
input == "+.inf" || input == "+.Inf" || input == "+.INF";
@ -76,7 +89,7 @@ struct convert<const char*> {
template <std::size_t N>
struct convert<const char[N]> {
static Node encode(const char(&rhs)[N]) { return Node(rhs); }
static Node encode(const char (&rhs)[N]) { return Node(rhs); }
};
template <>
@ -88,70 +101,137 @@ struct convert<_Null> {
}
};
#define YAML_DEFINE_CONVERT_STREAMABLE(type, negative_op) \
template <> \
struct convert<type> { \
static Node encode(const type& rhs) { \
std::stringstream stream; \
stream.precision(std::numeric_limits<type>::digits10 + 1); \
stream << rhs; \
return Node(stream.str()); \
} \
\
static bool decode(const Node& node, type& rhs) { \
if (node.Type() != NodeType::Scalar) \
return false; \
const std::string& input = node.Scalar(); \
std::stringstream stream(input); \
stream.unsetf(std::ios::dec); \
if ((stream >> std::noskipws >> rhs) && (stream >> std::ws).eof()) \
return true; \
if (std::numeric_limits<type>::has_infinity) { \
if (conversion::IsInfinity(input)) { \
rhs = std::numeric_limits<type>::infinity(); \
return true; \
} else if (conversion::IsNegativeInfinity(input)) { \
rhs = negative_op std::numeric_limits<type>::infinity(); \
return true; \
} \
} \
\
if (std::numeric_limits<type>::has_quiet_NaN && \
conversion::IsNaN(input)) { \
rhs = std::numeric_limits<type>::quiet_NaN(); \
return true; \
} \
\
return false; \
} \
// signed integral
template <typename type>
struct convert<type,
typename std::enable_if<std::is_integral<type>::value &&
std::is_signed<type>::value>::type> {
static Node encode(const type& rhs) { return Node(std::to_string(rhs)); }
static bool decode(const Node& node, type& rhs) {
if (node.Type() != NodeType::Scalar)
return false;
const std::string& input = node.Scalar();
long long num;
if (std::regex_match(input, conversion::re_decimal)) {
try {
num = std::stoll(input);
} catch (const std::out_of_range&) {
return false;
}
} else if (std::regex_match(input, conversion::re_octal)) {
try {
num = std::stoll(input.substr(2), nullptr, 8);
} catch (const std::out_of_range&) {
return false;
}
} else if (std::regex_match(input, conversion::re_hex)) {
try {
num = std::stoll(input.substr(2), nullptr, 16);
} catch (const std::out_of_range&) {
return false;
}
} else {
return false;
}
if (num > std::numeric_limits<type>::max() ||
num < std::numeric_limits<type>::min()) {
return false;
}
rhs = num;
return true;
}
};
// unsigned integral
template <typename type>
struct convert<type,
typename std::enable_if<std::is_integral<type>::value &&
std::is_unsigned<type>::value>::type> {
static Node encode(const type& rhs) { return Node(std::to_string(rhs)); }
static bool decode(const Node& node, type& rhs) {
if (node.Type() != NodeType::Scalar)
return false;
const std::string& input = node.Scalar();
unsigned long long num;
if (std::regex_match(input, conversion::re_decimal)) {
try {
num = std::stoull(input);
} catch (const std::out_of_range&) {
return false;
}
} else if (std::regex_match(input, conversion::re_octal)) {
try {
num = std::stoull(input.substr(2), nullptr, 8);
} catch (const std::out_of_range&) {
return false;
}
} else if (std::regex_match(input, conversion::re_hex)) {
try {
num = std::stoull(input.substr(2), nullptr, 16);
} catch (const std::out_of_range&) {
return false;
}
} else {
return false;
}
if (num > std::numeric_limits<type>::max() ||
num < std::numeric_limits<type>::min()) {
return false;
}
rhs = num;
return true;
}
};
// floating point
template <typename type>
struct convert<
type, typename std::enable_if<std::is_floating_point<type>::value>::type> {
static Node encode(const type& rhs) {
if (std::isnan(rhs)) {
return Node(".nan");
}
if (std::isinf(rhs)) {
if (std::signbit(rhs)) {
return Node("-.inf");
}
return Node(".inf");
}
auto str = std::to_string(rhs);
if (std::regex_match(str, conversion::re_decimal)) {
return Node(str + "."); // Disambiguate float from int
}
return Node(std::to_string(rhs));
}
#define YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(type) \
YAML_DEFINE_CONVERT_STREAMABLE(type, -)
#define YAML_DEFINE_CONVERT_STREAMABLE_UNSIGNED(type) \
YAML_DEFINE_CONVERT_STREAMABLE(type, +)
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(int);
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(short);
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(long);
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(long long);
YAML_DEFINE_CONVERT_STREAMABLE_UNSIGNED(unsigned);
YAML_DEFINE_CONVERT_STREAMABLE_UNSIGNED(unsigned short);
YAML_DEFINE_CONVERT_STREAMABLE_UNSIGNED(unsigned long);
YAML_DEFINE_CONVERT_STREAMABLE_UNSIGNED(unsigned long long);
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(char);
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(signed char);
YAML_DEFINE_CONVERT_STREAMABLE_UNSIGNED(unsigned char);
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(float);
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(double);
YAML_DEFINE_CONVERT_STREAMABLE_SIGNED(long double);
#undef YAML_DEFINE_CONVERT_STREAMABLE_SIGNED
#undef YAML_DEFINE_CONVERT_STREAMABLE_UNSIGNED
#undef YAML_DEFINE_CONVERT_STREAMABLE
static bool decode(const Node& node, type& rhs) {
if (node.Type() != NodeType::Scalar)
return false;
const std::string& input = node.Scalar();
long double num;
if (std::regex_match(input, conversion::re_float)) {
try {
num = std::stold(input);
} catch (const std::out_of_range&) {
return false;
}
} else if (std::regex_match(input, conversion::re_inf)) {
if (input[0] == '-') {
num = -std::numeric_limits<long double>::infinity();
} else {
num = std::numeric_limits<long double>::infinity();
}
} else if (std::regex_match(input, conversion::re_nan)) {
num = std::numeric_limits<long double>::quiet_NaN();
} else {
return false;
}
rhs = num;
return true;
}
};
// bool
template <>

View File

@ -138,7 +138,7 @@ YAML_CPP_API bool operator==(const Node& lhs, const Node& rhs);
YAML_CPP_API Node Clone(const Node& node);
template <typename T>
template <typename T, typename Enable = void>
struct convert;
}