一、cpptoml.h文件内容
#ifndef CPPTOML_H #define CPPTOML_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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>; #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>; #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 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 T value_or(U&& alternative) const { if (!empty_) return value_; return static_cast(std::forward(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(dt) << "T" << static_cast(dt); } inline std::ostream& operator<<(std::ostream& os, const offset_datetime& dt) { return os << static_cast(dt) << static_cast(dt); } template struct is_one_of; template struct is_one_of : std::is_same { }; template struct is_one_of { const static bool value = std::is_same::value || is_one_of::value; }; template class value; template struct valid_value : is_one_of { }; template struct value_traits; template struct valid_value_or_string_convertible { const static bool value = valid_value::type>::value || std::is_convertible::value; }; template struct value_traits::value>::type> { using value_type = typename std::conditional< valid_value::type>::value, typename std::decay::type, std::string>::type; using type = value; static value_type construct(T&& val) { return value_type(val); } }; template struct value_traits< T, typename std::enable_if< !valid_value_or_string_convertible::value && std::is_floating_point::type>::value>::type> { using value_type = typename std::decay::type; using type = value; static value_type construct(T&& val) { return value_type(val); } }; template struct value_traits< T, typename std::enable_if< !valid_value_or_string_convertible::value && !std::is_floating_point::type>::value && std::is_signed::type>::value>::type> { using value_type = int64_t; using type = value; static value_type construct(T&& val) { if (val < (std::numeric_limits::min)()) throw std::underflow_error{"constructed value cannot be " "represented by a 64-bit signed " "integer"}; if (val > (std::numeric_limits::max)()) throw std::overflow_error{"constructed value cannot be represented " "by a 64-bit signed integer"}; return static_cast(val); } }; template struct value_traits< T, typename std::enable_if< !valid_value_or_string_convertible::value && std::is_unsigned::type>::value>::type> { using value_type = int64_t; using type = value; static value_type construct(T&& val) { if (val > static_cast((std::numeric_limits::max)())) throw std::overflow_error{"constructed value cannot be represented " "by a 64-bit signed integer"}; return static_cast(val); } }; class array; class table; class table_array; template struct array_of_trait { using return_type = option>; }; template <> struct array_of_trait { using return_type = option>; }; template inline std::shared_ptr::type> make_value(T&& val); inline std::shared_ptr make_array(); namespace detail { template inline std::shared_ptr make_element(); } inline std::shared_ptr make_table(); inline std::shared_ptr 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 struct base_type_traits; template <> struct base_type_traits { static const base_type type = base_type::STRING; }; template <> struct base_type_traits { static const base_type type = base_type::LOCAL_TIME; }; template <> struct base_type_traits { static const base_type type = base_type::LOCAL_DATE; }; template <> struct base_type_traits { static const base_type type = base_type::LOCAL_DATETIME; }; template <> struct base_type_traits { static const base_type type = base_type::OFFSET_DATETIME; }; template <> struct base_type_traits { static const base_type type = base_type::INT; }; template <> struct base_type_traits { static const base_type type = base_type::FLOAT; }; template <> struct base_type_traits { static const base_type type = base_type::BOOL; }; template <> struct base_type_traits { static const base_type type = base_type::TABLE; }; template <> struct base_type_traits { static const base_type type = base_type::ARRAY; }; template <> struct base_type_traits { static const base_type type = base_type::TABLE_ARRAY; }; #endif class base : public std::enable_shared_from_this { public: virtual ~base() = default; virtual std::shared_ptr clone() const = 0; virtual bool is_value() const { return false; } virtual bool is_table() const { return false; } std::shared_ptr as_table() { if (is_table()) return std::static_pointer_cast(shared_from_this()); return nullptr; } virtual bool is_array() const { return false; } std::shared_ptr as_array() { if (is_array()) return std::static_pointer_cast(shared_from_this()); return nullptr; } virtual bool is_table_array() const { return false; } std::shared_ptr as_table_array() { if (is_table_array()) return std::static_pointer_cast(shared_from_this()); return nullptr; } template std::shared_ptr> as(); template std::shared_ptr> as() const; template 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 }; template class value : public base { struct make_shared_enabler { // nothing; this is a private key accessible only to friends }; template friend std::shared_ptr::type> cpptoml::make_value(U&& val); public: static_assert(valid_value::value, "invalid value type"); std::shared_ptr 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; } T& get() { return data_; } const T& get() const { return data_; } private: T data_; #if defined(CPPTOML_NO_RTTI) value(const T& val) : base(base_type_traits::type), data_(val) { } #else value(const T& val) : data_(val) { } #endif value(const value& val) = delete; value& operator=(const value& val) = delete; }; template std::shared_ptr::type> make_value(T&& val) { using value_type = typename value_traits::type; using enabler = typename value_type::make_shared_enabler; return std::make_shared( enabler{}, value_traits::construct(std::forward(val))); } template inline std::shared_ptr> base::as() { #if defined(CPPTOML_NO_RTTI) if (type() == base_type_traits::type) return std::static_pointer_cast>(shared_from_this()); else return nullptr; #else return std::dynamic_pointer_cast>(shared_from_this()); #endif } // special case value to allow getting an integer parameter as a // double value template <> inline std::shared_ptr> base::as() { #if defined(CPPTOML_NO_RTTI) if (type() == base_type::FLOAT) return std::static_pointer_cast>(shared_from_this()); if (type() == base_type::INT) { auto v = std::static_pointer_cast>(shared_from_this()); return make_value(static_cast(v->get())); } #else if (auto v = std::dynamic_pointer_cast>(shared_from_this())) return v; if (auto v = std::dynamic_pointer_cast>(shared_from_this())) return make_value(static_cast(v->get())); #endif return nullptr; } template inline std::shared_ptr> base::as() const { #if defined(CPPTOML_NO_RTTI) if (type() == base_type_traits::type) return std::static_pointer_cast>(shared_from_this()); else return nullptr; #else return std::dynamic_pointer_cast>(shared_from_this()); #endif } // special case value to allow getting an integer parameter as a // double value template <> inline std::shared_ptr> base::as() const { #if defined(CPPTOML_NO_RTTI) if (type() == base_type::FLOAT) return std::static_pointer_cast>( shared_from_this()); if (type() == base_type::INT) { auto v = as(); // the below has to be a non-const value due to a bug in // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843 return make_value(static_cast(v->get())); } #else if (auto v = std::dynamic_pointer_cast>(shared_from_this())) return v; if (auto v = as()) { // the below has to be a non-const value due to a bug in // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843 return make_value(static_cast(v->get())); } #endif return nullptr; } 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 make_array(); std::shared_ptr clone() const override; virtual bool is_array() const override { return true; } using size_type = std::size_t; using iterator = std::vector>::iterator; using const_iterator = std::vector>::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(); } std::vector>& get() { return values_; } const std::vector>& get() const { return values_; } std::shared_ptr at(size_t idx) const { return values_.at(idx); } template std::vector>> array_of() const { std::vector>> result(values_.size()); std::transform(values_.begin(), values_.end(), result.begin(), [&](std::shared_ptr v) { return v->as(); }); return result; } template inline typename array_of_trait::return_type get_array_of() const { std::vector result; result.reserve(values_.size()); for (const auto& val : values_) { if (auto v = val->as()) result.push_back(v->get()); else return {}; } return {std::move(result)}; } std::vector nested_array() const { std::vector result(values_.size()); std::transform(values_.begin(), values_.end(), result.begin(), [&](std::shared_ptr v) -> std::shared_ptr { if (v->is_array()) return std::static_pointer_cast(v); return std::shared_ptr{}; }); return result; } template void push_back(const std::shared_ptr>& val) { if (values_.empty() || values_[0]->as()) { values_.push_back(val); } else { throw array_exception{"Arrays must be homogenous."}; } } void push_back(const std::shared_ptr& val) { if (values_.empty() || values_[0]->is_array()) { values_.push_back(val); } else { throw array_exception{"Arrays must be homogenous."}; } } template void push_back(T&& val, typename value_traits::type* = 0) { push_back(make_value(std::forward(val))); } template iterator insert(iterator position, const std::shared_ptr>& value) { if (values_.empty() || values_[0]->as()) { return values_.insert(position, value); } else { throw array_exception{"Arrays must be homogenous."}; } } iterator insert(iterator position, const std::shared_ptr& value) { if (values_.empty() || values_[0]->is_array()) { return values_.insert(position, value); } else { throw array_exception{"Arrays must be homogenous."}; } } template iterator insert(iterator position, T&& val, typename value_traits::type* = 0) { return insert(position, make_value(std::forward(val))); } iterator erase(iterator position) { return values_.erase(position); } void clear() { values_.clear(); } 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 array(InputIterator begin, InputIterator end) : values_{begin, end} { // nothing } array(const array& obj) = delete; array& operator=(const array& obj) = delete; std::vector> values_; }; inline std::shared_ptr make_array() { struct make_shared_enabler : public array { make_shared_enabler() { // nothing } }; return std::make_shared(); } namespace detail { template <> inline std::shared_ptr make_element() { return make_array(); } } // namespace detail template <> inline typename array_of_trait::return_type array::get_array_of() const { std::vector 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 make_table_array(bool); public: std::shared_ptr clone() const override; using size_type = std::size_t; using iterator = std::vector>::iterator; using const_iterator = std::vector>::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>& get() { return array_; } const std::vector>& get() const { return array_; } void push_back(const std::shared_ptr& val) { array_.push_back(val); } iterator insert(iterator position, const std::shared_ptr& value) { return array_.insert(position, value); } iterator erase(iterator position) { return array_.erase(position); } void clear() { array_.clear(); } void reserve(size_type n) { array_.reserve(n); } 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> array_; const bool is_inline_ = false; }; inline std::shared_ptr 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(is_inline); } namespace detail { template <> inline std::shared_ptr make_element() { 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 typename std::enable_if::value && std::is_signed::value, option>::type get_impl(const std::shared_ptr& elem) { if (auto v = elem->as()) { if (v->get() < (std::numeric_limits::min)()) throw std::underflow_error{ "T cannot represent the value requested in get"}; if (v->get() > (std::numeric_limits::max)()) throw std::overflow_error{ "T cannot represent the value requested in get"}; return {static_cast(v->get())}; } else { return {}; } } template typename std::enable_if::value && std::is_unsigned::value, option>::type get_impl(const std::shared_ptr& elem) { if (auto v = elem->as()) { if (v->get() < 0) throw std::underflow_error{"T cannot store negative value in get"}; if (static_cast(v->get()) > (std::numeric_limits::max)()) throw std::overflow_error{ "T cannot represent the value requested in get"}; return {static_cast(v->get())}; } else { return {}; } } template typename std::enable_if::value || std::is_same::value, option>::type get_impl(const std::shared_ptr& elem) { if (auto v = elem->as()) { return {v->get()}; } else { return {}; } } class table : public base { public: friend class table_array; friend std::shared_ptr make_table(); std::shared_ptr clone() const override; using iterator = string_to_base_map::iterator; 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(); } bool contains(const std::string& key) const { return map_.find(key) != map_.end(); } bool contains_qualified(const std::string& key) const { return resolve_qualified(key); } std::shared_ptr get(const std::string& key) const { return map_.at(key); } std::shared_ptr get_qualified(const std::string& key) const { std::shared_ptr p; resolve_qualified(key, &p); return p; } std::shared_ptr get_table(const std::string& key) const { if (contains(key) && get(key)->is_table()) return std::static_pointer_cast(get(key)); return nullptr; } std::shared_ptr get_table_qualified(const std::string& key) const { if (contains_qualified(key) && get_qualified(key)->is_table()) return std::static_pointer_cast(get_qualified(key)); return nullptr; } std::shared_ptr get_array(const std::string& key) const { if (!contains(key)) return nullptr; return get(key)->as_array(); } std::shared_ptr get_array_qualified(const std::string& key) const { if (!contains_qualified(key)) return nullptr; return get_qualified(key)->as_array(); } std::shared_ptr get_table_array(const std::string& key) const { if (!contains(key)) return nullptr; return get(key)->as_table_array(); } std::shared_ptr get_table_array_qualified(const std::string& key) const { if (!contains_qualified(key)) return nullptr; return get_qualified(key)->as_table_array(); } template option get_as(const std::string& key) const { try { return get_impl(get(key)); } catch (const std::out_of_range&) { return {}; } } template option get_qualified_as(const std::string& key) const { try { return get_impl(get_qualified(key)); } catch (const std::out_of_range&) { return {}; } } template inline typename array_of_trait::return_type get_array_of(const std::string& key) const { if (auto v = get_array(key)) { std::vector result; result.reserve(v->get().size()); for (const auto& b : v->get()) { if (auto val = b->as()) result.push_back(val->get()); else return {}; } return {std::move(result)}; } return {}; } template inline typename array_of_trait::return_type get_qualified_array_of(const std::string& key) const { if (auto v = get_array_qualified(key)) { std::vector result; result.reserve(v->get().size()); for (const auto& b : v->get()) { if (auto val = b->as()) result.push_back(val->get()); else return {}; } return {std::move(result)}; } return {}; } void insert(const std::string& key, const std::shared_ptr& value) { map_[key] = value; } template void insert(const std::string& key, T&& val, typename value_traits::type* = 0) { insert(key, make_value(std::forward(val))); } 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 split(const std::string& value, char separator) const { std::vector 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* 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_; }; template <> inline typename array_of_trait::return_type table::get_array_of(const std::string& key) const { if (auto v = get_array(key)) { std::vector 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 {}; } template <> inline typename array_of_trait::return_type table::get_qualified_array_of(const std::string& key) const { if (auto v = get_array_qualified(key)) { std::vector 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 make_table() { struct make_shared_enabler : public table { make_shared_enabler() { // nothing } }; return std::make_shared(); } namespace detail { template <> inline std::shared_ptr make_element() { return make_table(); } } // namespace detail template std::shared_ptr value::clone() const { return make_value(data_); } inline std::shared_ptr 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 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 table::clone() const { auto result = make_table(); for (const auto& pr : map_) result->insert(pr.first, pr.second->clone()); return result; } 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'); } template class consumer { public: consumer(std::string::iterator& it, const std::string::iterator& end, OnError&& on_error) : it_(it), end_(end), on_error_(std::forward(on_error)) { // nothing } void operator()(char c) { if (it_ == end_ || *it_ != c) on_error_(); ++it_; } template 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 consumer make_consumer(std::string::iterator& it, const std::string::iterator& end, OnError&& on_error) { return consumer(it, end, std::forward(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(c)); } } } // namespace detail class parser { public: parser(std::istream& stream) : input_(stream) { // nothing } parser& operator=(const parser& parser) = delete; std::shared_ptr parse() { std::shared_ptr 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 b = curr_table->get(part); #endif if (b->is_table()) curr_table = static_cast(b.get()); else if (b->is_table_array()) curr_table = std::static_pointer_cast(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(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&>& 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 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(b.get()); else if (b->is_table_array()) curr_table = std::static_pointer_cast(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( 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(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(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 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 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> 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