From 5e67f7af01440b6f91e4aae5bfc792202f6cec8f Mon Sep 17 00:00:00 2001 From: Niels Date: Mon, 15 Aug 2016 22:44:14 +0200 Subject: [PATCH] added a first version of a parser for #290 --- src/json.hpp | 93 +++++++++++++++++++++++++++---- src/json.hpp.re2c | 93 +++++++++++++++++++++++++++---- test/src/unit-deserialization.cpp | 50 ++++++++++++++++- 3 files changed, 215 insertions(+), 21 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 03c16253..6bafd007 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -5962,6 +5962,16 @@ class basic_json return parser(s, cb).parse(); } + /*! + @brief deserialize from string literal + @copydoc parse(const string_t&, const parser_callback_t) + */ + static basic_json parse(const typename string_t::value_type* s, + const parser_callback_t cb = nullptr) + { + return parser(s, cb).parse(); + } + /*! @brief deserialize from stream @@ -6001,6 +6011,75 @@ class basic_json return parser(i, cb).parse(); } + /*! + @brief deserialize from a container with contiguous storage + + This function reads from a nonempty iterator range of a container with + contiguous storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with an assertion.** + @pre The iterator range is nonempty. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + + @warning There is no way to enforce the preconditions at compile-time. If + the function is called with noncompliant iterators, the behavior + is undefined and will most liekely yield segmentation violation. + + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @todo Example and references. + + @since version 2.0.3 + */ + template ::iterator_category, std::random_access_iterator_tag>::value + , int>::type + = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate(first, last, std::make_pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + assert(std::all_of(first, last, [](decltype(*first) val) + { + return sizeof(val) == 1; + })); + + // assertion that the iterator range is not empty + assert(std::distance(first, last) > 0); + + return parser(first, last, cb).parse(); + } + /*! @brief deserialize from stream @@ -8875,10 +8954,10 @@ basic_json_parser_63: { public: /// a parser reading from a string literal - parser(const typename string_t::value_type* buff, parser_callback_t cb = nullptr) + parser(const typename string_t::value_type* buff, + const parser_callback_t cb = nullptr) : callback(cb), - m_lexer(reinterpret_cast(buff), - strlen(buff)) + m_lexer(reinterpret_cast(buff), strlen(buff)) {} /// a parser reading from a string container @@ -8902,13 +8981,7 @@ basic_json_parser_63: : callback(cb), m_lexer(reinterpret_cast(&(*first)), static_cast(std::distance(first, last))) - { - int i = 0; - assert(std::accumulate(first, last, true, [&i, &first](bool res, decltype(*first) val) - { - return res and (val == *(std::next(std::addressof(*first), i++))); - })); - } + {} /// public parser interface basic_json parse() diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 58724e91..87f7aabc 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -5962,6 +5962,16 @@ class basic_json return parser(s, cb).parse(); } + /*! + @brief deserialize from string literal + @copydoc parse(const string_t&, const parser_callback_t) + */ + static basic_json parse(const typename string_t::value_type* s, + const parser_callback_t cb = nullptr) + { + return parser(s, cb).parse(); + } + /*! @brief deserialize from stream @@ -6001,6 +6011,75 @@ class basic_json return parser(i, cb).parse(); } + /*! + @brief deserialize from a container with contiguous storage + + This function reads from a nonempty iterator range of a container with + contiguous storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with an assertion.** + @pre The iterator range is nonempty. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + + @warning There is no way to enforce the preconditions at compile-time. If + the function is called with noncompliant iterators, the behavior + is undefined and will most liekely yield segmentation violation. + + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @todo Example and references. + + @since version 2.0.3 + */ + template ::iterator_category, std::random_access_iterator_tag>::value + , int>::type + = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate(first, last, std::make_pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + assert(std::all_of(first, last, [](decltype(*first) val) + { + return sizeof(val) == 1; + })); + + // assertion that the iterator range is not empty + assert(std::distance(first, last) > 0); + + return parser(first, last, cb).parse(); + } + /*! @brief deserialize from stream @@ -8172,10 +8251,10 @@ class basic_json { public: /// a parser reading from a string literal - parser(const typename string_t::value_type* buff, parser_callback_t cb = nullptr) + parser(const typename string_t::value_type* buff, + const parser_callback_t cb = nullptr) : callback(cb), - m_lexer(reinterpret_cast(buff), - strlen(buff)) + m_lexer(reinterpret_cast(buff), strlen(buff)) {} /// a parser reading from a string container @@ -8199,13 +8278,7 @@ class basic_json : callback(cb), m_lexer(reinterpret_cast(&(*first)), static_cast(std::distance(first, last))) - { - int i = 0; - assert(std::accumulate(first, last, true, [&i, &first](bool res, decltype(*first) val) - { - return res and (val == *(std::next(std::addressof(*first), i++))); - })); - } + {} /// public parser interface basic_json parse() diff --git a/test/src/unit-deserialization.cpp b/test/src/unit-deserialization.cpp index 78188574..0e5e2122 100644 --- a/test/src/unit-deserialization.cpp +++ b/test/src/unit-deserialization.cpp @@ -31,6 +31,8 @@ SOFTWARE. #include "json.hpp" using nlohmann::json; +#include + TEST_CASE("deserialization") { SECTION("stream") @@ -41,13 +43,20 @@ TEST_CASE("deserialization") CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}})); } - SECTION("string") + SECTION("string literal") { auto s = "[\"foo\",1,2,3,false,{\"one\":1}]"; json j = json::parse(s); CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}})); } + SECTION("string_t") + { + json::string_t s = "[\"foo\",1,2,3,false,{\"one\":1}]"; + json j = json::parse(s); + CHECK(j == json({"foo", 1, 2, 3, false, {{"one", 1}}})); + } + SECTION("operator<<") { std::stringstream ss; @@ -70,4 +79,43 @@ TEST_CASE("deserialization") { CHECK("[\"foo\",1,2,3,false,{\"one\":1}]"_json == json({"foo", 1, 2, 3, false, {{"one", 1}}})); } + + SECTION("contiguous containers") + { + SECTION("from std::vector") + { + std::vector v = {'t', 'r', 'u', 'e', '\0'}; + CHECK(json::parse(std::begin(v), std::end(v)) == json(true)); + } + + SECTION("from std::array") + { + std::array v { {'t', 'r', 'u', 'e', '\0'} }; + CHECK(json::parse(std::begin(v), std::end(v)) == json(true)); + } + + SECTION("from array") + { + uint8_t v[] = {'t', 'r', 'u', 'e'}; + CHECK(json::parse(std::begin(v), std::end(v)) == json(true)); + } + + SECTION("from std::string") + { + std::string v = {'t', 'r', 'u', 'e'}; + CHECK(json::parse(std::begin(v), std::end(v)) == json(true)); + } + + SECTION("from std::initializer_list") + { + std::initializer_list v = {'t', 'r', 'u', 'e', '\0'}; + CHECK(json::parse(std::begin(v), std::end(v)) == json(true)); + } + + SECTION("from std::valarray") + { + std::valarray v = {'t', 'r', 'u', 'e', '\0'}; + CHECK(json::parse(std::begin(v), std::end(v)) == json(true)); + } + } }