From bb6bfe3aa3a7e1573dec4e56bd58fd72e87c0222 Mon Sep 17 00:00:00 2001 From: davidwed Date: Fri, 10 May 2019 15:34:37 +0200 Subject: [PATCH] cpptoml: updated to version v0.1.1 (#39) * cpptoml: updated to version v0.1.1 * cpptoml: updated to version v0.1.1 ( clang-format fix ) --- .../details/third_party/cpptoml.h | 562 ++++++++++++++---- 1 file changed, 448 insertions(+), 114 deletions(-) diff --git a/include/spdlog_setup/details/third_party/cpptoml.h b/include/spdlog_setup/details/third_party/cpptoml.h index f486bc5a..ab3fab34 100644 --- a/include/spdlog_setup/details/third_party/cpptoml.h +++ b/include/spdlog_setup/details/third_party/cpptoml.h @@ -4,11 +4,12 @@ * @date May 2013 */ -#ifndef _CPPTOML_H_ -#define _CPPTOML_H_ +#ifndef CPPTOML_H +#define CPPTOML_H #include #include +#include #include #include #include @@ -49,13 +50,17 @@ 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)} { + option(T value) : empty_{false}, value_(std::move(value)) { // nothing } @@ -65,10 +70,10 @@ template class option { const T *operator->() const { return &value_; } - const T &value_or(const T &alternative) const { + template T value_or(U &&alternative) const { if (!empty_) return value_; - return alternative; + return static_cast(std::forward(alternative)); } private: @@ -276,18 +281,19 @@ 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()) + 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()) + if (val > (std::numeric_limits::max)()) throw std::overflow_error{"constructed value cannot be represented " "by a 64-bit signed integer"}; @@ -306,7 +312,7 @@ struct value_traits< using type = value; static value_type construct(T &&val) { - if (val > static_cast(std::numeric_limits::max())) + if (val > static_cast((std::numeric_limits::max)())) throw std::overflow_error{"constructed value cannot be represented " "by a 64-bit signed integer"}; @@ -329,9 +335,78 @@ template <> struct array_of_trait { 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(); +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 /** * A generic base TOML value used for type erasure. @@ -399,10 +474,23 @@ class base : public std::enable_shared_from_this { 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 }; /** @@ -445,7 +533,11 @@ template class value : public base { /** * Constructs a value from the given 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; @@ -460,28 +552,64 @@ std::shared_ptr::type> make_value(T &&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; @@ -491,6 +619,7 @@ template <> inline std::shared_ptr> base::as() const { // libc++: https://llvm.org/bugs/show_bug.cgi?id=18843 return make_value(static_cast(v->get())); } +#endif return nullptr; } @@ -677,7 +806,13 @@ class array : public base { 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} { @@ -700,9 +835,11 @@ inline std::shared_ptr make_array() { return std::make_shared(); } +namespace detail { template <> inline std::shared_ptr make_element() { return make_array(); } +} // namespace detail /** * Obtains a option>. The option will be empty if the array @@ -728,7 +865,7 @@ class table; class table_array : public base { friend class table; - friend std::shared_ptr make_table_array(); + friend std::shared_ptr make_table_array(bool); public: std::shared_ptr clone() const override; @@ -786,30 +923,47 @@ class table_array : public base { */ 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: - table_array() { +#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() { +inline std::shared_ptr make_table_array(bool is_inline) { struct make_shared_enabler : public table_array { - make_shared_enabler() { + make_shared_enabler(bool mse_is_inline) : table_array(mse_is_inline) { // nothing } }; - return std::make_shared(); + return std::make_shared(is_inline); } +namespace detail { template <> inline std::shared_ptr make_element() { - return make_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 @@ -820,11 +974,11 @@ typename std::enable_if< option>::type get_impl(const std::shared_ptr &elem) { if (auto v = elem->as()) { - if (v->get() < std::numeric_limits::min()) + 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()) + if (v->get() > (std::numeric_limits::max)()) throw std::overflow_error{ "T cannot represent the value requested in get"}; @@ -843,7 +997,7 @@ get_impl(const std::shared_ptr &elem) { if (v->get() < 0) throw std::underflow_error{"T cannot store negative value in get"}; - if (static_cast(v->get()) > std::numeric_limits::max()) + if (static_cast(v->get()) > (std::numeric_limits::max)()) throw std::overflow_error{ "T cannot represent the value requested in get"}; @@ -1097,9 +1251,15 @@ class table : public base { 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; @@ -1128,10 +1288,10 @@ class table : public base { auto last_key = parts.back(); parts.pop_back(); - auto table = this; + auto cur_table = this; for (const auto &part : parts) { - table = table->get_table(part).get(); - if (!table) { + cur_table = cur_table->get_table(part).get(); + if (!cur_table) { if (!p) return false; @@ -1140,9 +1300,9 @@ class table : public base { } if (!p) - return table->map_.count(last_key) != 0; + return cur_table->map_.count(last_key) != 0; - *p = table->map_.at(last_key); + *p = cur_table->map_.at(last_key); return true; } @@ -1217,9 +1377,11 @@ std::shared_ptr
make_table() { 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_); @@ -1234,7 +1396,7 @@ inline std::shared_ptr array::clone() const { } inline std::shared_ptr table_array::clone() const { - auto result = make_table_array(); + auto result = make_table_array(is_inline()); result->reserve(array_.size()); for (const auto &ptr : array_) result->array_.push_back(ptr->clone()->as_table()); @@ -1261,6 +1423,10 @@ class parse_exception : public std::runtime_error { 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. */ @@ -1285,6 +1451,12 @@ template class consumer { 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) { @@ -1416,19 +1588,24 @@ class parser { std::string full_table_name; bool inserted = false; - while (it != end && *it != ']') { - auto part = - parse_key(it, end, [](char c) { return c == '.' || c == ']'; }); + 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 += '.'; 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()) @@ -1444,16 +1621,22 @@ class parser { curr_table->insert(part, make_table()); curr_table = static_cast
(curr_table->get(part).get()); } - consume_whitespace(it, end); - if (it != end && *it == '.') - ++it; - consume_whitespace(it, end); - } + }; + + 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< @@ -1486,33 +1669,42 @@ class parser { if (it == end || *it == ']') throw_parse_exception("Table array name cannot be empty"); - std::string full_ta_name; - while (it != end && *it != ']') { - auto part = - parse_key(it, end, [](char c) { return c == '.' || c == ']'; }); + 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 += '.'; full_ta_name += part; - consume_whitespace(it, end); - if (it != end && *it == '.') - ++it; - consume_whitespace(it, end); - 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 + // 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()) + 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(); } @@ -1549,15 +1741,16 @@ class parser { static_cast
(curr_table->get(part).get()); } } - } + }; + + key_part_handler(parse_key(it, end, key_end, key_part_handler)); // consume the last "]]" - if (it == end) - throw_parse_exception("Unterminated table array name"); - ++it; - if (it == end) + auto eat = make_consumer(it, end, [this]() { throw_parse_exception("Unterminated table array name"); - ++it; + }); + eat(']'); + eat(']'); consume_whitespace(it, end); eol_or_comment(it, end); @@ -1567,7 +1760,29 @@ class parser { std::string::iterator &it, std::string::iterator &end, table *curr_table) { - auto key = parse_key(it, end, [](char c) { return c == '='; }); + 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 != '=') @@ -1578,16 +1793,51 @@ class parser { consume_whitespace(it, end); } - template + template std::string parse_key( std::string::iterator &it, const std::string::iterator &end, - Function &&fun) { + 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 == '"') { - return parse_quoted_key(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, std::forward(fun)); + auto bke = std::find_if(it, end, [](char c) { + return c == '.' || c == '=' || c == ']'; + }); return parse_bare_key(it, bke); } } @@ -1626,11 +1876,6 @@ class parser { return key; } - std::string parse_quoted_key( - std::string::iterator &it, const std::string::iterator &end) { - return string_literal(it, end, '"'); - } - enum class parse_type { STRING = 1, LOCAL_TIME, @@ -1672,13 +1917,21 @@ class parser { 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 == '+') { + } 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; @@ -1696,6 +1949,13 @@ class parser { 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 == '.') { @@ -1736,48 +1996,48 @@ class parser { bool consuming = false; std::shared_ptr> ret; - auto handle_line = [&](std::string::iterator &it, - std::string::iterator &end) { + auto handle_line = [&](std::string::iterator &local_it, + std::string::iterator &local_end) { if (consuming) { - it = std::find_if_not(it, end, is_ws); + local_it = std::find_if_not(local_it, local_end, is_ws); // whole line is whitespace - if (it == end) + if (local_it == local_end) return; } consuming = false; - while (it != end) { + while (local_it != local_end) { // handle escaped characters - if (delim == '"' && *it == '\\') { - auto check = it; + 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, end); - if (check == end) { + consume_whitespace(check, local_end); + if (check == local_end) { consuming = true; break; } - ss << parse_escape_code(it, end); + ss << parse_escape_code(local_it, local_end); continue; } // if we can end the string - if (std::distance(it, end) >= 3) { - auto check = it; + if (std::distance(local_it, local_end) >= 3) { + auto check = local_it; // check for """ if (*check++ == delim && *check++ == delim && *check++ == delim) { - it = check; + local_it = check; ret = make_value(ss.str()); break; } } - ss << *it++; + ss << *local_it++; } }; @@ -1928,10 +2188,6 @@ class parser { return value; } - bool is_hex(char c) { - return is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); - } - uint32_t hex_to_digit(char c) { if (is_number(c)) return static_cast(c - '0'); @@ -1949,15 +2205,20 @@ class parser { ++check_it; }; - eat_sign(); + 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_numbers = [&]() { + auto eat_digits = [&](bool (*check_char)(char)) { auto beg = check_it; - while (check_it != end && is_number(*check_it)) { + while (check_it != end && check_char(*check_it)) { ++check_it; if (check_it != end && *check_it == '_') { ++check_it; - if (check_it == end || !is_number(*check_it)) + if (check_it == end || !check_char(*check_it)) throw_parse_exception("Malformed number"); } } @@ -1966,14 +2227,56 @@ class parser { throw_parse_exception("Malformed number"); }; - 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_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::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::quiet_NaN(); + if (*it == '-') + val = -val; + it = check_it + 3; + return make_value(val); + } + } + eat_numbers(); if (check_it != end && @@ -2007,13 +2310,17 @@ class parser { } } - std::shared_ptr> - parse_int(std::string::iterator &it, const std::string::iterator &end) { + std::shared_ptr> 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(std::stoll(v)); + return make_value(std::stoll(v, nullptr, base)); } catch (const std::invalid_argument &ex) { throw_parse_exception( "Malformed number (invalid argument: " + @@ -2030,6 +2337,8 @@ class parser { 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(std::stod(v)); } catch (const std::invalid_argument &ex) { @@ -2063,15 +2372,28 @@ class parser { std::string::iterator find_end_of_number(std::string::iterator it, std::string::iterator end) { - return std::find_if(it, end, [](char c) { + auto ret = std::find_if(it, end, [](char c) { return !is_number(c) && c != '_' && c != '.' && c != 'e' && - c != 'E' && c != '-' && c != '+'; + 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) { - return std::find_if(it, end, [](char c) { + 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 != '.'; }); @@ -2136,7 +2458,7 @@ class parser { if (it == date_end) return make_value(ldate); - eat('T'); + eat.eat_or('T', ' '); local_datetime ldt; static_cast(ldt) = ldate; @@ -2224,11 +2546,11 @@ class parser { parse_value_array(std::string::iterator &it, std::string::iterator &end) { auto arr = make_array(); while (it != end && *it != ']') { - auto value = parse_value(it, end); - if (auto v = value->as()) - arr->get().push_back(value); + auto val = parse_value(it, end); + if (auto v = val->as()) + arr->get().push_back(val); else - throw_parse_exception("Arrays must be heterogeneous"); + throw_parse_exception("Arrays must be homogeneous"); skip_whitespace_and_comments(it, end); if (*it != ',') break; @@ -2246,7 +2568,7 @@ class parser { char delim, std::string::iterator &it, std::string::iterator &end) { - auto arr = make_element(); + auto arr = detail::make_element(); while (it != end && *it != ']') { if (*it != delim) @@ -2255,7 +2577,7 @@ class parser { arr->get().push_back(((*this).*fun)(it, end)); skip_whitespace_and_comments(it, end); - if (*it != ',') + if (it == end || *it != ',') break; ++it; @@ -2278,8 +2600,10 @@ class parser { throw_parse_exception("Unterminated inline table"); consume_whitespace(it, end); - parse_key_value(it, end, tbl.get()); - 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 != '}') @@ -2352,7 +2676,8 @@ class parser { if (it[4] != '-' || it[7] != '-') return {}; - if (len >= 19 && it[10] == 'T' && is_time(it + 11, date_end)) { + 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) @@ -2557,7 +2882,7 @@ class toml_writer { res += "\\\""; } else if (*it == '\\') { res += "\\\\"; - } else if (*it >= 0x0000 && *it <= 0x001f) { + } else if (static_cast(*it) <= UINT32_C(0x001f)) { res += "\\u"; std::stringstream ss; ss << std::hex << static_cast(*it); @@ -2583,12 +2908,21 @@ class toml_writer { * Write out a double. */ void write(const value &v) { - std::ios::fmtflags flags{stream_.flags()}; - - stream_ << std::showpoint; - write(v.get()); - - stream_.flags(flags); + std::stringstream ss; + ss << std::showpoint + << std::setprecision(std::numeric_limits::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; } /** @@ -2737,4 +3071,4 @@ inline std::ostream &operator<<(std::ostream &stream, const array &a) { return stream; } } // namespace cpptoml -#endif +#endif // CPPTOML_H