/* * Copyright (C) 2017 Charybdis Development Team * Copyright (C) 2017 Jason Volk * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice is present in all copies. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #pragma once #define HAVE_IRCD_JSON_TUPLE_H namespace ircd { namespace json { /// All tuple templates inherit from this non-template type for tagging. struct tuple_base { // EBO tag }; /// A compile-time construct to describe a JSON object's members and types. /// /// Member access by name is O(1) because of recursive constexpr function /// inlining when translating a name to the index number which is then used /// as the template argument to std::get() for the value. /// /// Here we represent a JSON object with a named tuple, allowing the programmer /// to create a structure specifying all of the potentially valid members of the /// object. Thus at runtime, the tuple only carries around its values like a /// `struct`. Unlike a `struct`, the tuple is abstractly iterable and we have /// implemented logic operating on all JSON tuples regardless of their makeup /// without any effort from a developer when creating a new tuple. /// /// The member structure for the tuple is called `property` because json::member /// is already used to pair together runtime oriented json::values. /// /// Create and use a tuple to efficiently extract members from a json::object. /// The tuple will populate its own members during a single-pass iteration of /// the JSON input. If the JSON does not contain a member specified in the /// tuple, the value will be default initialized. If the JSON contains a member /// not specified in the tuple, it is ignored. If you need to know all of the /// members specified in the JSON dynamically, use a json::index or iterate /// manually. /// template struct tuple :std::tuple ,tuple_base { using tuple_type = std::tuple; using super_type = tuple; static constexpr size_t size(); tuple(const json::object &); tuple(const json::iov &); tuple(const std::initializer_list &); tuple() = default; }; template constexpr bool is_tuple() { return std::is_base_of::value; } template using enable_if_tuple = typename std::enable_if(), R>::type; template using tuple_type = typename tuple::tuple_type; template using tuple_size = std::tuple_size>; template using tuple_element = typename std::tuple_element>::type; template using tuple_value_type = typename tuple_element::value_type; template constexpr size_t tuple::size() { return std::tuple_size(); } template auto & stdcast(const tuple &o) { return static_cast(o); } template auto & stdcast(tuple &o) { return static_cast(o); } template constexpr enable_if_tuple size() { return tuple_size::value; } template constexpr enable_if_tuple key() { return tuple_element::key; } template enable_if_tuple key(const tuple &t) { return std::get(t).key; } template constexpr typename std::enable_if(), size_t>::type indexof(const char *const &name) { return size(); } template constexpr typename std::enable_if(), size_t>::type indexof(const char *const &name) { const auto equal { _constexpr_equal(key(), name) }; return equal? i : indexof(name); } template constexpr typename std::enable_if(), size_t>::type indexof(const string_view &name) { return size(); } template constexpr typename std::enable_if(), size_t>::type indexof(const string_view &name) { const auto equal { name == key() }; return equal? i : indexof(name); } template enable_if_tuple &> get(tuple &t) { return std::get(t); } template enable_if_tuple &> get(const tuple &t) { return std::get(t); } template enable_if_tuple &> val(const tuple &t) { using value_type = tuple_value_type; return static_cast(get(t)); } template enable_if_tuple &> val(tuple &t) { using value_type = tuple_value_type; return static_cast(get(t)); } template enable_if_tuple(name)> &> val(const tuple &t) { return val(name)>(t); } template enable_if_tuple(name)> &> val(tuple &t) { return val(name)>(t); } template enable_if_tuple(name)> &> at(const tuple &t) { constexpr size_t idx { indexof(name) }; auto &ret { val(t) }; using value_type = tuple_value_type; //TODO: does tcmalloc zero this or huh? if(ret == value_type{}) throw not_found("%s", name); return ret; } template enable_if_tuple(name)> &> at(tuple &t) { constexpr size_t idx { indexof(name) }; auto &ret { val(t) }; using value_type = tuple_value_type; //TODO: does tcmalloc zero this or huh? if(ret == value_type{}) throw not_found("%s", name); return ret; } template tuple_value_type, indexof>(name)> get(const tuple &t, const tuple_value_type, indexof>(name)> &def = {}) { constexpr size_t idx { indexof>(name) }; const auto &ret { val(t) }; using value_type = tuple_value_type, idx>; //TODO: does tcmalloc zero this or huh? return ret != value_type{}? ret : def; } template tuple_value_type, indexof>(name)> & get(tuple &t, tuple_value_type, indexof>(name)> &def) { constexpr size_t idx { indexof>(name) }; auto &ret { val(t) }; using value_type = tuple_value_type, idx>; //TODO: does tcmalloc zero this or huh? return ret != value_type{}? ret : def; } template typename std::enable_if(), void>::type for_each(const tuple &t, function&& f) {} template typename std::enable_if(), void>::type for_each(tuple &t, function&& f) {} template typename std::enable_if(), void>::type for_each(const tuple &t, function&& f) { f(key(t), val(t)); for_each(t, std::forward(f)); } template typename std::enable_if(), void>::type for_each(tuple &t, function&& f) { f(key(t), val(t)); for_each(t, std::forward(f)); } template typename std::enable_if<(i < 0), void>::type rfor_each(const tuple &t, function&& f) {} template typename std::enable_if<(i < 0), void>::type rfor_each(tuple &t, function&& f) {} template() - 1> typename std::enable_if(), void>::type rfor_each(const tuple &t, function&& f) { f(key(t), val(t)); rfor_each(t, std::forward(f)); } template() - 1> typename std::enable_if(), void>::type rfor_each(tuple &t, function&& f) { f(key(t), val(t)); rfor_each(t, std::forward(f)); } template typename std::enable_if(), bool>::type until(const tuple &t, function&& f) { return true; } template typename std::enable_if(), bool>::type until(tuple &t, function&& f) { return true; } template typename std::enable_if(), bool>::type until(const tuple &t, function&& f) { return f(key(t), val(t))? until(t, std::forward(f)): false; } template typename std::enable_if(), bool>::type until(tuple &t, function&& f) { return f(key(t), val(t))? until(t, std::forward(f)): false; } template typename std::enable_if<(i < 0), bool>::type runtil(const tuple &t, function&& f) { return true; } template typename std::enable_if<(i < 0), bool>::type runtil(tuple &t, function&& f) { return true; } template() - 1> typename std::enable_if(), bool>::type runtil(const tuple &t, function&& f) { return f(key(t), val(t))? runtil(t, std::forward(f)): false; } template() - 1> typename std::enable_if(), bool>::type runtil(tuple &t, function&& f) { return f(key(t), val(t))? runtil(t, std::forward(f)): false; } template typename std::enable_if(), void>::type at(tuple &t, const string_view &name, function&& f) { } template typename std::enable_if(), void>::type at(tuple &t, const string_view &name, function&& f) { if(indexof(name) == i) f(val(t)); else at(t, name, std::forward(f)); } template typename std::enable_if(), void>::type at(const tuple &t, const string_view &name, function&& f) { } template typename std::enable_if(), void>::type at(const tuple &t, const string_view &name, function&& f) { if(indexof(name) == i) f(val(t)); else at(t, name, std::forward(f)); } template tuple & set(tuple &t, const string_view &key, const V &val) { at(t, key, [&key, &val] (auto &target) { using target_type = decltype(target); using cast_type = typename std::remove_reference::type; target = byte_view(val); }); return t; } template tuple::tuple(const json::object &object) { //TODO: is tcmalloc zero-initializing all tuple elements, or is something else doing that? std::for_each(std::begin(object), std::end(object), [this] (const auto &member) { at(*this, member.first, [&member] (auto &target) { using target_type = decltype(target); using cast_type = typename std::remove_reference::type; try { target = lex_cast(member.second); } catch(const bad_lex_cast &e) { throw parse_error("member '%s' must convert to '%s'", member.first, typeid(target_type).name()); } }); }); } template tuple::tuple(const json::iov &iov) { //TODO: is tcmalloc zero-initializing all tuple elements, or is something else doing that? std::for_each(std::begin(iov), std::end(iov), [this] (const auto &member) { switch(type(member.second)) { case type::OBJECT: case type::ARRAY: if(unlikely(!member.second.serial)) throw print_error("iov member '%s' must be JSON to be used by the tuple", string_view{member.first}); default: break; } at(*this, member.first, [&member] (auto &target) { using target_type = decltype(target); using cast_type = typename std::remove_reference::type; try { target = static_cast(member.second); } catch(const bad_lex_cast &e) { throw parse_error("member '%s' must convert to '%s'", member.first, typeid(target_type).name()); } }); }); } template tuple::tuple(const std::initializer_list &members) { std::for_each(std::begin(members), std::end(members), [this] (const auto &member) { switch(type(member.second)) { case type::STRING: case type::LITERAL: set(*this, member.first, string_view{member.second}); break; case type::NUMBER: if(member.second.floats) set(*this, member.first, member.second.floating); else set(*this, member.first, member.second.integer); break; case type::ARRAY: case type::OBJECT: if(!member.second.serial) throw parse_error("Unserialized value not supported yet"); set(*this, member.first, string_view{member.second}); break; } }); } template constexpr void _key_transform(it_a it, const it_b end, closure&& lambda) { for(size_t i(0); i < tuple::size() && it != end; ++i) { *it = lambda(key()); ++it; } } template constexpr void _key_transform(it_a it, const it_b end) { for(size_t i(0); i < tuple::size() && it != end; ++i) { *it = key(); ++it; } } template void _key_transform(const tuple &tuple, it_a it, const it_b end) { for_each(tuple, [&it, &end] (const auto &key, const auto &val) { if(it != end) { *it = key; ++it; } }); } template void _member_transform(const tuple &tuple, it_a it, const it_b end, closure&& lambda) { until(tuple, [&it, &end, &lambda] (const auto &key, const auto &val) { if(it == end) return false; *it = lambda(key, val); ++it; return true; }); } template void _member_transform(const tuple &tuple, it_a it, const it_b end) { until(tuple, [&it, &end] (const auto &key, const auto &val) { if(it == end) return false; *it = member { key, val }; ++it; return true; }); } template constexpr bool serialized_lex_cast() { using type = typename std::remove_reference::type; return std::is_arithmetic::value; } template typename std::enable_if(), size_t>::type serialized(T&& t) { return lex_cast(t).size(); } template size_t serialized(const tuple &t) { constexpr const size_t member_count { tuple::size() }; // Number of commas for this object is one less than the member count, or 0 const size_t commas { member_count? member_count - 1 : 0 }; // 2 for the {} and the comma count const size_t overhead { 2 + commas }; std::array sizes; _member_transform(t, begin(sizes), end(sizes), [] (const string_view &key, auto&& val) { // " " : return 1 + key.size() + 1 + 1 + serialized(val); }); return std::accumulate(begin(sizes), end(sizes), overhead); } template string_view stringify(mutable_buffer &buf, const tuple &tuple) { std::array members; _member_transform(tuple, begin(members), end(members)); return stringify(buf, begin(members), end(members)); } template std::ostream & operator<<(std::ostream &s, const tuple &t) { s << json::string(t); return s; } } // namespace json } // namespace ircd