// Copyright (c) 2018-2020 configor - Nomango // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // is the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included is // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #pragma once #include "configor_iterator.hpp" #include // std::for_each, std::all_of #include // std::fabs #include // std::numeric_limits #include // std::allocator_traits #include // std::stringstream #include // std::enable_if, std::is_same, std::is_integral, std::is_floating_point #include // std::swap, std::forward, std::declval namespace configor { namespace detail { template class value_constructor; } // namespace detail template class basic_value final : public value_constant { friend detail::value_constructor; public: template using allocator_type = typename _Args::template allocator_type<_Ty>; using boolean_type = typename _Args::boolean_type; using integer_type = typename _Args::integer_type; using float_type = typename _Args::float_type; using char_type = typename _Args::char_type; using string_type = typename _Args::template string_type, allocator_type>; using array_type = typename _Args::template array_type>; using object_type = typename _Args::template object_type, allocator_type>>; template using binder_type = typename _Args::template binder_type<_Ty>; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = detail::iterator; using const_iterator = detail::iterator; using reverse_iterator = detail::reverse_iterator; using const_reverse_iterator = detail::reverse_iterator; union data_type { boolean_type boolean; integer_type integer; float_type floating; string_type* string; object_type* object; array_type* vector; }; basic_value(std::nullptr_t = nullptr) : type_{ value_constant::null } , data_{} { } basic_value(const value_constant::type t) : type_{ t } , data_{} { using accessor = detail::value_constructor; switch (t) { case value_constant::object: accessor::template construct(*this); break; case value_constant::array: accessor::template construct(*this); break; case value_constant::string: accessor::template construct(*this); break; case value_constant::integer: accessor::template construct(*this, integer_type(0)); break; case value_constant::floating: accessor::template construct(*this, float_type(0.0)); break; case value_constant::boolean: accessor::template construct(*this, boolean_type(false)); break; default: break; } } basic_value(const basic_value& other) : type_{ other.type() } , data_{} { using accessor = detail::value_constructor; switch (other.type()) { case value_constant::object: accessor::template construct(*this, *other.data().object); break; case value_constant::array: accessor::template construct(*this, *other.data().vector); break; case value_constant::string: accessor::template construct(*this, *other.data().string); break; case value_constant::integer: accessor::template construct(*this, other.data().integer); break; case value_constant::floating: accessor::template construct(*this, other.data().floating); break; case value_constant::boolean: accessor::template construct(*this, other.data().boolean); break; default: break; } } basic_value(basic_value&& other) noexcept : type_{ other.type() } , data_{ other.data() } { other.type(value_constant::null); other.data().object = nullptr; } template ::type, typename std::enable_if::value && detail::has_to_value::value, int>::type = 0> basic_value(_CompatibleTy&& value) : type_{ value_constant::null } , data_{} { binder_type<_UTy>::to_value(*this, std::forward<_CompatibleTy>(value)); } ~basic_value() { using accessor = detail::value_constructor; accessor::destroy(*this); } const data_type& data() const { return data_; } data_type& data() { return data_; } inline value_constant::type type() const { return type_; } private: inline void type(value_constant::type t) { type_ = t; } public: inline bool is_object() const { return type() == value_constant::object; } inline bool is_array() const { return type() == value_constant::array; } inline bool is_string() const { return type() == value_constant::string; } inline bool is_bool() const { return type() == value_constant::boolean; } inline bool is_integer() const { return type() == value_constant::integer; } inline bool is_floating() const { return type() == value_constant::floating; } inline bool is_number() const { return is_integer() || is_floating(); } inline bool is_null() const { return type() == value_constant::null; } public: inline iterator begin() { iterator iter(this); iter.set_begin(); return iter; } inline const_iterator begin() const { return cbegin(); } inline const_iterator cbegin() const { const_iterator iter(this); iter.set_begin(); return iter; } inline iterator end() { iterator iter(this); iter.set_end(); return iter; } inline const_iterator end() const { return cend(); } inline const_iterator cend() const { const_iterator iter(this); iter.set_end(); return iter; } inline reverse_iterator rbegin() { return reverse_iterator(end()); } inline const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } inline const_reverse_iterator crbegin() const { return rbegin(); } inline reverse_iterator rend() { return reverse_iterator(begin()); } inline const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } inline const_reverse_iterator crend() const { return rend(); } public: inline size_type size() const { switch (type()) { case value_constant::null: return 0; case value_constant::array: return data().vector->size(); case value_constant::object: return data().object->size(); default: return 1; } } bool empty() const { if (is_null()) return true; if (is_object()) return data().object->empty(); if (is_array()) return data().vector->empty(); return false; } iterator find(const typename object_type::key_type& key) { if (is_object()) { iterator iter(this); iter.object_it_ = data().object->find(key); return iter; } return end(); } const_iterator find(const typename object_type::key_type& key) const { if (is_object()) { const_iterator iter(this); iter.object_it_ = data().object->find(key); return iter; } return cend(); } inline size_type count(const typename object_type::key_type& key) const { return is_object() ? data().object->count(key) : 0; } size_type erase(const typename object_type::key_type& key) { if (!is_object()) { throw configor_invalid_key("cannot use erase() with non-object value"); } return data().object->erase(key); } void erase(const size_type index) { if (!is_array()) { throw configor_invalid_key("cannot use erase() with non-array value"); } data().vector->erase(data().vector->begin() + static_cast(index)); } template ::value || std::is_same<_IterTy, const_iterator>::value>::type> _IterTy erase(_IterTy pos) { _IterTy result = end(); switch (type()) { case value_constant::object: { result.object_it_ = data().object->erase(pos.object_it_); break; } case value_constant::array: { result.array_it_ = data().vector->erase(pos.array_it_); break; } default: throw configor_invalid_iterator("cannot use erase() with non-object & non-array value"); } return result; } template ::value || std::is_same<_IterTy, const_iterator>::value>::type> _IterTy erase(_IterTy first, _IterTy last) { _IterTy result = end(); switch (type()) { case value_constant::object: { result.object_it_ = data().object->erase(first.object_it_, last.object_it_); break; } case value_constant::array: { result.array_it_ = data().vector->erase(first.array_it_, last.array_it_); break; } default: throw configor_invalid_iterator("cannot use erase() with non-object & non-array value"); } return result; } void push_back(const basic_value& v) { emplace_back(v); } void push_back(basic_value&& v) { emplace_back(std::move(v)); } template void emplace_back(_Ty&&... v) { if (!is_null() && !is_array()) { throw configor_type_error("use push_back() or emplace_back() with non-array value"); } if (is_null()) { *this = value_constant::array; } data().vector->emplace_back(std::forward<_Ty>(v)...); } void clear() { switch (type()) { case value_constant::integer: data().integer = 0; break; case value_constant::floating: data().floating = static_cast(0.0); break; case value_constant::boolean: data().boolean = false; break; case value_constant::string: data().string->clear(); break; case value_constant::array: data().vector->clear(); break; case value_constant::object: data().object->clear(); break; default: break; } } private: // get pointer value inline boolean_type* do_get_ptr(boolean_type*) noexcept { return is_bool() ? &data().boolean : nullptr; } inline const boolean_type* do_get_ptr(const boolean_type*) const noexcept { return is_bool() ? &data().boolean : nullptr; } inline integer_type* do_get_ptr(integer_type*) noexcept { return is_integer() ? &data().integer : nullptr; } inline const integer_type* do_get_ptr(const integer_type*) const noexcept { return is_integer() ? &data().integer : nullptr; } inline float_type* do_get_ptr(float_type*) noexcept { return is_floating() ? &data().floating : nullptr; } inline const float_type* do_get_ptr(const float_type*) const noexcept { return is_floating() ? &data().floating : nullptr; } inline string_type* do_get_ptr(string_type*) noexcept { return is_string() ? data().string : nullptr; } inline const string_type* do_get_ptr(const string_type*) const noexcept { return is_string() ? data().string : nullptr; } inline array_type* do_get_ptr(array_type*) noexcept { return is_array() ? data().vector : nullptr; } inline const array_type* do_get_ptr(const array_type*) const noexcept { return is_array() ? data().vector : nullptr; } inline object_type* do_get_ptr(object_type*) noexcept { return is_object() ? data().object : nullptr; } inline const object_type* do_get_ptr(const object_type*) const noexcept { return is_object() ? data().object : nullptr; } public: // get_ptr template ::value>::type> auto get_ptr() const -> decltype(std::declval().do_get_ptr(std::declval<_Ty>())) { return do_get_ptr(static_cast<_Ty>(nullptr)); } template ::value>::type> auto get_ptr() -> decltype(std::declval().do_get_ptr(std::declval<_Ty>())) { return do_get_ptr(static_cast<_Ty>(nullptr)); } private: // get reference value template ::type> static auto do_get_ref(_ValTy& c) -> decltype(c.template get_ptr<_PtrTy>(), std::declval<_RefTy>()) { auto* ptr = c.template get_ptr<_PtrTy>(); if (ptr != nullptr) { return *ptr; } throw configor_type_error(std::string("incompatible reference type for get, actual type is ") + to_string(c.type())); } // get value template ::value>::type> _Ty do_get(detail::priority<3>) const { return *this; } template ::value>::type> auto do_get(detail::priority<2>) const noexcept -> decltype(std::declval().do_get_ptr(std::declval<_Ty>())) { return do_get_ptr(static_cast<_Ty>(nullptr)); } template ::value>::type> _Ty do_get(detail::priority<1>) const { return binder_type<_Ty>::from_value(*this); } template ::value && detail::has_from_value::value>::type> _Ty do_get(detail::priority<0>) const { _Ty value{}; binder_type<_Ty>::from_value(*this, value); return value; } // get value by reference template ::value>::type> _Ty& do_get(_Ty& value, detail::priority<3>) const { return (value = *this); } template ::value>::type> auto do_get(_Ty& value, detail::priority<2>) const noexcept -> decltype(std::declval().do_get_ptr(std::declval<_Ty>())) { return (value = do_get_ptr(static_cast<_Ty>(nullptr))); } template ::value>::type> _Ty& do_get(_Ty& value, detail::priority<1>) const { binder_type<_Ty>::from_value(*this, value); return value; } template ::value>::type> _Ty& do_get(_Ty& value, detail::priority<0>) const { value = binder_type<_Ty>::from_value(*this); return value; } public: // get template ::type, typename = typename std::enable_if::value>::type> auto get() const -> decltype(std::declval().template do_get<_UTy>(detail::priority<3>{})) { return do_get<_UTy>(detail::priority<3>{}); } template ::value>::type> auto get() -> decltype(std::declval().template get_ptr<_Ty>()) { return get_ptr<_Ty>(); } template ::value, int>::type = 0> auto get() -> decltype(basic_value::template do_get_ref<_Ty>(std::declval())) { return do_get_ref<_Ty>(*this); } template ::value && std::is_const::type>::value, int>::type = 0> auto get() const -> decltype(basic_value::template do_get_ref<_Ty>(std::declval())) { return do_get_ref<_Ty>(*this); } template auto get(_Ty& value) const -> decltype(std::declval().template do_get<_Ty>(std::declval<_Ty&>(), detail::priority<3>{}), bool{}) { try { do_get<_Ty>(value, detail::priority<3>{}); return true; } catch (...) { } return false; } template ::value && !std::is_pointer<_Ty>::value && !std::is_reference<_Ty>::value>::type> inline operator _Ty() const { return get<_Ty>(); } public: // swap function inline void swap(basic_value& rhs) { std::swap(type_, rhs.type_); std::swap(data_, rhs.data_); } // operator= functions basic_value& operator=(const basic_value& rhs) { if (this != &rhs) { basic_value tmp{ rhs }; this->swap(tmp); } return *this; } basic_value& operator=(basic_value&& rhs) { if (this != &rhs) { basic_value old{ std::move(*this) }; this->swap(rhs); } return *this; } // operator[] functions basic_value& operator[](const size_type index) { if (is_null()) { *this = value_constant::array; } if (!is_array()) { throw configor_invalid_key("operator[] called on a non-array object"); } if (index >= data().vector->size()) { data().vector->insert(data().vector->end(), index - data().vector->size() + 1, basic_value()); } return (*data().vector)[index]; } inline basic_value& operator[](const size_type index) const { return at(index); } basic_value& operator[](const typename object_type::key_type& key) { if (is_null()) { *this = value_constant::object; } if (!is_object()) { throw configor_invalid_key("operator[] called on a non-object type"); } return (*data().object)[key]; } inline basic_value& operator[](const typename object_type::key_type& key) const { return at(key); } basic_value& at(const size_type index) const { if (!is_array()) { throw configor_invalid_key("operator[] called on a non-array type"); } if (index >= data().vector->size()) { throw std::out_of_range("operator[] index out of range"); } return (*data().vector)[index]; } template basic_value& operator[](_CharTy* key) { if (is_null()) { *this = value_constant::object; } if (!is_object()) { throw configor_invalid_key("operator[] called on a non-object object"); } return (*data().object)[key]; } template inline basic_value& operator[](_CharTy* key) const { return at(key); } basic_value& at(const typename object_type::key_type& key) const { if (!is_object()) { throw configor_invalid_key("operator[] called on a non-object object"); } auto iter = data().object->find(key); if (iter == data().object->end()) { throw std::out_of_range("operator[] key out of range"); } return iter->second; } template basic_value& at(_CharTy* key) const { if (!is_object()) { throw configor_invalid_key("operator[] called on a non-object object"); } auto iter = data().object->find(key); if (iter == data().object->end()) { throw std::out_of_range("operator[] key out of range"); } return iter->second; } public: // eq functions friend bool operator==(const basic_value& lhs, const basic_value& rhs) { if (lhs.type() == rhs.type()) { switch (lhs.type()) { case value_constant::array: return (*lhs.data().vector == *rhs.data().vector); case value_constant::object: return (*lhs.data().object == *rhs.data().object); case value_constant::null: return true; case value_constant::string: return *lhs.data().string == *rhs.data().string; case value_constant::boolean: return lhs.data().boolean == rhs.data().boolean; case value_constant::integer: return lhs.data().integer == rhs.data().integer; case value_constant::floating: return lhs.data().floating == rhs.data().floating; default: return false; } } else if (lhs.type() == value_constant::integer && rhs.type() == value_constant::floating) { return static_cast(lhs.data().integer) == rhs.data().floating; } else if (lhs.type() == value_constant::floating && rhs.type() == value_constant::integer) { return lhs.data().floating == static_cast(rhs.data().integer); } return false; } template ::value>::type> friend inline bool operator==(const basic_value& lhs, _ScalarTy rhs) { return lhs == basic_value(rhs); } template ::value>::type> friend inline bool operator==(_ScalarTy lhs, const basic_value& rhs) { return basic_value(lhs) == rhs; } // ne functions friend inline bool operator!=(const basic_value& lhs, const basic_value& rhs) { return !(lhs == rhs); } template ::value>::type> friend inline bool operator!=(const basic_value& lhs, _ScalarTy rhs) { return lhs != basic_value(rhs); } template ::value>::type> friend inline bool operator!=(_ScalarTy lhs, const basic_value& rhs) { return basic_value(lhs) != rhs; } // lt functions friend bool operator<(const basic_value& lhs, const basic_value& rhs) { const auto lhs_type = lhs.type(); const auto rhs_type = rhs.type(); if (lhs_type == rhs_type) { switch (lhs_type) { case value_constant::array: return (*lhs.data().vector) < (*rhs.data().vector); case value_constant::object: return (*lhs.data().object) < (*rhs.data().object); case value_constant::null: return false; case value_constant::string: return (*lhs.data().string) < (*rhs.data().string); case value_constant::boolean: return (lhs.data().boolean < rhs.data().boolean); case value_constant::integer: return (lhs.data().integer < rhs.data().integer); case value_constant::floating: return (lhs.data().floating < rhs.data().floating); default: return false; } } else if (lhs_type == value_constant::integer && rhs_type == value_constant::floating) { return (static_cast(lhs.data().integer) < rhs.data().floating); } else if (lhs_type == value_constant::floating && rhs_type == value_constant::integer) { return (lhs.data().floating < static_cast(rhs.data().integer)); } return false; } template ::value>::type> friend inline bool operator<(const basic_value& lhs, _ScalarTy rhs) { return lhs < basic_value(rhs); } template ::value>::type> friend inline bool operator<(_ScalarTy lhs, const basic_value& rhs) { return basic_value(lhs) < rhs; } // lte functions friend inline bool operator<=(const basic_value& lhs, const basic_value& rhs) { return !(rhs < lhs); } template ::value>::type> friend inline bool operator<=(const basic_value& lhs, _ScalarTy rhs) { return lhs <= basic_value(rhs); } template ::value>::type> friend inline bool operator<=(_ScalarTy lhs, const basic_value& rhs) { return basic_value(lhs) <= rhs; } // gt functions friend inline bool operator>(const basic_value& lhs, const basic_value& rhs) { return rhs < lhs; } template ::value>::type> friend inline bool operator>(const basic_value& lhs, _ScalarTy rhs) { return lhs > basic_value(rhs); } template ::value>::type> friend inline bool operator>(_ScalarTy lhs, const basic_value& rhs) { return basic_value(lhs) > rhs; } // gte functions friend inline bool operator>=(const basic_value& lhs, const basic_value& rhs) { return !(lhs < rhs); } template ::value>::type> friend inline bool operator>=(const basic_value& lhs, _ScalarTy rhs) { return lhs >= basic_value(rhs); } template ::value>::type> friend inline bool operator>=(_ScalarTy lhs, const basic_value& rhs) { return basic_value(lhs) >= rhs; } private: value_constant::type type_; data_type data_; }; namespace detail { template class value_constructor { public: using value_type = _ValTy; template using allocator_type = typename value_type::template allocator_type<_Ty>; template using type_constant = std::integral_constant; template static void reset(value_type& v, _Args&&... args) { destroy(v); v.type(_Ty); construct<_Ty>(v, std::forward<_Args>(args)...); } template static inline void construct(value_type& v, _Args&&... args) { construct(type_constant<_Ty>{}, v, std::forward<_Args>(args)...); } static void destroy(value_type& v) { switch (v.type()) { case value_constant::object: destroy(v.data().object); break; case value_constant::array: destroy(v.data().vector); break; case value_constant::string: destroy(v.data().string); break; default: break; } } private: template static inline void construct(type_constant, value_type& v, _Args&&... args) { v.data().object = create_data(std::forward<_Args>(args)...); } template static inline void construct(type_constant, value_type& v, _Args&&... args) { v.data().vector = create_data(std::forward<_Args>(args)...); } template static inline void construct(type_constant, value_type& v, _Args&&... args) { v.data().string = create_data(std::forward<_Args>(args)...); } template static inline void construct(type_constant, value_type& v, _Args&&... args) { v.data().integer = typename value_type::integer_type{ std::forward<_Args>(args)... }; } template static inline void construct(type_constant, value_type& v, _Args&&... args) { v.data().floating = typename value_type::float_type{ std::forward<_Args>(args)... }; } template static inline void construct(type_constant, value_type& v, _Args&&... args) { v.data().boolean = typename value_type::boolean_type{ std::forward<_Args>(args)... }; } template static inline void construct(type_constant, value_type& v, _Args&&... args) { ((void)v); } template static _Ty* create_data(_Args&&... args) { using allocator_traits = std::allocator_traits>; static allocator_type<_Ty> allocator; auto ptr = allocator_traits::allocate(allocator, 1); allocator_traits::construct(allocator, ptr, std::forward<_Args>(args)...); return ptr; } template static void destroy(_Ty* ptr) { using allocator_traits = std::allocator_traits>; static allocator_type<_Ty> allocator; allocator_traits::destroy(allocator, ptr); allocator_traits::deallocate(allocator, ptr, 1); } }; template class value_maker { public: using value_type = _ValTy; struct object { using pair_type = std::pair; object(std::initializer_list list) : list_(list) { } operator value_type() const { value_type v{ value_constant::object }; std::for_each(list_.begin(), list_.end(), [&](const pair_type& pair) { v.data().object->emplace(pair.first, pair.second); }); return v; } private: std::initializer_list list_; }; struct array { array(std::initializer_list list) : list_(list) { } operator value_type() const { value_type v{ value_constant::array }; v.data().vector->reserve(list_.size()); v.data().vector->assign(list_.begin(), list_.end()); return v; } private: std::initializer_list list_; }; }; } // namespace detail template inline _ValTy make_object(std::initializer_list> list) { return typename detail::value_maker<_ValTy>::object{ list }; } template inline _ValTy make_array(std::initializer_list<_ValTy> list) { return typename detail::value_maker<_ValTy>::array{ list }; } } // namespace configor