diff --git a/include/ircd/json.h b/include/ircd/json.h index 14bdbca75..494d8ae5a 100644 --- a/include/ircd/json.h +++ b/include/ircd/json.h @@ -68,7 +68,7 @@ namespace ircd::json struct array; struct object; struct value; - struct index; + struct member; struct iov; enum type @@ -81,6 +81,7 @@ namespace ircd::json }; enum type type(const string_view &); enum type type(const string_view &, std::nothrow_t); + string_view reflect(const enum type &); /// Higher order type beyond a string to cleanly delimit multiple keys. using path = std::initializer_list; @@ -94,13 +95,14 @@ namespace ircd::json template std::string string(T&&... t); size_t serialized(const string_view &); + + using members = std::initializer_list; } #include "json/array.h" #include "json/object.h" #include "json/value.h" #include "json/member.h" -#include "json/index.h" #include "json/property.h" #include "json/iov.h" #include "json/tuple.h" @@ -168,7 +170,13 @@ ircd::json::string(T&&... t) std::string ret(size, char{}); const auto buf{const_cast(ret.data())}; const auto max{ret.size() + 1}; - print(buf, max, std::forward(t)...); + + const auto printed + { + print(buf, max, std::forward(t)...) + }; + + assert(printed == ret.size()); return ret; } diff --git a/include/ircd/json/array.h b/include/ircd/json/array.h index 6a80e2545..7bad6b5b5 100644 --- a/include/ircd/json/array.h +++ b/include/ircd/json/array.h @@ -22,15 +22,22 @@ #pragma once #define HAVE_IRCD_JSON_ARRAY_H +namespace ircd::json +{ + struct array; + + string_view stringify(mutable_buffer &buf, const string_view *const &begin, const string_view *const &end); + string_view stringify(mutable_buffer &buf, const std::string *const &begin, const std::string *const &end); +} + /// Lightweight interface to a JSON array string. /// -/// This is the rank1 analog to ircd::json::object. It accepts queries with -/// numerical indexing. The same parsing approach is used in ircd::json::object -/// and that is important to note here: iterating this array by incrementing -/// your own numerical index and making calls into this object is NOT efficient. -/// Simply put, do not do something like -/// `for(int x=0; x static string_view stringify(mutable_buffer &, const it &b, const it &e); friend string_view stringify(mutable_buffer &, const array &); - friend string_view stringify(mutable_buffer &, const std::vector &); friend std::ostream &operator<<(std::ostream &, const array &); }; @@ -69,7 +76,9 @@ struct ircd::json::array::const_iterator using value_type = const string_view; using pointer = value_type *; using reference = value_type &; - using difference_type = size_t; + using iterator = const_iterator; + using size_type = size_t; + using difference_type = ptrdiff_t; using iterator_category = std::forward_iterator_tag; protected: diff --git a/include/ircd/json/index.h b/include/ircd/json/index.h deleted file mode 100644 index 63a820119..000000000 --- a/include/ircd/json/index.h +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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_INDEX_H - -struct ircd::json::index -{ - struct const_iterator; - - using key_type = value; - using mapped_type = value; - using value_type = const member; - using size_type = size_t; - using difference_type = size_t; - using key_compare = std::less; - using index_type = std::vector; - - index_type idx; - - const_iterator begin() const; - const_iterator end() const; - const_iterator cbegin(); - const_iterator cend(); - - bool empty() const; - size_t count() const; - size_t serialized() const; - - const_iterator find(const string_view &name) const; - bool has(const string_view &name) const; - - const value &operator[](const string_view &name) const; - const value &at(const string_view &name) const; - - const_iterator erase(const const_iterator &s, const const_iterator &e); - void erase(const const_iterator &s); - bool erase(const string_view &name); - - operator std::string() const; - - IRCD_OVERLOAD(recursive) - - index(std::initializer_list); - index(const object &d); - index(recursive_t, const object &d); - index() = default; - index(index &&) = default; - index(const index &) = delete; - - friend index &operator+=(index &, const object &); // integration - friend index operator+(const object &, const object &); // integral - - friend size_t serialized(const index &); - friend string_view stringify(mutable_buffer &, const index &); - friend std::ostream &operator<<(std::ostream &, const index &); -}; - -struct ircd::json::index::const_iterator -{ - using value_type = const member; - using pointer = value_type *; - using reference = value_type &; - using difference_type = size_t; - using iterator_category = std::bidirectional_iterator_tag; - - protected: - friend class index; - - index::index_type::const_iterator it; - - operator const auto &() const { return it; } - - const_iterator(const decltype(it) &it) - :it{it} - {} - - public: - value_type *operator->() const { return it.operator->(); } - value_type &operator*() const { return it.operator*(); } - - const_iterator &operator++() { ++it; return *this; } - const_iterator &operator--() { --it; return *this; } - - friend bool operator==(const const_iterator &a, const const_iterator &b); - friend bool operator!=(const const_iterator &a, const const_iterator &b); - friend bool operator<(const const_iterator &a, const const_iterator &b); -}; - -inline const ircd::json::value & -ircd::json::index::operator[](const string_view &name) -const -{ - return at(name); -} - -inline bool -ircd::json::index::has(const string_view &name) -const -{ - return find(name) != end(); -} - -inline ircd::json::index::const_iterator -ircd::json::index::find(const string_view &name) -const -{ - return std::find(std::begin(idx), std::end(idx), name); -} - -inline size_t -ircd::json::index::count() -const -{ - return idx.size(); -} - -inline bool -ircd::json::index::empty() -const -{ - return idx.empty(); -} - -inline ircd::json::index::const_iterator -ircd::json::index::cbegin() -{ - return { std::begin(idx) }; -} - -inline ircd::json::index::const_iterator -ircd::json::index::cend() -{ - return { std::end(idx) }; -} - -inline ircd::json::index::const_iterator -ircd::json::index::begin() -const -{ - return { std::begin(idx) }; -} - -inline ircd::json::index::const_iterator -ircd::json::index::end() -const -{ - return { std::end(idx) }; -} - -inline bool -ircd::json::operator<(const index::const_iterator &a, const index::const_iterator &b) -{ - return a.it < b.it; -} - -inline bool -ircd::json::operator!=(const index::const_iterator &a, const index::const_iterator &b) -{ - return a.it != b.it; -} - -inline bool -ircd::json::operator==(const index::const_iterator &a, const index::const_iterator &b) -{ - return a.it == b.it; -} diff --git a/include/ircd/json/iov.h b/include/ircd/json/iov.h index ade2b5150..1da75bf50 100644 --- a/include/ircd/json/iov.h +++ b/include/ircd/json/iov.h @@ -55,16 +55,11 @@ struct ircd::json::iov struct set; struct set_if; - private: - std::forward_list allocated; - public: bool has(const string_view &key) const; const value &at(const string_view &key) const; iov() = default; - iov(member); - iov(members); friend string_view stringify(mutable_buffer &, const iov &); friend std::ostream &operator<<(std::ostream &, const iov &); @@ -78,6 +73,8 @@ struct ircd::json::iov::push push(iov &iov, args&&... a) :node{iov, std::forward(a)...} {} + + push() = default; }; struct ircd::json::iov::add @@ -88,7 +85,7 @@ struct ircd::json::iov::add }; struct ircd::json::iov::add_if -:ircd::json::iov::add +:protected ircd::json::iov::node { add_if(iov &, const bool &, member); add_if() = default; @@ -102,44 +99,8 @@ struct ircd::json::iov::set }; struct ircd::json::iov::set_if -:ircd::json::iov::set +:protected ircd::json::iov::node { set_if(iov &, const bool &, member); set_if() = default; }; - -inline -ircd::json::iov::iov(json::member m) -{ - allocated.emplace_front(*this, std::move(m)); -} - -inline -ircd::json::iov::iov(members m) -{ - for(auto&& member : m) - allocated.emplace_front(*this, std::move(member)); -} - -inline size_t -ircd::json::serialized(const iov &iov) -{ - const size_t ret - { - 1U + !iov.empty() - }; - - return std::accumulate(std::begin(iov), std::end(iov), ret, [] - (auto ret, const auto &member) - { - return ret += serialized(member); - }); -} - -inline std::ostream & -ircd::json::operator<<(std::ostream &s, const iov &iov) -{ - s << string(iov); - return s; -} - diff --git a/include/ircd/json/member.h b/include/ircd/json/member.h index 07ee9947d..4cbdff14c 100644 --- a/include/ircd/json/member.h +++ b/include/ircd/json/member.h @@ -31,7 +31,10 @@ namespace ircd::json string_view stringify(mutable_buffer &, const member *const &begin, const member *const &end); } -/// A pair of json::value representing an object member. +/// A pair of json::value representing state for a member of an object. +/// +/// This is slightly heavier than object::member as that only deals with +/// a pair of strings while the value here holds more diverse native state. /// /// The key value (member.first) should always be a STRING type. We don't use /// string_view directly in member.first because json::value can take ownership @@ -42,7 +45,6 @@ struct ircd::json::member :std::pair { using std::pair::pair; - template member(const K &k, std::initializer_list v); template member(const K &k, V&& v); explicit member(const string_view &k); explicit member(const object::member &m); @@ -79,15 +81,6 @@ ircd::json::member::member(const K &k, } {} -template -ircd::json::member::member(const K &k, - std::initializer_list v) -:std::pair -{ - value { k }, value { std::make_unique(std::move(v)) } -} -{} - inline ircd::json::member::member(const object::member &m) :std::pair diff --git a/include/ircd/json/object.h b/include/ircd/json/object.h index 78381ffbc..77e651b76 100644 --- a/include/ircd/json/object.h +++ b/include/ircd/json/object.h @@ -31,7 +31,9 @@ namespace ircd::json /// /// This makes queries into a string of JSON. This is a read-only device. /// It is merely functionality built on top of a string_view which is just a -/// pair of const char* pointers to the borders of the JSON object. +/// pair of `const char*` pointers to the borders of the JSON object. The first +/// character should be '{' and the last character should be '}' but this is +/// not checked on construction. /// /// This class computes over strings of JSON by parsing it on-the-fly /// via forward iteration. The const_iterator is fundamental. All other member @@ -50,12 +52,10 @@ namespace ircd::json /// type if they want a truly pure value string. Our zero-copy string_view utils /// make this to a simple ballet of pointers. /// -/// Other devices for dealing with strings of JSON are available: if an index -/// should be populated (ircd::json::index), or if a certain set of keys -/// should be found and extracted with a single pass (ircd::json::extract). -/// -/// Some serialization/write functions are actually provided here, these -/// are to *rewrite* JSON into our desired output form. +/// Some serialization/write functions are actually provided here. They will +/// always *rewrite* JSON through our generator correcting any imperfections +/// that may have been allowed by the parsing grammar (if such allowances are +/// ever made). /// /// Recursive traversal cannot be achieved via a single key string value; so /// any string_view argument for a key will not be recursive. In other words, @@ -123,12 +123,12 @@ struct ircd::json::object::member :std::pair{first, second} {} - friend bool operator==(const member &, const member &); - friend bool operator!=(const member &, const member &); - friend bool operator<=(const member &, const member &); - friend bool operator>=(const member &, const member &); - friend bool operator<(const member &, const member &); - friend bool operator>(const member &, const member &); + friend bool operator==(const object::member &, const object::member &); + friend bool operator!=(const object::member &, const object::member &); + friend bool operator<=(const object::member &, const object::member &); + friend bool operator>=(const object::member &, const object::member &); + friend bool operator<(const object::member &, const object::member &); + friend bool operator>(const object::member &, const object::member &); // writes a single member onto stream friend string_view stringify(mutable_buffer &, const object::member &); @@ -137,10 +137,14 @@ struct ircd::json::object::member struct ircd::json::object::const_iterator { + using key_type = string_view; + using mapped_type = string_view; using value_type = const member; using pointer = value_type *; using reference = value_type &; + using size_type = size_t; using difference_type = size_t; + using key_compare = std::less; using iterator_category = std::forward_iterator_tag; protected: diff --git a/include/ircd/json/tuple.h b/include/ircd/json/tuple.h index 4a5e508b5..8fa301278 100644 --- a/include/ircd/json/tuple.h +++ b/include/ircd/json/tuple.h @@ -33,17 +33,19 @@ struct tuple_base /// 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 creating a new tuple. +/// 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. This system -/// only decays into runtime members and values when compile-time logic cannot -/// be achieved. +/// 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 @@ -585,7 +587,6 @@ tuple::tuple(const json::iov &iov) { switch(type(member.second)) { - case type::STRING: case type::OBJECT: case type::ARRAY: if(unlikely(!member.second.serial)) @@ -729,7 +730,7 @@ _member_transform(const tuple &tuple, if(it == end) return false; - *it = { key, val }; + *it = member { key, val }; ++it; return true; }); diff --git a/include/ircd/json/value.h b/include/ircd/json/value.h index 62d647be3..e47b5860a 100644 --- a/include/ircd/json/value.h +++ b/include/ircd/json/value.h @@ -22,21 +22,47 @@ #pragma once #define HAVE_IRCD_JSON_VALUE_H +namespace ircd::json +{ + struct value; + + using values = std::initializer_list; + + size_t serialized(const value *const &begin, const value *const &end); + size_t serialized(const values &); + + string_view stringify(mutable_buffer &, const value *const &begin, const value *const &end); +} + /// A primitive of the ircd::json system representing a value at runtime. /// -/// This holds state for values apropos a JSON object. -/// -/// Value's data can either be in the form of a JSON string or it can be -/// native machine state. The serial flag indicates the former. -/// -/// Value is capable of allocation and ownership of its internal data if -/// necessary with move semantics, but copying may be not implemented in -/// exceptional cases. +/// This holds state for values apropos a JSON object or array. Value's +/// data can either be in the form of a JSON string or it can be some native +/// machine state. The serial flag indicates the former. /// /// Value can can hold any of the JSON types in either of these states. /// This is accomplished with runtime switching and branching but this is -/// still lightweight. The structure is just the size of two pointers, the -/// same as a string_view. +/// still lightweight and without a vtable pointer. The structure is just +/// the size of two pointers like a string_view; we commandeer bits of the +/// second word to hold type, flags, and length information. Thus we can hold +/// large vectors of values at 16 byte alignment and not 24 byte. +/// +/// Value is capable of allocation and ownership of its internal data and copy +/// semantics. This is primarily to support recursion and various developer +/// conveniences like nested initializer_list's etc. It is better to +/// std::move() a value than copy it, but the full copy semantic is supported; +/// however, if serial=false then a copy will stringify the data into JSON and +/// the destination will be serial=true,alloc=true; thus copying of complex +/// native values never occurs. +/// +/// Take careful note of a quirk with `operator string_view()`: when the +/// value is a STRING type string_view()'ing the value will never show +/// the string with surrounding quotes in view. This is because the value +/// accepts both quoted and unquoted strings as input from the developer, then +/// always serializes correctly; unquoted strings are more natural to work +/// with. This does not apply to other types like OBJECT and array as +/// string_view()'ing those when in a serial state will show surrounding '{' +/// etc. /// struct ircd::json::value { @@ -45,41 +71,56 @@ struct ircd::json::value int64_t integer; double floating; const char *string; - const struct index *object; - const struct array *array; + const struct value *array; + const struct member *object; }; uint64_t len : 57; ///< length indicator enum type type : 3; ///< json::type indicator - uint64_t serial : 1; ///< only string* is used. type indicates JSON + uint64_t serial : 1; ///< only *string is used. type indicates JSON uint64_t alloc : 1; ///< indicates the pointer for type is owned uint64_t floats : 1; ///< for NUMBER type, integer or floating - public: - bool null() const; - bool empty() const; - bool undefined() const; + static const string_view literal_null; + static const string_view literal_true; + static const string_view literal_false; + static const string_view empty_string; + static const string_view empty_number; + static const string_view empty_object; + static const string_view empty_array; - operator string_view() const; + using create_string_closure = std::function; + void create_string(const size_t &len, const create_string_closure &); + + public: + bool null() const; ///< literal null or assets are really null + bool empty() const; ///< null() or assets are empty + bool undefined() const; + bool operator!() const; ///< null() or undefined() or empty() or asset Falsy + + operator string_view() const; ///< NOTE unquote()'s the string value explicit operator double() const; explicit operator int64_t() const; - explicit operator std::string() const; + explicit operator std::string() const; ///< NOTE full stringify() of value template explicit value(const T &specialized); + value(const string_view &sv, const enum type &); template value(const char (&)[N]); value(const char *const &s); - value(const string_view &sv, const enum type &); value(const string_view &sv); - value(const index *const &); // alloc = false - value(std::unique_ptr); // alloc = true + value(const struct member *const &, const size_t &len); + value(std::unique_ptr, const size_t &len); // alloc = true + value(const struct value *const &, const size_t &len); + value(std::unique_ptr, const size_t &len); // alloc = true + value(const members &); // alloc = true + value(const nullptr_t &); value(); value(value &&) noexcept; value(const value &); value &operator=(value &&) noexcept; - value &operator=(const value &) = delete; + value &operator=(const value &); ~value() noexcept; - friend enum type type(const value &a); friend bool operator==(const value &a, const value &b); friend bool operator!=(const value &a, const value &b); friend bool operator<=(const value &a, const value &b); @@ -87,6 +128,7 @@ struct ircd::json::value friend bool operator<(const value &a, const value &b); friend bool operator>(const value &a, const value &b); + friend enum type type(const value &a); friend size_t serialized(const value &); friend string_view stringify(mutable_buffer &, const value &); friend std::ostream &operator<<(std::ostream &, const value &); @@ -107,10 +149,10 @@ static_assert(sizeof(ircd::json::value) == 16, ""); inline ircd::json::value::value() -:string{""} +:string{nullptr} ,len{0} ,type{STRING} -,serial{true} +,serial{false} ,alloc{false} ,floats{false} {} @@ -121,15 +163,93 @@ ircd::json::value::value(const string_view &sv, :string{sv.data()} ,len{sv.size()} ,type{type} -,serial{true} +,serial{type == STRING? surrounds(sv, '"') : true} ,alloc{false} ,floats{false} {} inline -ircd::json::value::value(const index *const &object) +ircd::json::value::value(const char *const &s) +:string{s} +,len{strlen(s)} +,type{json::type(s, std::nothrow)} +,serial{type == STRING? surrounds(s, '"') : true} +,alloc{false} +,floats{false} +{} + +template +ircd::json::value::value(const char (&str)[N]) +:string{str} +,len{strnlen(str, N)} +,type{json::type(str, std::nothrow)} +,serial{type == STRING? surrounds(str, '"') : true} +,alloc{false} +,floats{false} +{} + +template<> inline +ircd::json::value::value(const std::string &str) +:value{string_view{str}} +{} + +inline +ircd::json::value::value(const string_view &sv) +:value{sv, json::type(sv, std::nothrow)} +{} + +template +ircd::json::value::value(const T &t) +:value{static_cast(t)} +{ + static_assert(std::is_base_of() || + std::is_convertible(), ""); +} + +inline +ircd::json::value::value(const nullptr_t &) +:value +{ + literal_null, type::LITERAL +}{} + +template<> inline +ircd::json::value::value(const bool &boolean) +:value +{ + boolean? literal_true : literal_false, type::LITERAL +}{} + +inline +ircd::json::value::value(const struct value *const &array, + const size_t &len) +:array{array} +,len{len} +,type{ARRAY} +,serial{false} +,alloc{false} +,floats{false} +{ +} + +inline +ircd::json::value::value(std::unique_ptr array, + const size_t &len) +:array{array.get()} +,len{len} +,type{ARRAY} +,serial{false} +,alloc{true} +,floats{false} +{ + array.release(); +} + +inline +ircd::json::value::value(const struct member *const &object, + const size_t &len) :object{object} -,len{0} +,len{len} ,type{OBJECT} ,serial{false} ,alloc{false} @@ -137,9 +257,10 @@ ircd::json::value::value(const index *const &object) {} inline -ircd::json::value::value(std::unique_ptr object) +ircd::json::value::value(std::unique_ptr object, + const size_t &len) :object{object.get()} -,len{0} +,len{len} ,type{OBJECT} ,serial{false} ,alloc{true} @@ -183,50 +304,6 @@ ircd::json::value::value(const int16_t &integer) :value{int64_t(integer)} {} -template<> inline -ircd::json::value::value(const bool &boolean) -:value -{ - boolean? "true" : "false", - type::LITERAL -}{} - -template<> inline -ircd::json::value::value(const std::string &str) -:value{string_view{str}} -{} - -template -ircd::json::value::value(const T &t) -:value{static_cast(t)} -{ -} - -template -ircd::json::value::value(const char (&str)[N]) -:string{str} -,len{strnlen(str, N)} -,type{json::type(str, std::nothrow)} -,serial{true} -,alloc{false} -,floats{false} -{} - -inline -ircd::json::value::value(const char *const &s) -:string{s} -,len{strlen(s)} -,type{json::type(s, std::nothrow)} -,serial{true} -,alloc{false} -,floats{false} -{} - -inline -ircd::json::value::value(const string_view &sv) -:value{sv, json::type(sv, std::nothrow)} -{} - inline ircd::json::value::value(value &&other) noexcept @@ -240,33 +317,6 @@ noexcept other.alloc = false; } -inline -ircd::json::value::value(const value &other) -:integer{other.integer} -,len{other.len} -,type{other.type} -,serial{other.serial} -,alloc{other.alloc} -,floats{other.floats} -{ - switch(type) - { - case NUMBER: - break; - - case LITERAL: - case STRING: - assert(serial); - assert(!alloc); - break; - - case OBJECT: - case ARRAY: - assert(serial); - break; - } -} - inline ircd::json::value & ircd::json::value::operator=(value &&other) noexcept diff --git a/include/ircd/m/error.h b/include/ircd/m/error.h index bb48d2ce1..63f486ab5 100644 --- a/include/ircd/m/error.h +++ b/include/ircd/m/error.h @@ -36,7 +36,7 @@ struct ircd::m::error template error(const http::code &, const string_view &errcode, const char *const &fmt, args&&...); template error(const string_view &errcode, const char *const &fmt, args&&...); error(const http::code &, const json::object &object = {}); - error(const http::code &, const json::index &idx); + error(const http::code &, const json::members &); error(const http::code &, const json::iov &); error(const http::code &); error(std::string = {}); @@ -83,8 +83,14 @@ ircd::m::error::error(const http::code &c) inline ircd::m::error::error(const http::code &c, - const json::index &index) -:http::error{c, std::string{index}} + const json::members &members) +:http::error{c, json::string(members)} +{} + +inline +ircd::m::error::error(const http::code &c, + const json::iov &iov) +:http::error{c, json::string(iov)} {} inline @@ -111,15 +117,15 @@ ircd::m::error::error(const http::code &status, { status, [&]() -> std::string { - char estr[256]; const auto estr_len + char estr[512]; const auto estr_len { fmt::snprintf{estr, sizeof(estr), fmt, std::forward(a)...} }; - return json::index + return json::string(json::members { { "errcode", errcode }, { "error", string_view(estr, estr_len) } - }; + }); }() }{} diff --git a/include/ircd/m/request.h b/include/ircd/m/request.h index 9621e3c21..77788b337 100644 --- a/include/ircd/m/request.h +++ b/include/ircd/m/request.h @@ -29,17 +29,18 @@ namespace ircd { namespace m { struct request -:json::index { string_view method; string_view path; string_view query; string_view access_token; + std::string _content; + json::object content; request(const string_view &method, const string_view &path, const string_view &query = {}, - std::initializer_list body = {}); + json::members body = {}); request(const string_view &method, const string_view &path, @@ -54,11 +55,12 @@ inline ircd::m::request::request(const string_view &method, const string_view &path, const string_view &query, - std::initializer_list body) -:json::index{std::move(body)} -,method{method} + json::members body) +:method{method} ,path{path} ,query{query} +,_content{json::string(body)} +,content{_content} { } @@ -67,9 +69,9 @@ ircd::m::request::request(const string_view &method, const string_view &path, const string_view &query, const json::object &content) -:json::index{content} -,method{method} +:method{method} ,path{path} ,query{query} +,content{content} { } diff --git a/ircd/db.cc b/ircd/db.cc index f97722a09..0e7794ea1 100644 --- a/ircd/db.cc +++ b/ircd/db.cc @@ -2416,6 +2416,22 @@ ircd::db::seek(column::const_iterator &it, template bool ircd::db::seek(column::const_iterator &, const pos &); template bool ircd::db::seek(column::const_iterator &, const string_view &); +/////////////////////////////////////////////////////////////////////////////// +// +// merge.h +// + +std::string +ircd::db::merge_operator(const string_view &key, + const std::pair &delta) +{ + //ircd::json::index index{delta.first}; + //index += delta.second; + //return index; + assert(0); + return {}; +} + /////////////////////////////////////////////////////////////////////////////// // // writebatch diff --git a/ircd/json.cc b/ircd/json.cc index 08003decc..d27013c57 100644 --- a/ircd/json.cc +++ b/ircd/json.cc @@ -22,6 +22,7 @@ */ #include +#include BOOST_FUSION_ADAPT_STRUCT ( @@ -30,40 +31,55 @@ BOOST_FUSION_ADAPT_STRUCT ( decltype(ircd::json::object::member::second), second ) ) -namespace ircd { -namespace json { +BOOST_FUSION_ADAPT_STRUCT +( + ircd::json::member, + ( decltype(ircd::json::member::first), first ) + ( decltype(ircd::json::member::second), second ) +) -namespace spirit = boost::spirit; -namespace ascii = spirit::ascii; -namespace karma = spirit::karma; -namespace qi = spirit::qi; +namespace ircd::json +{ + namespace spirit = boost::spirit; + namespace ascii = spirit::ascii; + namespace karma = spirit::karma; + namespace qi = spirit::qi; -using spirit::unused_type; + using spirit::unused_type; -using qi::lit; -using qi::char_; -using qi::long_; -using qi::double_; -using qi::raw; -using qi::omit; -using qi::matches; -using qi::hold; -using qi::eoi; -using qi::eps; -using qi::attr; + using qi::lit; + using qi::char_; + using qi::long_; + using qi::double_; + using qi::raw; + using qi::omit; + using qi::matches; + using qi::hold; + using qi::eoi; + using qi::eps; + using qi::attr; -using karma::lit; -using karma::char_; -using karma::long_; -using karma::double_; -using karma::bool_; -using karma::maxwidth; -using karma::buffer; -using karma::eps; -using karma::attr_cast; + using karma::lit; + using karma::char_; + using karma::long_; + using karma::double_; + using karma::bool_; + using karma::maxwidth; + using karma::buffer; + using karma::eps; + using karma::attr_cast; + + template struct input; + template struct output; + + // Instantiations of the grammars + struct parser extern const parser; + struct printer extern const printer; + struct ostreamer extern const ostreamer; +} template -struct input +struct ircd::json::input :qi::grammar { template using rule = qi::rule; @@ -90,9 +106,9 @@ struct input rule<> value_sep { lit(',') ,"value sep" }; // literal - rule lit_true { lit("true") ,"literal true" }; rule lit_false { lit("false") ,"literal false" }; - rule lit_null { lit("null") ,"literal null" }; + rule lit_true { lit("true") ,"literal true" }; + rule lit_null { lit("null") ,"null" }; rule<> quote { lit('"') ,"quote" }; rule chars { raw[*(char_ - quote)] ,"characters" }; @@ -103,30 +119,30 @@ struct input rule literal { lit_true | lit_false | lit_null ,"literal" }; rule number { raw[double_] ,"number" }; - rule array + rule member { - array_begin >> -(omit[ws >> value >> ws] % value_sep) >> ws >> array_end - ,"array" + name >> name_sep >> value + ,"member" }; rule object { - object_begin >> -(omit[ws >> member >> ws] % value_sep) >> ws >> object_end + raw[object_begin >> -(member % value_sep) >> object_end] ,"object" }; + rule array + { + raw[array_begin >> -(value % value_sep) >> array_end] + ,"array" + }; + rule value { - lit_false | lit_true | lit_null | object | array | number | string + raw[lit_false | lit_null | lit_true | object | array | number | string] ,"value" }; - rule member - { - name >> ws >> name_sep >> ws >> value - ,"member" - }; - rule type { (omit[object_begin] >> attr(json::OBJECT)) | @@ -140,13 +156,14 @@ struct input input() :input::base_type{rule<>{}} { - array %= array_begin >> -(omit[ws >> value >> ws] % value_sep) >> ws >> array_end; - object %= object_begin >> -(omit[ws >> member >> ws] % value_sep) >> ws >> object_end; + member %= name >> name_sep >> value; + array %= raw[array_begin >> -(value % value_sep) >> array_end]; + object %= raw[object_begin >> -(member % value_sep) >> object_end]; } }; template -struct output +struct ircd::json::output :karma::grammar { template using rule = karma::rule; @@ -167,8 +184,8 @@ struct output // structural rule<> object_begin { lit('{') ,"object begin" }; rule<> object_end { lit('}') ,"object end" }; - rule<> array_begin { lit('[') ,"array begin" }; - rule<> array_end { lit(']') ,"array end" }; + rule<> array_begin { lit('[') ,"array begin" }; + rule<> array_end { lit(']') ,"array end" }; rule<> name_sep { lit(':') ,"name separator" }; rule<> value_sep { lit(',') ,"value separator" }; rule<> quote { lit('"') ,"quote" }; @@ -182,63 +199,20 @@ struct output rule string { quote << chars << quote ,"string" }; rule number { double_ ,"number" }; rule name { quote << +(~char_('"')) << quote ,"name" }; - rule value { rule{} /* subclass implemented */ ,"value" }; - - rule elem - { - value - ,"element" - }; - - rule elems - { - -(value % value_sep) - ,"elements" - }; - - rule array - { - array_begin << elems << array_end - ,"array" - }; - - rule member - { - name << name_sep << value - ,"member" - }; - - rule members - { - -(member % value_sep) - ,"members" - }; - - rule object - { - object_begin << members << object_end - ,"object" - }; output() :output::base_type{rule<>{}} {} }; -} // namespace json -} // namespace ircd - -namespace ircd { -namespace json { - -struct parser +struct ircd::json::parser :input { - using input::input; + using input::input; } -const parser; +const ircd::json::parser; -struct printer +struct ircd::json::printer :output { template(a)...); } - - printer(); } -const printer; +const ircd::json::printer; -struct ostreamer +struct ircd::json::ostreamer :output> { - ostreamer(); -} -const ostreamer; - -} // namespace json -} // namespace ircd - -namespace ircd { -namespace json { -namespace { - -void -failed_to_serialize_object() -{ - throw print_error("The JSON generator failed to serialize object"); -} - -void -failed_to_stream_object() -{ - throw print_error("The JSON generator failed to stream object"); -} - -} // namespace -} // namespace json -} // namespace ircd - -namespace ircd { -namespace db { - -std::string -merge_operator(const string_view &key, - const std::pair &delta) -{ - ircd::json::index index{delta.first}; - index += delta.second; - return index; -} - -} // namespace db -} // namespace ircd - -ircd::json::printer::printer() -{ - const auto recursor([this](auto &a, auto &b, auto &c) - { - const auto recurse_array([&a] - { - mutable_buffer mb - { - const_cast(a.data()), a.size() - }; - - const auto r(stringify(mb, json::array(a))); - a.resize(r.size()); - }); - - const auto recurse_object([&a] - { - mutable_buffer mb - { - const_cast(a.data()), a.size() - }; - - const auto r(stringify(mb, json::object(a))); - a.resize(r.size()); - }); - - const auto quote_string([&] - { - a.insert(a.end(), '"'); - a.insert(a.begin(), '"'); - }); - - if(likely(!a.empty())) switch(a.front()) - { - case '{': recurse_object(); break; - case '[': recurse_array(); 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; - } - }); - - value %= karma::string[recursor]; } +const ircd::json::ostreamer; template @@ -374,9 +245,17 @@ ircd::json::printer::operator()(char *&out, attr&& a) const { + const auto throws([&out, &stop] + { + throw print_error("Failed to print attribute '%s' generator '%s' (%zd bytes in buffer)", + demangle(), + demangle(), + size_t(stop - out)); + }); + const auto gg { - maxwidth(size_t(stop - out))[std::forward(g)] | eps[failed_to_serialize_object] + maxwidth(size_t(stop - out))[std::forward(g)] | eps[throws] }; return karma::generate(out, gg, std::forward(a)); @@ -389,44 +268,150 @@ ircd::json::printer::operator()(char *&out, gen&& g) const { + const auto throws([&out, &stop] + { + throw print_error("Failed to print generator '%s' (%zd bytes in buffer)", + demangle(), + size_t(stop - out)); + }); + const auto gg { - maxwidth(size_t(stop - out))[std::forward(g)] | eps[failed_to_serialize_object] + maxwidth(size_t(stop - out))[std::forward(g)] | eps[throws] }; return karma::generate(out, gg); } -ircd::string_view -ircd::json::stringify(mutable_buffer &buf, - const std::vector &v) +/////////////////////////////////////////////////////////////////////////////// +// +// iov.h +// + +std::ostream & +ircd::json::operator<<(std::ostream &s, const iov &iov) { - const auto print_member([&buf](const string_view &elem) + s << json::string(iov); + return s; +} + +ircd::string_view +ircd::json::stringify(mutable_buffer &head, + const iov &iov) +{ + const auto num{iov.size()}; + const member *m[num]; + + size_t i(0); + std::for_each(std::begin(iov), std::end(iov), [&i, &m] + (const auto &member) { - //printer(buf, printer.elem, elem); - const auto cpsz(std::min(size(buf), elem.size())); - memcpy(begin(buf), elem.data(), cpsz); - begin(buf) += cpsz; + m[i++] = &member; }); - char *const start(begin(buf)); - printer(buf, printer.array_begin); - - auto it(std::begin(v)); - if(it != std::end(v)) - { - print_member(*it); - for(++it; it != std::end(v); ++it) - { - printer(buf, printer.value_sep); - print_member(*it); - } - } - - printer(buf, printer.array_end); - return string_view{start, begin(buf)}; + return stringify(head, m, m + num); } +size_t +ircd::json::serialized(const iov &iov) +{ + const size_t ret + { + 1U + iov.empty() + }; + + return std::accumulate(std::begin(iov), std::end(iov), ret, [] + (auto ret, const auto &member) + { + return ret += serialized(member) + 1; + }); +} + +bool +ircd::json::iov::has(const string_view &key) +const +{ + return std::any_of(std::begin(*this), std::end(*this), [&key] + (const auto &member) + { + return string_view{member.first} == key; + }); +} + +const ircd::json::value & +ircd::json::iov::at(const string_view &key) +const +{ + const auto it(std::find_if(std::begin(*this), std::end(*this), [&key] + (const auto &member) + { + return string_view{member.first} == key; + })); + + return it->second; +} + +ircd::json::iov::add::add(iov &iov, + member member) +:node +{ + iov, [&iov, &member] + { + if(iov.has(member.first)) + throw exists("failed to add member '%s': already exists", + string_view{member.first}); + + return std::move(member); + }() +} +{ +} + +ircd::json::iov::add_if::add_if(iov &iov, + const bool &b, + member member) +:node +{ + iov, std::move(member) +} +{ + if(!b) + iov.pop_front(); +} + +ircd::json::iov::set::set(iov &iov, member member) +:node +{ + iov, [&iov, &member] + { + iov.remove_if([&member](const auto &existing) + { + return string_view{existing.first} == string_view{member.first}; + }); + + return std::move(member); + }() +} +{ +} + +ircd::json::iov::set_if::set_if(iov &iov, + const bool &b, + member member) +:node +{ + iov, std::move(member) +} +{ + if(!b) + iov.pop_front(); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// json/member.h +// + ircd::string_view ircd::json::stringify(mutable_buffer &buf, const members &list) @@ -434,15 +419,6 @@ ircd::json::stringify(mutable_buffer &buf, return stringify(buf, std::begin(list), std::end(list)); } -ircd::string_view -ircd::json::stringify(mutable_buffer &buf, - const index &obj) -{ - const member *const &begin(&obj.idx.front()); - const member *const &end(begin + obj.idx.size()); - return stringify(buf, begin, end); -} - ircd::string_view ircd::json::stringify(mutable_buffer &buf, const member &m) @@ -468,76 +444,14 @@ ircd::json::stringify(mutable_buffer &buf, const member *const *const &b, const member *const *const &e) { - const auto print_string([&buf](const value &value) + const auto &print_member { - printer(buf, printer.string, string_view{value}); - }); - - const auto print_literal([&buf](const value &value) - { - printer(buf, printer.literal, string_view{value}); - }); - - const auto print_object([&buf](const value &value) - { - if(value.serial) + [&](const member &member) { - printer(buf, printer.object, string_view{value}); - return; + printer(buf, printer.name << printer.name_sep, member.first); + stringify(buf, member.second); } - - assert(value.object); - stringify(buf, *value.object); - }); - - const auto print_array([&buf](const value &value) - { - if(value.serial) - { - //printer(out, stop, printer.array, value); - const string_view data(value); - const auto cpsz(std::min(size(buf), data.size())); - memcpy(begin(buf), data.data(), cpsz); - begin(buf) += cpsz; - return; - } - - assert(0); - //assert(value.object); - //serialize(*value.object, out, stop); - }); - - const auto print_number([&buf](const value &value) - { - if(value.serial) - { - if(value.floats) - printer(buf, double_, string_view{value}); - else - printer(buf, long_, string_view{value}); - - return; - } - - if(value.floats) - printer(buf, double_, value.floating); - else - printer(buf, long_, value.integer); - }); - - const auto print_member([&](const member &member) - { - printer(buf, printer.name << printer.name_sep, member.first); - - switch(member.second.type) - { - case STRING: print_string(member.second); break; - case OBJECT: print_object(member.second); break; - case ARRAY: print_array(member.second); break; - case NUMBER: print_number(member.second); break; - case LITERAL: print_literal(member.second); break; - } - }); + }; char *const start(begin(buf)); printer(buf, printer.object_begin); @@ -557,157 +471,6 @@ ircd::json::stringify(mutable_buffer &buf, return string_view{start, begin(buf)}; } -ircd::json::ostreamer::ostreamer() -{ - const auto recursor([this](auto &a, auto &b, auto &c) - { - const auto recurse_array([&a] - { - mutable_buffer mb - { - const_cast(a.data()), a.size() - }; - - const auto r(stringify(mb, json::array(a))); - a.resize(r.size()); - }); - - const auto recurse_object([&a] - { - mutable_buffer mb - { - const_cast(a.data()), a.size() - }; - - const auto r(stringify(mb, json::object(a))); - a.resize(r.size()); - }); - - const auto quote_string([&] - { - a.insert(a.end(), '"'); - a.insert(a.begin(), '"'); - }); - - if(likely(!a.empty())) switch(a.front()) - { - case '{': recurse_object(); break; - case '[': recurse_array(); 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; - } - }); - - value %= karma::string[recursor]; -} - -std::ostream & -ircd::json::operator<<(std::ostream &s, const index &obj) -{ - karma::ostream_iterator osi(s); - - const auto stream_string([&osi](const value &value) - { - karma::generate(osi, ostreamer.string, string_view{value}); - }); - - const auto stream_literal([&osi](const value &value) - { - karma::generate(osi, ostreamer.literal, string_view{value}); - }); - - const auto stream_object([&osi, &s](const value &value) - { - if(value.serial) - { - karma::generate(osi, ostreamer.object, string_view{value}); - return; - } - - assert(value.object); - s << *value.object; - }); - - const auto stream_array([&osi](const value &value) - { - if(value.serial) - { - karma::generate(osi, ostreamer.array, string_view{value}); - return; - } - - assert(0); - //assert(value.object); - //s << *value.object; - }); - - const auto stream_number([&osi](const value &value) - { - if(value.serial) - { - if(value.floats) - karma::generate(osi, double_, string_view{value}); - else - karma::generate(osi, long_, string_view{value}); - - return; - } - - if(value.floats) - karma::generate(osi, double_, value.floating); - else - karma::generate(osi, long_, value.integer); - }); - - const auto stream_member([&](const member &member) - { - karma::generate(osi, ostreamer.name << ostreamer.name_sep, string_view(member.first)); - - switch(member.second.type) - { - case STRING: stream_string(member.second); break; - case OBJECT: stream_object(member.second); break; - case ARRAY: stream_array(member.second); break; - case NUMBER: stream_number(member.second); break; - case LITERAL: stream_literal(member.second); break; - } - }); - - 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; -} - size_t ircd::json::serialized(const members &m) { @@ -732,205 +495,264 @@ ircd::json::serialized(const member &member) return serialized(member.first) + 1 + serialized(member.second); } -ircd::json::index -ircd::json::operator+(const object &left, const object &right) +/////////////////////////////////////////////////////////////////////////////// +// +// json/object.h +// + +ircd::string_view +ircd::json::stringify(mutable_buffer &buf, + const object &object) { - index ret(left); - ret += right; + const char *const start(begin(buf)); + consume(buf, copy(buf, string_view{object})); + return { start, begin(buf) }; +} + +ircd::string_view +ircd::json::stringify(mutable_buffer &buf, + const object::member &member) +{ + static const auto throws([] + { + throw print_error("The JSON generator failed to stringify object::member"); + }); + + const char *const start(begin(buf)); + consume(buf, copy(buf, string_view{member.first})); + printer(buf, printer.name_sep | eps[throws]); + consume(buf, copy(buf, string_view{member.second})); + return string_view{start, begin(buf)}; +} + +std::ostream & +ircd::json::operator<<(std::ostream &s, const object &object) +{ + s << json::string(object); + return s; +} + +std::ostream & +ircd::json::operator<<(std::ostream &s, const object::member &member) +{ + s << json::string(member); + return s; +} + +ircd::json::object::const_iterator & +ircd::json::object::const_iterator::operator++() +try +{ + static const qi::rule member + { + parser.name >> parser.name_sep >> parser.value + ,"next object member" + }; + + static const qi::rule parse_next + { + (parser.value_sep >> member) | parser.object_end + ,"next object member or end" + }; + + state.first = string_view{}; + state.second = string_view{}; + qi::parse(start, stop, eps > parse_next, state); + return *this; +} +catch(const qi::expectation_failure &e) +{ + const auto rule(ircd::string(e.what_)); + const long size(std::distance(e.first, e.last)); + throw parse_error("Expected JSON %s. You input %zu invalid characters starting with `%s`.", + between(rule, "<", ">"), + size, + string_view(e.first, e.first + std::min(size, 64L))); +} + +ircd::json::object::operator std::string() +const +{ + return json::string(*this); +} + +ircd::json::object::const_iterator +ircd::json::object::begin() +const try +{ + static const qi::rule object_member + { + parser.name >> parser.name_sep >> parser.value + ,"object member" + }; + + static const qi::rule parse_begin + { + parser.object_begin >> (parser.object_end | object_member) + ,"object begin and member or end" + }; + + const_iterator ret + { + string_view::begin(), string_view::end() + }; + + if(!empty()) + qi::parse(ret.start, ret.stop, eps > parse_begin, ret.state); + return ret; } - -ircd::json::index & -ircd::json::operator+=(index &left, const object &right) +catch(const qi::expectation_failure &e) { - for(const auto &b : right) + const auto rule(ircd::string(e.what_)); + const long size(std::distance(e.first, e.last)); + throw parse_error("Expected JSON %s. You input %zu invalid characters starting with `%s`.", + between(rule, "<", ">"), + size, + string_view(e.first, e.first + std::min(size, 64L))); +} + +ircd::json::object::const_iterator +ircd::json::object::end() +const +{ + return { string_view::end(), string_view::end() }; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// json/array.h +// + +ircd::string_view +ircd::json::stringify(mutable_buffer &buf, + const array &v) +{ + consume(buf, copy(buf, string_view{v})); + return string_view{v}; +} + +ircd::string_view +ircd::json::stringify(mutable_buffer &buf, + const std::string *const &b, + const std::string *const &e) +{ + return array::stringify(buf, b, e); +} + +ircd::string_view +ircd::json::stringify(mutable_buffer &buf, + const string_view *const &b, + const string_view *const &e) +{ + return array::stringify(buf, b, e); +} + +template +ircd::string_view +ircd::json::array::stringify(mutable_buffer &buf, + const it &b, + const it &e) +{ + const auto print_element { - const auto deletion + [&buf](const string_view &element) { - // empty replacement value indicates deletion - b.second.empty() || - b.second == "\"\"" || - b.second == "{}" || - b.second == "[]" - }; - - const auto it(left.find(b.first)); - if(it == left.end()) - { - if(deletion) // prevents adding the empty indicator as the new key! - continue; - - left.idx.emplace_back(member{b}); - continue; + if(!consume(buf, buffer::copy(buf, element))) + throw print_error("The JSON generator ran out of space in supplied buffer"); } + }; - if(deletion) + char *const start(std::begin(buf)); + printer(buf, printer.array_begin); + + auto i(b); + if(i != e) + { + print_element(*i); + for(++i; i != e; ++i) { - left.erase(it); - continue; - } - - auto &a(const_cast(*it)); - switch(type(a.second)) - { - default: - { - a = member{b}; - continue; - } - - // merge recursively - case OBJECT: - { - auto merged(std::make_unique(json::object{a.second})); - *merged += json::object{b.second}; - if(merged->empty()) - { - // Child merge was empty value. Now we can also remove this empty parent. - left.erase(it); - continue; - } - - a = member{string_view{a.first}, std::move(merged)}; - continue; - } + printer(buf, printer.value_sep); + print_element(*i); } } - return left; + printer(buf, printer.array_end); + return string_view{start, std::begin(buf)}; } -ircd::json::index::index(recursive_t recursive, - const object &object) -:idx{object.count()} +std::ostream & +ircd::json::operator<<(std::ostream &s, const array &a) { - std::transform(std::begin(object), std::end(object), std::begin(idx), [&] - (const object::member &m) -> member + s << json::string(a); + return s; +} + +ircd::json::array::const_iterator & +ircd::json::array::const_iterator::operator++() +try +{ + static const qi::rule parse_next { - switch(type(m.second)) - { - case OBJECT: - return member{m.first, std::make_unique(recursive, m.second)}; + parser.array_end | (parser.value_sep >> parser.value) + ,"next array element or end" + }; - default: - return member{m}; - }; - }); + state = string_view{}; + qi::parse(start, stop, eps > parse_next, state); + return *this; } - -ircd::json::index::index(const object &object) -:idx{object.count()} +catch(const qi::expectation_failure &e) { - std::transform(std::begin(object), std::end(object), std::begin(idx), [] - (const object::member &m) -> member - { - return member{m}; - }); + const auto rule(ircd::string(e.what_)); + const long size(std::distance(e.first, e.last)); + throw parse_error("Expected JSON %s. You input %zd invalid characters starting with `%s`.", + between(rule, "<", ">"), + size, + string_view(e.first, e.first + std::min(size, 64L))); } -ircd::json::index::index(std::initializer_list builder) -:idx{builder.size()} -{ - std::transform(std::begin(builder), std::end(builder), std::begin(idx), [] - (auto&& m) - { - return std::move(const_cast(m)); - }); - - const auto empty([](const auto &member) - { - return member.first.empty(); - }); - - idx.erase(std::remove_if(std::begin(idx), std::end(idx), empty), idx.end()); -} - -bool -ircd::json::index::erase(const string_view &name) -{ - const auto it(find(name)); - if(it == end()) - return false; - - erase(it); - return true; -} - -void -ircd::json::index::erase(const const_iterator &it) -{ - idx.erase(it); -} - -ircd::json::index::const_iterator -ircd::json::index::erase(const const_iterator &start, - const const_iterator &stop) -{ - return { idx.erase(start, stop) }; -} - -const ircd::json::value & -ircd::json::index::at(const string_view &path) +ircd::json::array::operator std::string() const { - const auto elem(split(path, '.')); - const auto ident(split(elem.first, '[')); - const auto &name(ident.first); - const auto &indice(ident.second); - - const auto it(find(name)); - if(unlikely(it == end())) - throw not_found("'%s'", name); - - const auto &val(it->second); - if(!indice.empty()) - { - const auto idx(lex_cast(rstrip(indice, ']'))); - - if(type(val) != ARRAY) - throw not_found("cannot recurse through non-array \"%s\" for indice [%zu]", name, idx); - - if(val.serial) - throw not_found("cannot recurse through unparsed array \"%s\" for indice [%zu]", name, idx); - - assert(0); - //return val.array->at(idx); - } - else if(!elem.second.empty()) - { - if(type(val) != OBJECT) - throw not_found("cannot recurse through non-object \"%s\" for \"%s\"", name, elem.second); - - if(val.serial) - throw not_found("cannot recurse through unparsed object \"%s\" for \"%s\"", name, elem.second); - - return val.object->at(elem.second); - } - - return it->second; + return json::string(*this); } -size_t -ircd::json::serialized(const index &index) +ircd::json::array::const_iterator +ircd::json::array::begin() +const try { - return index.serialized(); + static const qi::rule parse_begin + { + parser.array_begin >> (parser.array_end | parser.value) + ,"array begin and element or end" + }; + + const_iterator ret + { + string_view::begin(), string_view::end() + }; + + if(!empty()) + qi::parse(ret.start, ret.stop, eps > parse_begin, ret.state); + + return ret; +} +catch(const qi::expectation_failure &e) +{ + const auto rule(ircd::string(e.what_)); + const long size(std::distance(e.first, e.last)); + throw parse_error("Expected JSON %s. You input %zd invalid characters starting with `%s`.", + between(rule, "<", ">"), + size, + string_view(e.first, e.first + std::min(size, 64L))); } -size_t -ircd::json::index::serialized() +ircd::json::array::const_iterator +ircd::json::array::end() const { - const member *const &begin(&idx.front()); - const member *const &end(begin + idx.size()); - return json::serialized(begin, end); -} - -ircd::json::index::operator std::string() -const -{ - const member *const &begin(&idx.front()); - const member *const &end(begin + idx.size()); - return json::string(begin, end); + return { string_view::end(), string_view::end() }; } /////////////////////////////////////////////////////////////////////////////// @@ -938,49 +760,313 @@ const // json/value.h // +const ircd::string_view ircd::json::value::literal_null {"null"}; +const ircd::string_view ircd::json::value::literal_true {"true"}; +const ircd::string_view ircd::json::value::literal_false {"false"}; +const ircd::string_view ircd::json::value::empty_string {"\"\""}; +const ircd::string_view ircd::json::value::empty_number {"0"}; +const ircd::string_view ircd::json::value::empty_object {"{}"}; +const ircd::string_view ircd::json::value::empty_array {"[]"}; + +std::ostream & +ircd::json::operator<<(std::ostream &s, const value &v) +{ + s << json::string(v); + return s; +} + +ircd::string_view +ircd::json::stringify(mutable_buffer &buf, + const value *const &b, + const value *const &e) +{ + static const auto throws([] + { + throw print_error("The JSON generator failed to print array values"); + }); + + char *const start(begin(buf)); + printer(buf, printer.array_begin); + + auto it(b); + if(it != e) + { + stringify(buf, *it); + for(++it; it != e; ++it) + { + printer(buf, printer.value_sep); + stringify(buf, *it); + } + } + + printer(buf, printer.array_end); + return string_view{start, begin(buf)}; +} + +ircd::string_view +ircd::json::stringify(mutable_buffer &buf, + const value &v) +{ + const auto start + { + begin(buf) + }; + + switch(v.type) + { + case STRING: + { + const string_view sv{v}; + printer(buf, printer.string, sv); + break; + } + + case LITERAL: + { + consume(buf, copy(buf, string_view{v})); + break; + } + + case OBJECT: + { + if(v.serial) + { + consume(buf, copy(buf, string_view{v})); + break; + } + + if(v.object) + { + stringify(buf, v.object, v.object + v.len); + break; + } + + consume(buf, copy(buf, v.literal_null)); + break; + } + + case ARRAY: + { + if(v.serial) + { + consume(buf, copy(buf, string_view{v})); + break; + } + + if(v.array) + { + stringify(buf, v.array, v.array + v.len); + break; + } + + consume(buf, copy(buf, v.literal_null)); + break; + } + + case NUMBER: + { + if(v.serial) + { + if(v.floats) + printer(buf, double_, string_view{v}); + else + printer(buf, long_, string_view{v}); + + break; + } + + if(v.floats) + printer(buf, double_, v.floating); + else + printer(buf, long_, v.integer); + + break; + } + } + + return { start, begin(buf) }; +} + +size_t +ircd::json::serialized(const values &v) +{ + return serialized(std::begin(v), std::end(v)); +} + +size_t +ircd::json::serialized(const value *const &begin, + const value *const &end) +{ + // One opening '[' and either one ']' or comma count. + const size_t ret(1 + !std::distance(begin, end)); + return std::accumulate(begin, end, size_t(ret), [] + (auto ret, const value &v) + { + return ret += serialized(v) + 1; // 1 comma + }); +} + +size_t +ircd::json::serialized(const value &v) +{ + switch(v.type) + { + case OBJECT: + return v.serial? v.len : serialized(v.object, v.object + v.len); + + case ARRAY: + return v.serial? v.len : serialized(v.array, v.array + v.len); + + case LITERAL: + return v.len; + + case NUMBER: + { + if(v.serial) + return v.len; + + static thread_local char test_buffer[4096]; + const auto test + { + stringify(mutable_buffer{test_buffer}, v) + }; + + return test.size(); + } + + case STRING: + { + if(!v.string) + return 2; + + size_t ret(v.len); + const string_view sv{v.string, v.len}; + ret += !startswith(sv, '"'); + ret += !endswith(sv, '"'); + return ret; + } + }; + + throw type_error("deciding the size of a type[%u] is undefined", int(v.type)); +} + +// +// json::value +// + +ircd::json::value::value(const json::members &members) +:string{nullptr} +,len{serialized(members)} +,type{OBJECT} +,serial{true} +,alloc{true} +,floats{false} +{ + create_string(len, [&members] + (mutable_buffer buffer) + { + json::stringify(buffer, members); + }); +} + +ircd::json::value::value(const value &other) +:integer{other.integer} +,len{other.len} +,type{other.type} +,serial{other.serial} +,alloc{other.alloc} +,floats{other.floats} +{ + if(alloc && serial) + { + create_string(len, [this](mutable_buffer buffer) + { + copy(buffer, string_view{*this}); + }); + } + else switch(type) + { + case OBJECT: + if(!serial && object) + { + const size_t count(this->len); + create_string(serialized(object, object + count), [this, &count] + (mutable_buffer buffer) + { + json::stringify(buffer, object, object + count); + }); + } + + break; + + case ARRAY: + if(!serial && array) + { + const size_t count(this->len); + create_string(serialized(array, array + count), [this, &count] + (mutable_buffer buffer) + { + json::stringify(buffer, array, array + count); + }); + } + break; + + case STRING: + if(!serial && alloc && string) + { + create_string(serialized(*this), [this] + (mutable_buffer buffer) + { + json::stringify(buffer, *this); + }); + } + + case LITERAL: + case NUMBER: + break; + } +} + +ircd::json::value & +ircd::json::value::operator=(const value &other) +{ + this->~value(); + new (this) value(other); + return *this; +} + ircd::json::value::~value() noexcept { - switch(type) + if(!alloc) + return; + + else if(serial) + delete[] string; + + else switch(type) { - case STRING: if(alloc) delete[] string; break; - case OBJECT: if(alloc) delete object; break; - //case ARRAY: if(alloc) delete array; break; - default: break; + case STRING: + delete[] string; + break; + + case OBJECT: + delete[] object; + break; + + case ARRAY: + delete[] array; + break; + + default: + break; } } ircd::json::value::operator std::string() const { - switch(type) - { - case STRING: - case LITERAL: - return std::string(unquote(string_view(*this))); - - case OBJECT: - if(serial) - return std::string(string_view(*this)); - else - return json::string(*object); - - case ARRAY: - if(serial) - return std::string(string_view(*this)); - else - break; - - case NUMBER: - if(serial) - return std::string(string_view(*this)); - else if(floats) - return std::string(lex_cast(floating)); - else - return std::string(lex_cast(integer)); - } - - throw type_error("cannot index type[%d]", int(type)); + return json::string(*this); } ircd::json::value::operator string_view() @@ -992,17 +1078,13 @@ const return unquote(string_view{string, len}); case NUMBER: - if(serial) - return string_view{string, len}; - else if(floats) - return byte_view{floating}; - else - return byte_view{integer}; - + return serial? string_view{string, len}: + floats? byte_view{floating}: + byte_view{integer}; case ARRAY: case OBJECT: case LITERAL: - if(serial) + if(likely(serial)) return string_view{string, len}; else break; @@ -1020,8 +1102,7 @@ const return likely(!floats)? integer : floating; case STRING: - assert(serial); - return lex_cast(string_view(*this)); + return lex_cast(string_view{*this}); case ARRAY: case OBJECT: @@ -1041,8 +1122,7 @@ const return likely(floats)? floating : integer; case STRING: - assert(serial); - return lex_cast(string_view(*this)); + return lex_cast(string_view{*this}); case ARRAY: case OBJECT: @@ -1053,6 +1133,104 @@ const throw type_error("value type[%d] is not a float", int(type)); } +bool +ircd::json::value::operator!() +const +{ + switch(type) + { + case NUMBER: + return floats? !(floating > 0.0 || floating < 0.0): + !bool(integer); + + case STRING: + return string? !len || string_view{*this} == empty_string: + true; + + case OBJECT: + return serial? !len || string_view{*this} == empty_object: + object? !len: + true; + + case ARRAY: + return serial? !len || (string_view{*this} == empty_array): + array? !len: + true; + + case LITERAL: + if(serial) + return string == nullptr || + string_view{*this} == literal_false || + string_view{*this} == literal_null; + break; + }; + + throw type_error("deciding if a type[%u] is falsy is undefined", int(type)); +} + +bool +ircd::json::value::empty() +const +{ + switch(type) + { + case NUMBER: + return serial? !len: + floats? !(floating > 0.0 || floating < 0.0): + !bool(integer); + + case STRING: + return !string || !len || string_view{*this} == empty_string; + + case OBJECT: + return serial? !len || string_view{*this} == empty_object: + object? !len: + true; + + case ARRAY: + return serial? !len || string_view{*this} == empty_array: + array? false: + true; //TODO: XXX arr + + case LITERAL: + return serial? !len: + true; + }; + + throw type_error("deciding if a type[%u] is empty is undefined", int(type)); +} + +bool +ircd::json::value::null() +const +{ + switch(type) + { + case NUMBER: + return floats? !(floating > 0.0 || floating < 0.0): + !bool(integer); + + case STRING: + return string == nullptr; + + case OBJECT: + return serial? string == nullptr: + object? false: + true; + + case ARRAY: + return serial? string == nullptr: + array? array == nullptr: + true; + + case LITERAL: + return serial? string == nullptr: + true; + }; + + throw type_error("deciding if a type[%u] is null is undefined", int(type)); +} + bool ircd::json::value::undefined() const @@ -1067,12 +1245,12 @@ const case OBJECT: return serial? string == nullptr: - object? object->empty(): + object? false: true; case ARRAY: return serial? string == nullptr: - array? array == nullptr: + array? false: true; case LITERAL: @@ -1083,103 +1261,31 @@ const throw type_error("deciding if a type[%u] is undefined is undefined", int(type)); } -bool -ircd::json::value::empty() -const +void +ircd::json::value::create_string(const size_t &len, + const create_string_closure &closure) { - switch(type) + const size_t max { - case NUMBER: return lex_cast(integer).empty(); - case STRING: return !len; - case OBJECT: return serial? !len : object? object->empty() : true; - case ARRAY: return serial? !len : array? false : true; //TODO: XXX arr - case LITERAL: return serial? !len : true; + len + 1 }; - throw type_error("deciding if a type[%u] is empty is undefined", int(type)); -} - -bool -ircd::json::value::null() -const -{ - switch(type) + std::unique_ptr string { - case NUMBER: - return floats? !(floating > 0.0 || floating < 0.0): - bool(integer); - - case STRING: - return string == nullptr; - - case OBJECT: - return serial? string == nullptr: - object? object->empty(): - true; - - case ARRAY: - return serial? string == nullptr: - array? array == nullptr: - true; - - case LITERAL: - return serial? string == nullptr: - true; + new char[max] }; - throw type_error("deciding if a type[%u] is null is undefined", int(type)); -} - -size_t -ircd::json::serialized(const value &v) -{ - switch(v.type) + const mutable_buffer buffer { - case NUMBER: return lex_cast(v.integer).size(); - case STRING: return 1 + v.len + 1; - case OBJECT: return v.serial? v.len : serialized(*v.object); - case ARRAY: return v.serial? v.len : 2; - case LITERAL: return v.len; + string.get(), len }; - throw type_error("deciding the size of a type[%u] is undefined", int(v.type)); -} - -std::ostream & -ircd::json::operator<<(std::ostream &s, const value &v) -{ - switch(v.type) - { - case STRING: - case LITERAL: - s << string_view(v); - break; - - case OBJECT: - if(v.serial) - s << string_view(v); - else - s << *v.object; - break; - - case ARRAY: - if(v.serial) - s << string_view(v); - else - assert(0); - break; - - case NUMBER: - if(v.serial) - s << string_view(v); - else if(v.floats) - s << v.floating; - else - s << v.integer; - break; - } - - return s; + closure(buffer); + (string.get())[len] = '\0'; + this->alloc = true; + this->serial = true; + this->len = len; + this->string = string.release(); } bool @@ -1236,208 +1342,15 @@ ircd::json::operator==(const value &a, const value &b) return static_cast(a) == static_cast(b); } -ircd::string_view -ircd::json::stringify(mutable_buffer &buf, - const object &object) -{ - static const auto throws([] - { - throw print_error("The JSON generator failed to print object"); - }); - - char *const start(begin(buf)); - karma::generate(begin(buf), maxwidth(size(buf))[printer.object] | eps[throws], object); - - return string_view - { - start, begin(buf) - }; -} - -std::ostream & -ircd::json::operator<<(std::ostream &s, const object &object) -{ - const auto &os(ostreamer); - static const auto throws([] - { - throw print_error("The JSON generator failed to output object to stream"); - }); - - karma::ostream_iterator osi(s); - karma::generate(osi, os.object | eps[throws], object); - return s; -} - -std::ostream & -ircd::json::operator<<(std::ostream &s, const object::member &member) -{ - static const auto throws([] - { - throw print_error("The JSON generator failed to output object member to stream"); - }); - - karma::ostream_iterator osi(s); - karma::generate(osi, ostreamer.member | eps[throws], member); - return s; -} - -ircd::json::object::const_iterator & -ircd::json::object::const_iterator::operator++() -{ - static const qi::rule member - { - parser.name >> parser.ws >> parser.name_sep >> parser.ws >> raw[parser.value] - }; - - static const qi::rule parse_next - { - parser.object_end | (parser.value_sep >> parser.ws >> member) - }; - - state.first = string_view{}; - state.second = string_view{}; - if(!qi::phrase_parse(start, stop, parse_next, parser.WS, state)) - start = stop; - - return *this; -} - -ircd::json::object::operator std::string() -const -{ - //TODO: tmp - std::stringstream ret; - ret << (*this); - return ret.str(); -} - -ircd::json::object::const_iterator -ircd::json::object::begin() -const -{ - static const qi::rule member - { - parser.name >> parser.ws >> parser.name_sep >> parser.ws >> raw[parser.value] - }; - - static const qi::rule parse_begin - { - parser.object_begin >> parser.ws >> (parser.object_end | member) - }; - - const_iterator ret(string_view::begin(), string_view::end()); - if(!qi::phrase_parse(ret.start, ret.stop, parse_begin, parser.WS, ret.state)) - ret.start = ret.stop; - - return ret; -} - -ircd::json::object::const_iterator -ircd::json::object::end() -const -{ - return { string_view::end(), string_view::end() }; -} - -ircd::string_view -ircd::json::stringify(mutable_buffer &b, - const array &a) -{ - static const auto throws([] - { - throw print_error("The JSON generator failed to print array"); - }); - - char *const start(begin(b)); - karma::generate(begin(b), maxwidth(size(b))[printer.array_begin] | eps[throws]); - - auto it(begin(a)); - if(it != end(a)) - { - karma::generate(begin(b), maxwidth(size(b))[printer.elem] | eps[throws], *it); - for(++it; it != end(a); ++it) - karma::generate(begin(b), maxwidth(size(b))[printer.value_sep << printer.elem] | eps[throws], *it); - } - - karma::generate(begin(b), maxwidth(size(b))[printer.array_end] | eps[throws]); - return string_view{start, begin(b)}; -} - -std::ostream & -ircd::json::operator<<(std::ostream &s, const array &a) -{ - const auto &os(ostreamer); - static const auto throws([] - { - throw print_error("The JSON generator failed to output array to stream"); - }); - - karma::ostream_iterator osi(s); - karma::generate(osi, ostreamer.array_begin | eps[throws]); - - auto it(begin(a)); - if(it != end(a)) - { - karma::generate(osi, ostreamer.elem | eps[throws], *it); - for(++it; it != end(a); ++it) - karma::generate(osi, (ostreamer.value_sep << ostreamer.elem) | eps[throws], *it); - } - - karma::generate(osi, ostreamer.array_end | eps[throws]); - return s; -} - -ircd::json::array::const_iterator & -ircd::json::array::const_iterator::operator++() -{ - static const qi::rule parse_next - { - parser.array_end | (parser.value_sep >> parser.ws >> raw[parser.value]) - }; - - state = string_view{}; - if(!qi::phrase_parse(start, stop, parse_next, parser.WS, state)) - start = stop; - - return *this; -} - -ircd::json::array::operator std::string() -const -{ - //TODO: tmp - std::stringstream ret; - ret << (*this); - return ret.str(); -} - -ircd::json::array::const_iterator -ircd::json::array::begin() -const -{ - static const qi::rule parse_begin - { - parser.array_begin >> parser.ws >> (parser.array_end | raw[parser.value]) - }; - - const_iterator ret(string_view::begin(), string_view::end()); - if(!qi::phrase_parse(ret.start, ret.stop, parse_begin, parser.WS, ret.state)) - ret.start = ret.stop; - - return ret; -} - -ircd::json::array::const_iterator -ircd::json::array::end() -const -{ - return { string_view::end(), string_view::end() }; -} +/////////////////////////////////////////////////////////////////////////////// +// +// json.h +// size_t ircd::json::serialized(const string_view &s) { - switch(type(s, std::nothrow)) + if(!s.empty()) switch(type(s, std::nothrow)) { case NUMBER: case OBJECT: @@ -1480,110 +1393,17 @@ ircd::json::type(const string_view &buf, return ret; } -/////////////////////////////////////////////////////////////////////////////// -// -// iov.h -// - ircd::string_view -ircd::json::stringify(mutable_buffer &head, - const iov &iov) +ircd::json::reflect(const enum type &type) { - const auto num{iov.size()}; - const member *m[num]; - - size_t i(0); - std::for_each(std::begin(iov), std::end(iov), [&i, &m] - (const auto &member) + switch(type) { - m[i++] = &member; - }); - - return stringify(head, m, m + num); -} - -bool -ircd::json::iov::has(const string_view &key) -const -{ - return std::any_of(std::begin(*this), std::end(*this), [&key] - (const auto &member) - { - return string_view{member.first} == key; - }); -} - -const ircd::json::value & -ircd::json::iov::at(const string_view &key) -const -{ - const auto it(std::find_if(std::begin(*this), std::end(*this), [&key] - (const auto &member) - { - return string_view{member.first} == key; - })); - - return it->second; -} - -ircd::json::iov::add::add(iov &iov, - member member) -:node -{ - iov, [&iov, &member] - { - if(iov.has(member.first)) - throw exists("failed to add member '%s': already exists", - string_view{member.first}); - - return std::move(member); - }() -} -{ -} - -ircd::json::iov::add_if::add_if(iov &iov, - const bool &b, - member member) -:add -{ - iov, std::move(member) -} -{ - if(!b) - { - assert(iov.front() == member); - iov.pop_front(); + case NUMBER: return "NUMBER"; + case OBJECT: return "OBJECT"; + case ARRAY: return "ARRAY"; + case LITERAL: return "LITERAL"; + case STRING: return "STRING"; } -} -ircd::json::iov::set::set(iov &iov, member member) -:node -{ - iov, [&iov, &member] - { - iov.remove_if([&member](const auto &existing) - { - return string_view{existing.first} == string_view{member.first}; - }); - - return std::move(member); - }() -} -{ -} - -ircd::json::iov::set_if::set_if(iov &iov, - const bool &b, - member member) -:set -{ - iov, std::move(member) -} -{ - if(!b) - { - assert(iov.front() == member); - iov.pop_front(); - } + return {}; }