/* * charybdis: standing on the shoulders of giant build times * * 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. */ #include #include namespace ircd::json { namespace spirit = boost::spirit; namespace ascii = spirit::ascii; namespace karma = spirit::karma; namespace qi = spirit::qi; 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 karma::lit; using karma::char_; using karma::long_; using karma::double_; using karma::bool_; using karma::maxwidth; 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; } BOOST_FUSION_ADAPT_STRUCT ( ircd::json::member, ( decltype(ircd::json::member::first), first ) ( decltype(ircd::json::member::second), second ) ) BOOST_FUSION_ADAPT_STRUCT ( ircd::json::object::member, ( decltype(ircd::json::object::member::first), first ) ( decltype(ircd::json::object::member::second), second ) ) template struct ircd::json::input :qi::grammar { template using rule = qi::rule; rule<> NUL { lit('\0') ,"nul" }; // insignificant whitespaces rule<> SP { lit('\x20') ,"space" }; rule<> HT { lit('\x09') ,"horizontal tab" }; rule<> CR { lit('\x0D') ,"carriage return" }; rule<> LF { lit('\x0A') ,"line feed" }; // whitespace skipping rule<> WS { SP | HT | CR | LF ,"whitespace" }; rule<> ws { *(WS) ,"whitespace monoid" }; rule<> wsp { +(WS) ,"whitespace semigroup" }; // 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<> name_sep { lit(':') ,"name sep" }; rule<> value_sep { lit(',') ,"value sep" }; // literal rule lit_false { lit("false") ,"literal false" }; rule lit_true { lit("true") ,"literal true" }; rule lit_null { lit("null") ,"null" }; rule<> quote { lit('"') ,"quote" }; rule chars { raw[*(char_ - quote)] ,"characters" }; rule string { quote >> chars >> quote ,"string" }; rule name { quote >> raw[+(char_ - quote)] >> quote ,"name" }; rule boolean { lit_true | lit_false ,"boolean" }; rule literal { lit_true | lit_false | lit_null ,"literal" }; rule number { raw[double_] ,"number" }; rule member { name >> name_sep >> value ,"member" }; rule object { raw[object_begin >> -(member % value_sep) >> object_end] ,"object" }; rule array { raw[array_begin >> -(value % value_sep) >> array_end] ,"array" }; rule value { raw[lit_false | lit_null | lit_true | object | array | number | string] ,"value" }; rule type { (omit[object_begin] >> attr(json::OBJECT)) | (omit[array_begin] >> attr(json::ARRAY)) | (omit[quote] >> attr(json::STRING)) | (omit[number >> eoi] >> attr(json::NUMBER)) | (omit[literal >> eoi] >> attr(json::LITERAL)) ,"type" }; input() :input::base_type{rule<>{}} { 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 ircd::json::output :karma::grammar { template using rule = karma::rule; rule<> NUL { lit('\0') ,"nul" }; // insignificant whitespaces rule<> SP { lit('\x20') ,"space" }; rule<> HT { lit('\x09') ,"horizontal tab" }; rule<> CR { lit('\x0D') ,"carriage return" }; rule<> LF { lit('\x0A') ,"line feed" }; // whitespace skipping rule<> WS { SP | HT | CR | LF ,"whitespace" }; rule<> ws { *(WS) ,"whitespace monoid" }; rule<> wsp { +(WS) ,"whitespace semigroup" }; // 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<> name_sep { lit(':') ,"name separator" }; rule<> value_sep { lit(',') ,"value separator" }; rule<> quote { lit('"') ,"quote" }; rule lit_true { karma::string("true") ,"literal true" }; rule lit_false { karma::string("false") ,"literal false" }; rule lit_null { karma::string("null") ,"literal null" }; rule boolean { lit_true | lit_false ,"boolean" }; rule literal { lit_true | lit_false | lit_null ,"literal" }; rule chars { *(~char_('"')) ,"characters" }; rule string { quote << chars << quote ,"string" }; rule number { double_ ,"number" }; rule name { quote << +(~char_('"')) << quote ,"name" }; output() :output::base_type{rule<>{}} {} }; struct ircd::json::parser :input { using input::input; } const ircd::json::parser; struct ircd::json::printer :output { template bool operator()(char *&out, char *const &stop, generator&& gen, attribute&& a) const; template bool operator()(char *&out, char *const &stop, generator&& gen) const; template bool operator()(mutable_buffer &out, args&&... a) const { return operator()(begin(out), end(out), std::forward(a)...); } template static void list_protocol(mutable_buffer &, it_a begin, const it_b &end, closure&&); } const ircd::json::printer; struct ircd::json::ostreamer :output> { } const ircd::json::ostreamer; template bool ircd::json::printer::operator()(char *&out, char *const &stop, gen&& g, 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[throws] }; return karma::generate(out, gg, std::forward(a)); } template bool ircd::json::printer::operator()(char *&out, char *const &stop, 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[throws] }; return karma::generate(out, gg); } template void ircd::json::printer::list_protocol(mutable_buffer &buffer, it_a it, const it_b &end, closure&& lambda) { if(it != end) { lambda(buffer, *it); for(++it; it != end; ++it) { json::printer(buffer, json::printer.value_sep); lambda(buffer, *it); } } } /////////////////////////////////////////////////////////////////////////////// // // iov.h // std::ostream & ircd::json::operator<<(std::ostream &s, const iov &iov) { s << json::strung(iov); return s; } ircd::string_view ircd::json::stringify(mutable_buffer &head, const iov &iov) { const member *m[iov.size()]; std::transform(std::begin(iov), std::end(iov), m, [] (const member &m) { return &m; }); std::sort(m, m + iov.size(), [] (const member *const &a, const member *const &b) { return *a < *b; }); return stringify(head, m, m + iov.size()); } 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(); } ircd::json::iov::defaults::defaults(iov &iov, member member) :node { iov, std::move(member) } { const auto count { std::count_if(std::begin(iov), std::end(iov), [&member] (const auto &existing) { return string_view{existing.first} == string_view{member.first}; }) }; if(count > 1) iov.pop_front(); } ircd::json::iov::defaults_if::defaults_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) { return stringify(buf, std::begin(list), std::end(list)); } 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, const member *const &end) { const auto num(std::distance(begin, end)); const member *vec[num]; for(auto i(0); i < num; ++i) vec[i] = begin + i; return stringify(buf, vec, vec + num); } ircd::string_view ircd::json::stringify(mutable_buffer &buf, const member *const *const &b, const member *const *const &e) { const auto print_member { [](mutable_buffer &buf, const auto &it) { const auto &m(*it); printer(buf, printer.name << printer.name_sep, m.first); stringify(buf, m.second); } }; char *const start{begin(buf)}; printer(buf, printer.object_begin); printer::list_protocol(buf, b, e, print_member); printer(buf, printer.object_end); return { start, begin(buf) }; } size_t ircd::json::serialized(const members &m) { return serialized(std::begin(m), std::end(m)); } size_t ircd::json::serialized(const member *const &begin, const member *const &end) { const size_t ret(1 + !std::distance(begin, end)); return std::accumulate(begin, end, ret, [] (auto ret, const auto &member) { return ret += serialized(member) + 1; }); } size_t ircd::json::serialized(const member &member) { return serialized(member.first) + 1 + serialized(member.second); } /////////////////////////////////////////////////////////////////////////////// // // json/object.h // std::ostream & ircd::json::operator<<(std::ostream &s, const object::member &member) { s << json::strung(member); return s; } ircd::string_view ircd::json::stringify(mutable_buffer &buf, const object::member &member) { char *const start(begin(buf)); printer(buf, printer.name, member.first); printer(buf, printer.name_sep); consume(buf, copy(buf, member.second)); return string_view { start, begin(buf) }; } size_t ircd::json::serialized(const object::member &member) { return serialized(member.first) + 1 + serialized(member.second); } std::ostream & ircd::json::operator<<(std::ostream &s, const object &object) { s << json::strung(object); return s; } ircd::string_view ircd::json::stringify(mutable_buffer &buf, const object &object) { const auto b(std::begin(object)); const auto e(std::end(object)); char *const start(begin(buf)); static const auto stringify_member { [](mutable_buffer &buf, const object::member &member) { stringify(buf, member); } }; printer(buf, printer.object_begin); printer::list_protocol(buf, b, e, stringify_member); printer(buf, printer.object_end); return { start, begin(buf) }; } size_t ircd::json::serialized(const object &object) { const auto begin(std::begin(object)); const auto end(std::end(object)); const size_t ret(1 + (begin == end)); return std::accumulate(begin, end, ret, [] (auto ret, const object::member &member) { return ret += serialized(member) + 1; }); } 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)); const long cat(std::distance(start, e.first)); throw parse_error("Expected %s. You input %zd invalid characters at position %zd: %s", between(rule, "<", ">"), size, cat, string_view(e.first, e.first + std::min(size, 64L))); } ircd::json::object::operator std::string() const { return json::strung(*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; } catch(const qi::expectation_failure &e) { const auto rule(ircd::string(e.what_)); const long size(std::distance(e.first, e.last)); const long cat(std::distance(string_view::data(), e.first)); throw parse_error("Expected %s. You input %zd invalid characters at position %zd: %s.", between(rule, "<", ">"), size, cat, 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); } size_t ircd::json::serialized(const std::string *const &b, const std::string *const &e) { const size_t ret(1 + !std::distance(b, e)); return std::accumulate(b, e, ret, [] (auto ret, const auto &value) { return ret += serialized(string_view{value}) + 1; }); } size_t ircd::json::serialized(const string_view *const &b, const string_view *const &e) { const size_t ret(1 + !std::distance(b, e)); return std::accumulate(b, e, ret, [] (auto ret, const auto &value) { return ret += serialized(value) + 1; }); } template ircd::string_view ircd::json::array::stringify(mutable_buffer &buf, const it &b, const it &e) { static const auto print_element { [](mutable_buffer &buf, const string_view &element) { if(!consume(buf, ircd::buffer::copy(buf, element))) throw print_error("The JSON generator ran out of space in supplied buffer"); } }; char *const start(std::begin(buf)); printer(buf, printer.array_begin); printer::list_protocol(buf, b, e, print_element); printer(buf, printer.array_end); return { start, std::begin(buf) }; } std::ostream & ircd::json::operator<<(std::ostream &s, const array &a) { s << json::strung(a); return s; } ircd::json::array::const_iterator & ircd::json::array::const_iterator::operator++() try { static const qi::rule parse_next { parser.array_end | (parser.value_sep >> parser.value) ,"next array element or end" }; state = 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 %zd invalid characters starting with `%s`.", between(rule, "<", ">"), size, string_view(e.first, e.first + std::min(size, 64L))); } ircd::json::array::operator std::string() const { return json::strung(*this); } ircd::json::array::const_iterator ircd::json::array::begin() const try { 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))); } ircd::json::array::const_iterator ircd::json::array::end() const { return { string_view::end(), string_view::end() }; } /////////////////////////////////////////////////////////////////////////////// // // 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::strung(v); return s; } ircd::string_view ircd::json::stringify(mutable_buffer &buf, const value *const &b, const value *const &e) { static const auto print_value { [](mutable_buffer &buf, const value &value) { stringify(buf, value); } }; char *const start(begin(buf)); printer(buf, printer.array_begin); printer::list_protocol(buf, b, e, print_value); printer(buf, printer.array_end); return { 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.serial? v.len : serialized(bool(v.integer)); } 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 { if(!alloc) return; else if(serial) delete[] string; else switch(type) { case STRING: delete[] string; break; case OBJECT: delete[] object; break; case ARRAY: delete[] array; break; default: break; } } ircd::json::value::operator std::string() const { return json::strung(*this); } ircd::json::value::operator string_view() const { switch(type) { case STRING: return unquote(string_view{string, len}); case NUMBER: return serial? string_view{string, len}: floats? byte_view{floating}: byte_view{integer}; case ARRAY: case OBJECT: case LITERAL: if(likely(serial)) return string_view{string, len}; else break; } 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::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 { switch(type) { case NUMBER: return false; case STRING: return string == nullptr; case OBJECT: return serial? string == nullptr: object? false: true; case ARRAY: return serial? string == nullptr: array? false: true; case LITERAL: return serial? string == nullptr: true; }; throw type_error("deciding if a type[%u] is undefined is undefined", int(type)); } void ircd::json::value::create_string(const size_t &len, const create_string_closure &closure) { const size_t max { len + 1 }; std::unique_ptr string { new char[max] }; const mutable_buffer buffer { string.get(), len }; closure(buffer); (string.get())[len] = '\0'; this->alloc = true; this->serial = true; this->len = len; this->string = string.release(); } bool ircd::json::operator>(const value &a, const value &b) { if(unlikely(type(a) != STRING || type(b) != STRING)) throw type_error("cannot compare values"); return static_cast(a) > static_cast(b); } bool ircd::json::operator<(const value &a, const value &b) { if(unlikely(type(a) != STRING || type(b) != STRING)) throw type_error("cannot compare values"); return static_cast(a) < static_cast(b); } bool ircd::json::operator>=(const value &a, const value &b) { if(unlikely(type(a) != STRING || type(b) != STRING)) throw type_error("cannot compare values"); return static_cast(a) >= static_cast(b); } bool ircd::json::operator<=(const value &a, const value &b) { if(unlikely(type(a) != STRING || type(b) != STRING)) throw type_error("cannot compare values"); return static_cast(a) <= static_cast(b); } bool ircd::json::operator!=(const value &a, const value &b) { if(unlikely(type(a) != STRING || type(b) != STRING)) throw type_error("cannot compare values"); return static_cast(a) != static_cast(b); } bool ircd::json::operator==(const value &a, const value &b) { if(unlikely(type(a) != STRING || type(b) != STRING)) throw type_error("cannot compare values"); return static_cast(a) == static_cast(b); } /////////////////////////////////////////////////////////////////////////////// // // json.h // size_t ircd::json::serialized(const string_view &s) { size_t ret { s.size() }; switch(type(s, std::nothrow)) { case NUMBER: case OBJECT: case ARRAY: case LITERAL: break; case STRING: ret += !startswith(s, '"'); ret += !endswith(s, '"'); break; } return ret; } enum ircd::json::type ircd::json::type(const string_view &buf) { static const auto flag(qi::skip_flag::dont_postskip); enum type ret; if(!qi::phrase_parse(begin(buf), end(buf), parser.type, parser.WS, flag, ret)) throw type_error("Failed to get type from buffer"); return ret; } enum ircd::json::type ircd::json::type(const string_view &buf, std::nothrow_t) { static const auto flag(qi::skip_flag::dont_postskip); enum type ret; if(!qi::phrase_parse(begin(buf), end(buf), parser.type, parser.WS, flag, ret)) return STRING; return ret; } ircd::string_view ircd::json::reflect(const enum type &type) { switch(type) { case NUMBER: return "NUMBER"; case OBJECT: return "OBJECT"; case ARRAY: return "ARRAY"; case LITERAL: return "LITERAL"; case STRING: return "STRING"; } return {}; }