about summary refs log tree commit diff
path: root/third_party/nix/src/cpptoml/cpptoml.h
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/nix/src/cpptoml/cpptoml.h')
-rw-r--r--third_party/nix/src/cpptoml/cpptoml.h3668
1 files changed, 0 insertions, 3668 deletions
diff --git a/third_party/nix/src/cpptoml/cpptoml.h b/third_party/nix/src/cpptoml/cpptoml.h
deleted file mode 100644
index 150b53ff86..0000000000
--- a/third_party/nix/src/cpptoml/cpptoml.h
+++ /dev/null
@@ -1,3668 +0,0 @@
-/**
- * @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<std::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<std::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