Files
toml11/include/toml11/types.hpp
ToruNiina 844e8fd4e0 fix(#279): fix compilation with older msvc
by avoiding known SFINAE problem in msvc 2017
2025-01-13 00:47:58 +09:00

375 lines
11 KiB
C++

#ifndef TOML11_TYPES_HPP
#define TOML11_TYPES_HPP
#include "comments.hpp"
#include "compat.hpp"
#include "error_info.hpp"
#include "format.hpp"
#include "ordered_map.hpp"
#include "value.hpp"
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include <cstdint>
#include <cstdio>
namespace toml
{
// forward decl
template<typename TypeConfig>
class basic_value;
// when you use a special integer type as toml::value::integer_type, parse must
// be able to read it. So, type_config has static member functions that read the
// integer_type as {dec, hex, oct, bin}-integer. But, in most cases, operator<<
// is enough. To make config easy, we provide the default read functions.
//
// Before this functions is called, syntax is checked and prefix(`0x` etc) and
// spacer(`_`) are removed.
template<typename T>
result<T, error_info>
read_dec_int(const std::string& str, const source_location src)
{
constexpr auto max_digits = std::numeric_limits<T>::digits;
assert( ! str.empty());
T val{0};
std::istringstream iss(str);
iss >> val;
if(iss.fail())
{
return err(make_error_info("toml::parse_dec_integer: "
"too large integer: current max digits = 2^" + std::to_string(max_digits),
std::move(src), "must be < 2^" + std::to_string(max_digits)));
}
return ok(val);
}
template<typename T>
result<T, error_info>
read_hex_int(const std::string& str, const source_location src)
{
constexpr auto max_digits = std::numeric_limits<T>::digits;
assert( ! str.empty());
T val{0};
std::istringstream iss(str);
iss >> std::hex >> val;
if(iss.fail())
{
return err(make_error_info("toml::parse_hex_integer: "
"too large integer: current max value = 2^" + std::to_string(max_digits),
std::move(src), "must be < 2^" + std::to_string(max_digits)));
}
return ok(val);
}
template<typename T>
result<T, error_info>
read_oct_int(const std::string& str, const source_location src)
{
constexpr auto max_digits = std::numeric_limits<T>::digits;
assert( ! str.empty());
T val{0};
std::istringstream iss(str);
iss >> std::oct >> val;
if(iss.fail())
{
return err(make_error_info("toml::parse_oct_integer: "
"too large integer: current max value = 2^" + std::to_string(max_digits),
std::move(src), "must be < 2^" + std::to_string(max_digits)));
}
return ok(val);
}
template<typename T>
result<T, error_info>
read_bin_int(const std::string& str, const source_location src)
{
constexpr auto is_bounded = std::numeric_limits<T>::is_bounded;
constexpr auto max_digits = std::numeric_limits<T>::digits;
const auto max_value = (std::numeric_limits<T>::max)();
T val{0};
T base{1};
for(auto i = str.rbegin(); i != str.rend(); ++i)
{
const auto c = *i;
if(c == '1')
{
val += base;
// prevent `base` from overflow
if(is_bounded && max_value / 2 < base && std::next(i) != str.rend())
{
base = 0;
}
else
{
base *= 2;
}
}
else
{
assert(c == '0');
if(is_bounded && max_value / 2 < base && std::next(i) != str.rend())
{
base = 0;
}
else
{
base *= 2;
}
}
}
if(base == 0)
{
return err(make_error_info("toml::parse_bin_integer: "
"too large integer: current max value = 2^" + std::to_string(max_digits),
std::move(src), "must be < 2^" + std::to_string(max_digits)));
}
return ok(val);
}
template<typename T>
result<T, error_info>
read_int(const std::string& str, const source_location src, const std::uint8_t base)
{
assert(base == 10 || base == 16 || base == 8 || base == 2);
switch(base)
{
case 2: { return read_bin_int<T>(str, src); }
case 8: { return read_oct_int<T>(str, src); }
case 16: { return read_hex_int<T>(str, src); }
default:
{
assert(base == 10);
return read_dec_int<T>(str, src);
}
}
}
inline result<float, error_info>
read_hex_float(const std::string& str, const source_location src, float val)
{
#if defined(_MSC_VER) && ! defined(__clang__)
const auto res = ::sscanf_s(str.c_str(), "%a", std::addressof(val));
#else
const auto res = std::sscanf(str.c_str(), "%a", std::addressof(val));
#endif
if(res != 1)
{
return err(make_error_info("toml::parse_floating: "
"failed to read hexadecimal floating point value ",
std::move(src), "here"));
}
return ok(val);
}
inline result<double, error_info>
read_hex_float(const std::string& str, const source_location src, double val)
{
#if defined(_MSC_VER) && ! defined(__clang__)
const auto res = ::sscanf_s(str.c_str(), "%la", std::addressof(val));
#else
const auto res = std::sscanf(str.c_str(), "%la", std::addressof(val));
#endif
if(res != 1)
{
return err(make_error_info("toml::parse_floating: "
"failed to read hexadecimal floating point value ",
std::move(src), "here"));
}
return ok(val);
}
template<typename T>
cxx::enable_if_t<cxx::conjunction<
cxx::negation<std::is_same<cxx::remove_cvref_t<T>, double>>,
cxx::negation<std::is_same<cxx::remove_cvref_t<T>, float>>
>::value, result<T, error_info>>
read_hex_float(const std::string&, const source_location src, T)
{
return err(make_error_info("toml::parse_floating: failed to read "
"floating point value because of unknown type in type_config",
std::move(src), "here"));
}
template<typename T>
result<T, error_info>
read_dec_float(const std::string& str, const source_location src)
{
T val;
std::istringstream iss(str);
iss >> val;
if(iss.fail())
{
return err(make_error_info("toml::parse_floating: "
"failed to read floating point value from stream",
std::move(src), "here"));
}
return ok(val);
}
template<typename T>
result<T, error_info>
read_float(const std::string& str, const source_location src, const bool is_hex)
{
if(is_hex)
{
return read_hex_float(str, src, T{});
}
else
{
return read_dec_float<T>(str, src);
}
}
struct type_config
{
using comment_type = preserve_comments;
using boolean_type = bool;
using integer_type = std::int64_t;
using floating_type = double;
using string_type = std::string;
template<typename T>
using array_type = std::vector<T>;
template<typename K, typename T>
using table_type = std::unordered_map<K, T>;
static result<integer_type, error_info>
parse_int(const std::string& str, const source_location src, const std::uint8_t base)
{
return read_int<integer_type>(str, src, base);
}
static result<floating_type, error_info>
parse_float(const std::string& str, const source_location src, const bool is_hex)
{
return read_float<floating_type>(str, src, is_hex);
}
};
using value = basic_value<type_config>;
using table = typename value::table_type;
using array = typename value::array_type;
struct ordered_type_config
{
using comment_type = preserve_comments;
using boolean_type = bool;
using integer_type = std::int64_t;
using floating_type = double;
using string_type = std::string;
template<typename T>
using array_type = std::vector<T>;
template<typename K, typename T>
using table_type = ordered_map<K, T>;
static result<integer_type, error_info>
parse_int(const std::string& str, const source_location src, const std::uint8_t base)
{
return read_int<integer_type>(str, src, base);
}
static result<floating_type, error_info>
parse_float(const std::string& str, const source_location src, const bool is_hex)
{
return read_float<floating_type>(str, src, is_hex);
}
};
using ordered_value = basic_value<ordered_type_config>;
using ordered_table = typename ordered_value::table_type;
using ordered_array = typename ordered_value::array_type;
// ----------------------------------------------------------------------------
// meta functions for internal use
namespace detail
{
// ----------------------------------------------------------------------------
// check if type T has all the needed member types
template<typename T, typename U = void>
struct has_comment_type: std::false_type{};
template<typename T>
struct has_comment_type<T, cxx::void_t<typename T::comment_type>>: std::true_type{};
template<typename T, typename U = void>
struct has_integer_type: std::false_type{};
template<typename T>
struct has_integer_type<T, cxx::void_t<typename T::integer_type>>: std::true_type{};
template<typename T, typename U = void>
struct has_floating_type: std::false_type{};
template<typename T>
struct has_floating_type<T, cxx::void_t<typename T::floating_type>>: std::true_type{};
template<typename T, typename U = void>
struct has_string_type: std::false_type{};
template<typename T>
struct has_string_type<T, cxx::void_t<typename T::string_type>>: std::true_type{};
template<typename T, typename U = void>
struct has_array_type: std::false_type{};
template<typename T>
struct has_array_type<T, cxx::void_t<typename T::template array_type<int>>>: std::true_type{};
template<typename T, typename U = void>
struct has_table_type: std::false_type{};
template<typename T>
struct has_table_type<T, cxx::void_t<typename T::template table_type<int, int>>>: std::true_type{};
template<typename T, typename U = void>
struct has_parse_int: std::false_type{};
template<typename T>
struct has_parse_int<T, cxx::void_t<decltype(std::declval<T>().parse_int(
std::declval<std::string const&>(),
std::declval<::toml::source_location const&>(),
std::declval<std::uint8_t>()
))>>: std::true_type{};
template<typename T, typename U = void>
struct has_parse_float: std::false_type{};
template<typename T>
struct has_parse_float<T, cxx::void_t<decltype(std::declval<T>().parse_float(
std::declval<std::string const&>(),
std::declval<::toml::source_location const&>(),
std::declval<bool>()
))>>: std::true_type{};
template<typename T>
using is_type_config = cxx::conjunction<
has_comment_type<T>,
has_integer_type<T>,
has_floating_type<T>,
has_string_type<T>,
has_array_type<T>,
has_table_type<T>,
has_parse_int<T>,
has_parse_float<T>
>;
} // namespace detail
} // namespace toml
#if defined(TOML11_COMPILE_SOURCES)
namespace toml
{
extern template class basic_value<type_config>;
extern template class basic_value<ordered_type_config>;
} // toml
#endif // TOML11_COMPILE_SOURCES
#endif // TOML11_TYPES_HPP