From e95fe7fbd95fde3e937f46f4c47eb4c35df7546e Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Fri, 8 Sep 2017 12:29:21 -0700 Subject: [PATCH] ircd::json: Improve tuple/builder related. --- include/ircd/json.h | 2 +- include/ircd/json/builder.h | 268 +++++++++++++++++++++--------------- include/ircd/json/tuple.h | 121 ++++++++++------ include/ircd/json/value.h | 2 + ircd/json.cc | 267 +++++++++++++++++++++++++++++++++++ ircd/matrix.cc | 91 ++++++------ 6 files changed, 554 insertions(+), 197 deletions(-) diff --git a/include/ircd/json.h b/include/ircd/json.h index 7609d3b58..6114c5738 100644 --- a/include/ircd/json.h +++ b/include/ircd/json.h @@ -96,8 +96,8 @@ namespace ircd::json #include "json/member.h" #include "json/index.h" #include "json/property.h" -#include "json/tuple.h" #include "json/builder.h" +#include "json/tuple.h" namespace ircd { diff --git a/include/ircd/json/builder.h b/include/ircd/json/builder.h index 594cce362..a4ad8dfe5 100644 --- a/include/ircd/json/builder.h +++ b/include/ircd/json/builder.h @@ -22,153 +22,197 @@ #pragma once #define HAVE_IRCD_JSON_BUILDER_H -// ircd::json::builder is an interface to compose JSON dynamically and -// efficiently. The product of the builder is an iteration of the added members -// for use by stringifying and iovectoring. This gathers the members on a trip -// up the stack without rewriting a JSON string at each frame. +// ircd::json::builder is forward list to compose JSON dynamically and +// efficiently on the stack. The product of the builder is an iteration of the +// added members for use by stringifying and iovectoring. This gathers the +// members on a trip up the stack without rewriting a JSON string at each frame. // -struct ircd::json::builder +namespace ircd::json { + struct builder; + using member_closure = std::function; using member_closure_bool = std::function; + using builder_closure_bool = std::function; + + builder *head(builder *const &); + builder *next(builder *const &); + builder *prev(builder *const &); + builder *tail(builder *); + builder *find(builder *, const builder_closure_bool &); + + const builder *head(const builder *const &); + const builder *next(const builder *const &); + const builder *prev(const builder *const &); + const builder *tail(const builder *); + const builder *find(const builder *, const builder_closure_bool &); + + void for_each(const builder &, const member_closure &); + bool until(const builder &, const member_closure_bool &); + size_t count(const builder &, const member_closure_bool &); + size_t count(const builder &); +} + +struct ircd::json::builder +{ + struct add; + struct set; + struct push; + + IRCD_EXCEPTION(json::error, error); + IRCD_EXCEPTION(error, exists); - const builder *parent; member m; const members *ms; + builder *head; + builder *child; - void for_each(const member_closure &) const; - bool until(const member_closure_bool &) const; - size_t count(const member_closure_bool &) const; - size_t count() const; + bool test(const member_closure_bool &) const; + // recursive! const member *find(const string_view &key) const; const json::value &at(const string_view &key) const; + bool has(const string_view &key) const; - builder(const builder *const &parent, const members *const &); - builder(const builder *const &parent, member); + builder(member m = {}, + const members *const &ms = nullptr, + builder *const &head = nullptr, + builder *const &child = nullptr) + :m{std::move(m)} + ,ms{ms} + ,head{head} + ,child{child} + {} + + builder(const members &ms) + :builder{{}, &ms} + {} + + builder(member m) + :builder{std::move(m)} + {} friend string_view stringify(mutable_buffer &, const builder &); }; -inline ircd::string_view -ircd::json::stringify(mutable_buffer &head, - const builder &builder) +struct ircd::json::builder::push +:private ircd::json::builder { - const auto num{builder.count()}; - const member *m[num]; - - size_t i(0); - builder.for_each([&i, &m] - (const auto &member) + push(builder &head, const members &ms) + :builder{{}, &ms, &head} { - m[i++] = &member; - }); + tail(&head)->child = this; + } - return stringify(head, m, m + num); -} - -inline -ircd::json::builder::builder(const builder *const &parent, - member m) -:parent{parent} -,m{std::move(m)} -,ms{nullptr} -{ -} - -inline -ircd::json::builder::builder(const builder *const &parent, - const members *const &ms) -:parent{parent} -,m{} -,ms{ms} -{ -} - -inline const ircd::json::value & -ircd::json::builder::at(const string_view &key) -const -{ - const auto *const member(find(key)); - if(!member) - throw not_found("'%s'", key); - - return member->second; -} - -inline size_t -ircd::json::builder::count() -const -{ - return count([] - (const auto &) + push(builder &head, member m) + :builder{std::move(m), nullptr, &head} { - return true; - }); + tail(&head)->child = this; + } +}; + +struct ircd::json::builder::add +:private ircd::json::builder +{ + add(builder &head, const members &ms); + add(builder &head, member member); +}; + +struct ircd::json::builder::set +:private ircd::json::builder +{ + set(builder &head, const members &ms); + set(builder &head, member member); +}; + +inline ircd::json::builder * +ircd::json::find(builder *builder, + const builder_closure_bool &test) +{ + for(; builder; builder = next(builder)) + if(test(*builder)) + return builder; + + return nullptr; } -inline size_t -ircd::json::builder::count(const member_closure_bool &closure) -const +inline const ircd::json::builder * +ircd::json::find(const builder *builder, + const builder_closure_bool &test) { - size_t ret(0); - for_each([&closure, &ret] - (const auto &member) - { - ret += closure(member); - }); + for(; builder; builder = next(builder)) + if(test(*builder)) + return builder; + + return nullptr; +} + +inline ircd::json::builder * +ircd::json::tail(builder *ret) +{ + while(ret && next(ret)) + ret = next(ret); return ret; } -inline const ircd::json::member * -ircd::json::builder::find(const string_view &key) -const +inline const ircd::json::builder * +ircd::json::tail(const builder *ret) { - const member *ret; - const auto test - { - [&key, &ret](const auto &member) - { - if(key == string_view{member.first}) - { - ret = &member; - return false; - } - else return true; - } - }; + while(ret && next(ret)) + ret = next(ret); - return !until(test)? ret : nullptr; + return ret; } -inline void -ircd::json::builder::for_each(const member_closure &closure) -const +inline ircd::json::builder * +ircd::json::prev(builder *const &builder) { - if(ms) - std::for_each(begin(*ms), end(*ms), closure); - else - closure(m); + assert(builder); + auto *ret(builder->head); + for(; ret; ret = next(ret)) + if(next(ret) == builder) + return ret; - if(parent) - parent->for_each(closure); + return nullptr; } -inline bool -ircd::json::builder::until(const member_closure_bool &closure) -const +inline const ircd::json::builder * +ircd::json::prev(const builder *const &builder) { - if(ms) - { - if(!ircd::until(begin(*ms), end(*ms), closure)) - return false; - } - else if(!closure(m)) - return false; + assert(builder); + const auto *ret(builder->head); + for(; ret; ret = next(ret)) + if(next(ret) == builder) + return ret; - if(parent) - return parent->until(closure); - - return true; + return nullptr; +} + +inline ircd::json::builder * +ircd::json::next(builder *const &builder) +{ + assert(builder); + return builder->child; +} + +inline const ircd::json::builder * +ircd::json::next(const builder *const &builder) +{ + assert(builder); + return builder->child; +} + +inline ircd::json::builder * +ircd::json::head(builder *const &builder) +{ + assert(builder); + return builder->head; +} + +inline const ircd::json::builder * +ircd::json::head(const builder *const &builder) +{ + assert(builder); + return builder->head; } diff --git a/include/ircd/json/tuple.h b/include/ircd/json/tuple.h index 7f3ad2aa0..bfbbe00be 100644 --- a/include/ircd/json/tuple.h +++ b/include/ircd/json/tuple.h @@ -51,10 +51,15 @@ namespace json { // ircd::json::tuple template. Create your own struct inheriting this // class template with the members. // +struct tuple_base +{ + // EBO tag +}; template struct tuple :std::tuple +,tuple_base { using tuple_type = std::tuple; using super_type = tuple; @@ -62,10 +67,21 @@ struct tuple static constexpr size_t size(); tuple(const json::object &); + tuple(const json::builder &); 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; @@ -102,7 +118,7 @@ stdcast(tuple &o) } template -constexpr size_t +constexpr enable_if_tuple size() { return tuple_size::value; @@ -110,18 +126,18 @@ size() template -constexpr auto & +constexpr enable_if_tuple key() { return tuple_element::key; } template -auto & -key(const tuple &t) + class tuple> +enable_if_tuple +key(const tuple &t) { - return get(t).key; + return std::get(t).key; } template -auto & -get(const tuple &t) + class tuple> +enable_if_tuple &> +get(tuple &t) { return std::get(t); } template -auto & -get(tuple &t) + class tuple> +enable_if_tuple &> +get(const tuple &t) { return std::get(t); } template -auto & -val(const tuple &t) + class tuple> +enable_if_tuple &> +val(const tuple &t) { - using value_type = tuple_value_type, i>; - + using value_type = tuple_value_type; return static_cast(get(t)); } template -auto & -val(tuple &t) + class tuple> +enable_if_tuple &> +val(tuple &t) { - using value_type = tuple_value_type, i>; - + using value_type = tuple_value_type; return static_cast(get(t)); } template -auto & -val(const tuple &t) + class tuple> +enable_if_tuple(name)> &> +val(const tuple &t) { - return val>(name)>(t); + return val(name)>(t); } template -auto & -val(tuple &t) + class tuple> +enable_if_tuple(name)> &> +val(tuple &t) { - return val>(name)>(t); + return val(name)>(t); } template -const tuple_value_type, indexof>(name)> & -at(const tuple &t) + class tuple> +enable_if_tuple(name)> &> +at(const tuple &t) { constexpr size_t idx { - indexof>(name) + indexof(name) }; auto &ret @@ -233,7 +247,7 @@ at(const tuple &t) val(t) }; - using value_type = tuple_value_type, idx>; + using value_type = tuple_value_type; //TODO: does tcmalloc zero this or huh? if(ret == value_type{}) @@ -243,13 +257,13 @@ at(const tuple &t) } template -tuple_value_type, indexof>(name)> & -at(tuple &t) + class tuple> +enable_if_tuple(name)> &> +at(tuple &t) { constexpr size_t idx { - indexof>(name) + indexof(name) }; auto &ret @@ -257,7 +271,7 @@ at(tuple &t) val(t) }; - using value_type = tuple_value_type, idx>; + using value_type = tuple_value_type; //TODO: does tcmalloc zero this or huh? if(ret == value_type{}) @@ -565,6 +579,31 @@ tuple::tuple(const json::object &object) }); } +template +tuple::tuple(const json::builder &builder) +{ + //TODO: is tcmalloc zero-initializing all tuple elements, or is something else doing that? + for_each(builder, [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 = 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) { @@ -748,7 +787,7 @@ template std::ostream & operator<<(std::ostream &s, const tuple &t) { - s << string(t); + s << json::string(t); return s; } diff --git a/include/ircd/json/value.h b/include/ircd/json/value.h index 61ed995eb..208d19ca2 100644 --- a/include/ircd/json/value.h +++ b/include/ircd/json/value.h @@ -57,6 +57,8 @@ struct ircd::json::value bool empty() const; size_t serialized() const; operator string_view() const; + explicit operator double() const; + explicit operator int64_t() const; explicit operator std::string() const; template explicit value(const T &specialized); diff --git a/ircd/json.cc b/ircd/json.cc index 4ce95d560..ef7e4aa6a 100644 --- a/ircd/json.cc +++ b/ircd/json.cc @@ -443,6 +443,13 @@ ircd::json::stringify(mutable_buffer &buf, return stringify(buf, begin, end); } +ircd::string_view +ircd::json::stringify(mutable_buffer &buf, + const member &m) +{ + return stringify(buf, &m, &m + 1); +} + ircd::string_view ircd::json::stringify(mutable_buffer &buf, const member *const &begin, @@ -992,6 +999,46 @@ const throw type_error("value type[%d] is not a string", int(type)); } +ircd::json::value::operator int64_t() +const +{ + switch(type) + { + case NUMBER: + return likely(!floats)? integer : floating; + + case STRING: + return lex_cast(string_view(*this)); + + case ARRAY: + case OBJECT: + case LITERAL: + break; + } + + throw type_error("value type[%d] is not an int64_t", int(type)); +} + +ircd::json::value::operator double() +const +{ + switch(type) + { + case NUMBER: + return likely(floats)? floating : integer; + + case STRING: + return lex_cast(string_view(*this)); + + case ARRAY: + case OBJECT: + case LITERAL: + break; + } + + throw type_error("value type[%d] is not a float", int(type)); +} + bool ircd::json::value::empty() const @@ -1364,3 +1411,223 @@ ircd::json::type(const string_view &buf, return ret; } + +/////////////////////////////////////////////////////////////////////////////// +// +// builder.h +// + +ircd::string_view +ircd::json::stringify(mutable_buffer &head, + const builder &builder) +{ + const auto num{count(builder)}; + const member *m[num]; + + size_t i(0); + for_each(builder, [&i, &m] + (const auto &member) + { + m[i++] = &member; + }); + + return stringify(head, m, m + num); +} + +size_t +ircd::json::count(const builder &builder) +{ + return count(builder, [] + (const auto &) + { + return true; + }); +} + +size_t +ircd::json::count(const builder &builder, + const member_closure_bool &closure) +{ + size_t ret(0); + for_each(builder, [&closure, &ret] + (const auto &member) + { + ret += closure(member); + }); + + return ret; +} + +void +ircd::json::for_each(const builder &b, + const member_closure &closure) +{ + if(b.ms) + std::for_each(begin(*b.ms), end(*b.ms), closure); + else if(!b.m.first.empty()) + closure(b.m); + + if(b.child) + for_each(*b.child, closure); +} + +bool +ircd::json::until(const builder &b, + const member_closure_bool &closure) +{ + if(b.ms) + { + if(!ircd::until(begin(*b.ms), end(*b.ms), closure)) + return false; + } + else if(!b.m.first.empty() && !closure(b.m)) + return false; + + if(b.child) + return until(*b.child, closure); + + return true; +} + +bool +ircd::json::builder::has(const string_view &key) +const +{ + const auto *const member(find(key)); + return member != nullptr; +} + +const ircd::json::value & +ircd::json::builder::at(const string_view &key) +const +{ + const auto *const member(find(key)); + if(!member) + throw not_found("'%s'", key); + + return member->second; +} + +const ircd::json::member * +ircd::json::builder::find(const string_view &key) +const +{ + const member *ret; + const auto test + { + [&key, &ret](const auto &member) -> bool + { + if(key == string_view{member.first}) + { + ret = &member; + return false; + } + else return true; + } + }; + + return !until(*this, test)? ret : nullptr; +} + +bool +ircd::json::builder::test(const member_closure_bool &closure) +const +{ + if(ms) + return ircd::until(begin(*ms), end(*ms), closure); + + if(!m.first.empty()) + return closure(m); + + return true; +} + +ircd::json::builder::add::add(builder &head, const members &ms) +:builder{{}, &ms, &head} +{ + const auto existing(json::find(&head, [&ms](const builder &existing) + { + return existing.test([&ms](const member &existing) + { + return std::all_of(begin(ms), end(ms), [&existing] + (const auto &member) + { + return member.first == existing.first; + }); + }); + })); + + if(existing) + throw exists("failed to add member '%s': already exists", + string_view{existing->m.first}); //TODO BUG + + tail(&head)->child = this; +} + +ircd::json::builder::add::add(builder &head, member member) +:builder{std::move(member), nullptr, &head} +{ + const auto existing(json::find(&head, [&member](const builder &existing) + { + return existing.test([&member](const auto &existing) + { + return member.first == existing.first; + }); + })); + + if(existing) + throw exists("failed to add member '%s': already exists", + string_view{member.first}); + + tail(&head)->child = this; +} + +ircd::json::builder::set::set(builder &head, const members &ms) +:builder{{}, &ms, &head} +{ + const auto existing(json::find(&head, [&ms](const builder &existing) + { + return existing.test([&ms](const member &existing) + { + return std::all_of(begin(ms), end(ms), [&existing] + (const auto &member) + { + return member.first == existing.first; + }); + }); + })); + + if(existing) + { + const auto p(prev(existing)); + if(p) + { + p->child = this; + this->child = existing->child; + } + } + else tail(&head)->child = this; +} + +ircd::json::builder::set::set(builder &head, member member) +:builder{std::move(member), nullptr, &head} +{ + const auto existing(json::find(&head, [&member](const builder &existing) + { + return existing.test([&member](const auto &existing) + { + return member.first == existing.first; + }); + })); + + if(existing) + { + const auto p(prev(existing)); + if(p) + { + p->child = this; + this->child = existing->child; + } + } + else tail(&head)->child = this; +} diff --git a/ircd/matrix.cc b/ircd/matrix.cc index 4db661a8c..a45cae93e 100644 --- a/ircd/matrix.cc +++ b/ircd/matrix.cc @@ -248,14 +248,33 @@ namespace ircd::m::events ircd::database *ircd::m::events::events; void -ircd::m::events::insert(const event &a) +ircd::m::events::insert(json::builder &builder) { - event b(a); - insert(b); + const id::event::buf generated_event_id + { + builder.has("event_id")? id::event::buf{} : id::event::buf{id::generate, "cdc.z"} + }; + + const json::builder::add event_id + { + builder, { "event_id", generated_event_id } + }; + + const json::builder::set event_id2 + { + builder, { "event_id", generated_event_id } + }; + + const json::builder::add origin_server_ts + { + builder, { "origin_server_ts", time() } + }; + + insert(event{builder}); } void -ircd::m::events::insert(event &event) +ircd::m::events::insert(const event &event) { if(!json::val(event)) throw BAD_JSON("Required event field: '%s'", name::type); @@ -263,32 +282,11 @@ ircd::m::events::insert(event &event) if(!json::val(event)) throw BAD_JSON("Required event field: '%s'", name::sender); - bool has_event_id - { - !json::val(event).empty() - }; + if(!json::val(event)) + throw BAD_JSON("Required event field: '%s'", name::event_id); - const id::event::buf generated_event_id - { - has_event_id? id::event::buf{} : id::event::buf{id::generate, "cdc.z"} - }; - - if(!has_event_id) - json::val(event) = generated_event_id; - - const scope remove_our_event_id([&] - { - if(!has_event_id) - json::val(event) = {}; - }); - - bool has_origin_server_ts - { - json::val(event) != 0 - }; - - if(!has_origin_server_ts) - json::val(event) = time(); + if(!json::val(event)) + throw BAD_JSON("Required event field: '%s'", name::origin_server_ts); for(const auto &transition : events::transitions) if(!transition->valid(event)) @@ -410,20 +408,19 @@ ircd::m::events::write(const event &event) void ircd::m::room::join(const m::id::user &user_id, - const json::builder &content) + json::builder &content) { - const json::builder content_with_membership + json::builder::set membership_join { - &content, - { "membership", "join" } + content, { "membership", "join" } }; - membership(user_id, content_with_membership); + membership(user_id, content); } void ircd::m::room::membership(const m::id::user &user_id, - const json::builder &content) + json::builder &content) { if(is_member(user_id, content.at("membership"))) throw m::ALREADY_MEMBER @@ -431,15 +428,23 @@ ircd::m::room::membership(const m::id::user &user_id, "Already a member with this membership." }; - char cbuf[512]; - m::events::insert(m::event + char buffer[512]; + const auto printed_content { - { "room_id", room_id }, - { "type", "m.room.member" }, - { "state_key", user_id }, - { "sender", user_id }, - { "content", stringify(cbuf, content) } - }); + stringify(buffer, content) + }; + + json::builder event; + json::builder::set fields{event, + { + { "room_id", room_id }, + { "type", "m.room.member" }, + { "state_key", user_id }, + { "sender", user_id }, + { "content", printed_content } + }}; + + m::events::insert(event); } bool