From 2e98e250a499f6c1a8f4a5d4983a2c5cbe846d62 Mon Sep 17 00:00:00 2001 From: Martin Staley Date: Thu, 1 Feb 2024 13:22:08 -0700 Subject: [PATCH] More JSON updates. Basically code cleanup and redux. --- simple-json/src/json-array.hpp | 40 +++++-- simple-json/src/json-boolean.hpp | 26 ++++- simple-json/src/json-chars.hpp | 16 ++- simple-json/src/json-detail-pre.hpp | 25 ++-- simple-json/src/json-diagnostic-detail.hpp | 4 +- simple-json/src/json-literal.hpp | 28 +++-- simple-json/src/json-null.hpp | 18 ++- simple-json/src/json-number.hpp | 88 ++++++++++---- simple-json/src/json-object-def.hpp | 24 ++-- simple-json/src/json-object.hpp | 56 ++++++--- simple-json/src/json-string.hpp | 76 +++++++++---- simple-json/src/json-value.hpp | 126 ++++++++++++++------- simple-json/src/json-write.hpp | 21 ++-- 13 files changed, 389 insertions(+), 159 deletions(-) diff --git a/simple-json/src/json-array.hpp b/simple-json/src/json-array.hpp index 825da642e..ab0521d9b 100644 --- a/simple-json/src/json-array.hpp +++ b/simple-json/src/json-array.hpp @@ -6,22 +6,46 @@ class array : public std::vector { public: + + // ------------------------ + // Construction + // ------------------------ + using vector::vector; - using vector::operator=; - array(const vector &from) : vector(from) { } - array(vector &&from) : vector(std::move(from)) { } - array &operator=(const vector &from) - { static_cast(*this) = from; return *this; } - array &operator=(vector &&from) - { static_cast(*this) = std::move(from); return *this; } // constructor: from std::vector template>> - array(const std::vector &from) : vector(from.begin(), from.end()) { } + array(const std::vector &from) : + vector(from.begin(), from.end()) + { } + + array(const vector &from) : + vector(from) + { } + + array(vector &&from) : + vector(std::move(from)) + { } + + // ------------------------ + // Assignment + // ------------------------ + template>> + array &operator=(const T &from) + { vector::operator=(from); return *this; } + + template>> + array &operator=(T &&from) + { vector::operator=(std::move(from)); return *this; } + + // ------------------------ // read, write + // ------------------------ + template auto read(std::istream &is, const int = as_literal::none) -> decltype(number().read(is,0)); // SFINAE: need number::read + void write(std::ostream & = std::cout, const int = 0, const int = -1) const; }; diff --git a/simple-json/src/json-boolean.hpp b/simple-json/src/json-boolean.hpp index f4b5d1703..9db211baf 100644 --- a/simple-json/src/json-boolean.hpp +++ b/simple-json/src/json-boolean.hpp @@ -9,19 +9,35 @@ class boolean { public: - // constructor: default - boolean() : b(false) { } + // ------------------------ + // Construction + // ------------------------ - // constructor: from bool + // default + boolean() : + b(false) + { } + + // from bool template>> - boolean(const T &from) : b(from) { } + boolean(const T &from) : + b(from) + { } + + // ------------------------ + // Conversion + // ------------------------ - // convert: to bool + // to bool operator const bool &() const { return b; } operator bool &() { return b; } + // ------------------------ // read, write + // ------------------------ + template std::string read(std::istream &, const int = as_literal::none); + void write(std::ostream & = std::cout, const int = 0, const int = -1) const; }; diff --git a/simple-json/src/json-chars.hpp b/simple-json/src/json-chars.hpp index db8987adc..343e474ce 100644 --- a/simple-json/src/json-chars.hpp +++ b/simple-json/src/json-chars.hpp @@ -22,10 +22,14 @@ class chars { public: - // constructor: default + // ------------------------ + // Construction + // ------------------------ + + // default chars() { } - // constructor: from float, double, or long double + // from float, double, or long double template< class T, class = std::enable_if_t< @@ -39,11 +43,15 @@ class chars { str(buf, std::to_chars(buf,buf+SIZE,from,format).ptr - buf) { } - // convert: to std::string + // ------------------------ + // Conversion + // ------------------------ + + // to std::string operator const std::string &() const { return str; } operator std::string &() { return str; } - // convert: to literal + // to literal operator literal() const { return literal(str); diff --git a/simple-json/src/json-detail-pre.hpp b/simple-json/src/json-detail-pre.hpp index 3e181bb88..34a147a28 100644 --- a/simple-json/src/json-detail-pre.hpp +++ b/simple-json/src/json-detail-pre.hpp @@ -6,7 +6,7 @@ namespace detail { // ----------------------------------------------------------------------------- // inVariant -// count == number of times T is exactly the same as a type in the std::variant +// count == number of times T is exactly the same as a type in a std::variant template struct inVariant { }; @@ -17,7 +17,7 @@ struct inVariant> }; // toVariant -// count == number of times T is convertible to a type in the std::variant +// count == number of times T is convertible to a type in a std::variant template struct toVariant { }; @@ -36,15 +36,18 @@ struct toVariant> }; // For brevity -template inline constexpr bool - invar = inVariant::count == 1; -template inline constexpr bool - tovar = toVariant::count == 1; - -template inline constexpr bool isintegral = - std::is_integral_v>; -template inline constexpr bool isfloating = - std::is_floating_point_v>; +// invar +// tovar +// isintegral +// isfloating +template +inline constexpr bool invar = inVariant::count == 1; +template +inline constexpr bool tovar = toVariant::count == 1; +template +inline constexpr bool isintegral = std::is_integral_v>; +template +inline constexpr bool isfloating = std::is_floating_point_v>; // variant2tuple template diff --git a/simple-json/src/json-diagnostic-detail.hpp b/simple-json/src/json-diagnostic-detail.hpp index 5255c743a..11c0b61a5 100644 --- a/simple-json/src/json-diagnostic-detail.hpp +++ b/simple-json/src/json-diagnostic-detail.hpp @@ -13,7 +13,8 @@ inline std::string color(const int r, const int g, const int b) // diagnostic inline void diagnostic( std::istream &is, - const std::string &message, const std::string &label + const std::string &message, + const std::string &label ) { static const std::string reset = "\033[0m"; // all colors/decorations off const std::string spaces(indent,' '); @@ -62,6 +63,7 @@ inline void diagnostic( for (const char &ch : message) std::cout << ch << (ch == '\n' ? spaces : ""); std::cout << reset << std::endl; + if (quit) error("Unable to recover. istream.seekg() failed after attempt\n" "to determine line number for previous diagnostic."); diff --git a/simple-json/src/json-literal.hpp b/simple-json/src/json-literal.hpp index fc06eead4..5c92d527c 100644 --- a/simple-json/src/json-literal.hpp +++ b/simple-json/src/json-literal.hpp @@ -10,22 +10,36 @@ class literal { public: - // constructor: default + // ------------------------ + // Construction + // ------------------------ + + // default literal() { } - // constructor: from std::string + // from std::string // Explicit, so that std::string prefers string's constructor, not literal's. // We want literal to be something we get only by specifically asking for it. // Note also that this constructor's being explicit lets us dispense with the // enable_if business that we spoke of above class null's definition. - explicit literal(const std::string &from) : str(from) { } + explicit literal(const std::string &from) : + str(from) + { } + + // ------------------------ + // Conversion + // ------------------------ + + // to std::string + operator const std::string &() const { return str; } + operator std::string &() { return str; } + // ------------------------ // read, write + // ------------------------ + template std::string read(std::istream &, const int = as_literal::none); - void write(std::ostream & = std::cout, const int = 0, const int = -1) const; - // convert: to std::string - operator const std::string &() const { return str; } - operator std::string &() { return str; } + void write(std::ostream & = std::cout, const int = 0, const int = -1) const; }; diff --git a/simple-json/src/json-null.hpp b/simple-json/src/json-null.hpp index dc79bfa8c..e7e3c63ac 100644 --- a/simple-json/src/json-null.hpp +++ b/simple-json/src/json-null.hpp @@ -31,21 +31,33 @@ class null { public: - // constructor: default + // ------------------------ + // Construction + // ------------------------ + + // default null() { } - // constructor: from std::nullptr_t + // from std::nullptr_t template>> null(const T &) { } - // convert: to std::nullptr_t + // ------------------------ + // Conversion + // ------------------------ + + // to std::nullptr_t operator const std::nullptr_t &() const { static const std::nullptr_t ret = nullptr; return ret; } operator std::nullptr_t &() { static std::nullptr_t ret = nullptr; return ret; } + // ------------------------ // read, write + // ------------------------ + template std::string read(std::istream &, const int = as_literal::none); + void write(std::ostream & = std::cout, const int = 0, const int = -1) const; }; diff --git a/simple-json/src/json-number.hpp b/simple-json/src/json-number.hpp index 5f96e8cb9..d0080ed8b 100644 --- a/simple-json/src/json-number.hpp +++ b/simple-json/src/json-number.hpp @@ -27,30 +27,64 @@ class number #include "json-number-helper.hpp" public: - using variant::operator=; - number(const variant &from) : variant(from) { } - number(variant &&from) : variant(std::move(from)) { } - number &operator=(const variant &from) - { static_cast(*this) = from; return *this; } - number &operator=(variant &&from) - { static_cast(*this) = std::move(from); return *this; } - - // constructor: default - number() : variant(0) { } - - // constructor: from T in the variant + + // ------------------------ + // Construction + // ------------------------ + + // default + number() : + variant(0) + { } + + // from variant + number(const variant &from) : + variant(from) + { } + number(variant &&from) : + variant(std::move(from)) + { } + + // from T in the variant template>> - number(const T &from) : variant(from) { } + number(const T &from) : + variant(from) + { } - // read, write - template< - class T = void, class U = void, - class = std::enable_if_t::compatible> - > - std::string read(std::istream &, const int = as_literal::none); - void write(std::ostream & = std::cout, const int = 0, const int = -1) const; + // ------------------------ + // Assignment + // ------------------------ + + template>> + number &operator=(const T &from) + { variant::operator=(from); return *this; } + + template>> + number &operator=(T &&from) + { variant::operator=(std::move(from)); return *this; } - // has + // ------------------------ + // Conversion + // ------------------------ + + // to T + template>> + operator T() const + { + return std::visit( + [](const auto &alt) + { + return T(alt); + }, + static_cast(*this) + ); + } + + // ------------------------ + // Other + // ------------------------ + + // has template>> bool has() const { return std::holds_alternative(*this); } @@ -59,4 +93,16 @@ class number const T &get() const { return std::get(*this); } template>> T &get() { return std::get(*this); } + + // ------------------------ + // read, write + // ------------------------ + + template< + class T = void, class U = void, + class = std::enable_if_t::compatible> + > + std::string read(std::istream &, const int = as_literal::none); + + void write(std::ostream & = std::cout, const int = 0, const int = -1) const; }; diff --git a/simple-json/src/json-object-def.hpp b/simple-json/src/json-object-def.hpp index 4a0355711..19a93dd40 100644 --- a/simple-json/src/json-object-def.hpp +++ b/simple-json/src/json-object-def.hpp @@ -1,15 +1,4 @@ -// operator[]: non-const -// Feature, not defect: referring to an element creates it, if it isn't already -// there. Use operator[] const, defined below, if you don't want this behavior. -inline value &object::operator[](const string &key) -{ - for (pair &elem : *this) - if (elem.first == key) - return elem.second; - return push_back(pair(key,value())), back().second; -} - // operator[]: const inline const value &object::operator[](const string &key) const { @@ -18,11 +7,22 @@ inline const value &object::operator[](const string &key) const return elem.second; error("const json::object[key=\"" + key + "\"]: key not found."); - // error() throws, so the following just suppresses compilation errors. + // error() throws; the following just suppresses compilation errors. static const value ret; return ret; } +// operator[]: non-const +// Feature, not defect: referring to an element creates it, if it isn't +// already there. Use operator[] const if you don't want this behavior. +inline value &object::operator[](const string &key) +{ + for (pair &elem : *this) + if (elem.first == key) + return elem.second; + return push_back(pair(key,value())), back().second; +} + // has key inline bool object::has(const string &key) const { diff --git a/simple-json/src/json-object.hpp b/simple-json/src/json-object.hpp index a487cc302..4ee2d642f 100644 --- a/simple-json/src/json-object.hpp +++ b/simple-json/src/json-object.hpp @@ -6,32 +6,56 @@ class object : public std::vector { public: + + // ------------------------ + // Construction + // ------------------------ + using vector::vector; - using vector::operator=; + object(const vector &from) : vector(from) { } object(vector &&from) : vector(std::move(from)) { } - object &operator=(const vector &from) - { static_cast(*this) = from; return *this; } - object &operator=(vector &&from) - { static_cast(*this) = std::move(from); return *this; } - // read, write - template - auto read(std::istream &is, const int = as_literal::none) - -> decltype(number().read(is,0)); // SFINAE: need number::read - void write(std::ostream & = std::cout, const int = 0, const int = -1) const; + // ------------------------ + // Assignment + // ------------------------ - // items - // Returns the std::vector base. - const vector &items() const { return *this; } - vector &items() { return *this; } + template>> + object &operator=(const T &from) + { vector::operator=(from); return *this; } + template>> + object &operator=(T &&from) + { vector::operator=(std::move(from)); return *this; } + + // ------------------------ // operator[] + // ------------------------ + // Provided directly - not inherited - because we want certain behavior - // that std::vector's operator[]s don't have. - value &operator[](const string &key); + // that std::vector's operator[] doesn't have. const value &operator[](const string &key) const; + value &operator[](const string &key); + + // ------------------------ + // Other + // ------------------------ + + // items + // Returns the std::vector base. + const vector &items() const { return *this; } + vector &items() { return *this; } // has key bool has(const string &key) const; + + // ------------------------ + // read, write + // ------------------------ + + template + auto read(std::istream &is, const int = as_literal::none) + -> decltype(number().read(is,0)); // SFINAE: need number::read + + void write(std::ostream & = std::cout, const int = 0, const int = -1) const; }; diff --git a/simple-json/src/json-string.hpp b/simple-json/src/json-string.hpp index 14a2d8069..8e02da989 100644 --- a/simple-json/src/json-string.hpp +++ b/simple-json/src/json-string.hpp @@ -5,24 +5,19 @@ // ----------------------------------------------------------------------------- class string : public std::string { - using STRING = std::string; - public: - using STRING::operator=; - string(const STRING &from) : STRING(from) { } - string(STRING &&from) : STRING(std::move(from)) { } - string &operator=(const STRING &from) - { static_cast(*this) = from; return *this; } - string &operator=(STRING &&from) - { static_cast(*this) = std::move(from); return *this; } - - // constructor: default + + // ------------------------ + // Construction + // ------------------------ + + // default string() { } - // constructor: from std::string - // constructor: from const char * - // constructor: from char * - // constructor: from char + // from std::string + // from const char * + // from char * + // from char // Remark. For the "from char" case, using str{from} below, not str(from), // allows std::string's constructor from initializer_list to be used. template< @@ -34,14 +29,57 @@ class string : public std::string { std::is_same_v > > - string(const T &from) : STRING{from} { } + string(const T &from) : + std::string{from} + { } + + string(const std::string &from) : + std::string(from) + { } + + string(std::string &&from) : + std::string(std::move(from)) + { } + + // from char[N] + template< + class T, size_t N, + class = std::enable_if_t> + > + string(const T (&from)[N]) : + std::string(from) + { } + + // ------------------------ + // Assignment + // ------------------------ + + template< + class T, + class = std::enable_if_t> + > + string &operator=(const T &from) + { + std::string::operator=(from); + return *this; + } - // constructor: from char[N] - template>> - string(const T (&from)[N]) : STRING(from) { } + template< + class T, + class = std::enable_if_t> + > + string &operator=(T &&from) + { + std::string::operator=(std::move(from)); + return *this; + } + // ------------------------ // read, write + // ------------------------ + template std::string read(std::istream &, const int = as_literal::none); + void write(std::ostream & = std::cout, const int = 0, const int = -1) const; }; diff --git a/simple-json/src/json-value.hpp b/simple-json/src/json-value.hpp index 95b3d1e82..b25f22145 100644 --- a/simple-json/src/json-value.hpp +++ b/simple-json/src/json-value.hpp @@ -16,63 +16,103 @@ class value #include "json-value-helper.hpp" public: - using variant::variant; - using variant::operator=; - value(const variant &from) : variant(from) { } - value(variant &&from) : variant(std::move(from)) { } - value &operator=(const variant &from) - { static_cast(*this) = from; return *this; } - value &operator=(variant &&from) - { static_cast(*this) = std::move(from); return *this; } // ------------------------ - // Constructors + // Construction // ------------------------ + using variant::variant; + + // default value() : variant(object()) { } + + // from variant + value(const variant &from) : variant(from) { } + value(variant &&from) : variant(std::move(from)) { } + + // from initializer_list value(const std::initializer_list &from) : variant(array(from)) { } value(const std::initializer_list &from) : variant(object(from)) { } // ------------------------ - // Assignment operators + // Assignment // ------------------------ - // assignment: from T convertible to anything in the variant - template>> + template>> value &operator=(const T &from) { - return static_cast(*this) = from, *this; + variant::operator=(from); + return *this; } - // zzz Is this necessary? - // assignment: from std::vector - template - value &operator=(const std::vector &from) + template>> + value &operator=(T &&from) { - return static_cast(*this) = array(from), *this; + variant::operator=(std::move(from)); + return *this; } - // assignment: from std::initializer_list - value &operator=(const std::initializer_list &from) + // ------------------------ + // Conversion + // ------------------------ + + // to T + template< + class T, + class = std::enable_if_t< + std::is_arithmetic_v || + std::is_same_v || + detail::invar + > + > + operator T() const { - return static_cast(*this) = array(from), *this; + if constexpr (std::is_arithmetic_v) + return T(get()); + else if constexpr (std::is_same_v) + return get(); + else + return get(); } - // assignment: from std::initializer_list - value &operator=(const std::initializer_list &from) + // ------------------------ + // operator[] + // ------------------------ + + // const + template< + class T, + class = std::enable_if_t< + detail::isintegral || std::is_constructible_v + > + > + const value &operator[](const T &key) const { - return static_cast(*this) = object(from), *this; + if constexpr (detail::isintegral) + return get()[key]; + else + return get()[key]; + } + + // non-const + template< + class T, + class = std::enable_if_t< + detail::isintegral || std::is_constructible_v + > + > + value &operator[](const T &key) + { + return const_cast(std::as_const(*this).operator[](key)); } // ------------------------ - // Miscellaneous + // Other // ------------------------ - // read, write - template - auto read(std::istream &is, const int = as_literal::none) - -> decltype(number().read(is,0)); // SFINAE: need number::read - void write(std::ostream & = std::cout, const int = 0, const int = -1) const; + // items + const std::vector &items() const { return get().items(); } + std::vector &items() { return get().items(); } // has alternative template< @@ -93,6 +133,13 @@ class value return has() && get().has(); } + // has key + // Assumes this value is an object + bool has(const string &key) const + { + return get().has(key); + } + // get #include "json-value-get.hpp" @@ -106,18 +153,13 @@ class value return write(oss), oss.str(); } - // has key (and, itself, has an object, so that we can get the object, - // and check that the object has the key) - bool has(const string &key) const - { return has() && get().has(key); } + // ------------------------ + // read, write + // ------------------------ - // operator[] - value &operator[](const string &key) - { return get()[key]; } - const value &operator[](const string &key) const - { return get()[key]; } + template + auto read(std::istream &is, const int = as_literal::none) + -> decltype(number().read(is,0)); // SFINAE: need number::read - // items - const std::vector &items() const { return get().items(); } - std::vector &items() { return get().items(); } + void write(std::ostream & = std::cout, const int = 0, const int = -1) const; }; diff --git a/simple-json/src/json-write.hpp b/simple-json/src/json-write.hpp index 7bf92c8b0..d57114ff4 100644 --- a/simple-json/src/json-write.hpp +++ b/simple-json/src/json-write.hpp @@ -8,21 +8,22 @@ inline void null:: write(std::ostream &os, const int, const int) const { os << "null"; -} // null::write +} inline void boolean:: write(std::ostream &os, const int, const int) const { os << (b ? "true" : "false"); -} // boolean::write +} // ----------------------------------------------------------------------------- // number // ----------------------------------------------------------------------------- -// zzz Idea: give all write() function , like the read()s, and then be able -// zzz to do floating ==> chars ==> literal ==> literal.write() from here... +// zzz +// Idea: give all write() functions , like the read()s; then be able +// to do floating ==> chars ==> literal ==> literal.write() from here... inline void number:: write(std::ostream &os, const int, const int) const { @@ -40,7 +41,7 @@ write(std::ostream &os, const int, const int) const }, static_cast(*this) ); -} // number::write +} // ----------------------------------------------------------------------------- @@ -63,7 +64,7 @@ write(std::ostream &os, const int, const int) const ch == '\t' ? os << "\\t" : os << ch; os << '"'; -} // string::write +} // ----------------------------------------------------------------------------- @@ -89,7 +90,7 @@ write(std::ostream &os, const int indentLevel, const int width) const // ] detail::suffix(os, ']', indentNSpaces, indentLevel, first); -} // array::write +} // ----------------------------------------------------------------------------- @@ -117,7 +118,7 @@ write(std::ostream &os, const int indentLevel, const int width) const // } detail::suffix(os, '}', indentNSpaces, indentLevel, first); -} // object::write +} // ----------------------------------------------------------------------------- @@ -131,7 +132,7 @@ write(std::ostream &os, const int, const int) const // Write the literal string literally, with no processing or assumptions // at all. Know what you're doing, or you could produce invalid JSON. :-) os << str; -} // literal::write +} inline void value:: write(std::ostream &os, const int indentLevel, const int indentNSpaces) const @@ -143,4 +144,4 @@ write(std::ostream &os, const int indentLevel, const int indentNSpaces) const }, static_cast(*this) ); -} // value::write +}