forked from lix-project/lix
3669 lines
94 KiB
C++
3669 lines
94 KiB
C++
/**
|
|
* @file cpptoml.h
|
|
* @author Chase Geigle
|
|
* @date May 2013
|
|
*/
|
|
|
|
#ifndef CPPTOML_H
|
|
#define CPPTOML_H
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <clocale>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#if __cplusplus > 201103L
|
|
#define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]]
|
|
#elif defined(__clang__)
|
|
#define CPPTOML_DEPRECATED(reason) __attribute__((deprecated(reason)))
|
|
#elif defined(__GNUG__)
|
|
#define CPPTOML_DEPRECATED(reason) __attribute__((deprecated))
|
|
#elif defined(_MSC_VER)
|
|
#if _MSC_VER < 1910
|
|
#define CPPTOML_DEPRECATED(reason) __declspec(deprecated)
|
|
#else
|
|
#define CPPTOML_DEPRECATED(reason) [[deprecated(reason)]]
|
|
#endif
|
|
#endif
|
|
|
|
namespace cpptoml
|
|
{
|
|
class writer; // forward declaration
|
|
class base; // forward declaration
|
|
#if defined(CPPTOML_USE_MAP)
|
|
// a std::map will ensure that entries a sorted, albeit at a slight
|
|
// performance penalty relative to the (default) unordered_map
|
|
using string_to_base_map = std::map<std::string, std::shared_ptr<base>>;
|
|
#else
|
|
// by default an unordered_map is used for best performance as the
|
|
// toml specification does not require entries to be sorted
|
|
using string_to_base_map
|
|
= std::unordered_map<std::string, std::shared_ptr<base>>;
|
|
#endif
|
|
|
|
// if defined, `base` will retain type information in form of an enum class
|
|
// such that static_cast can be used instead of dynamic_cast
|
|
// #define CPPTOML_NO_RTTI
|
|
|
|
template <class T>
|
|
class option
|
|
{
|
|
public:
|
|
option() : empty_{true}
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
option(T value) : empty_{false}, value_(std::move(value))
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
explicit operator bool() const
|
|
{
|
|
return !empty_;
|
|
}
|
|
|
|
const T& operator*() const
|
|
{
|
|
return value_;
|
|
}
|
|
|
|
const T* operator->() const
|
|
{
|
|
return &value_;
|
|
}
|
|
|
|
template <class U>
|
|
T value_or(U&& alternative) const
|
|
{
|
|
if (!empty_)
|
|
return value_;
|
|
return static_cast<T>(std::forward<U>(alternative));
|
|
}
|
|
|
|
private:
|
|
bool empty_;
|
|
T value_;
|
|
};
|
|
|
|
struct local_date
|
|
{
|
|
int year = 0;
|
|
int month = 0;
|
|
int day = 0;
|
|
};
|
|
|
|
struct local_time
|
|
{
|
|
int hour = 0;
|
|
int minute = 0;
|
|
int second = 0;
|
|
int microsecond = 0;
|
|
};
|
|
|
|
struct zone_offset
|
|
{
|
|
int hour_offset = 0;
|
|
int minute_offset = 0;
|
|
};
|
|
|
|
struct local_datetime : local_date, local_time
|
|
{
|
|
};
|
|
|
|
struct offset_datetime : local_datetime, zone_offset
|
|
{
|
|
static inline struct offset_datetime from_zoned(const struct tm& t)
|
|
{
|
|
offset_datetime dt;
|
|
dt.year = t.tm_year + 1900;
|
|
dt.month = t.tm_mon + 1;
|
|
dt.day = t.tm_mday;
|
|
dt.hour = t.tm_hour;
|
|
dt.minute = t.tm_min;
|
|
dt.second = t.tm_sec;
|
|
|
|
char buf[16];
|
|
strftime(buf, 16, "%z", &t);
|
|
|
|
int offset = std::stoi(buf);
|
|
dt.hour_offset = offset / 100;
|
|
dt.minute_offset = offset % 100;
|
|
return dt;
|
|
}
|
|
|
|
CPPTOML_DEPRECATED("from_local has been renamed to from_zoned")
|
|
static inline struct offset_datetime from_local(const struct tm& t)
|
|
{
|
|
return from_zoned(t);
|
|
}
|
|
|
|
static inline struct offset_datetime from_utc(const struct tm& t)
|
|
{
|
|
offset_datetime dt;
|
|
dt.year = t.tm_year + 1900;
|
|
dt.month = t.tm_mon + 1;
|
|
dt.day = t.tm_mday;
|
|
dt.hour = t.tm_hour;
|
|
dt.minute = t.tm_min;
|
|
dt.second = t.tm_sec;
|
|
return dt;
|
|
}
|
|
};
|
|
|
|
CPPTOML_DEPRECATED("datetime has been renamed to offset_datetime")
|
|
typedef offset_datetime datetime;
|
|
|
|
class fill_guard
|
|
{
|
|
public:
|
|
fill_guard(std::ostream& os) : os_(os), fill_{os.fill()}
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
~fill_guard()
|
|
{
|
|
os_.fill(fill_);
|
|
}
|
|
|
|
private:
|
|
std::ostream& os_;
|
|
std::ostream::char_type fill_;
|
|
};
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, const local_date& dt)
|
|
{
|
|
fill_guard g{os};
|
|
os.fill('0');
|
|
|
|
using std::setw;
|
|
os << setw(4) << dt.year << "-" << setw(2) << dt.month << "-" << setw(2)
|
|
<< dt.day;
|
|
|
|
return os;
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, const local_time& ltime)
|
|
{
|
|
fill_guard g{os};
|
|
os.fill('0');
|
|
|
|
using std::setw;
|
|
os << setw(2) << ltime.hour << ":" << setw(2) << ltime.minute << ":"
|
|
<< setw(2) << ltime.second;
|
|
|
|
if (ltime.microsecond > 0)
|
|
{
|
|
os << ".";
|
|
int power = 100000;
|
|
for (int curr_us = ltime.microsecond; curr_us; power /= 10)
|
|
{
|
|
auto num = curr_us / power;
|
|
os << num;
|
|
curr_us -= num * power;
|
|
}
|
|
}
|
|
|
|
return os;
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, const zone_offset& zo)
|
|
{
|
|
fill_guard g{os};
|
|
os.fill('0');
|
|
|
|
using std::setw;
|
|
|
|
if (zo.hour_offset != 0 || zo.minute_offset != 0)
|
|
{
|
|
if (zo.hour_offset > 0)
|
|
{
|
|
os << "+";
|
|
}
|
|
else
|
|
{
|
|
os << "-";
|
|
}
|
|
os << setw(2) << std::abs(zo.hour_offset) << ":" << setw(2)
|
|
<< std::abs(zo.minute_offset);
|
|
}
|
|
else
|
|
{
|
|
os << "Z";
|
|
}
|
|
|
|
return os;
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, const local_datetime& dt)
|
|
{
|
|
return os << static_cast<const local_date&>(dt) << "T"
|
|
<< static_cast<const local_time&>(dt);
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& os, const offset_datetime& dt)
|
|
{
|
|
return os << static_cast<const local_datetime&>(dt)
|
|
<< static_cast<const zone_offset&>(dt);
|
|
}
|
|
|
|
template <class T, class... Ts>
|
|
struct is_one_of;
|
|
|
|
template <class T, class V>
|
|
struct is_one_of<T, V> : std::is_same<T, V>
|
|
{
|
|
};
|
|
|
|
template <class T, class V, class... Ts>
|
|
struct is_one_of<T, V, Ts...>
|
|
{
|
|
const static bool value
|
|
= std::is_same<T, V>::value || is_one_of<T, Ts...>::value;
|
|
};
|
|
|
|
template <class T>
|
|
class value;
|
|
|
|
template <class T>
|
|
struct valid_value
|
|
: is_one_of<T, std::string, int64_t, double, bool, local_date, local_time,
|
|
local_datetime, offset_datetime>
|
|
{
|
|
};
|
|
|
|
template <class T, class Enable = void>
|
|
struct value_traits;
|
|
|
|
template <class T>
|
|
struct valid_value_or_string_convertible
|
|
{
|
|
|
|
const static bool value = valid_value<typename std::decay<T>::type>::value
|
|
|| std::is_convertible<T, std::string>::value;
|
|
};
|
|
|
|
template <class T>
|
|
struct value_traits<T, typename std::enable_if<
|
|
valid_value_or_string_convertible<T>::value>::type>
|
|
{
|
|
using value_type = typename std::conditional<
|
|
valid_value<typename std::decay<T>::type>::value,
|
|
typename std::decay<T>::type, std::string>::type;
|
|
|
|
using type = value<value_type>;
|
|
|
|
static value_type construct(T&& val)
|
|
{
|
|
return value_type(val);
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
struct value_traits<
|
|
T,
|
|
typename std::enable_if<
|
|
!valid_value_or_string_convertible<T>::value
|
|
&& std::is_floating_point<typename std::decay<T>::type>::value>::type>
|
|
{
|
|
using value_type = typename std::decay<T>::type;
|
|
|
|
using type = value<double>;
|
|
|
|
static value_type construct(T&& val)
|
|
{
|
|
return value_type(val);
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
struct value_traits<
|
|
T, typename std::enable_if<
|
|
!valid_value_or_string_convertible<T>::value
|
|
&& !std::is_floating_point<typename std::decay<T>::type>::value
|
|
&& std::is_signed<typename std::decay<T>::type>::value>::type>
|
|
{
|
|
using value_type = int64_t;
|
|
|
|
using type = value<int64_t>;
|
|
|
|
static value_type construct(T&& val)
|
|
{
|
|
if (val < (std::numeric_limits<int64_t>::min)())
|
|
throw std::underflow_error{"constructed value cannot be "
|
|
"represented by a 64-bit signed "
|
|
"integer"};
|
|
|
|
if (val > (std::numeric_limits<int64_t>::max)())
|
|
throw std::overflow_error{"constructed value cannot be represented "
|
|
"by a 64-bit signed integer"};
|
|
|
|
return static_cast<int64_t>(val);
|
|
}
|
|
};
|
|
|
|
template <class T>
|
|
struct value_traits<
|
|
T, typename std::enable_if<
|
|
!valid_value_or_string_convertible<T>::value
|
|
&& std::is_unsigned<typename std::decay<T>::type>::value>::type>
|
|
{
|
|
using value_type = int64_t;
|
|
|
|
using type = value<int64_t>;
|
|
|
|
static value_type construct(T&& val)
|
|
{
|
|
if (val > static_cast<uint64_t>((std::numeric_limits<int64_t>::max)()))
|
|
throw std::overflow_error{"constructed value cannot be represented "
|
|
"by a 64-bit signed integer"};
|
|
|
|
return static_cast<int64_t>(val);
|
|
}
|
|
};
|
|
|
|
class array;
|
|
class table;
|
|
class table_array;
|
|
|
|
template <class T>
|
|
struct array_of_trait
|
|
{
|
|
using return_type = option<std::vector<T>>;
|
|
};
|
|
|
|
template <>
|
|
struct array_of_trait<array>
|
|
{
|
|
using return_type = option<std::vector<std::shared_ptr<array>>>;
|
|
};
|
|
|
|
template <class T>
|
|
inline std::shared_ptr<typename value_traits<T>::type> make_value(T&& val);
|
|
inline std::shared_ptr<array> make_array();
|
|
|
|
namespace detail
|
|
{
|
|
template <class T>
|
|
inline std::shared_ptr<T> make_element();
|
|
}
|
|
|
|
inline std::shared_ptr<table> make_table();
|
|
inline std::shared_ptr<table_array> make_table_array(bool is_inline = false);
|
|
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
/// Base type used to store underlying data type explicitly if RTTI is disabled
|
|
enum class base_type
|
|
{
|
|
NONE,
|
|
STRING,
|
|
LOCAL_TIME,
|
|
LOCAL_DATE,
|
|
LOCAL_DATETIME,
|
|
OFFSET_DATETIME,
|
|
INT,
|
|
FLOAT,
|
|
BOOL,
|
|
TABLE,
|
|
ARRAY,
|
|
TABLE_ARRAY
|
|
};
|
|
|
|
/// Type traits class to convert C++ types to enum member
|
|
template <class T>
|
|
struct base_type_traits;
|
|
|
|
template <>
|
|
struct base_type_traits<std::string>
|
|
{
|
|
static const base_type type = base_type::STRING;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<local_time>
|
|
{
|
|
static const base_type type = base_type::LOCAL_TIME;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<local_date>
|
|
{
|
|
static const base_type type = base_type::LOCAL_DATE;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<local_datetime>
|
|
{
|
|
static const base_type type = base_type::LOCAL_DATETIME;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<offset_datetime>
|
|
{
|
|
static const base_type type = base_type::OFFSET_DATETIME;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<int64_t>
|
|
{
|
|
static const base_type type = base_type::INT;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<double>
|
|
{
|
|
static const base_type type = base_type::FLOAT;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<bool>
|
|
{
|
|
static const base_type type = base_type::BOOL;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<table>
|
|
{
|
|
static const base_type type = base_type::TABLE;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<array>
|
|
{
|
|
static const base_type type = base_type::ARRAY;
|
|
};
|
|
|
|
template <>
|
|
struct base_type_traits<table_array>
|
|
{
|
|
static const base_type type = base_type::TABLE_ARRAY;
|
|
};
|
|
#endif
|
|
|
|
/**
|
|
* A generic base TOML value used for type erasure.
|
|
*/
|
|
class base : public std::enable_shared_from_this<base>
|
|
{
|
|
public:
|
|
virtual ~base() = default;
|
|
|
|
virtual std::shared_ptr<base> clone() const = 0;
|
|
|
|
/**
|
|
* Determines if the given TOML element is a value.
|
|
*/
|
|
virtual bool is_value() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Determines if the given TOML element is a table.
|
|
*/
|
|
virtual bool is_table() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Converts the TOML element into a table.
|
|
*/
|
|
std::shared_ptr<table> as_table()
|
|
{
|
|
if (is_table())
|
|
return std::static_pointer_cast<table>(shared_from_this());
|
|
return nullptr;
|
|
}
|
|
/**
|
|
* Determines if the TOML element is an array of "leaf" elements.
|
|
*/
|
|
virtual bool is_array() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Converts the TOML element to an array.
|
|
*/
|
|
std::shared_ptr<array> as_array()
|
|
{
|
|
if (is_array())
|
|
return std::static_pointer_cast<array>(shared_from_this());
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Determines if the given TOML element is an array of tables.
|
|
*/
|
|
virtual bool is_table_array() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Converts the TOML element into a table array.
|
|
*/
|
|
std::shared_ptr<table_array> as_table_array()
|
|
{
|
|
if (is_table_array())
|
|
return std::static_pointer_cast<table_array>(shared_from_this());
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Attempts to coerce the TOML element into a concrete TOML value
|
|
* of type T.
|
|
*/
|
|
template <class T>
|
|
std::shared_ptr<value<T>> as();
|
|
|
|
template <class T>
|
|
std::shared_ptr<const value<T>> as() const;
|
|
|
|
template <class Visitor, class... Args>
|
|
void accept(Visitor&& visitor, Args&&... args) const;
|
|
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
base_type type() const
|
|
{
|
|
return type_;
|
|
}
|
|
|
|
protected:
|
|
base(const base_type t) : type_(t)
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
private:
|
|
const base_type type_ = base_type::NONE;
|
|
|
|
#else
|
|
protected:
|
|
base()
|
|
{
|
|
// nothing
|
|
}
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* A concrete TOML value representing the "leaves" of the "tree".
|
|
*/
|
|
template <class T>
|
|
class value : public base
|
|
{
|
|
struct make_shared_enabler
|
|
{
|
|
// nothing; this is a private key accessible only to friends
|
|
};
|
|
|
|
template <class U>
|
|
friend std::shared_ptr<typename value_traits<U>::type>
|
|
cpptoml::make_value(U&& val);
|
|
|
|
public:
|
|
static_assert(valid_value<T>::value, "invalid value type");
|
|
|
|
std::shared_ptr<base> clone() const override;
|
|
|
|
value(const make_shared_enabler&, const T& val) : value(val)
|
|
{
|
|
// nothing; note that users cannot actually invoke this function
|
|
// because they lack access to the make_shared_enabler.
|
|
}
|
|
|
|
bool is_value() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets the data associated with this value.
|
|
*/
|
|
T& get()
|
|
{
|
|
return data_;
|
|
}
|
|
|
|
/**
|
|
* Gets the data associated with this value. Const version.
|
|
*/
|
|
const T& get() const
|
|
{
|
|
return data_;
|
|
}
|
|
|
|
private:
|
|
T data_;
|
|
|
|
/**
|
|
* Constructs a value from the given data.
|
|
*/
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
value(const T& val) : base(base_type_traits<T>::type), data_(val)
|
|
{
|
|
}
|
|
#else
|
|
value(const T& val) : data_(val)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
value(const value& val) = delete;
|
|
value& operator=(const value& val) = delete;
|
|
};
|
|
|
|
template <class T>
|
|
std::shared_ptr<typename value_traits<T>::type> make_value(T&& val)
|
|
{
|
|
using value_type = typename value_traits<T>::type;
|
|
using enabler = typename value_type::make_shared_enabler;
|
|
return std::make_shared<value_type>(
|
|
enabler{}, value_traits<T>::construct(std::forward<T>(val)));
|
|
}
|
|
|
|
template <class T>
|
|
inline std::shared_ptr<value<T>> base::as()
|
|
{
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
if (type() == base_type_traits<T>::type)
|
|
return std::static_pointer_cast<value<T>>(shared_from_this());
|
|
else
|
|
return nullptr;
|
|
#else
|
|
return std::dynamic_pointer_cast<value<T>>(shared_from_this());
|
|
#endif
|
|
}
|
|
|
|
// special case value<double> to allow getting an integer parameter as a
|
|
// double value
|
|
template <>
|
|
inline std::shared_ptr<value<double>> base::as()
|
|
{
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
if (type() == base_type::FLOAT)
|
|
return std::static_pointer_cast<value<double>>(shared_from_this());
|
|
|
|
if (type() == base_type::INT)
|
|
{
|
|
auto v = std::static_pointer_cast<value<int64_t>>(shared_from_this());
|
|
return make_value<double>(static_cast<double>(v->get()));
|
|
}
|
|
#else
|
|
if (auto v = std::dynamic_pointer_cast<value<double>>(shared_from_this()))
|
|
return v;
|
|
|
|
if (auto v = std::dynamic_pointer_cast<value<int64_t>>(shared_from_this()))
|
|
return make_value<double>(static_cast<double>(v->get()));
|
|
#endif
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
template <class T>
|
|
inline std::shared_ptr<const value<T>> base::as() const
|
|
{
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
if (type() == base_type_traits<T>::type)
|
|
return std::static_pointer_cast<const value<T>>(shared_from_this());
|
|
else
|
|
return nullptr;
|
|
#else
|
|
return std::dynamic_pointer_cast<const value<T>>(shared_from_this());
|
|
#endif
|
|
}
|
|
|
|
// special case value<double> to allow getting an integer parameter as a
|
|
// double value
|
|
template <>
|
|
inline std::shared_ptr<const value<double>> base::as() const
|
|
{
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
if (type() == base_type::FLOAT)
|
|
return std::static_pointer_cast<const value<double>>(
|
|
shared_from_this());
|
|
|
|
if (type() == base_type::INT)
|
|
{
|
|
auto v = as<int64_t>();
|
|
// the below has to be a non-const value<double> due to a bug in
|
|
// libc++: https://llvm.org/bugs/show_bug.cgi?id=18843
|
|
return make_value<double>(static_cast<double>(v->get()));
|
|
}
|
|
#else
|
|
if (auto v
|
|
= std::dynamic_pointer_cast<const value<double>>(shared_from_this()))
|
|
return v;
|
|
|
|
if (auto v = as<int64_t>())
|
|
{
|
|
// the below has to be a non-const value<double> due to a bug in
|
|
// libc++: https://llvm.org/bugs/show_bug.cgi?id=18843
|
|
return make_value<double>(static_cast<double>(v->get()));
|
|
}
|
|
#endif
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Exception class for array insertion errors.
|
|
*/
|
|
class array_exception : public std::runtime_error
|
|
{
|
|
public:
|
|
array_exception(const std::string& err) : std::runtime_error{err}
|
|
{
|
|
}
|
|
};
|
|
|
|
class array : public base
|
|
{
|
|
public:
|
|
friend std::shared_ptr<array> make_array();
|
|
|
|
std::shared_ptr<base> clone() const override;
|
|
|
|
virtual bool is_array() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
using size_type = std::size_t;
|
|
|
|
/**
|
|
* arrays can be iterated over
|
|
*/
|
|
using iterator = std::vector<std::shared_ptr<base>>::iterator;
|
|
|
|
/**
|
|
* arrays can be iterated over. Const version.
|
|
*/
|
|
using const_iterator = std::vector<std::shared_ptr<base>>::const_iterator;
|
|
|
|
iterator begin()
|
|
{
|
|
return values_.begin();
|
|
}
|
|
|
|
const_iterator begin() const
|
|
{
|
|
return values_.begin();
|
|
}
|
|
|
|
iterator end()
|
|
{
|
|
return values_.end();
|
|
}
|
|
|
|
const_iterator end() const
|
|
{
|
|
return values_.end();
|
|
}
|
|
|
|
/**
|
|
* Obtains the array (vector) of base values.
|
|
*/
|
|
std::vector<std::shared_ptr<base>>& get()
|
|
{
|
|
return values_;
|
|
}
|
|
|
|
/**
|
|
* Obtains the array (vector) of base values. Const version.
|
|
*/
|
|
const std::vector<std::shared_ptr<base>>& get() const
|
|
{
|
|
return values_;
|
|
}
|
|
|
|
std::shared_ptr<base> at(size_t idx) const
|
|
{
|
|
return values_.at(idx);
|
|
}
|
|
|
|
/**
|
|
* Obtains an array of value<T>s. Note that elements may be
|
|
* nullptr if they cannot be converted to a value<T>.
|
|
*/
|
|
template <class T>
|
|
std::vector<std::shared_ptr<value<T>>> array_of() const
|
|
{
|
|
std::vector<std::shared_ptr<value<T>>> result(values_.size());
|
|
|
|
std::transform(values_.begin(), values_.end(), result.begin(),
|
|
[&](std::shared_ptr<base> v) { return v->as<T>(); });
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Obtains a option<vector<T>>. The option will be empty if the array
|
|
* contains values that are not of type T.
|
|
*/
|
|
template <class T>
|
|
inline typename array_of_trait<T>::return_type get_array_of() const
|
|
{
|
|
std::vector<T> result;
|
|
result.reserve(values_.size());
|
|
|
|
for (const auto& val : values_)
|
|
{
|
|
if (auto v = val->as<T>())
|
|
result.push_back(v->get());
|
|
else
|
|
return {};
|
|
}
|
|
|
|
return {std::move(result)};
|
|
}
|
|
|
|
/**
|
|
* Obtains an array of arrays. Note that elements may be nullptr
|
|
* if they cannot be converted to a array.
|
|
*/
|
|
std::vector<std::shared_ptr<array>> nested_array() const
|
|
{
|
|
std::vector<std::shared_ptr<array>> result(values_.size());
|
|
|
|
std::transform(values_.begin(), values_.end(), result.begin(),
|
|
[&](std::shared_ptr<base> v) -> std::shared_ptr<array> {
|
|
if (v->is_array())
|
|
return std::static_pointer_cast<array>(v);
|
|
return std::shared_ptr<array>{};
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Add a value to the end of the array
|
|
*/
|
|
template <class T>
|
|
void push_back(const std::shared_ptr<value<T>>& val)
|
|
{
|
|
if (values_.empty() || values_[0]->as<T>())
|
|
{
|
|
values_.push_back(val);
|
|
}
|
|
else
|
|
{
|
|
throw array_exception{"Arrays must be homogenous."};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add an array to the end of the array
|
|
*/
|
|
void push_back(const std::shared_ptr<array>& val)
|
|
{
|
|
if (values_.empty() || values_[0]->is_array())
|
|
{
|
|
values_.push_back(val);
|
|
}
|
|
else
|
|
{
|
|
throw array_exception{"Arrays must be homogenous."};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenience function for adding a simple element to the end
|
|
* of the array.
|
|
*/
|
|
template <class T>
|
|
void push_back(T&& val, typename value_traits<T>::type* = 0)
|
|
{
|
|
push_back(make_value(std::forward<T>(val)));
|
|
}
|
|
|
|
/**
|
|
* Insert a value into the array
|
|
*/
|
|
template <class T>
|
|
iterator insert(iterator position, const std::shared_ptr<value<T>>& value)
|
|
{
|
|
if (values_.empty() || values_[0]->as<T>())
|
|
{
|
|
return values_.insert(position, value);
|
|
}
|
|
else
|
|
{
|
|
throw array_exception{"Arrays must be homogenous."};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Insert an array into the array
|
|
*/
|
|
iterator insert(iterator position, const std::shared_ptr<array>& value)
|
|
{
|
|
if (values_.empty() || values_[0]->is_array())
|
|
{
|
|
return values_.insert(position, value);
|
|
}
|
|
else
|
|
{
|
|
throw array_exception{"Arrays must be homogenous."};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenience function for inserting a simple element in the array
|
|
*/
|
|
template <class T>
|
|
iterator insert(iterator position, T&& val,
|
|
typename value_traits<T>::type* = 0)
|
|
{
|
|
return insert(position, make_value(std::forward<T>(val)));
|
|
}
|
|
|
|
/**
|
|
* Erase an element from the array
|
|
*/
|
|
iterator erase(iterator position)
|
|
{
|
|
return values_.erase(position);
|
|
}
|
|
|
|
/**
|
|
* Clear the array
|
|
*/
|
|
void clear()
|
|
{
|
|
values_.clear();
|
|
}
|
|
|
|
/**
|
|
* Reserve space for n values.
|
|
*/
|
|
void reserve(size_type n)
|
|
{
|
|
values_.reserve(n);
|
|
}
|
|
|
|
private:
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
array() : base(base_type::ARRAY)
|
|
{
|
|
// empty
|
|
}
|
|
#else
|
|
array() = default;
|
|
#endif
|
|
|
|
template <class InputIterator>
|
|
array(InputIterator begin, InputIterator end) : values_{begin, end}
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
array(const array& obj) = delete;
|
|
array& operator=(const array& obj) = delete;
|
|
|
|
std::vector<std::shared_ptr<base>> values_;
|
|
};
|
|
|
|
inline std::shared_ptr<array> make_array()
|
|
{
|
|
struct make_shared_enabler : public array
|
|
{
|
|
make_shared_enabler()
|
|
{
|
|
// nothing
|
|
}
|
|
};
|
|
|
|
return std::make_shared<make_shared_enabler>();
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
template <>
|
|
inline std::shared_ptr<array> make_element<array>()
|
|
{
|
|
return make_array();
|
|
}
|
|
} // namespace detail
|
|
|
|
/**
|
|
* Obtains a option<vector<T>>. The option will be empty if the array
|
|
* contains values that are not of type T.
|
|
*/
|
|
template <>
|
|
inline typename array_of_trait<array>::return_type
|
|
array::get_array_of<array>() const
|
|
{
|
|
std::vector<std::shared_ptr<array>> result;
|
|
result.reserve(values_.size());
|
|
|
|
for (const auto& val : values_)
|
|
{
|
|
if (auto v = val->as_array())
|
|
result.push_back(v);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
return {std::move(result)};
|
|
}
|
|
|
|
class table;
|
|
|
|
class table_array : public base
|
|
{
|
|
friend class table;
|
|
friend std::shared_ptr<table_array> make_table_array(bool);
|
|
|
|
public:
|
|
std::shared_ptr<base> clone() const override;
|
|
|
|
using size_type = std::size_t;
|
|
|
|
/**
|
|
* arrays can be iterated over
|
|
*/
|
|
using iterator = std::vector<std::shared_ptr<table>>::iterator;
|
|
|
|
/**
|
|
* arrays can be iterated over. Const version.
|
|
*/
|
|
using const_iterator = std::vector<std::shared_ptr<table>>::const_iterator;
|
|
|
|
iterator begin()
|
|
{
|
|
return array_.begin();
|
|
}
|
|
|
|
const_iterator begin() const
|
|
{
|
|
return array_.begin();
|
|
}
|
|
|
|
iterator end()
|
|
{
|
|
return array_.end();
|
|
}
|
|
|
|
const_iterator end() const
|
|
{
|
|
return array_.end();
|
|
}
|
|
|
|
virtual bool is_table_array() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::shared_ptr<table>>& get()
|
|
{
|
|
return array_;
|
|
}
|
|
|
|
const std::vector<std::shared_ptr<table>>& get() const
|
|
{
|
|
return array_;
|
|
}
|
|
|
|
/**
|
|
* Add a table to the end of the array
|
|
*/
|
|
void push_back(const std::shared_ptr<table>& val)
|
|
{
|
|
array_.push_back(val);
|
|
}
|
|
|
|
/**
|
|
* Insert a table into the array
|
|
*/
|
|
iterator insert(iterator position, const std::shared_ptr<table>& value)
|
|
{
|
|
return array_.insert(position, value);
|
|
}
|
|
|
|
/**
|
|
* Erase an element from the array
|
|
*/
|
|
iterator erase(iterator position)
|
|
{
|
|
return array_.erase(position);
|
|
}
|
|
|
|
/**
|
|
* Clear the array
|
|
*/
|
|
void clear()
|
|
{
|
|
array_.clear();
|
|
}
|
|
|
|
/**
|
|
* Reserve space for n tables.
|
|
*/
|
|
void reserve(size_type n)
|
|
{
|
|
array_.reserve(n);
|
|
}
|
|
|
|
/**
|
|
* Whether or not the table array is declared inline. This mostly
|
|
* matters for parsing, where statically defined arrays cannot be
|
|
* appended to using the array-of-table syntax.
|
|
*/
|
|
bool is_inline() const
|
|
{
|
|
return is_inline_;
|
|
}
|
|
|
|
private:
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
table_array(bool is_inline = false)
|
|
: base(base_type::TABLE_ARRAY), is_inline_(is_inline)
|
|
{
|
|
// nothing
|
|
}
|
|
#else
|
|
table_array(bool is_inline = false) : is_inline_(is_inline)
|
|
{
|
|
// nothing
|
|
}
|
|
#endif
|
|
|
|
table_array(const table_array& obj) = delete;
|
|
table_array& operator=(const table_array& rhs) = delete;
|
|
|
|
std::vector<std::shared_ptr<table>> array_;
|
|
const bool is_inline_ = false;
|
|
};
|
|
|
|
inline std::shared_ptr<table_array> make_table_array(bool is_inline)
|
|
{
|
|
struct make_shared_enabler : public table_array
|
|
{
|
|
make_shared_enabler(bool mse_is_inline) : table_array(mse_is_inline)
|
|
{
|
|
// nothing
|
|
}
|
|
};
|
|
|
|
return std::make_shared<make_shared_enabler>(is_inline);
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
template <>
|
|
inline std::shared_ptr<table_array> make_element<table_array>()
|
|
{
|
|
return make_table_array(true);
|
|
}
|
|
} // namespace detail
|
|
|
|
// The below are overloads for fetching specific value types out of a value
|
|
// where special casting behavior (like bounds checking) is desired
|
|
|
|
template <class T>
|
|
typename std::enable_if<!std::is_floating_point<T>::value
|
|
&& std::is_signed<T>::value,
|
|
option<T>>::type
|
|
get_impl(const std::shared_ptr<base>& elem)
|
|
{
|
|
if (auto v = elem->as<int64_t>())
|
|
{
|
|
if (v->get() < (std::numeric_limits<T>::min)())
|
|
throw std::underflow_error{
|
|
"T cannot represent the value requested in get"};
|
|
|
|
if (v->get() > (std::numeric_limits<T>::max)())
|
|
throw std::overflow_error{
|
|
"T cannot represent the value requested in get"};
|
|
|
|
return {static_cast<T>(v->get())};
|
|
}
|
|
else
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
typename std::enable_if<!std::is_same<T, bool>::value
|
|
&& std::is_unsigned<T>::value,
|
|
option<T>>::type
|
|
get_impl(const std::shared_ptr<base>& elem)
|
|
{
|
|
if (auto v = elem->as<int64_t>())
|
|
{
|
|
if (v->get() < 0)
|
|
throw std::underflow_error{"T cannot store negative value in get"};
|
|
|
|
if (static_cast<uint64_t>(v->get()) > (std::numeric_limits<T>::max)())
|
|
throw std::overflow_error{
|
|
"T cannot represent the value requested in get"};
|
|
|
|
return {static_cast<T>(v->get())};
|
|
}
|
|
else
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
typename std::enable_if<!std::is_integral<T>::value
|
|
|| std::is_same<T, bool>::value,
|
|
option<T>>::type
|
|
get_impl(const std::shared_ptr<base>& elem)
|
|
{
|
|
if (auto v = elem->as<T>())
|
|
{
|
|
return {v->get()};
|
|
}
|
|
else
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a TOML keytable.
|
|
*/
|
|
class table : public base
|
|
{
|
|
public:
|
|
friend class table_array;
|
|
friend std::shared_ptr<table> make_table();
|
|
|
|
std::shared_ptr<base> clone() const override;
|
|
|
|
/**
|
|
* tables can be iterated over.
|
|
*/
|
|
using iterator = string_to_base_map::iterator;
|
|
|
|
/**
|
|
* tables can be iterated over. Const version.
|
|
*/
|
|
using const_iterator = string_to_base_map::const_iterator;
|
|
|
|
iterator begin()
|
|
{
|
|
return map_.begin();
|
|
}
|
|
|
|
const_iterator begin() const
|
|
{
|
|
return map_.begin();
|
|
}
|
|
|
|
iterator end()
|
|
{
|
|
return map_.end();
|
|
}
|
|
|
|
const_iterator end() const
|
|
{
|
|
return map_.end();
|
|
}
|
|
|
|
bool is_table() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool empty() const
|
|
{
|
|
return map_.empty();
|
|
}
|
|
|
|
/**
|
|
* Determines if this key table contains the given key.
|
|
*/
|
|
bool contains(const std::string& key) const
|
|
{
|
|
return map_.find(key) != map_.end();
|
|
}
|
|
|
|
/**
|
|
* Determines if this key table contains the given key. Will
|
|
* resolve "qualified keys". Qualified keys are the full access
|
|
* path separated with dots like "grandparent.parent.child".
|
|
*/
|
|
bool contains_qualified(const std::string& key) const
|
|
{
|
|
return resolve_qualified(key);
|
|
}
|
|
|
|
/**
|
|
* Obtains the base for a given key.
|
|
* @throw std::out_of_range if the key does not exist
|
|
*/
|
|
std::shared_ptr<base> get(const std::string& key) const
|
|
{
|
|
return map_.at(key);
|
|
}
|
|
|
|
/**
|
|
* Obtains the base for a given key. Will resolve "qualified
|
|
* keys". Qualified keys are the full access path separated with
|
|
* dots like "grandparent.parent.child".
|
|
*
|
|
* @throw std::out_of_range if the key does not exist
|
|
*/
|
|
std::shared_ptr<base> get_qualified(const std::string& key) const
|
|
{
|
|
std::shared_ptr<base> p;
|
|
resolve_qualified(key, &p);
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Obtains a table for a given key, if possible.
|
|
*/
|
|
std::shared_ptr<table> get_table(const std::string& key) const
|
|
{
|
|
if (contains(key) && get(key)->is_table())
|
|
return std::static_pointer_cast<table>(get(key));
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Obtains a table for a given key, if possible. Will resolve
|
|
* "qualified keys".
|
|
*/
|
|
std::shared_ptr<table> get_table_qualified(const std::string& key) const
|
|
{
|
|
if (contains_qualified(key) && get_qualified(key)->is_table())
|
|
return std::static_pointer_cast<table>(get_qualified(key));
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Obtains an array for a given key.
|
|
*/
|
|
std::shared_ptr<array> get_array(const std::string& key) const
|
|
{
|
|
if (!contains(key))
|
|
return nullptr;
|
|
return get(key)->as_array();
|
|
}
|
|
|
|
/**
|
|
* Obtains an array for a given key. Will resolve "qualified keys".
|
|
*/
|
|
std::shared_ptr<array> get_array_qualified(const std::string& key) const
|
|
{
|
|
if (!contains_qualified(key))
|
|
return nullptr;
|
|
return get_qualified(key)->as_array();
|
|
}
|
|
|
|
/**
|
|
* Obtains a table_array for a given key, if possible.
|
|
*/
|
|
std::shared_ptr<table_array> get_table_array(const std::string& key) const
|
|
{
|
|
if (!contains(key))
|
|
return nullptr;
|
|
return get(key)->as_table_array();
|
|
}
|
|
|
|
/**
|
|
* Obtains a table_array for a given key, if possible. Will resolve
|
|
* "qualified keys".
|
|
*/
|
|
std::shared_ptr<table_array>
|
|
get_table_array_qualified(const std::string& key) const
|
|
{
|
|
if (!contains_qualified(key))
|
|
return nullptr;
|
|
return get_qualified(key)->as_table_array();
|
|
}
|
|
|
|
/**
|
|
* Helper function that attempts to get a value corresponding
|
|
* to the template parameter from a given key.
|
|
*/
|
|
template <class T>
|
|
option<T> get_as(const std::string& key) const
|
|
{
|
|
try
|
|
{
|
|
return get_impl<T>(get(key));
|
|
}
|
|
catch (const std::out_of_range&)
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function that attempts to get a value corresponding
|
|
* to the template parameter from a given key. Will resolve "qualified
|
|
* keys".
|
|
*/
|
|
template <class T>
|
|
option<T> get_qualified_as(const std::string& key) const
|
|
{
|
|
try
|
|
{
|
|
return get_impl<T>(get_qualified(key));
|
|
}
|
|
catch (const std::out_of_range&)
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function that attempts to get an array of values of a given
|
|
* type corresponding to the template parameter for a given key.
|
|
*
|
|
* If the key doesn't exist, doesn't exist as an array type, or one or
|
|
* more keys inside the array type are not of type T, an empty option
|
|
* is returned. Otherwise, an option containing a vector of the values
|
|
* is returned.
|
|
*/
|
|
template <class T>
|
|
inline typename array_of_trait<T>::return_type
|
|
get_array_of(const std::string& key) const
|
|
{
|
|
if (auto v = get_array(key))
|
|
{
|
|
std::vector<T> result;
|
|
result.reserve(v->get().size());
|
|
|
|
for (const auto& b : v->get())
|
|
{
|
|
if (auto val = b->as<T>())
|
|
result.push_back(val->get());
|
|
else
|
|
return {};
|
|
}
|
|
return {std::move(result)};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* Helper function that attempts to get an array of values of a given
|
|
* type corresponding to the template parameter for a given key. Will
|
|
* resolve "qualified keys".
|
|
*
|
|
* If the key doesn't exist, doesn't exist as an array type, or one or
|
|
* more keys inside the array type are not of type T, an empty option
|
|
* is returned. Otherwise, an option containing a vector of the values
|
|
* is returned.
|
|
*/
|
|
template <class T>
|
|
inline typename array_of_trait<T>::return_type
|
|
get_qualified_array_of(const std::string& key) const
|
|
{
|
|
if (auto v = get_array_qualified(key))
|
|
{
|
|
std::vector<T> result;
|
|
result.reserve(v->get().size());
|
|
|
|
for (const auto& b : v->get())
|
|
{
|
|
if (auto val = b->as<T>())
|
|
result.push_back(val->get());
|
|
else
|
|
return {};
|
|
}
|
|
return {std::move(result)};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* Adds an element to the keytable.
|
|
*/
|
|
void insert(const std::string& key, const std::shared_ptr<base>& value)
|
|
{
|
|
map_[key] = value;
|
|
}
|
|
|
|
/**
|
|
* Convenience shorthand for adding a simple element to the
|
|
* keytable.
|
|
*/
|
|
template <class T>
|
|
void insert(const std::string& key, T&& val,
|
|
typename value_traits<T>::type* = 0)
|
|
{
|
|
insert(key, make_value(std::forward<T>(val)));
|
|
}
|
|
|
|
/**
|
|
* Removes an element from the table.
|
|
*/
|
|
void erase(const std::string& key)
|
|
{
|
|
map_.erase(key);
|
|
}
|
|
|
|
private:
|
|
#if defined(CPPTOML_NO_RTTI)
|
|
table() : base(base_type::TABLE)
|
|
{
|
|
// nothing
|
|
}
|
|
#else
|
|
table()
|
|
{
|
|
// nothing
|
|
}
|
|
#endif
|
|
|
|
table(const table& obj) = delete;
|
|
table& operator=(const table& rhs) = delete;
|
|
|
|
std::vector<std::string> split(const std::string& value,
|
|
char separator) const
|
|
{
|
|
std::vector<std::string> result;
|
|
std::string::size_type p = 0;
|
|
std::string::size_type q;
|
|
while ((q = value.find(separator, p)) != std::string::npos)
|
|
{
|
|
result.emplace_back(value, p, q - p);
|
|
p = q + 1;
|
|
}
|
|
result.emplace_back(value, p);
|
|
return result;
|
|
}
|
|
|
|
// If output parameter p is specified, fill it with the pointer to the
|
|
// specified entry and throw std::out_of_range if it couldn't be found.
|
|
//
|
|
// Otherwise, just return true if the entry could be found or false
|
|
// otherwise and do not throw.
|
|
bool resolve_qualified(const std::string& key,
|
|
std::shared_ptr<base>* p = nullptr) const
|
|
{
|
|
auto parts = split(key, '.');
|
|
auto last_key = parts.back();
|
|
parts.pop_back();
|
|
|
|
auto cur_table = this;
|
|
for (const auto& part : parts)
|
|
{
|
|
cur_table = cur_table->get_table(part).get();
|
|
if (!cur_table)
|
|
{
|
|
if (!p)
|
|
return false;
|
|
|
|
throw std::out_of_range{key + " is not a valid key"};
|
|
}
|
|
}
|
|
|
|
if (!p)
|
|
return cur_table->map_.count(last_key) != 0;
|
|
|
|
*p = cur_table->map_.at(last_key);
|
|
return true;
|
|
}
|
|
|
|
string_to_base_map map_;
|
|
};
|
|
|
|
/**
|
|
* Helper function that attempts to get an array of arrays for a given
|
|
* key.
|
|
*
|
|
* If the key doesn't exist, doesn't exist as an array type, or one or
|
|
* more keys inside the array type are not of type T, an empty option
|
|
* is returned. Otherwise, an option containing a vector of the values
|
|
* is returned.
|
|
*/
|
|
template <>
|
|
inline typename array_of_trait<array>::return_type
|
|
table::get_array_of<array>(const std::string& key) const
|
|
{
|
|
if (auto v = get_array(key))
|
|
{
|
|
std::vector<std::shared_ptr<array>> result;
|
|
result.reserve(v->get().size());
|
|
|
|
for (const auto& b : v->get())
|
|
{
|
|
if (auto val = b->as_array())
|
|
result.push_back(val);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
return {std::move(result)};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* Helper function that attempts to get an array of arrays for a given
|
|
* key. Will resolve "qualified keys".
|
|
*
|
|
* If the key doesn't exist, doesn't exist as an array type, or one or
|
|
* more keys inside the array type are not of type T, an empty option
|
|
* is returned. Otherwise, an option containing a vector of the values
|
|
* is returned.
|
|
*/
|
|
template <>
|
|
inline typename array_of_trait<array>::return_type
|
|
table::get_qualified_array_of<array>(const std::string& key) const
|
|
{
|
|
if (auto v = get_array_qualified(key))
|
|
{
|
|
std::vector<std::shared_ptr<array>> result;
|
|
result.reserve(v->get().size());
|
|
|
|
for (const auto& b : v->get())
|
|
{
|
|
if (auto val = b->as_array())
|
|
result.push_back(val);
|
|
else
|
|
return {};
|
|
}
|
|
|
|
return {std::move(result)};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
std::shared_ptr<table> make_table()
|
|
{
|
|
struct make_shared_enabler : public table
|
|
{
|
|
make_shared_enabler()
|
|
{
|
|
// nothing
|
|
}
|
|
};
|
|
|
|
return std::make_shared<make_shared_enabler>();
|
|
}
|
|
|
|
namespace detail
|
|
{
|
|
template <>
|
|
inline std::shared_ptr<table> make_element<table>()
|
|
{
|
|
return make_table();
|
|
}
|
|
} // namespace detail
|
|
|
|
template <class T>
|
|
std::shared_ptr<base> value<T>::clone() const
|
|
{
|
|
return make_value(data_);
|
|
}
|
|
|
|
inline std::shared_ptr<base> array::clone() const
|
|
{
|
|
auto result = make_array();
|
|
result->reserve(values_.size());
|
|
for (const auto& ptr : values_)
|
|
result->values_.push_back(ptr->clone());
|
|
return result;
|
|
}
|
|
|
|
inline std::shared_ptr<base> table_array::clone() const
|
|
{
|
|
auto result = make_table_array(is_inline());
|
|
result->reserve(array_.size());
|
|
for (const auto& ptr : array_)
|
|
result->array_.push_back(ptr->clone()->as_table());
|
|
return result;
|
|
}
|
|
|
|
inline std::shared_ptr<base> table::clone() const
|
|
{
|
|
auto result = make_table();
|
|
for (const auto& pr : map_)
|
|
result->insert(pr.first, pr.second->clone());
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Exception class for all TOML parsing errors.
|
|
*/
|
|
class parse_exception : public std::runtime_error
|
|
{
|
|
public:
|
|
parse_exception(const std::string& err) : std::runtime_error{err}
|
|
{
|
|
}
|
|
|
|
parse_exception(const std::string& err, std::size_t line_number)
|
|
: std::runtime_error{err + " at line " + std::to_string(line_number)}
|
|
{
|
|
}
|
|
};
|
|
|
|
inline bool is_number(char c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
inline bool is_hex(char c)
|
|
{
|
|
return is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
|
}
|
|
|
|
/**
|
|
* Helper object for consuming expected characters.
|
|
*/
|
|
template <class OnError>
|
|
class consumer
|
|
{
|
|
public:
|
|
consumer(std::string::iterator& it, const std::string::iterator& end,
|
|
OnError&& on_error)
|
|
: it_(it), end_(end), on_error_(std::forward<OnError>(on_error))
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
void operator()(char c)
|
|
{
|
|
if (it_ == end_ || *it_ != c)
|
|
on_error_();
|
|
++it_;
|
|
}
|
|
|
|
template <std::size_t N>
|
|
void operator()(const char (&str)[N])
|
|
{
|
|
std::for_each(std::begin(str), std::end(str) - 1,
|
|
[&](char c) { (*this)(c); });
|
|
}
|
|
|
|
void eat_or(char a, char b)
|
|
{
|
|
if (it_ == end_ || (*it_ != a && *it_ != b))
|
|
on_error_();
|
|
++it_;
|
|
}
|
|
|
|
int eat_digits(int len)
|
|
{
|
|
int val = 0;
|
|
for (int i = 0; i < len; ++i)
|
|
{
|
|
if (!is_number(*it_) || it_ == end_)
|
|
on_error_();
|
|
val = 10 * val + (*it_++ - '0');
|
|
}
|
|
return val;
|
|
}
|
|
|
|
void error()
|
|
{
|
|
on_error_();
|
|
}
|
|
|
|
private:
|
|
std::string::iterator& it_;
|
|
const std::string::iterator& end_;
|
|
OnError on_error_;
|
|
};
|
|
|
|
template <class OnError>
|
|
consumer<OnError> make_consumer(std::string::iterator& it,
|
|
const std::string::iterator& end,
|
|
OnError&& on_error)
|
|
{
|
|
return consumer<OnError>(it, end, std::forward<OnError>(on_error));
|
|
}
|
|
|
|
// replacement for std::getline to handle incorrectly line-ended files
|
|
// https://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
|
|
namespace detail
|
|
{
|
|
inline std::istream& getline(std::istream& input, std::string& line)
|
|
{
|
|
line.clear();
|
|
|
|
std::istream::sentry sentry{input, true};
|
|
auto sb = input.rdbuf();
|
|
|
|
while (true)
|
|
{
|
|
auto c = sb->sbumpc();
|
|
if (c == '\r')
|
|
{
|
|
if (sb->sgetc() == '\n')
|
|
c = sb->sbumpc();
|
|
}
|
|
|
|
if (c == '\n')
|
|
return input;
|
|
|
|
if (c == std::istream::traits_type::eof())
|
|
{
|
|
if (line.empty())
|
|
input.setstate(std::ios::eofbit);
|
|
return input;
|
|
}
|
|
|
|
line.push_back(static_cast<char>(c));
|
|
}
|
|
}
|
|
} // namespace detail
|
|
|
|
/**
|
|
* The parser class.
|
|
*/
|
|
class parser
|
|
{
|
|
public:
|
|
/**
|
|
* Parsers are constructed from streams.
|
|
*/
|
|
parser(std::istream& stream) : input_(stream)
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
parser& operator=(const parser& parser) = delete;
|
|
|
|
/**
|
|
* Parses the stream this parser was created on until EOF.
|
|
* @throw parse_exception if there are errors in parsing
|
|
*/
|
|
std::shared_ptr<table> parse()
|
|
{
|
|
std::shared_ptr<table> root = make_table();
|
|
|
|
table* curr_table = root.get();
|
|
|
|
while (detail::getline(input_, line_))
|
|
{
|
|
line_number_++;
|
|
auto it = line_.begin();
|
|
auto end = line_.end();
|
|
consume_whitespace(it, end);
|
|
if (it == end || *it == '#')
|
|
continue;
|
|
if (*it == '[')
|
|
{
|
|
curr_table = root.get();
|
|
parse_table(it, end, curr_table);
|
|
}
|
|
else
|
|
{
|
|
parse_key_value(it, end, curr_table);
|
|
consume_whitespace(it, end);
|
|
eol_or_comment(it, end);
|
|
}
|
|
}
|
|
return root;
|
|
}
|
|
|
|
private:
|
|
#if defined _MSC_VER
|
|
__declspec(noreturn)
|
|
#elif defined __GNUC__
|
|
__attribute__((noreturn))
|
|
#endif
|
|
void throw_parse_exception(const std::string& err)
|
|
{
|
|
throw parse_exception{err, line_number_};
|
|
}
|
|
|
|
void parse_table(std::string::iterator& it,
|
|
const std::string::iterator& end, table*& curr_table)
|
|
{
|
|
// remove the beginning keytable marker
|
|
++it;
|
|
if (it == end)
|
|
throw_parse_exception("Unexpected end of table");
|
|
if (*it == '[')
|
|
parse_table_array(it, end, curr_table);
|
|
else
|
|
parse_single_table(it, end, curr_table);
|
|
}
|
|
|
|
void parse_single_table(std::string::iterator& it,
|
|
const std::string::iterator& end,
|
|
table*& curr_table)
|
|
{
|
|
if (it == end || *it == ']')
|
|
throw_parse_exception("Table name cannot be empty");
|
|
|
|
std::string full_table_name;
|
|
bool inserted = false;
|
|
|
|
auto key_end = [](char c) { return c == ']'; };
|
|
|
|
auto key_part_handler = [&](const std::string& part) {
|
|
if (part.empty())
|
|
throw_parse_exception("Empty component of table name");
|
|
|
|
if (!full_table_name.empty())
|
|
full_table_name += '.';
|
|
full_table_name += part;
|
|
|
|
if (curr_table->contains(part))
|
|
{
|
|
#if !defined(__PGI)
|
|
auto b = curr_table->get(part);
|
|
#else
|
|
// Workaround for PGI compiler
|
|
std::shared_ptr<base> b = curr_table->get(part);
|
|
#endif
|
|
if (b->is_table())
|
|
curr_table = static_cast<table*>(b.get());
|
|
else if (b->is_table_array())
|
|
curr_table = std::static_pointer_cast<table_array>(b)
|
|
->get()
|
|
.back()
|
|
.get();
|
|
else
|
|
throw_parse_exception("Key " + full_table_name
|
|
+ "already exists as a value");
|
|
}
|
|
else
|
|
{
|
|
inserted = true;
|
|
curr_table->insert(part, make_table());
|
|
curr_table = static_cast<table*>(curr_table->get(part).get());
|
|
}
|
|
};
|
|
|
|
key_part_handler(parse_key(it, end, key_end, key_part_handler));
|
|
|
|
if (it == end)
|
|
throw_parse_exception(
|
|
"Unterminated table declaration; did you forget a ']'?");
|
|
|
|
if (*it != ']')
|
|
{
|
|
std::string errmsg{"Unexpected character in table definition: "};
|
|
errmsg += '"';
|
|
errmsg += *it;
|
|
errmsg += '"';
|
|
throw_parse_exception(errmsg);
|
|
}
|
|
|
|
// table already existed
|
|
if (!inserted)
|
|
{
|
|
auto is_value
|
|
= [](const std::pair<const std::string&,
|
|
const std::shared_ptr<base>&>& p) {
|
|
return p.second->is_value();
|
|
};
|
|
|
|
// if there are any values, we can't add values to this table
|
|
// since it has already been defined. If there aren't any
|
|
// values, then it was implicitly created by something like
|
|
// [a.b]
|
|
if (curr_table->empty()
|
|
|| std::any_of(curr_table->begin(), curr_table->end(),
|
|
is_value))
|
|
{
|
|
throw_parse_exception("Redefinition of table "
|
|
+ full_table_name);
|
|
}
|
|
}
|
|
|
|
++it;
|
|
consume_whitespace(it, end);
|
|
eol_or_comment(it, end);
|
|
}
|
|
|
|
void parse_table_array(std::string::iterator& it,
|
|
const std::string::iterator& end, table*& curr_table)
|
|
{
|
|
++it;
|
|
if (it == end || *it == ']')
|
|
throw_parse_exception("Table array name cannot be empty");
|
|
|
|
auto key_end = [](char c) { return c == ']'; };
|
|
|
|
std::string full_ta_name;
|
|
auto key_part_handler = [&](const std::string& part) {
|
|
if (part.empty())
|
|
throw_parse_exception("Empty component of table array name");
|
|
|
|
if (!full_ta_name.empty())
|
|
full_ta_name += '.';
|
|
full_ta_name += part;
|
|
|
|
if (curr_table->contains(part))
|
|
{
|
|
#if !defined(__PGI)
|
|
auto b = curr_table->get(part);
|
|
#else
|
|
// Workaround for PGI compiler
|
|
std::shared_ptr<base> b = curr_table->get(part);
|
|
#endif
|
|
|
|
// if this is the end of the table array name, add an
|
|
// element to the table array that we just looked up,
|
|
// provided it was not declared inline
|
|
if (it != end && *it == ']')
|
|
{
|
|
if (!b->is_table_array())
|
|
{
|
|
throw_parse_exception("Key " + full_ta_name
|
|
+ " is not a table array");
|
|
}
|
|
|
|
auto v = b->as_table_array();
|
|
|
|
if (v->is_inline())
|
|
{
|
|
throw_parse_exception("Static array " + full_ta_name
|
|
+ " cannot be appended to");
|
|
}
|
|
|
|
v->get().push_back(make_table());
|
|
curr_table = v->get().back().get();
|
|
}
|
|
// otherwise, just keep traversing down the key name
|
|
else
|
|
{
|
|
if (b->is_table())
|
|
curr_table = static_cast<table*>(b.get());
|
|
else if (b->is_table_array())
|
|
curr_table = std::static_pointer_cast<table_array>(b)
|
|
->get()
|
|
.back()
|
|
.get();
|
|
else
|
|
throw_parse_exception("Key " + full_ta_name
|
|
+ " already exists as a value");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if this is the end of the table array name, add a new
|
|
// table array and a new table inside that array for us to
|
|
// add keys to next
|
|
if (it != end && *it == ']')
|
|
{
|
|
curr_table->insert(part, make_table_array());
|
|
auto arr = std::static_pointer_cast<table_array>(
|
|
curr_table->get(part));
|
|
arr->get().push_back(make_table());
|
|
curr_table = arr->get().back().get();
|
|
}
|
|
// otherwise, create the implicitly defined table and move
|
|
// down to it
|
|
else
|
|
{
|
|
curr_table->insert(part, make_table());
|
|
curr_table
|
|
= static_cast<table*>(curr_table->get(part).get());
|
|
}
|
|
}
|
|
};
|
|
|
|
key_part_handler(parse_key(it, end, key_end, key_part_handler));
|
|
|
|
// consume the last "]]"
|
|
auto eat = make_consumer(it, end, [this]() {
|
|
throw_parse_exception("Unterminated table array name");
|
|
});
|
|
eat(']');
|
|
eat(']');
|
|
|
|
consume_whitespace(it, end);
|
|
eol_or_comment(it, end);
|
|
}
|
|
|
|
void parse_key_value(std::string::iterator& it, std::string::iterator& end,
|
|
table* curr_table)
|
|
{
|
|
auto key_end = [](char c) { return c == '='; };
|
|
|
|
auto key_part_handler = [&](const std::string& part) {
|
|
// two cases: this key part exists already, in which case it must
|
|
// be a table, or it doesn't exist in which case we must create
|
|
// an implicitly defined table
|
|
if (curr_table->contains(part))
|
|
{
|
|
auto val = curr_table->get(part);
|
|
if (val->is_table())
|
|
{
|
|
curr_table = static_cast<table*>(val.get());
|
|
}
|
|
else
|
|
{
|
|
throw_parse_exception("Key " + part
|
|
+ " already exists as a value");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto newtable = make_table();
|
|
curr_table->insert(part, newtable);
|
|
curr_table = newtable.get();
|
|
}
|
|
};
|
|
|
|
auto key = parse_key(it, end, key_end, key_part_handler);
|
|
|
|
if (curr_table->contains(key))
|
|
throw_parse_exception("Key " + key + " already present");
|
|
if (it == end || *it != '=')
|
|
throw_parse_exception("Value must follow after a '='");
|
|
++it;
|
|
consume_whitespace(it, end);
|
|
curr_table->insert(key, parse_value(it, end));
|
|
consume_whitespace(it, end);
|
|
}
|
|
|
|
template <class KeyEndFinder, class KeyPartHandler>
|
|
std::string
|
|
parse_key(std::string::iterator& it, const std::string::iterator& end,
|
|
KeyEndFinder&& key_end, KeyPartHandler&& key_part_handler)
|
|
{
|
|
// parse the key as a series of one or more simple-keys joined with '.'
|
|
while (it != end && !key_end(*it))
|
|
{
|
|
auto part = parse_simple_key(it, end);
|
|
consume_whitespace(it, end);
|
|
|
|
if (it == end || key_end(*it))
|
|
{
|
|
return part;
|
|
}
|
|
|
|
if (*it != '.')
|
|
{
|
|
std::string errmsg{"Unexpected character in key: "};
|
|
errmsg += '"';
|
|
errmsg += *it;
|
|
errmsg += '"';
|
|
throw_parse_exception(errmsg);
|
|
}
|
|
|
|
key_part_handler(part);
|
|
|
|
// consume the dot
|
|
++it;
|
|
}
|
|
|
|
throw_parse_exception("Unexpected end of key");
|
|
}
|
|
|
|
std::string parse_simple_key(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
consume_whitespace(it, end);
|
|
|
|
if (it == end)
|
|
throw_parse_exception("Unexpected end of key (blank key?)");
|
|
|
|
if (*it == '"' || *it == '\'')
|
|
{
|
|
return string_literal(it, end, *it);
|
|
}
|
|
else
|
|
{
|
|
auto bke = std::find_if(it, end, [](char c) {
|
|
return c == '.' || c == '=' || c == ']';
|
|
});
|
|
return parse_bare_key(it, bke);
|
|
}
|
|
}
|
|
|
|
std::string parse_bare_key(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
if (it == end)
|
|
{
|
|
throw_parse_exception("Bare key missing name");
|
|
}
|
|
|
|
auto key_end = end;
|
|
--key_end;
|
|
consume_backwards_whitespace(key_end, it);
|
|
++key_end;
|
|
std::string key{it, key_end};
|
|
|
|
if (std::find(it, key_end, '#') != key_end)
|
|
{
|
|
throw_parse_exception("Bare key " + key + " cannot contain #");
|
|
}
|
|
|
|
if (std::find_if(it, key_end,
|
|
[](char c) { return c == ' ' || c == '\t'; })
|
|
!= key_end)
|
|
{
|
|
throw_parse_exception("Bare key " + key
|
|
+ " cannot contain whitespace");
|
|
}
|
|
|
|
if (std::find_if(it, key_end,
|
|
[](char c) { return c == '[' || c == ']'; })
|
|
!= key_end)
|
|
{
|
|
throw_parse_exception("Bare key " + key
|
|
+ " cannot contain '[' or ']'");
|
|
}
|
|
|
|
it = end;
|
|
return key;
|
|
}
|
|
|
|
enum class parse_type
|
|
{
|
|
STRING = 1,
|
|
LOCAL_TIME,
|
|
LOCAL_DATE,
|
|
LOCAL_DATETIME,
|
|
OFFSET_DATETIME,
|
|
INT,
|
|
FLOAT,
|
|
BOOL,
|
|
ARRAY,
|
|
INLINE_TABLE
|
|
};
|
|
|
|
std::shared_ptr<base> parse_value(std::string::iterator& it,
|
|
std::string::iterator& end)
|
|
{
|
|
parse_type type = determine_value_type(it, end);
|
|
switch (type)
|
|
{
|
|
case parse_type::STRING:
|
|
return parse_string(it, end);
|
|
case parse_type::LOCAL_TIME:
|
|
return parse_time(it, end);
|
|
case parse_type::LOCAL_DATE:
|
|
case parse_type::LOCAL_DATETIME:
|
|
case parse_type::OFFSET_DATETIME:
|
|
return parse_date(it, end);
|
|
case parse_type::INT:
|
|
case parse_type::FLOAT:
|
|
return parse_number(it, end);
|
|
case parse_type::BOOL:
|
|
return parse_bool(it, end);
|
|
case parse_type::ARRAY:
|
|
return parse_array(it, end);
|
|
case parse_type::INLINE_TABLE:
|
|
return parse_inline_table(it, end);
|
|
default:
|
|
throw_parse_exception("Failed to parse value");
|
|
}
|
|
}
|
|
|
|
parse_type determine_value_type(const std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
if (it == end)
|
|
{
|
|
throw_parse_exception("Failed to parse value type");
|
|
}
|
|
if (*it == '"' || *it == '\'')
|
|
{
|
|
return parse_type::STRING;
|
|
}
|
|
else if (is_time(it, end))
|
|
{
|
|
return parse_type::LOCAL_TIME;
|
|
}
|
|
else if (auto dtype = date_type(it, end))
|
|
{
|
|
return *dtype;
|
|
}
|
|
else if (is_number(*it) || *it == '-' || *it == '+'
|
|
|| (*it == 'i' && it + 1 != end && it[1] == 'n'
|
|
&& it + 2 != end && it[2] == 'f')
|
|
|| (*it == 'n' && it + 1 != end && it[1] == 'a'
|
|
&& it + 2 != end && it[2] == 'n'))
|
|
{
|
|
return determine_number_type(it, end);
|
|
}
|
|
else if (*it == 't' || *it == 'f')
|
|
{
|
|
return parse_type::BOOL;
|
|
}
|
|
else if (*it == '[')
|
|
{
|
|
return parse_type::ARRAY;
|
|
}
|
|
else if (*it == '{')
|
|
{
|
|
return parse_type::INLINE_TABLE;
|
|
}
|
|
throw_parse_exception("Failed to parse value type");
|
|
}
|
|
|
|
parse_type determine_number_type(const std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
// determine if we are an integer or a float
|
|
auto check_it = it;
|
|
if (*check_it == '-' || *check_it == '+')
|
|
++check_it;
|
|
|
|
if (check_it == end)
|
|
throw_parse_exception("Malformed number");
|
|
|
|
if (*check_it == 'i' || *check_it == 'n')
|
|
return parse_type::FLOAT;
|
|
|
|
while (check_it != end && is_number(*check_it))
|
|
++check_it;
|
|
if (check_it != end && *check_it == '.')
|
|
{
|
|
++check_it;
|
|
while (check_it != end && is_number(*check_it))
|
|
++check_it;
|
|
return parse_type::FLOAT;
|
|
}
|
|
else
|
|
{
|
|
return parse_type::INT;
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<value<std::string>> parse_string(std::string::iterator& it,
|
|
std::string::iterator& end)
|
|
{
|
|
auto delim = *it;
|
|
assert(delim == '"' || delim == '\'');
|
|
|
|
// end is non-const here because we have to be able to potentially
|
|
// parse multiple lines in a string, not just one
|
|
auto check_it = it;
|
|
++check_it;
|
|
if (check_it != end && *check_it == delim)
|
|
{
|
|
++check_it;
|
|
if (check_it != end && *check_it == delim)
|
|
{
|
|
it = ++check_it;
|
|
return parse_multiline_string(it, end, delim);
|
|
}
|
|
}
|
|
return make_value<std::string>(string_literal(it, end, delim));
|
|
}
|
|
|
|
std::shared_ptr<value<std::string>>
|
|
parse_multiline_string(std::string::iterator& it,
|
|
std::string::iterator& end, char delim)
|
|
{
|
|
std::stringstream ss;
|
|
|
|
auto is_ws = [](char c) { return c == ' ' || c == '\t'; };
|
|
|
|
bool consuming = false;
|
|
std::shared_ptr<value<std::string>> ret;
|
|
|
|
auto handle_line = [&](std::string::iterator& local_it,
|
|
std::string::iterator& local_end) {
|
|
if (consuming)
|
|
{
|
|
local_it = std::find_if_not(local_it, local_end, is_ws);
|
|
|
|
// whole line is whitespace
|
|
if (local_it == local_end)
|
|
return;
|
|
}
|
|
|
|
consuming = false;
|
|
|
|
while (local_it != local_end)
|
|
{
|
|
// handle escaped characters
|
|
if (delim == '"' && *local_it == '\\')
|
|
{
|
|
auto check = local_it;
|
|
// check if this is an actual escape sequence or a
|
|
// whitespace escaping backslash
|
|
++check;
|
|
consume_whitespace(check, local_end);
|
|
if (check == local_end)
|
|
{
|
|
consuming = true;
|
|
break;
|
|
}
|
|
|
|
ss << parse_escape_code(local_it, local_end);
|
|
continue;
|
|
}
|
|
|
|
// if we can end the string
|
|
if (std::distance(local_it, local_end) >= 3)
|
|
{
|
|
auto check = local_it;
|
|
// check for """
|
|
if (*check++ == delim && *check++ == delim
|
|
&& *check++ == delim)
|
|
{
|
|
local_it = check;
|
|
ret = make_value<std::string>(ss.str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
ss << *local_it++;
|
|
}
|
|
};
|
|
|
|
// handle the remainder of the current line
|
|
handle_line(it, end);
|
|
if (ret)
|
|
return ret;
|
|
|
|
// start eating lines
|
|
while (detail::getline(input_, line_))
|
|
{
|
|
++line_number_;
|
|
|
|
it = line_.begin();
|
|
end = line_.end();
|
|
|
|
handle_line(it, end);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!consuming)
|
|
ss << std::endl;
|
|
}
|
|
|
|
throw_parse_exception("Unterminated multi-line basic string");
|
|
}
|
|
|
|
std::string string_literal(std::string::iterator& it,
|
|
const std::string::iterator& end, char delim)
|
|
{
|
|
++it;
|
|
std::string val;
|
|
while (it != end)
|
|
{
|
|
// handle escaped characters
|
|
if (delim == '"' && *it == '\\')
|
|
{
|
|
val += parse_escape_code(it, end);
|
|
}
|
|
else if (*it == delim)
|
|
{
|
|
++it;
|
|
consume_whitespace(it, end);
|
|
return val;
|
|
}
|
|
else
|
|
{
|
|
val += *it++;
|
|
}
|
|
}
|
|
throw_parse_exception("Unterminated string literal");
|
|
}
|
|
|
|
std::string parse_escape_code(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
++it;
|
|
if (it == end)
|
|
throw_parse_exception("Invalid escape sequence");
|
|
char value;
|
|
if (*it == 'b')
|
|
{
|
|
value = '\b';
|
|
}
|
|
else if (*it == 't')
|
|
{
|
|
value = '\t';
|
|
}
|
|
else if (*it == 'n')
|
|
{
|
|
value = '\n';
|
|
}
|
|
else if (*it == 'f')
|
|
{
|
|
value = '\f';
|
|
}
|
|
else if (*it == 'r')
|
|
{
|
|
value = '\r';
|
|
}
|
|
else if (*it == '"')
|
|
{
|
|
value = '"';
|
|
}
|
|
else if (*it == '\\')
|
|
{
|
|
value = '\\';
|
|
}
|
|
else if (*it == 'u' || *it == 'U')
|
|
{
|
|
return parse_unicode(it, end);
|
|
}
|
|
else
|
|
{
|
|
throw_parse_exception("Invalid escape sequence");
|
|
}
|
|
++it;
|
|
return std::string(1, value);
|
|
}
|
|
|
|
std::string parse_unicode(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
bool large = *it++ == 'U';
|
|
auto codepoint = parse_hex(it, end, large ? 0x10000000 : 0x1000);
|
|
|
|
if ((codepoint > 0xd7ff && codepoint < 0xe000) || codepoint > 0x10ffff)
|
|
{
|
|
throw_parse_exception(
|
|
"Unicode escape sequence is not a Unicode scalar value");
|
|
}
|
|
|
|
std::string result;
|
|
// See Table 3-6 of the Unicode standard
|
|
if (codepoint <= 0x7f)
|
|
{
|
|
// 1-byte codepoints: 00000000 0xxxxxxx
|
|
// repr: 0xxxxxxx
|
|
result += static_cast<char>(codepoint & 0x7f);
|
|
}
|
|
else if (codepoint <= 0x7ff)
|
|
{
|
|
// 2-byte codepoints: 00000yyy yyxxxxxx
|
|
// repr: 110yyyyy 10xxxxxx
|
|
//
|
|
// 0x1f = 00011111
|
|
// 0xc0 = 11000000
|
|
//
|
|
result += static_cast<char>(0xc0 | ((codepoint >> 6) & 0x1f));
|
|
//
|
|
// 0x80 = 10000000
|
|
// 0x3f = 00111111
|
|
//
|
|
result += static_cast<char>(0x80 | (codepoint & 0x3f));
|
|
}
|
|
else if (codepoint <= 0xffff)
|
|
{
|
|
// 3-byte codepoints: zzzzyyyy yyxxxxxx
|
|
// repr: 1110zzzz 10yyyyyy 10xxxxxx
|
|
//
|
|
// 0xe0 = 11100000
|
|
// 0x0f = 00001111
|
|
//
|
|
result += static_cast<char>(0xe0 | ((codepoint >> 12) & 0x0f));
|
|
result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x1f));
|
|
result += static_cast<char>(0x80 | (codepoint & 0x3f));
|
|
}
|
|
else
|
|
{
|
|
// 4-byte codepoints: 000uuuuu zzzzyyyy yyxxxxxx
|
|
// repr: 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
|
|
//
|
|
// 0xf0 = 11110000
|
|
// 0x07 = 00000111
|
|
//
|
|
result += static_cast<char>(0xf0 | ((codepoint >> 18) & 0x07));
|
|
result += static_cast<char>(0x80 | ((codepoint >> 12) & 0x3f));
|
|
result += static_cast<char>(0x80 | ((codepoint >> 6) & 0x3f));
|
|
result += static_cast<char>(0x80 | (codepoint & 0x3f));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
uint32_t parse_hex(std::string::iterator& it,
|
|
const std::string::iterator& end, uint32_t place)
|
|
{
|
|
uint32_t value = 0;
|
|
while (place > 0)
|
|
{
|
|
if (it == end)
|
|
throw_parse_exception("Unexpected end of unicode sequence");
|
|
|
|
if (!is_hex(*it))
|
|
throw_parse_exception("Invalid unicode escape sequence");
|
|
|
|
value += place * hex_to_digit(*it++);
|
|
place /= 16;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
uint32_t hex_to_digit(char c)
|
|
{
|
|
if (is_number(c))
|
|
return static_cast<uint32_t>(c - '0');
|
|
return 10
|
|
+ static_cast<uint32_t>(c
|
|
- ((c >= 'a' && c <= 'f') ? 'a' : 'A'));
|
|
}
|
|
|
|
std::shared_ptr<base> parse_number(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
auto check_it = it;
|
|
auto check_end = find_end_of_number(it, end);
|
|
|
|
auto eat_sign = [&]() {
|
|
if (check_it != end && (*check_it == '-' || *check_it == '+'))
|
|
++check_it;
|
|
};
|
|
|
|
auto check_no_leading_zero = [&]() {
|
|
if (check_it != end && *check_it == '0' && check_it + 1 != check_end
|
|
&& check_it[1] != '.')
|
|
{
|
|
throw_parse_exception("Numbers may not have leading zeros");
|
|
}
|
|
};
|
|
|
|
auto eat_digits = [&](bool (*check_char)(char)) {
|
|
auto beg = check_it;
|
|
while (check_it != end && check_char(*check_it))
|
|
{
|
|
++check_it;
|
|
if (check_it != end && *check_it == '_')
|
|
{
|
|
++check_it;
|
|
if (check_it == end || !check_char(*check_it))
|
|
throw_parse_exception("Malformed number");
|
|
}
|
|
}
|
|
|
|
if (check_it == beg)
|
|
throw_parse_exception("Malformed number");
|
|
};
|
|
|
|
auto eat_hex = [&]() { eat_digits(&is_hex); };
|
|
|
|
auto eat_numbers = [&]() { eat_digits(&is_number); };
|
|
|
|
if (check_it != end && *check_it == '0' && check_it + 1 != check_end
|
|
&& (check_it[1] == 'x' || check_it[1] == 'o' || check_it[1] == 'b'))
|
|
{
|
|
++check_it;
|
|
char base = *check_it;
|
|
++check_it;
|
|
if (base == 'x')
|
|
{
|
|
eat_hex();
|
|
return parse_int(it, check_it, 16);
|
|
}
|
|
else if (base == 'o')
|
|
{
|
|
auto start = check_it;
|
|
eat_numbers();
|
|
auto val = parse_int(start, check_it, 8, "0");
|
|
it = start;
|
|
return val;
|
|
}
|
|
else // if (base == 'b')
|
|
{
|
|
auto start = check_it;
|
|
eat_numbers();
|
|
auto val = parse_int(start, check_it, 2);
|
|
it = start;
|
|
return val;
|
|
}
|
|
}
|
|
|
|
eat_sign();
|
|
check_no_leading_zero();
|
|
|
|
if (check_it != end && check_it + 1 != end && check_it + 2 != end)
|
|
{
|
|
if (check_it[0] == 'i' && check_it[1] == 'n' && check_it[2] == 'f')
|
|
{
|
|
auto val = std::numeric_limits<double>::infinity();
|
|
if (*it == '-')
|
|
val = -val;
|
|
it = check_it + 3;
|
|
return make_value(val);
|
|
}
|
|
else if (check_it[0] == 'n' && check_it[1] == 'a'
|
|
&& check_it[2] == 'n')
|
|
{
|
|
auto val = std::numeric_limits<double>::quiet_NaN();
|
|
if (*it == '-')
|
|
val = -val;
|
|
it = check_it + 3;
|
|
return make_value(val);
|
|
}
|
|
}
|
|
|
|
eat_numbers();
|
|
|
|
if (check_it != end
|
|
&& (*check_it == '.' || *check_it == 'e' || *check_it == 'E'))
|
|
{
|
|
bool is_exp = *check_it == 'e' || *check_it == 'E';
|
|
|
|
++check_it;
|
|
if (check_it == end)
|
|
throw_parse_exception("Floats must have trailing digits");
|
|
|
|
auto eat_exp = [&]() {
|
|
eat_sign();
|
|
check_no_leading_zero();
|
|
eat_numbers();
|
|
};
|
|
|
|
if (is_exp)
|
|
eat_exp();
|
|
else
|
|
eat_numbers();
|
|
|
|
if (!is_exp && check_it != end
|
|
&& (*check_it == 'e' || *check_it == 'E'))
|
|
{
|
|
++check_it;
|
|
eat_exp();
|
|
}
|
|
|
|
return parse_float(it, check_it);
|
|
}
|
|
else
|
|
{
|
|
return parse_int(it, check_it);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<value<int64_t>> parse_int(std::string::iterator& it,
|
|
const std::string::iterator& end,
|
|
int base = 10,
|
|
const char* prefix = "")
|
|
{
|
|
std::string v{it, end};
|
|
v = prefix + v;
|
|
v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
|
|
it = end;
|
|
try
|
|
{
|
|
return make_value<int64_t>(std::stoll(v, nullptr, base));
|
|
}
|
|
catch (const std::invalid_argument& ex)
|
|
{
|
|
throw_parse_exception("Malformed number (invalid argument: "
|
|
+ std::string{ex.what()} + ")");
|
|
}
|
|
catch (const std::out_of_range& ex)
|
|
{
|
|
throw_parse_exception("Malformed number (out of range: "
|
|
+ std::string{ex.what()} + ")");
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<value<double>> parse_float(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
std::string v{it, end};
|
|
v.erase(std::remove(v.begin(), v.end(), '_'), v.end());
|
|
it = end;
|
|
char decimal_point = std::localeconv()->decimal_point[0];
|
|
std::replace(v.begin(), v.end(), '.', decimal_point);
|
|
try
|
|
{
|
|
return make_value<double>(std::stod(v));
|
|
}
|
|
catch (const std::invalid_argument& ex)
|
|
{
|
|
throw_parse_exception("Malformed number (invalid argument: "
|
|
+ std::string{ex.what()} + ")");
|
|
}
|
|
catch (const std::out_of_range& ex)
|
|
{
|
|
throw_parse_exception("Malformed number (out of range: "
|
|
+ std::string{ex.what()} + ")");
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<value<bool>> parse_bool(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
auto eat = make_consumer(it, end, [this]() {
|
|
throw_parse_exception("Attempted to parse invalid boolean value");
|
|
});
|
|
|
|
if (*it == 't')
|
|
{
|
|
eat("true");
|
|
return make_value<bool>(true);
|
|
}
|
|
else if (*it == 'f')
|
|
{
|
|
eat("false");
|
|
return make_value<bool>(false);
|
|
}
|
|
|
|
eat.error();
|
|
return nullptr;
|
|
}
|
|
|
|
std::string::iterator find_end_of_number(std::string::iterator it,
|
|
std::string::iterator end)
|
|
{
|
|
auto ret = std::find_if(it, end, [](char c) {
|
|
return !is_number(c) && c != '_' && c != '.' && c != 'e' && c != 'E'
|
|
&& c != '-' && c != '+' && c != 'x' && c != 'o' && c != 'b';
|
|
});
|
|
if (ret != end && ret + 1 != end && ret + 2 != end)
|
|
{
|
|
if ((ret[0] == 'i' && ret[1] == 'n' && ret[2] == 'f')
|
|
|| (ret[0] == 'n' && ret[1] == 'a' && ret[2] == 'n'))
|
|
{
|
|
ret = ret + 3;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
std::string::iterator find_end_of_date(std::string::iterator it,
|
|
std::string::iterator end)
|
|
{
|
|
auto end_of_date = std::find_if(it, end, [](char c) {
|
|
return !is_number(c) && c != '-';
|
|
});
|
|
if (end_of_date != end && *end_of_date == ' ' && end_of_date + 1 != end
|
|
&& is_number(end_of_date[1]))
|
|
end_of_date++;
|
|
return std::find_if(end_of_date, end, [](char c) {
|
|
return !is_number(c) && c != 'T' && c != 'Z' && c != ':'
|
|
&& c != '-' && c != '+' && c != '.';
|
|
});
|
|
}
|
|
|
|
std::string::iterator find_end_of_time(std::string::iterator it,
|
|
std::string::iterator end)
|
|
{
|
|
return std::find_if(it, end, [](char c) {
|
|
return !is_number(c) && c != ':' && c != '.';
|
|
});
|
|
}
|
|
|
|
local_time read_time(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
auto time_end = find_end_of_time(it, end);
|
|
|
|
auto eat = make_consumer(
|
|
it, time_end, [&]() { throw_parse_exception("Malformed time"); });
|
|
|
|
local_time ltime;
|
|
|
|
ltime.hour = eat.eat_digits(2);
|
|
eat(':');
|
|
ltime.minute = eat.eat_digits(2);
|
|
eat(':');
|
|
ltime.second = eat.eat_digits(2);
|
|
|
|
int power = 100000;
|
|
if (it != time_end && *it == '.')
|
|
{
|
|
++it;
|
|
while (it != time_end && is_number(*it))
|
|
{
|
|
ltime.microsecond += power * (*it++ - '0');
|
|
power /= 10;
|
|
}
|
|
}
|
|
|
|
if (it != time_end)
|
|
throw_parse_exception("Malformed time");
|
|
|
|
return ltime;
|
|
}
|
|
|
|
std::shared_ptr<value<local_time>>
|
|
parse_time(std::string::iterator& it, const std::string::iterator& end)
|
|
{
|
|
return make_value(read_time(it, end));
|
|
}
|
|
|
|
std::shared_ptr<base> parse_date(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
auto date_end = find_end_of_date(it, end);
|
|
|
|
auto eat = make_consumer(
|
|
it, date_end, [&]() { throw_parse_exception("Malformed date"); });
|
|
|
|
local_date ldate;
|
|
ldate.year = eat.eat_digits(4);
|
|
eat('-');
|
|
ldate.month = eat.eat_digits(2);
|
|
eat('-');
|
|
ldate.day = eat.eat_digits(2);
|
|
|
|
if (it == date_end)
|
|
return make_value(ldate);
|
|
|
|
eat.eat_or('T', ' ');
|
|
|
|
local_datetime ldt;
|
|
static_cast<local_date&>(ldt) = ldate;
|
|
static_cast<local_time&>(ldt) = read_time(it, date_end);
|
|
|
|
if (it == date_end)
|
|
return make_value(ldt);
|
|
|
|
offset_datetime dt;
|
|
static_cast<local_datetime&>(dt) = ldt;
|
|
|
|
int hoff = 0;
|
|
int moff = 0;
|
|
if (*it == '+' || *it == '-')
|
|
{
|
|
auto plus = *it == '+';
|
|
++it;
|
|
|
|
hoff = eat.eat_digits(2);
|
|
dt.hour_offset = (plus) ? hoff : -hoff;
|
|
eat(':');
|
|
moff = eat.eat_digits(2);
|
|
dt.minute_offset = (plus) ? moff : -moff;
|
|
}
|
|
else if (*it == 'Z')
|
|
{
|
|
++it;
|
|
}
|
|
|
|
if (it != date_end)
|
|
throw_parse_exception("Malformed date");
|
|
|
|
return make_value(dt);
|
|
}
|
|
|
|
std::shared_ptr<base> parse_array(std::string::iterator& it,
|
|
std::string::iterator& end)
|
|
{
|
|
// this gets ugly because of the "homogeneity" restriction:
|
|
// arrays can either be of only one type, or contain arrays
|
|
// (each of those arrays could be of different types, though)
|
|
//
|
|
// because of the latter portion, we don't really have a choice
|
|
// but to represent them as arrays of base values...
|
|
++it;
|
|
|
|
// ugh---have to read the first value to determine array type...
|
|
skip_whitespace_and_comments(it, end);
|
|
|
|
// edge case---empty array
|
|
if (*it == ']')
|
|
{
|
|
++it;
|
|
return make_array();
|
|
}
|
|
|
|
auto val_end = std::find_if(
|
|
it, end, [](char c) { return c == ',' || c == ']' || c == '#'; });
|
|
parse_type type = determine_value_type(it, val_end);
|
|
switch (type)
|
|
{
|
|
case parse_type::STRING:
|
|
return parse_value_array<std::string>(it, end);
|
|
case parse_type::LOCAL_TIME:
|
|
return parse_value_array<local_time>(it, end);
|
|
case parse_type::LOCAL_DATE:
|
|
return parse_value_array<local_date>(it, end);
|
|
case parse_type::LOCAL_DATETIME:
|
|
return parse_value_array<local_datetime>(it, end);
|
|
case parse_type::OFFSET_DATETIME:
|
|
return parse_value_array<offset_datetime>(it, end);
|
|
case parse_type::INT:
|
|
return parse_value_array<int64_t>(it, end);
|
|
case parse_type::FLOAT:
|
|
return parse_value_array<double>(it, end);
|
|
case parse_type::BOOL:
|
|
return parse_value_array<bool>(it, end);
|
|
case parse_type::ARRAY:
|
|
return parse_object_array<array>(&parser::parse_array, '[', it,
|
|
end);
|
|
case parse_type::INLINE_TABLE:
|
|
return parse_object_array<table_array>(
|
|
&parser::parse_inline_table, '{', it, end);
|
|
default:
|
|
throw_parse_exception("Unable to parse array");
|
|
}
|
|
}
|
|
|
|
template <class Value>
|
|
std::shared_ptr<array> parse_value_array(std::string::iterator& it,
|
|
std::string::iterator& end)
|
|
{
|
|
auto arr = make_array();
|
|
while (it != end && *it != ']')
|
|
{
|
|
auto val = parse_value(it, end);
|
|
if (auto v = val->as<Value>())
|
|
arr->get().push_back(val);
|
|
else
|
|
throw_parse_exception("Arrays must be homogeneous");
|
|
skip_whitespace_and_comments(it, end);
|
|
if (*it != ',')
|
|
break;
|
|
++it;
|
|
skip_whitespace_and_comments(it, end);
|
|
}
|
|
if (it != end)
|
|
++it;
|
|
return arr;
|
|
}
|
|
|
|
template <class Object, class Function>
|
|
std::shared_ptr<Object> parse_object_array(Function&& fun, char delim,
|
|
std::string::iterator& it,
|
|
std::string::iterator& end)
|
|
{
|
|
auto arr = detail::make_element<Object>();
|
|
|
|
while (it != end && *it != ']')
|
|
{
|
|
if (*it != delim)
|
|
throw_parse_exception("Unexpected character in array");
|
|
|
|
arr->get().push_back(((*this).*fun)(it, end));
|
|
skip_whitespace_and_comments(it, end);
|
|
|
|
if (it == end || *it != ',')
|
|
break;
|
|
|
|
++it;
|
|
skip_whitespace_and_comments(it, end);
|
|
}
|
|
|
|
if (it == end || *it != ']')
|
|
throw_parse_exception("Unterminated array");
|
|
|
|
++it;
|
|
return arr;
|
|
}
|
|
|
|
std::shared_ptr<table> parse_inline_table(std::string::iterator& it,
|
|
std::string::iterator& end)
|
|
{
|
|
auto tbl = make_table();
|
|
do
|
|
{
|
|
++it;
|
|
if (it == end)
|
|
throw_parse_exception("Unterminated inline table");
|
|
|
|
consume_whitespace(it, end);
|
|
if (it != end && *it != '}')
|
|
{
|
|
parse_key_value(it, end, tbl.get());
|
|
consume_whitespace(it, end);
|
|
}
|
|
} while (*it == ',');
|
|
|
|
if (it == end || *it != '}')
|
|
throw_parse_exception("Unterminated inline table");
|
|
|
|
++it;
|
|
consume_whitespace(it, end);
|
|
|
|
return tbl;
|
|
}
|
|
|
|
void skip_whitespace_and_comments(std::string::iterator& start,
|
|
std::string::iterator& end)
|
|
{
|
|
consume_whitespace(start, end);
|
|
while (start == end || *start == '#')
|
|
{
|
|
if (!detail::getline(input_, line_))
|
|
throw_parse_exception("Unclosed array");
|
|
line_number_++;
|
|
start = line_.begin();
|
|
end = line_.end();
|
|
consume_whitespace(start, end);
|
|
}
|
|
}
|
|
|
|
void consume_whitespace(std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
while (it != end && (*it == ' ' || *it == '\t'))
|
|
++it;
|
|
}
|
|
|
|
void consume_backwards_whitespace(std::string::iterator& back,
|
|
const std::string::iterator& front)
|
|
{
|
|
while (back != front && (*back == ' ' || *back == '\t'))
|
|
--back;
|
|
}
|
|
|
|
void eol_or_comment(const std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
if (it != end && *it != '#')
|
|
throw_parse_exception("Unidentified trailing character '"
|
|
+ std::string{*it}
|
|
+ "'---did you forget a '#'?");
|
|
}
|
|
|
|
bool is_time(const std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
auto time_end = find_end_of_time(it, end);
|
|
auto len = std::distance(it, time_end);
|
|
|
|
if (len < 8)
|
|
return false;
|
|
|
|
if (it[2] != ':' || it[5] != ':')
|
|
return false;
|
|
|
|
if (len > 8)
|
|
return it[8] == '.' && len > 9;
|
|
|
|
return true;
|
|
}
|
|
|
|
option<parse_type> date_type(const std::string::iterator& it,
|
|
const std::string::iterator& end)
|
|
{
|
|
auto date_end = find_end_of_date(it, end);
|
|
auto len = std::distance(it, date_end);
|
|
|
|
if (len < 10)
|
|
return {};
|
|
|
|
if (it[4] != '-' || it[7] != '-')
|
|
return {};
|
|
|
|
if (len >= 19 && (it[10] == 'T' || it[10] == ' ')
|
|
&& is_time(it + 11, date_end))
|
|
{
|
|
// datetime type
|
|
auto time_end = find_end_of_time(it + 11, date_end);
|
|
if (time_end == date_end)
|
|
return {parse_type::LOCAL_DATETIME};
|
|
else
|
|
return {parse_type::OFFSET_DATETIME};
|
|
}
|
|
else if (len == 10)
|
|
{
|
|
// just a regular date
|
|
return {parse_type::LOCAL_DATE};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
std::istream& input_;
|
|
std::string line_;
|
|
std::size_t line_number_ = 0;
|
|
};
|
|
|
|
/**
|
|
* Utility function to parse a file as a TOML file. Returns the root table.
|
|
* Throws a parse_exception if the file cannot be opened.
|
|
*/
|
|
inline std::shared_ptr<table> parse_file(const std::string& filename)
|
|
{
|
|
#if defined(BOOST_NOWIDE_FSTREAM_INCLUDED_HPP)
|
|
boost::nowide::ifstream file{filename.c_str()};
|
|
#elif defined(NOWIDE_FSTREAM_INCLUDED_HPP)
|
|
nowide::ifstream file{filename.c_str()};
|
|
#else
|
|
std::ifstream file{filename};
|
|
#endif
|
|
if (!file.is_open())
|
|
throw parse_exception{filename + " could not be opened for parsing"};
|
|
parser p{file};
|
|
return p.parse();
|
|
}
|
|
|
|
template <class... Ts>
|
|
struct value_accept;
|
|
|
|
template <>
|
|
struct value_accept<>
|
|
{
|
|
template <class Visitor, class... Args>
|
|
static void accept(const base&, Visitor&&, Args&&...)
|
|
{
|
|
// nothing
|
|
}
|
|
};
|
|
|
|
template <class T, class... Ts>
|
|
struct value_accept<T, Ts...>
|
|
{
|
|
template <class Visitor, class... Args>
|
|
static void accept(const base& b, Visitor&& visitor, Args&&... args)
|
|
{
|
|
if (auto v = b.as<T>())
|
|
{
|
|
visitor.visit(*v, std::forward<Args>(args)...);
|
|
}
|
|
else
|
|
{
|
|
value_accept<Ts...>::accept(b, std::forward<Visitor>(visitor),
|
|
std::forward<Args>(args)...);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* base implementation of accept() that calls visitor.visit() on the concrete
|
|
* class.
|
|
*/
|
|
template <class Visitor, class... Args>
|
|
void base::accept(Visitor&& visitor, Args&&... args) const
|
|
{
|
|
if (is_value())
|
|
{
|
|
using value_acceptor
|
|
= value_accept<std::string, int64_t, double, bool, local_date,
|
|
local_time, local_datetime, offset_datetime>;
|
|
value_acceptor::accept(*this, std::forward<Visitor>(visitor),
|
|
std::forward<Args>(args)...);
|
|
}
|
|
else if (is_table())
|
|
{
|
|
visitor.visit(static_cast<const table&>(*this),
|
|
std::forward<Args>(args)...);
|
|
}
|
|
else if (is_array())
|
|
{
|
|
visitor.visit(static_cast<const array&>(*this),
|
|
std::forward<Args>(args)...);
|
|
}
|
|
else if (is_table_array())
|
|
{
|
|
visitor.visit(static_cast<const table_array&>(*this),
|
|
std::forward<Args>(args)...);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writer that can be passed to accept() functions of cpptoml objects and
|
|
* will output valid TOML to a stream.
|
|
*/
|
|
class toml_writer
|
|
{
|
|
public:
|
|
/**
|
|
* Construct a toml_writer that will write to the given stream
|
|
*/
|
|
toml_writer(std::ostream& s, const std::string& indent_space = "\t")
|
|
: stream_(s), indent_(indent_space), has_naked_endline_(false)
|
|
{
|
|
// nothing
|
|
}
|
|
|
|
public:
|
|
/**
|
|
* Output a base value of the TOML tree.
|
|
*/
|
|
template <class T>
|
|
void visit(const value<T>& v, bool = false)
|
|
{
|
|
write(v);
|
|
}
|
|
|
|
/**
|
|
* Output a table element of the TOML tree
|
|
*/
|
|
void visit(const table& t, bool in_array = false)
|
|
{
|
|
write_table_header(in_array);
|
|
std::vector<std::string> values;
|
|
std::vector<std::string> tables;
|
|
|
|
for (const auto& i : t)
|
|
{
|
|
if (i.second->is_table() || i.second->is_table_array())
|
|
{
|
|
tables.push_back(i.first);
|
|
}
|
|
else
|
|
{
|
|
values.push_back(i.first);
|
|
}
|
|
}
|
|
|
|
for (unsigned int i = 0; i < values.size(); ++i)
|
|
{
|
|
path_.push_back(values[i]);
|
|
|
|
if (i > 0)
|
|
endline();
|
|
|
|
write_table_item_header(*t.get(values[i]));
|
|
t.get(values[i])->accept(*this, false);
|
|
path_.pop_back();
|
|
}
|
|
|
|
for (unsigned int i = 0; i < tables.size(); ++i)
|
|
{
|
|
path_.push_back(tables[i]);
|
|
|
|
if (values.size() > 0 || i > 0)
|
|
endline();
|
|
|
|
write_table_item_header(*t.get(tables[i]));
|
|
t.get(tables[i])->accept(*this, false);
|
|
path_.pop_back();
|
|
}
|
|
|
|
endline();
|
|
}
|
|
|
|
/**
|
|
* Output an array element of the TOML tree
|
|
*/
|
|
void visit(const array& a, bool = false)
|
|
{
|
|
write("[");
|
|
|
|
for (unsigned int i = 0; i < a.get().size(); ++i)
|
|
{
|
|
if (i > 0)
|
|
write(", ");
|
|
|
|
if (a.get()[i]->is_array())
|
|
{
|
|
a.get()[i]->as_array()->accept(*this, true);
|
|
}
|
|
else
|
|
{
|
|
a.get()[i]->accept(*this, true);
|
|
}
|
|
}
|
|
|
|
write("]");
|
|
}
|
|
|
|
/**
|
|
* Output a table_array element of the TOML tree
|
|
*/
|
|
void visit(const table_array& t, bool = false)
|
|
{
|
|
for (unsigned int j = 0; j < t.get().size(); ++j)
|
|
{
|
|
if (j > 0)
|
|
endline();
|
|
|
|
t.get()[j]->accept(*this, true);
|
|
}
|
|
|
|
endline();
|
|
}
|
|
|
|
/**
|
|
* Escape a string for output.
|
|
*/
|
|
static std::string escape_string(const std::string& str)
|
|
{
|
|
std::string res;
|
|
for (auto it = str.begin(); it != str.end(); ++it)
|
|
{
|
|
if (*it == '\b')
|
|
{
|
|
res += "\\b";
|
|
}
|
|
else if (*it == '\t')
|
|
{
|
|
res += "\\t";
|
|
}
|
|
else if (*it == '\n')
|
|
{
|
|
res += "\\n";
|
|
}
|
|
else if (*it == '\f')
|
|
{
|
|
res += "\\f";
|
|
}
|
|
else if (*it == '\r')
|
|
{
|
|
res += "\\r";
|
|
}
|
|
else if (*it == '"')
|
|
{
|
|
res += "\\\"";
|
|
}
|
|
else if (*it == '\\')
|
|
{
|
|
res += "\\\\";
|
|
}
|
|
else if (static_cast<uint32_t>(*it) <= UINT32_C(0x001f))
|
|
{
|
|
res += "\\u";
|
|
std::stringstream ss;
|
|
ss << std::hex << static_cast<uint32_t>(*it);
|
|
res += ss.str();
|
|
}
|
|
else
|
|
{
|
|
res += *it;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* Write out a string.
|
|
*/
|
|
void write(const value<std::string>& v)
|
|
{
|
|
write("\"");
|
|
write(escape_string(v.get()));
|
|
write("\"");
|
|
}
|
|
|
|
/**
|
|
* Write out a double.
|
|
*/
|
|
void write(const value<double>& v)
|
|
{
|
|
std::stringstream ss;
|
|
ss << std::showpoint
|
|
<< std::setprecision(std::numeric_limits<double>::max_digits10)
|
|
<< v.get();
|
|
|
|
auto double_str = ss.str();
|
|
auto pos = double_str.find("e0");
|
|
if (pos != std::string::npos)
|
|
double_str.replace(pos, 2, "e");
|
|
pos = double_str.find("e-0");
|
|
if (pos != std::string::npos)
|
|
double_str.replace(pos, 3, "e-");
|
|
|
|
stream_ << double_str;
|
|
has_naked_endline_ = false;
|
|
}
|
|
|
|
/**
|
|
* Write out an integer, local_date, local_time, local_datetime, or
|
|
* offset_datetime.
|
|
*/
|
|
template <class T>
|
|
typename std::enable_if<
|
|
is_one_of<T, int64_t, local_date, local_time, local_datetime,
|
|
offset_datetime>::value>::type
|
|
write(const value<T>& v)
|
|
{
|
|
write(v.get());
|
|
}
|
|
|
|
/**
|
|
* Write out a boolean.
|
|
*/
|
|
void write(const value<bool>& v)
|
|
{
|
|
write((v.get() ? "true" : "false"));
|
|
}
|
|
|
|
/**
|
|
* Write out the header of a table.
|
|
*/
|
|
void write_table_header(bool in_array = false)
|
|
{
|
|
if (!path_.empty())
|
|
{
|
|
indent();
|
|
|
|
write("[");
|
|
|
|
if (in_array)
|
|
{
|
|
write("[");
|
|
}
|
|
|
|
for (unsigned int i = 0; i < path_.size(); ++i)
|
|
{
|
|
if (i > 0)
|
|
{
|
|
write(".");
|
|
}
|
|
|
|
if (path_[i].find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"
|
|
"fghijklmnopqrstuvwxyz0123456789"
|
|
"_-")
|
|
== std::string::npos)
|
|
{
|
|
write(path_[i]);
|
|
}
|
|
else
|
|
{
|
|
write("\"");
|
|
write(escape_string(path_[i]));
|
|
write("\"");
|
|
}
|
|
}
|
|
|
|
if (in_array)
|
|
{
|
|
write("]");
|
|
}
|
|
|
|
write("]");
|
|
endline();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write out the identifier for an item in a table.
|
|
*/
|
|
void write_table_item_header(const base& b)
|
|
{
|
|
if (!b.is_table() && !b.is_table_array())
|
|
{
|
|
indent();
|
|
|
|
if (path_.back().find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcde"
|
|
"fghijklmnopqrstuvwxyz0123456789"
|
|
"_-")
|
|
== std::string::npos)
|
|
{
|
|
write(path_.back());
|
|
}
|
|
else
|
|
{
|
|
write("\"");
|
|
write(escape_string(path_.back()));
|
|
write("\"");
|
|
}
|
|
|
|
write(" = ");
|
|
}
|
|
}
|
|
|
|
private:
|
|
/**
|
|
* Indent the proper number of tabs given the size of
|
|
* the path.
|
|
*/
|
|
void indent()
|
|
{
|
|
for (std::size_t i = 1; i < path_.size(); ++i)
|
|
write(indent_);
|
|
}
|
|
|
|
/**
|
|
* Write a value out to the stream.
|
|
*/
|
|
template <class T>
|
|
void write(const T& v)
|
|
{
|
|
stream_ << v;
|
|
has_naked_endline_ = false;
|
|
}
|
|
|
|
/**
|
|
* Write an endline out to the stream
|
|
*/
|
|
void endline()
|
|
{
|
|
if (!has_naked_endline_)
|
|
{
|
|
stream_ << "\n";
|
|
has_naked_endline_ = true;
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::ostream& stream_;
|
|
const std::string indent_;
|
|
std::vector<std::string> path_;
|
|
bool has_naked_endline_;
|
|
};
|
|
|
|
inline std::ostream& operator<<(std::ostream& stream, const base& b)
|
|
{
|
|
toml_writer writer{stream};
|
|
b.accept(writer);
|
|
return stream;
|
|
}
|
|
|
|
template <class T>
|
|
std::ostream& operator<<(std::ostream& stream, const value<T>& v)
|
|
{
|
|
toml_writer writer{stream};
|
|
v.accept(writer);
|
|
return stream;
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& stream, const table& t)
|
|
{
|
|
toml_writer writer{stream};
|
|
t.accept(writer);
|
|
return stream;
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& stream, const table_array& t)
|
|
{
|
|
toml_writer writer{stream};
|
|
t.accept(writer);
|
|
return stream;
|
|
}
|
|
|
|
inline std::ostream& operator<<(std::ostream& stream, const array& a)
|
|
{
|
|
toml_writer writer{stream};
|
|
a.accept(writer);
|
|
return stream;
|
|
}
|
|
} // namespace cpptoml
|
|
#endif // CPPTOML_H
|