From 1d0e2b0a6a521eedb55ed44eb5f3169d9fd46c0d Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Thu, 16 Mar 2017 13:36:18 -0700 Subject: [PATCH] fixup! ircd::json: Add stronger value typing with runtime visitor switching. --- ircd/json.cc | 541 ++++++++++++++++++++++++++------------------------- 1 file changed, 275 insertions(+), 266 deletions(-) diff --git a/ircd/json.cc b/ircd/json.cc index ba1ddd8f4..4cbbcb6a8 100644 --- a/ircd/json.cc +++ b/ircd/json.cc @@ -54,6 +54,9 @@ using qi::attr; using karma::lit; using karma::char_; +using karma::int_; +using karma::double_; +using karma::bool_; using karma::maxwidth; using karma::buffer; using karma::eps; @@ -254,19 +257,17 @@ struct output rule chars { *(~char_("\"")) ,"chars" }; rule string { quote << chars << quote ,"string" }; + rule number { double_ ,"number" }; + rule name { string ,"name" }; - rule value { karma::string ,"value" }; + rule value { rule{} ,"value" }; rule elems { (value % value_sep) ,"elements" }; rule array { array_begin << elems << array_end ,"array" }; - rule dmember { name << name_sep << value ,"member" }; - rule dmembers { (dmember % value_sep) ,"members" }; - rule document { object_begin << dmembers << object_end ,"document" }; - rule member { name << name_sep << value ,"member" }; - rule members { (member % value_sep) ,"members" }; - rule object { object_begin << members << object_end ,"object" }; + rule members { (member % value_sep) ,"members" }; + rule document { object_begin << members << object_end ,"document" }; output() :output::base_type{rule<>{}} @@ -300,7 +301,7 @@ struct ostreamer } const ostreamer; -doc serialize(const obj &, char *const &start, char *const &stop); +doc serialize(const obj &, char *&start, char *const &stop); size_t print(char *const &buf, const size_t &max, const obj &); size_t print(char *const &buf, const size_t &max, const doc &); @@ -322,10 +323,36 @@ ircd::json::printer::printer() a.resize(size_t(out - a.data())); }); + const auto quote_string([&] + { + a.insert(a.end(), '"'); + a.insert(a.begin(), '"'); + }); + if(likely(!a.empty())) switch(a.front()) { case '{': recurse_document(); break; - default: break; + case '[': c = false; break; + case '"': break; + case '0': break; + case '1': break; + case '2': break; + case '3': break; + case '4': break; + case '5': break; + case '6': break; + case '7': break; + case '8': break; + case '9': break; + case 't': + case 'f': + case 'n': + if(a == "true" || a == "false" || a == "null") + break; + + default: + quote_string(); + break; } }); @@ -339,15 +366,40 @@ ircd::json::ostreamer::ostreamer() const auto recurse_document([&] { char *out(const_cast(a.data())); - const auto count(print(out, a.size(), json::doc(a))); + const auto count(print(out, a.size() + 1, json::doc(a))); a.resize(count); }); + const auto quote_string([&] + { + a.insert(a.end(), '"'); + a.insert(a.begin(), '"'); + }); + if(likely(!a.empty())) switch(a.front()) { case '{': recurse_document(); break; case '[': c = false; break; - default: break; + case '"': break; + case '0': break; + case '1': break; + case '2': break; + case '3': break; + case '4': break; + case '5': break; + case '6': break; + case '7': break; + case '8': break; + case '9': break; + case 't': + case 'f': + case 'n': + if(a == "true" || a == "false" || a == "null") + break; + + default: + quote_string(); + break; } }); @@ -359,13 +411,18 @@ ircd::json::print(char *const &buf, const size_t &max, const obj &obj) { - const auto doc(serialize(obj, buf, buf + max)); - return doc.size(); + if(unlikely(!max)) + return 0; + + char *out(buf); + serialize(obj, out, out + (max - 1)); + *out = '\0'; + return out - buf; } ircd::json::doc ircd::json::serialize(const obj &obj, - char *const &start, + char *&out, char *const &stop) { static const auto throws([] @@ -373,31 +430,54 @@ ircd::json::serialize(const obj &obj, throw print_error("The JSON generator failed to serialize object"); }); - static const karma::rule> generate_kv + const auto print_string([&stop, &out](const val &val) { - printer.quote << +char_ << printer.quote << ':' << +char_ - }; - - char *out(start); - const auto generate_member([&out, &stop] - (const doc::member &member) - { - static const karma::rule> generate_member - { - printer.value_sep << generate_kv - }; - - karma::generate(out, maxwidth(stop - out)[generate_member], member); + karma::generate(out, maxwidth(stop - out)[printer.string] | eps[throws], val); }); - karma::generate(out, maxwidth(stop - out)[printer.object_begin] | eps[throws]); - if(obj.count()) + const auto print_object([&stop, &out](const val &val) { - const auto &front(*begin(obj)); - karma::generate(out, maxwidth(stop - out)[generate_kv], front); + if(val.serial) + { + karma::generate(out, maxwidth(stop - out)[printer.document] | eps[throws], val); + return; + } + + assert(val.object); + serialize(*val.object, out, stop); + }); + + const auto print_member([&](const obj::member &member) + { + const auto generate_name + { + maxwidth(stop - out)[printer.name << printer.name_sep] | eps[throws] + }; + + karma::generate(out, generate_name, member.first); + + switch(member.second.type) + { + case OBJECT: print_object(member.second); break; + case STRING: print_string(member.second); break; + default: throw type_error("Cannot stream unsupported member type"); + } + }); + + char *const start(out); + karma::generate(out, maxwidth(stop - out)[printer.object_begin] | eps[throws]); + + auto it(begin(obj)); + if(it != end(obj)) + { + print_member(*it); + for(++it; it != end(obj); ++it) + { + karma::generate(out, maxwidth(stop - out)[printer.value_sep] | eps[throws]); + print_member(*it); + } } - std::for_each(std::next(begin(obj), 1), end(obj), generate_member); karma::generate(out, maxwidth(stop - out)[printer.object_end] | eps[throws]); return string_view{start, out}; } @@ -405,66 +485,99 @@ ircd::json::serialize(const obj &obj, std::ostream & ircd::json::operator<<(std::ostream &s, const obj &obj) { + karma::ostream_iterator osi(s); + static const auto throws([] { throw print_error("The JSON generator failed to output object to stream"); }); - karma::ostream_iterator osi(s); - karma::generate(osi, ostreamer.object | eps[throws], obj); + const auto stream_string([&osi](const val &val) + { + karma::generate(osi, ostreamer.string, string_view{val}); + }); + + const auto stream_object([&osi, &s](const val &val) + { + if(val.serial) + { + karma::generate(osi, ostreamer.document, string_view{val}); + return; + } + + assert(val.object); + s << *val.object; + }); + + const auto stream_member([&](const obj::member &member) + { + karma::generate(osi, ostreamer.name << ostreamer.name_sep, string_view(member.first)); + + switch(member.second.type) + { + case OBJECT: stream_object(member.second); break; + case STRING: stream_string(member.second); break; + default: throw type_error("cannot stream unsupported member type"); + } + }); + + karma::generate(osi, ostreamer.object_begin); + + auto it(begin(obj)); + if(it != end(obj)) + { + stream_member(*it); + for(++it; it != end(obj); ++it) + { + karma::generate(osi, ostreamer.value_sep); + stream_member(*it); + } + } + + karma::generate(osi, ostreamer.object_end); return s; } -ircd::json::obj::obj() -:owns_state{false} +ircd::json::obj::obj(std::initializer_list builder) { + std::transform(std::begin(builder), std::end(builder), std::back_inserter(idx), [] + (auto&& m) + { + return std::move(const_cast(m)); + }); } ircd::json::obj::obj(const doc &doc) -:state{doc} -,idx{std::begin(doc), std::end(doc)} -,owns_state{false} { -} - -ircd::json::obj::obj(const obj &other) -:state{[&other] -{ - const size_t size(other.size()); - std::unique_ptr buf(new char[size + 1]); - const auto ret(serialize(other, buf.get(), buf.get() + size)); - buf.get()[size] = '\0'; - buf.release(); - return ret; -}()} -,idx{std::begin(state), std::end(state)} -,owns_state{true} -{ - //TODO: if idx throws state.data() will leak -} - -ircd::json::obj::~obj() -noexcept -{ - if(owns_state) - delete[] state.data(); - - for(const auto &member : idx) + std::transform(std::begin(doc), std::end(doc), std::back_inserter(idx), [] + (const doc::member &m) -> obj::member { - if(member.owns_first) delete[] member.first.data(); - if(member.owns_second) delete[] member.second.data(); - } + return { val { m.first }, val { m.second, type(m.second), true } }; + }); } bool -ircd::json::obj::serialized() -const +ircd::json::obj::erase(const string_view &name) { - return std::all_of(std::begin(idx), std::end(idx), [this] - (const auto &member) - { - return state.contains(member.first) && state.contains(member.second); - }); + const auto it(find(name)); + if(it == end()) + return false; + + erase(it); + return true; +} + +void +ircd::json::obj::erase(const const_iterator &it) +{ + idx.erase(it); +} + +ircd::json::obj::const_iterator +ircd::json::obj::erase(const const_iterator &start, + const const_iterator &stop) +{ + return { idx.erase(start, stop) }; } size_t @@ -472,208 +585,85 @@ ircd::json::obj::size() const { const size_t ret(1 + idx.empty()); - return std::accumulate(std::begin(idx), std::end(idx), ret, [] + return std::accumulate(std::begin(idx), std::end(idx), ret, [this] (auto ret, const auto &member) { - return ret += 1 + member.first.size() + 1 + 1 + member.second.size() + 1; + return ret += member.first.size() + 1 + 1 + member.second.size() + 1; }); } -ircd::json::obj::delta -ircd::json::obj::operator[](const char *const &name) +ircd::json::obj::operator std::string() +const { - const auto it(idx.emplace(idx.end(), string_view{name}, string_view{})); - auto &member(const_cast(*it)); -/* - if(pit.second) + std::string ret(size(), char()); + ret.resize(print(const_cast(ret.data()), ret.size() + 1, *this)); + return ret; +} + +std::ostream & +ircd::json::operator<<(std::ostream &s, const val &v) +{ + switch(v.type) { - const size_t size(strlen(name)); - std::unique_ptr rename(new char[size + 1]); - strlcpy(rename.get(), name, size + 1); - member.first = { rename.get(), size }; - member.owns_first = true; - rename.release(); + case OBJECT: + if(v.serial) + s << string_view(v); + else + s << *v.object; + break; + + case STRING: + s << string_view(v); + break; + + default: + throw type_error("cannot stream value"); } -*/ - return { *this, member, member.second }; + + return s; } -ircd::json::obj::delta -ircd::json::obj::at(const char *const &name) +inline +ircd::json::val::~val() +noexcept { - const auto it(std::find(std::begin(idx), std::end(idx), string_view{name})); - if(unlikely(it == idx.end())) - throw not_found("name \"%s\"", name); - - auto &member(const_cast(*it)); - return { *this, member, member.second }; -} - -ircd::json::obj::iterator -ircd::json::obj::begin() -{ - return { *this, std::begin(idx) }; -} - -ircd::json::obj::iterator -ircd::json::obj::end() -{ - return { *this, std::end(idx) }; -} - -ircd::json::obj::const_iterator -ircd::json::obj::cbegin() -{ - return { std::begin(idx) }; -} - -ircd::json::obj::const_iterator -ircd::json::obj::cend() -{ - return { std::end(idx) }; -} - -ircd::json::obj::const_iterator -ircd::json::obj::begin() -const -{ - return { std::begin(idx) }; -} - -ircd::json::obj::const_iterator -ircd::json::obj::end() -const -{ - return { std::end(idx) }; -} - -ircd::json::obj::iterator::iterator(struct obj &obj, - obj::index::iterator it) -:obj{&obj} -,it{it} -{ -} - -ircd::json::obj::iterator::value_type * -ircd::json::obj::iterator::operator->() -{ - auto &member(const_cast(*it)); - state = proxy + switch(type) { - { *obj, member, member.first }, - { *obj, member, member.second } + case OBJECT: if(alloc) delete object; break; + case STRING: if(alloc) delete[] string; break; + default: break; + } +} + +ircd::json::val::operator std::string() +const +{ + switch(type) + { + case OBJECT: if(!serial) return std::string(*object); + case STRING: return std::string{string_view(*this)}; + default: throw type_error("cannot stringify type"); + } +} + +ircd::json::val::operator string_view() +const +{ + return serial? string_view { string, len }: + throw type_error("Value not a string"); +} + +size_t +ircd::json::val::size() +const +{ + switch(type) + { + case OBJECT: return serial? len : object->size(); + case STRING: return 1 + len + 1; + case NUMBER: return lex_cast(integer).size(); + default: throw type_error("cannot size type"); }; - return &state; -} - -ircd::json::obj::iterator & -ircd::json::obj::iterator::operator++() -{ - ++it; - return *this; -} - -ircd::json::obj::delta::delta(struct obj &obj, - obj::member &member, - const string_view ¤t) -:string_view{current} -,obj{&obj} -,member{&member} -{ -} - -void -ircd::json::obj::delta::set(const json::obj &obj) -{ - const size_t size(obj.size()); - std::unique_ptr buf(new char[size + 1]); - const auto doc(serialize(obj, buf.get(), buf.get() + size)); - buf.get()[size] = '\0'; - commit(doc); - member->owns_second = true; - buf.release(); -} - -void -ircd::json::obj::delta::set(const string_view &value) -{ - commit(value); -} - -void -ircd::json::obj::delta::set(const char *const &string) -{ - const auto size(strlen(string)); - const auto max(size + 1); - std::unique_ptr restart(new char[max]); - strlcpy(restart.get(), string, max); - commit(string_view(restart.get(), restart.get() + size)); - member->owns_second = true; - restart.release(); -} - -void -ircd::json::obj::delta::set(const std::string &string) -{ - const auto max(string.size() + 1); - std::unique_ptr restart(new char[max]); - strlcpy(restart.get(), string.data(), max); - commit(string_view(restart.get(), restart.get() + string.size())); - member->owns_second = true; - restart.release(); -} - -void -ircd::json::obj::delta::set(const bool &boolean) -{ - static const char *const true_p("true"); - static const char *const false_p("false"); - - const auto ptr(boolean? true_p : false_p); - commit(string_view(ptr)); -} - -void -ircd::json::obj::delta::set(const int32_t &number) -{ - static const size_t max(16); - std::unique_ptr restart(new char[max]); - commit(lex_cast(number, restart.get(), max)); - member->owns_second = true; - restart.release(); -} - -void -ircd::json::obj::delta::set(const uint64_t &number) -{ - static const size_t max(32); - std::unique_ptr restart(new char[max]); - commit(lex_cast(number, restart.get(), max)); - member->owns_second = true; - restart.release(); -} - -void -ircd::json::obj::delta::set(const double &number) -{ - static const size_t max(64); - std::unique_ptr restart(new char[max]); - commit(lex_cast(number, restart.get(), max)); - member->owns_second = true; - restart.release(); -} - -void -ircd::json::obj::delta::commit(const string_view &buf) -{ - if(member->owns_second) - { - delete[] member->second.data(); - member->owns_second = false; - } - - static_cast(*this) = buf; - member->second = buf; } size_t @@ -686,11 +676,30 @@ ircd::json::print(char *const &buf, throw print_error("The JSON generator failed to print document"); }); + if(unlikely(!max)) + return 0; + char *out(buf); - karma::generate(out, maxwidth(max)[printer.document] | eps[throws], doc); + serialize(doc, out, out + (max - 1)); + *out = '\0'; return std::distance(buf, out); } +ircd::json::doc +ircd::json::serialize(const doc &doc, + char *&out, + char *const &stop) +{ + static const auto throws([] + { + throw print_error("The JSON generator failed to print document"); + }); + + char *const start(out); + karma::generate(out, maxwidth(stop - start)[printer.document] | eps[throws], doc); + return string_view{start, out}; +} + std::ostream & ircd::json::operator<<(std::ostream &s, const doc &doc) { @@ -714,7 +723,7 @@ ircd::json::operator<<(std::ostream &s, const doc::member &member) }); karma::ostream_iterator osi(s); - karma::generate(osi, ostreamer.dmember | eps[throws], member); + karma::generate(osi, ostreamer.member | eps[throws], member); return s; }