diff --git a/include/jsonv/all.hpp b/include/jsonv/all.hpp index fe94d9f..4c61478 100644 --- a/include/jsonv/all.hpp +++ b/include/jsonv/all.hpp @@ -731,6 +731,7 @@ namespace jsonv #include "parse.hpp" #include "parse_index.hpp" #include "path.hpp" +#include "reader.hpp" #include "result.hpp" #include "serialization.hpp" #include "serialization_builder.hpp" diff --git a/include/jsonv/ast.hpp b/include/jsonv/ast.hpp index 82ef084..f8fef37 100644 --- a/include/jsonv/ast.hpp +++ b/include/jsonv/ast.hpp @@ -134,7 +134,7 @@ enum class ast_error : std::uint64_t expected_key_delimiter, unexpected_token, unexpected_comma, - eof, + unexpected_eof, expected_eof, depth_exceeded, extra_close, @@ -164,7 +164,7 @@ class ast_node final { public: /// Get the \c ast_node_type type. - constexpr ast_node_type type() const + static constexpr ast_node_type type() { return KIndexToken; } @@ -430,7 +430,8 @@ class ast_node final literal_null, integer, decimal, - error>; + error + >; public: ast_node(const storage_type& value) : @@ -457,6 +458,13 @@ class ast_node final return std::visit(std::forward(visitor), _impl); } + /// Convenience function for calling \c std::visit on a key (see \c as_key). + template + auto visit_key(FVisitor&& visitor) const + { + return std::visit(std::forward(visitor), as_key()); + } + /// Get the \c ast_node_type that tells the underlying type of this instance. ast_node_type type() const { @@ -479,6 +487,17 @@ class ast_node final return std::get(_impl); } + /// Get the underlying data of this node as one of the key types: \c key_canonical or \c key_escaped. + /// + /// \throw std::bad_variant_access if the \c type of this instance is neither \c key_canonical nor \c key_escaped. + std::variant as_key() const + { + if (type() == ast_node_type::key_canonical) + return as(); + else + return as(); + } + private: storage_type _impl; }; diff --git a/include/jsonv/detail/scope_exit.hpp b/include/jsonv/detail/scope_exit.hpp index ce8f1cb..afca180 100644 --- a/include/jsonv/detail/scope_exit.hpp +++ b/include/jsonv/detail/scope_exit.hpp @@ -1,24 +1,20 @@ -/** \file jsonv/detail/scope_exit.hpp - * Definition of the \c on_scope_exit utility. - * - * Copyright (c) 2015 by Travis Gockel. All rights reserved. - * - * This program is free software: you can redistribute it and/or modify it under the terms of the Apache License - * as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later - * version. - * - * \author Travis Gockel (travis@gockelhut.com) -**/ -#ifndef __JSONV_DETAIL_SCOPE_EXIT_HPP_INCLUDED__ -#define __JSONV_DETAIL_SCOPE_EXIT_HPP_INCLUDED__ +/// \file jsonv/detail/scope_exit.hpp +/// Definition of the \c on_scope_exit utility. +/// +/// Copyright (c) 2015 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#pragma once #include #include -namespace jsonv -{ -namespace detail +namespace jsonv::detail { template @@ -29,29 +25,29 @@ class scope_exit_invoker _func(std::move(func)), _responsible(true) { } - + scope_exit_invoker(scope_exit_invoker&& src) : _func(std::move(src._func)), _responsible(src._responsible) { src._responsible = false; } - + scope_exit_invoker(const scope_exit_invoker&) = delete; scope_exit_invoker& operator=(const scope_exit_invoker&) = delete; scope_exit_invoker& operator=(scope_exit_invoker&&) = delete; - + ~scope_exit_invoker() { if (_responsible) _func(); } - + void release() { _responsible = false; } - + private: Function _func; bool _responsible; @@ -64,6 +60,3 @@ scope_exit_invoker on_scope_exit(Function func) } } -} - -#endif/*__JSONV_DETAIL_SCOPE_EXIT_HPP_INCLUDED__*/ diff --git a/include/jsonv/forward.hpp b/include/jsonv/forward.hpp index 9b7b2c3..47d3d61 100644 --- a/include/jsonv/forward.hpp +++ b/include/jsonv/forward.hpp @@ -37,6 +37,7 @@ class path; class path_element; enum class path_element_kind : unsigned char; template class polymorphic_adapter_builder; +class reader; template class result; class serializer; class serialization_context; diff --git a/include/jsonv/reader.hpp b/include/jsonv/reader.hpp new file mode 100644 index 0000000..b0f6cf8 --- /dev/null +++ b/include/jsonv/reader.hpp @@ -0,0 +1,261 @@ +/// \file jsonv/reader.hpp +/// Read a JSON AST. +/// +/// Copyright (c) 2015-2022 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace jsonv +{ + +class parse_index; +class parse_options; +class path; +class value; + +/// \ingroup Serialization +/// \{ + +/// A reader instance reads from some form of JSON source (probably a string) and converts it into a JSON \ref ast_node +/// sequence. +/// +/// Readers normalize access to JSON source for conversion to some other format. They can be provided with pre-parsed +/// JSON through a \c parse_index or \c value. They can be provided with a \c std::string or \c string_view directly. +/// This allows \c extractor implementations to operate on all forms of JSON without worrying about the implementation. +class JSONV_PUBLIC reader final +{ +public: + /// Create a reader which reads from the given \a index. + explicit reader(parse_index index); + + /// Create a reader which reads from an in-memory \a value. + /// + /// \param value The value to read from. This must remain valid for the lifetime of the reader. + explicit reader(const value* value); + + /// \{ + /// Create a reader which reads from JSON \a source. + /// + /// \param source The JSON source code to parse from. This must stay in memory for the duration of this instance's + /// use. If source is an rvalue reference to a \c std::string instance, the \a source is moved to the + /// reader's implementation to keep alive. + /// \param parse_options If specified, use these options to parse \a source. If unspecified, use the default values + /// of \a parse_options for \c parse_index::parse. + explicit reader(string_view source); + explicit reader(string_view source, const parse_options& parse_options); + explicit reader(const char* source); + explicit reader(const char* source, const parse_options& parse_options); + + explicit reader(std::string&& source); + explicit reader(std::string&& source, const parse_options& parse_options); + /// \} + + // Not copyable. + reader(const reader&) = delete; + reader& operator=(const reader&) = delete; + + reader(reader&&) noexcept = default; + reader& operator=(reader&&) noexcept = default; + + ~reader() noexcept; + + /// Check if this reader is still good to read from. This will be \c true if this instance has not been moved-from + /// and has not reached EOF. If this is \c false, \c current or \c current_path will throw an exception. + bool good() const; + + /// Get the current AST node this reader is pointing at. + /// + /// \throws std::invalid_argument if this instance is not \c good. + const ast_node& current() const; + + /// \{ + /// Check that the \c current AST node has the given \a type or is one of the expected \a types. Using this leads to + /// a slightly more informative error message than `current.as()` call. + /// + /// \throws extraction_error if the current node does not match the expected \a type or \a types. + /// \throws std::invalid_argument if this instance is not \c good. + void expect(ast_node_type type); + void expect(std::initializer_list types); + /// \} + + /// Get the \c current AST node as a specific \c TAstNode subtype, calling \c expect beforehand. + /// + /// \throws std::invalid_argument if this instance is not \c good. + /// \throws extraction_error if the current node does not match `TAstNode::type()`. + template + TAstNode current_as() const + { + expect(TAstNode::type()); + return current().as(); + } + + /// Get the path to the current node this reader is pointing at. This is used in the generation of error messages to + /// describe the location of something that could not be extracted. + /// + /// \code + /// ^ /* "." -- start of document is the empty path */ + /// { /* "." -- opening { is still an empty path */ + /// "a": /* ".a" -- the key starts the path */ + /// [ /* ".a" -- the path refers to the entire array */ + /// 1, /* ".a[0]" */ + /// 2, /* ".a[1]" */ + /// 3, /* ".a[2]" */ + /// ], /* ".a" -- the path at the end of the array refers to the entire array again */ + /// "b": /* ".b" */ + /// { /* ".b" -- the path refers to the entire object */ + /// "x": /* ".b.x" */ + /// "taco" /* ".b.x" */ + /// }, /* ".b" */ + /// "c": /* ".c" */ + /// 4 /* ".c" */ + /// } /* "." */ + /// $ /* "." */ + /// \endcode + /// + /// \throws std::invalid_argument if this instance is not \c good. + const path& current_path() const; + + /// Go to the next token. + /// + /// \code + /// ^ + /// { /* <- go to "a" */ + /// "a": /* <- go to [ */ + /// [ /* <- go to 1 */ + /// 1, /* <- go to 2 */ + /// 2, /* ...and so on */ + /// 3, + /// ], + /// "b": + /// { + /// }, + /// "c": + /// 4 + /// } + /// $ + /// \endcode + /// + /// \returns \c true if the reader is still \c good to read from \c current. + [[nodiscard]] + bool next_token() noexcept; + + /// Go to one past the end of the current structure. + /// + /// \code + /// ^ + /// { + /// "a": /* <- go to end of document */ + /// [ /* <- go to "b" */ + /// 1, /* <- go to "b", too */ + /// 2, + /// 3, + /// ], /* <- go to "b" */ + /// "b": /* <- go to end of document */ + /// { /* <- go to "c" */ + /// }, /* <- go to "c" */ + /// "c": + /// 4 + /// } /* <- go to end of document */ + /// $ + /// \endcode + /// + /// This is meant to be used when parsing an object or array + /// + /// \code + /// my_object extract_my_object(jsonv::reader& from) + /// { + /// from.expect(jsonv::ast_node_type::object_begin); + /// from.next_token(); + /// + /// // Use this helper macro to always execute code + /// JSONV_SCOPE_EXIT { from.next_structure(); }; + /// + /// std::optional a; + /// while (from.good()) + /// { + /// auto token = from.current(); + /// if (token.type() == jsonv::ast_node_type::object_end) + /// { + /// return my_object(a.value_or(0)); + /// } + /// else if (token.type() == jsonv::ast_node_type::key_canonical) + /// { + /// if (token.value() == "a") + /// { + /// reader.next_token(); + /// a = reader.current_as().value(); + /// reader.next_token(); + /// } + /// else + /// { + /// // ignore + /// reader.next_key(); + /// } + /// } + /// else + /// { + /// throw jsonv::extraction_error(from.current_path(), "Did not handle escaped keys"); + /// } + /// } + /// } + /// \endcode + /// + /// \returns \c true if the reader is still \c good to read from \c current. + [[nodiscard]] + bool next_structure() noexcept; + + /// Go to the next object key or end-of-object. + /// + /// \code + /// ^ + /// { + /// "a": /* <- calling here goes to "b" */ + /// [ /* <- calling when not on an object key throws */ + /// 1, + /// 2, + /// 3, + /// ], + /// "b": /* <- calling here goes to "c" */ + /// { + /// }, + /// "c": /* <- calling here goes to end of object */ + /// 4 + /// } + /// $ + /// \endcode + /// + /// \returns \c true if the reader is still \c good to read from \c current. + /// \throws std::invalid_argument if the reader is not currently at the start of a key. + [[nodiscard]] + bool next_key() noexcept; + +private: + class impl; + class impl_parse_index; + class impl_parse_index_owning; + class impl_value; + + template + explicit reader(std::in_place_type_t, TArgs&&...); + +private: + std::unique_ptr _impl; +}; + +/// \} + +} diff --git a/include/jsonv/serialization.hpp b/include/jsonv/serialization.hpp index 0bb0292..8573c17 100644 --- a/include/jsonv/serialization.hpp +++ b/include/jsonv/serialization.hpp @@ -601,10 +601,10 @@ class JSONV_PUBLIC extraction_context : public context_base { public: - /** Create a new instance using the default \c formats (\c formats::global). **/ + /// Create a new instance using the default \c formats (\c formats::global). extraction_context(); - /** Create a new instance using the given \a fmt, \a ver and \a p. **/ + /// Create a new instance using the given \a fmt, \a ver and \a p. explicit extraction_context(jsonv::formats fmt, const jsonv::version& ver = jsonv::version(), jsonv::path p = jsonv::path(), @@ -613,9 +613,8 @@ class JSONV_PUBLIC extraction_context : virtual ~extraction_context() noexcept; - /** Get the current \c path this \c extraction_context is extracting for. This is useful when debugging and - * generating error messages. - **/ + /// Get the current \c path this \c extraction_context is extracting for. This is useful when debugging and + /// generating error messages. const jsonv::path& path() const { return _path; diff --git a/src/jsonv-tests/reader_tests.cpp b/src/jsonv-tests/reader_tests.cpp new file mode 100644 index 0000000..104c5b8 --- /dev/null +++ b/src/jsonv-tests/reader_tests.cpp @@ -0,0 +1,463 @@ +/// \file +/// +/// Copyright (c) 2020 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#include + +#include +#include + +#include +#include +#include +#include + +#ifndef JSONV_READER_TESTS_LOG_ENABLED +# define JSONV_READER_TESTS_LOG_ENABLED 0 +#endif + +#if JSONV_READER_TESTS_LOG_ENABLED +# define JSONV_READER_TESTS_LOG(...) std::cout << __VA_ARGS__ +#else +# define JSONV_READER_TESTS_LOG(...) do {} while (false) +#endif + +namespace jsonv_tests +{ + +namespace +{ + +struct walk_info +{ + jsonv::ast_node_type type; + jsonv::path path; + std::size_t next_key_idx; + std::size_t next_struct_idx; + + walk_info(jsonv::ast_node_type type, + jsonv::string_view path, + std::size_t next_key_idx, + std::size_t next_struct_idx) : + type(type), + path(jsonv::path::create(path)), + next_key_idx(next_key_idx), + next_struct_idx(next_struct_idx) + { } +}; + +struct example_data_type +{ + jsonv::string_view name; + jsonv::string_view source; + std::vector expected; +}; + +} + +template +static void walk_expecting(jsonv::reader& reader, + const std::vector& expected, + FShouldJumpNextKey&& should_jump_next_key, + FShouldJumpNextStruct&& should_jump_next_struct + ) +{ + + ensure_eq(jsonv::ast_node_type::document_start, reader.current().type()); + ensure(reader.next_token()); + + for (std::size_t idx = 0U; idx < expected.size(); /* inline */) + { + auto current = reader.current(); + + ensure_eq(expected[idx].type, current.type()); + ensure_eq(expected[idx].path, reader.current_path()); + + if (auto type = current.type(); + ( type == jsonv::ast_node_type::object_begin + || type == jsonv::ast_node_type::key_canonical + || type == jsonv::ast_node_type::key_escaped + ) + && should_jump_next_key(current, idx) + ) + { + ensure(reader.next_key()); + + JSONV_READER_TESTS_LOG + ( + std::endl + << " - next_key: from " << expected[idx].path << " @ [" << idx << "] " + << "to " << expected[expected[idx].next_key_idx].path << " @ [" << expected[idx].next_key_idx << "]" + ); + idx = expected[idx].next_key_idx; + } + else if (should_jump_next_struct(current, idx)) + { + JSONV_READER_TESTS_LOG(std::endl << " - next_struct: from " << expected[idx].path << " @ [" << idx << "] "); + ensure(reader.next_structure()); + JSONV_READER_TESTS_LOG("to [" << expected[idx].next_struct_idx << "]"); + idx = expected[idx].next_struct_idx; + } + else + { + ensure(reader.next_token()); + ++idx; + } + } + + ensure_eq(jsonv::ast_node_type::document_end, reader.current().type()); + ensure(!reader.next_token()); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Examples // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static const char example_basic_source[] = + R"( + { + "a": [ 1, 2, 3 ], + "b": { "key": "value" }, + "c": 4 + } + )"; + +static std::vector example_basic_walk_info = +{ // node type, path, key, struct, listing + { jsonv::ast_node_type::object_begin, ".", 1U, 15U }, // 0: { + { jsonv::ast_node_type::key_canonical, ".a", 7U, 15U }, // 1: "a": + { jsonv::ast_node_type::array_begin, ".a", 0U, 7U }, // 2: [ + { jsonv::ast_node_type::integer, ".a[0]", 0U, 7U }, // 3: 1, + { jsonv::ast_node_type::integer, ".a[1]", 0U, 7U }, // 4: 2, + { jsonv::ast_node_type::integer, ".a[2]", 0U, 7U }, // 5: 3 + { jsonv::ast_node_type::array_end, ".a", 0U, 7U }, // 6: ], + { jsonv::ast_node_type::key_canonical, ".b", 12U, 15U }, // 7: "b": + { jsonv::ast_node_type::object_begin, ".b", 9U, 12U }, // 8: { + { jsonv::ast_node_type::key_canonical, ".b.key", 11U, 12U }, // 9: "key": + { jsonv::ast_node_type::string_canonical, ".b.key", 0U, 12U }, // 10: "value" + { jsonv::ast_node_type::object_end, ".b", 0U, 12U }, // 11: }, + { jsonv::ast_node_type::key_canonical, ".c", 14U, 15U }, // 12: "c": + { jsonv::ast_node_type::integer, ".c", 0U, 15U }, // 13: 4 + { jsonv::ast_node_type::object_end, ".", 0U, 15U }, // 14: } +}; + +static const char example_nestings_source[] = + R"( + { + "cat": [ {}, {}, { "mouse": [{}] }, "\n" ], + "dog": { "food": [[[[], [-5]], 3.14]] } + } + )"; + +static std::vector example_nestings_walk_info = +{ // node type, path, key, struct, listing + { jsonv::ast_node_type::object_begin, ".", 1U, 33U }, // 0: { + { jsonv::ast_node_type::key_canonical, ".cat", 16U, 33U }, // 1: "cat": + { jsonv::ast_node_type::array_begin, ".cat", 0U, 16U }, // 2: [ + { jsonv::ast_node_type::object_begin, ".cat[0]", 4U, 5U }, // 3: { + { jsonv::ast_node_type::object_end, ".cat[0]", 0U, 5U }, // 4: }, + { jsonv::ast_node_type::object_begin, ".cat[1]", 6U, 7U }, // 5: { + { jsonv::ast_node_type::object_end, ".cat[1]", 0U, 7U }, // 6: }, + { jsonv::ast_node_type::object_begin, ".cat[2]", 8U, 14U }, // 7: { + { jsonv::ast_node_type::key_canonical, ".cat[2].mouse", 13U, 14U }, // 8: "mouse": + { jsonv::ast_node_type::array_begin, ".cat[2].mouse", 0U, 13U }, // 9: [ + { jsonv::ast_node_type::object_begin, ".cat[2].mouse[0]", 11U, 12U }, // 10: { + { jsonv::ast_node_type::object_end, ".cat[2].mouse[0]", 0U, 12U }, // 11: } + { jsonv::ast_node_type::array_end, ".cat[2].mouse", 0U, 13U }, // 12: ] + { jsonv::ast_node_type::object_end, ".cat[2]", 0U, 14U }, // 13: }, + { jsonv::ast_node_type::string_escaped, ".cat[3]", 0U, 16U }, // 14: "\n" + { jsonv::ast_node_type::array_end, ".cat", 0U, 16U }, // 15: ], + { jsonv::ast_node_type::key_canonical, ".dog", 32U, 33U }, // 16: "dog": + { jsonv::ast_node_type::object_begin, ".dog", 18U, 32U }, // 17: { + { jsonv::ast_node_type::key_canonical, ".dog.food", 31U, 32U }, // 18: "food": + { jsonv::ast_node_type::array_begin, ".dog.food", 0U, 31U }, // 19: [ + { jsonv::ast_node_type::array_begin, ".dog.food[0]", 0U, 30U }, // 20: [ + { jsonv::ast_node_type::array_begin, ".dog.food[0][0]", 0U, 28U }, // 21: [ + { jsonv::ast_node_type::array_begin, ".dog.food[0][0][0]", 0U, 24U }, // 22: [ + { jsonv::ast_node_type::array_end, ".dog.food[0][0][0]", 0U, 24U }, // 23: ], + { jsonv::ast_node_type::array_begin, ".dog.food[0][0][1]", 0U, 27U }, // 24: [ + { jsonv::ast_node_type::integer, ".dog.food[0][0][1][0]", 0U, 27U }, // 25: -5 + { jsonv::ast_node_type::array_end, ".dog.food[0][0][1]", 0U, 27U }, // 26: ] + { jsonv::ast_node_type::array_end, ".dog.food[0][0]", 0U, 28U }, // 27: ], + { jsonv::ast_node_type::decimal, ".dog.food[0][1]", 0U, 30U }, // 28: 3.14 + { jsonv::ast_node_type::array_end, ".dog.food[0]", 0U, 30U }, // 29: ] + { jsonv::ast_node_type::array_end, ".dog.food", 0U, 31U }, // 30: ] + { jsonv::ast_node_type::object_end, ".dog", 0U, 32U }, // 31: } + { jsonv::ast_node_type::object_end, ".", 0U, 33U }, // 32: } +}; + +static const char example_array_source[] = R"([ "a", "b", "c" ])"; + +static const std::vector example_array_walk_info = +{ // node type, path, key, struct, listing + { jsonv::ast_node_type::array_begin, ".", 0U, 5U }, // 0: [ + { jsonv::ast_node_type::string_canonical, "[0]", 0U, 5U }, // 1: "a", + { jsonv::ast_node_type::string_canonical, "[1]", 0U, 5U }, // 2: "b", + { jsonv::ast_node_type::string_canonical, "[2]", 0U, 5U }, // 3: "c" + { jsonv::ast_node_type::array_end, ".", 0U, 5U }, // 4: ] +}; + +static const char example_string_source[] = R"("taco\nstand")"; + +static const std::vector example_string_walk_info = +{ + { jsonv::ast_node_type::string_escaped, ".", 0U, 1U } +}; + +static const std::vector examples = +{ + { "basic", example_basic_source, example_basic_walk_info }, + { "nestings", example_nestings_source, example_nestings_walk_info }, + { "array", example_array_source, example_array_walk_info }, + { "string", example_string_source, example_string_walk_info }, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Unit Tests // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/// These tests walk through the entire object without doing any jumps. +class reader_example_walkthrough_no_jump_test final : + public jsonv_test::unit_test +{ +public: + reader_example_walkthrough_no_jump_test(example_data_type data) : + jsonv_test::unit_test(std::string("reader_example_walkthrough_no_jump/") + std::string(data.name)), + _data(std::move(data)) + { } + + static std::vector> make_tests(const example_data_type& data) + { + std::vector> out; + out.emplace_back(std::make_unique(data)); + return out; + } + +protected: + virtual void run_impl() override + { + jsonv::reader reader(_data.source); + walk_expecting(reader, _data.expected, [](auto&&...) { return false; }, [](auto&&...) { return false; }); + } + +private: + example_data_type _data; +}; + +static void walk_expecting_next_struct_at_index(jsonv::string_view src, + const std::vector& expected, + std::size_t jump_from_idx + ) +{ + JSONV_READER_TESTS_LOG(std::endl << "to " << jump_from_idx << "..."); + jsonv::reader reader(src); + walk_expecting(reader, + expected, + [](auto&&...) { return false; }, + [&](auto&&, auto idx) { return idx == jump_from_idx; } + ); +} + +class reader_example_walkthrough_next_struct_at_index final : + public jsonv_test::unit_test +{ +public: + reader_example_walkthrough_next_struct_at_index(example_data_type data, std::size_t next_struct_at_idx) : + jsonv_test::unit_test(std::string("reader_example_walkthrough_next_struct_at_index/") + + std::string(data.name) + "/" + std::to_string(next_struct_at_idx)), + _data(std::move(data)), + _next_struct_at_idx(next_struct_at_idx) + { } + + static std::vector> make_tests(const example_data_type& data) + { + std::vector> out; + out.reserve(data.expected.size()); + for (std::size_t idx = 0U; idx < data.expected.size(); ++idx) + out.emplace_back(std::make_unique(data, idx)); + return out; + } + +protected: + virtual void run_impl() override + { + walk_expecting_next_struct_at_index(_data.source, _data.expected, _next_struct_at_idx); + } + +private: + example_data_type _data; + std::size_t _next_struct_at_idx; +}; + +static void walk_expecting_next_key_at_index(jsonv::string_view src, + const std::vector& expected, + std::size_t jump_from_idx + ) +{ + JSONV_READER_TESTS_LOG(std::endl << "to " << jump_from_idx << "..."); + jsonv::reader reader(src); + walk_expecting(reader, + expected, + [&](auto&&, auto idx) { return idx == jump_from_idx; }, + [](auto&&...) { return false; } + ); +} + +class reader_example_walkthrough_next_key_at_index final : + public jsonv_test::unit_test +{ +public: + reader_example_walkthrough_next_key_at_index(example_data_type data, std::size_t next_key_at_idx) : + jsonv_test::unit_test(std::string("reader_example_walkthrough_next_key_at_index/") + + std::string(data.name) + "/" + std::to_string(next_key_at_idx)), + _data(std::move(data)), + _next_key_at_idx(next_key_at_idx) + { } + + static std::vector> make_tests(const example_data_type& data) + { + std::vector> out; + out.reserve(data.expected.size()); + for (std::size_t idx = 0U; idx < data.expected.size(); ++idx) + { + if (data.expected[idx].next_key_idx != 0U) + out.emplace_back(std::make_unique(data, idx)); + } + return out; + } + +protected: + virtual void run_impl() override + { + walk_expecting_next_key_at_index(_data.source, _data.expected, _next_key_at_idx); + } + +private: + example_data_type _data; + std::size_t _next_key_at_idx; +}; + +/// Walk through \a source, randomly calling \c next_key with \a next_key_probability (when current position is a key +/// type) and \c next_struct with \a next_struct_probability. The random number generator is seeded with \a rng_seed so +/// behavior is deterministic. +static void walk_random_expecting(jsonv::string_view source, + const std::vector& expected, + double next_key_probability = 0.0, + double next_struct_probability = 0.0, + std::size_t rng_seed = 0U + ) +{ + if ((next_key_probability > 0.0 || next_struct_probability > 0.0) && rng_seed == 0U) + { + rng_seed = std::random_device{}(); + JSONV_READER_TESTS_LOG("seed=" << rng_seed); + } + + std::minstd_rand rng(rng_seed); + std::bernoulli_distribution next_key_dist(next_key_probability); + std::bernoulli_distribution next_struct_dist(next_struct_probability); + + jsonv::reader reader(source); + return walk_expecting(reader, + expected, + [&](auto&&...) { return next_key_dist(rng); }, + [&](auto&&...) { return next_struct_dist(rng); } + ); +} + +class reader_example_walkthrough_randomly final : + public jsonv_test::unit_test +{ +public: + reader_example_walkthrough_randomly(example_data_type data, + double next_struct_probability, + double next_key_probability, + std::size_t seed + ) : + jsonv_test::unit_test(std::string("reader_example_walkthrough_randomly/") + + std::string(data.name) + + "/P(next_struct=" + std::to_string(next_struct_probability) + + ",next_key=" + std::to_string(next_key_probability) + ")/" + + std::to_string(seed)), + _data(std::move(data)), + _next_struct_probability(next_struct_probability), + _next_key_probability(next_key_probability), + _seed(seed) + { } + + static std::vector> make_tests(const example_data_type& data) + { + if (data.expected.size() == 1U) + return {}; + + std::vector> out; + + std::mt19937_64 rng(std::random_device{}()); + std::uniform_int_distribution dist; + + for (double next_struct_p : { 0.0, 0.1, 0.2, 0.4, 0.8 }) + { + for (double next_key_p : { 0.0, 0.1, 0.2, 0.4, 0.8 }) + { + for (std::size_t seed_idx = 0U; seed_idx < 10U; ++seed_idx) + { + out.emplace_back(std::make_unique(data, + next_struct_p, + next_key_p, + dist(rng) + ) + ); + } + } + } + + // Throw in some random distributions for good measure + std::uniform_real_distribution next_struct_dist; + std::uniform_real_distribution next_key_dist; + for (std::size_t count = 0U; count < data.expected.size(); ++count) + { + out.emplace_back(std::make_unique(data, + next_struct_dist(rng), + next_key_dist(rng), + dist(rng) + ) + ); + } + + return out; + } + +protected: + virtual void run_impl() override + { + walk_random_expecting(_data.source, _data.expected, _next_key_probability, _next_struct_probability, _seed); + } + +private: + example_data_type _data; + double _next_struct_probability; + double _next_key_probability; + std::size_t _seed; +}; + +static std::vector>> create_all_tests() +{ + std::vector>> out; + for (const auto& example : examples) + { + out.emplace_back(reader_example_walkthrough_no_jump_test::make_tests(example)); + out.emplace_back(reader_example_walkthrough_next_struct_at_index::make_tests(example)); + out.emplace_back(reader_example_walkthrough_next_key_at_index::make_tests(example)); + out.emplace_back(reader_example_walkthrough_randomly::make_tests(example)); + } + return out; +} + +static std::vector>> all_tests = create_all_tests(); + +} diff --git a/src/jsonv/ast.cpp b/src/jsonv/ast.cpp index 7d6e9cd..1e1133a 100644 --- a/src/jsonv/ast.cpp +++ b/src/jsonv/ast.cpp @@ -166,7 +166,7 @@ std::ostream& operator<<(std::ostream& os, const ast_error& src) case ast_error::none: return os << "none"; case ast_error::expected_document: return os << "expected document (object or array)"; case ast_error::unexpected_token: return os << "unexpected token"; - case ast_error::eof: return os << "input ended unexpectedly"; + case ast_error::unexpected_eof: return os << "input ended unexpectedly"; case ast_error::expected_eof: return os << "extra characters in input"; case ast_error::depth_exceeded: return os << "max structural depth exceeded"; case ast_error::extra_close: return os << "extra closing character"; diff --git a/src/jsonv/char_convert.cpp b/src/jsonv/char_convert.cpp index 79767aa..067cc2f 100644 --- a/src/jsonv/char_convert.cpp +++ b/src/jsonv/char_convert.cpp @@ -524,10 +524,8 @@ std::string string_decode(string_view source) remaining_utf8_sequence = utf8_length - 1; ++idx; } - else if (require_printable && !is_print(current)) + else if (require_printable && !is_print(current)) JSONV_UNLIKELY { - JSONV_UNLIKELY - std::ostringstream os; os << "Unprintable character found in input: "; switch (current) @@ -556,10 +554,8 @@ std::string string_decode(string_view source) --remaining_utf8_sequence; } // not on a UTF8 continuation, even though we should be... - else + else JSONV_UNLIKELY { - JSONV_UNLIKELY - std::ostringstream os; os << "Invalid UTF-8 multi-byte sequence in source: \""; for (size_type pos = utf8_sequence_start; pos <= idx; ++pos) @@ -572,10 +568,8 @@ std::string string_decode(string_view source) } } - if (remaining_utf8_sequence > 0) + if (remaining_utf8_sequence > 0) JSONV_UNLIKELY { - JSONV_UNLIKELY - std::ostringstream os; os << "unterminated UTF-8 sequence at end of string: \""; os << std::hex; diff --git a/src/jsonv/detail/overload.hpp b/src/jsonv/detail/overload.hpp new file mode 100644 index 0000000..4d0d5ac --- /dev/null +++ b/src/jsonv/detail/overload.hpp @@ -0,0 +1,26 @@ +/// \file jsonv/detail/overload.hpp +/// +/// Copyright (c) 2020 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#pragma once + +#include + +namespace jsonv::detail +{ + +template +struct overload : F... +{ + using F::operator()...; +}; + +template +overload(F...) -> overload; + +} diff --git a/src/jsonv/parse_index.cpp b/src/jsonv/parse_index.cpp index 34673b7..7dd0c5b 100644 --- a/src/jsonv/parse_index.cpp +++ b/src/jsonv/parse_index.cpp @@ -21,6 +21,7 @@ #include "detail/fallthrough.hpp" #include "detail/match/number.hpp" #include "detail/match/string.hpp" +#include "detail/overload.hpp" namespace jsonv { @@ -274,7 +275,7 @@ void parse_index::impl::parse_literal(impl*& self, else { JSONV_UNLIKELY - throw push_error(*&self, ast_error::eof, begin, iter); + throw push_error(*&self, ast_error::unexpected_eof, begin, iter); } } @@ -393,7 +394,7 @@ void parse_index::impl::parse(impl*& self, string_view src, const parse_options& { fastforward_whitespace(*&iter, end); if (iter >= end) - throw push_error(self, ast_error::eof, begin, iter); + throw push_error(self, ast_error::unexpected_eof, begin, iter); else if (*iter == '}') return false; @@ -401,7 +402,7 @@ void parse_index::impl::parse(impl*& self, string_view src, const parse_options& fastforward_whitespace(*&iter, end); if (iter >= end) - throw push_error(self, ast_error::eof, begin, iter); + throw push_error(self, ast_error::unexpected_eof, begin, iter); if (*iter != ':') throw push_error(self, ast_error::expected_key_delimiter, begin, iter); @@ -702,15 +703,6 @@ ast_node parse_index::iterator::operator*() const namespace { -template -struct overload : F... -{ - using F::operator()...; -}; - -template -overload(F...) -> overload; - value extract_single(parse_index::const_iterator& iter, parse_index::const_iterator last, const extract_options& options @@ -736,7 +728,7 @@ value extract_object(parse_index::const_iterator& iter, return out; } - std::string key = key_token.visit(overload + std::string key = key_token.visit(detail::overload { [](const ast_node::key_canonical& x) { return std::string(x.value()); }, [](const ast_node::key_escaped& x) { return x.value(); }, @@ -785,7 +777,7 @@ value extract_array(parse_index::const_iterator& iter, ) { auto first_token = *iter; - value out = first_token.visit(overload + value out = first_token.visit(detail::overload { [](const ast_node::array_begin& arr) -> value { @@ -824,7 +816,7 @@ value extract_single(parse_index::const_iterator& iter, auto node = *iter; return node.visit( - overload + detail::overload { [&](const ast_node::object_begin&) -> value { return extract_object(*&iter, last, options); }, [&](const ast_node::array_begin&) -> value { return extract_array(*&iter, last, options); }, diff --git a/src/jsonv/path.cpp b/src/jsonv/path.cpp index 65b0204..c6f045f 100644 --- a/src/jsonv/path.cpp +++ b/src/jsonv/path.cpp @@ -394,6 +394,9 @@ static std::size_t extract_size_t(string_view src) path path::create(string_view specification) { + if (specification.size() == 1U && specification[0] == '.') + return path(); + path out; string_view remaining = specification; while (!remaining.empty()) @@ -451,6 +454,9 @@ path path::operator+(path_element elem) const std::ostream& operator<<(std::ostream& os, const path& val) { + if (val.empty()) + return os << '.'; + for (const path_element& elem : val) { stream_path_element(os, elem); diff --git a/src/jsonv/reader.cpp b/src/jsonv/reader.cpp new file mode 100644 index 0000000..969abbc --- /dev/null +++ b/src/jsonv/reader.cpp @@ -0,0 +1,138 @@ +/// \file +/// +/// Copyright (c) 2022 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#include +#include +#include +#include + +#include + +#include "reader_impl.hpp" +#include "reader_impl_parse_index.hpp" + +namespace jsonv +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// reader // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +template +reader::reader(std::in_place_type_t, TArgs&&... args) : + _impl(std::make_unique(std::forward(args)...)) +{ } + +reader::reader(parse_index index) : + reader(std::in_place_type, std::move(index)) +{ } + +reader::reader(string_view source) : + reader(std::in_place_type, source) +{ } + +reader::reader(string_view source, const parse_options& options) : + reader(std::in_place_type, source, options) +{ } + +reader::reader(const char* source) : + reader(string_view(source)) +{ } + +reader::reader(const char* source, const parse_options& options) : + reader(string_view(source), options) +{ } + +reader::reader(std::string&& source) : + reader(std::in_place_type, std::move(source)) +{ } + +reader::reader(std::string&& source, const parse_options& options) : + reader(std::in_place_type, std::move(source), options) +{ } + +reader::~reader() noexcept = default; + +bool reader::good() const +{ + if (_impl) + return _impl->good(); + else + return false; +} + +void reader::expect(ast_node_type type) +{ + if (current().type() != type) + { + std::ostringstream ss; + ss << "Read node of type " << current().type() << " when expecting " << type; + throw extraction_error(current_path(), std::move(ss).str()); + } +} + +void reader::expect(std::initializer_list types) +{ + if (types.size() == 0U) + throw std::invalid_argument("Cannot expect 0 types"); + else if (types.size() == 1U) + return expect(*types.begin()); + + if (std::find(types.begin(), types.end(), current().type()) == types.end()) + { + std::ostringstream ss; + ss << "Read node of type " << current().type() << " when expecting one of "; + for (const auto& type : types) + ss << type; + + throw extraction_error(current_path(), std::move(ss).str()); + } +} + +const ast_node& reader::current() const +{ + if (_impl) + return _impl->current(); + else + throw std::invalid_argument("reader instance has been moved-from"); +} + +const path& reader::current_path() const +{ + if (_impl) + return _impl->current_path(); + else + throw std::invalid_argument("reader instance has been moved-from"); +} + +bool reader::next_token() noexcept +{ + if (_impl) + return _impl->next_token(); + else + return false; +} + +bool reader::next_structure() noexcept +{ + if (_impl) + return _impl->next_structure(); + else + return false; +} + +bool reader::next_key() noexcept +{ + if (_impl) + return _impl->next_key(); + else + return false; +} + +} diff --git a/src/jsonv/reader_impl.cpp b/src/jsonv/reader_impl.cpp new file mode 100644 index 0000000..a7f9162 --- /dev/null +++ b/src/jsonv/reader_impl.cpp @@ -0,0 +1,171 @@ +/// \file +/// +/// Copyright (c) 2020 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#include "reader_impl.hpp" + +#include +#include + +namespace jsonv +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// reader::impl // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +reader::impl::impl() : + _current_dirty(true), + _current_path_dirty(true) +{ } + +reader::impl::~impl() noexcept = default; + +const ast_node& reader::impl::current() const +{ + if (_current_dirty) + { + _current = load_current(); + _current_dirty = false; + } + + if (_current) + return *_current; + else + throw std::logic_error("Cannot get current -- did `next` or `skip` return false?"); +} + +const path& reader::impl::current_path() const +{ + if (_current_path_dirty) + { + _current_path = load_current_path(); + _current_path_dirty = false; + } + + if (_current_path) + return *_current_path; + else + throw std::logic_error("Cannot get current path -- did `next` or `skip` return false?"); +} + +void reader::impl::mark_dirty() +{ + _current_dirty = true; + _current = nullopt; + _current_path_dirty = true; + _current_path = nullopt; +} + +bool reader::impl::next_token() +{ + mark_dirty(); + return next_token_impl(); +} + +bool reader::impl::next_structure() +{ + mark_dirty(); + return next_structure_impl(); +} + +bool reader::impl::next_key() +{ + ast_node_type current_type; + try + { + current_type = current().type(); + } + catch (...) + { + return false; + } + + if ( current_type != ast_node_type::key_canonical + && current_type != ast_node_type::key_escaped + && current_type != ast_node_type::object_begin + ) + { + std::ostringstream ss; + ss << "Cannot call next_key for non-key AST node type " << current().type(); + throw std::invalid_argument(std::move(ss).str()); + } + + mark_dirty(); + return next_key_impl(); +} + +bool reader::impl::next_structure_impl() noexcept +{ + // If we start at the end of a structure, then just go to the next thing + if (auto tok = current().type(); + tok == ast_node_type::object_end || tok == ast_node_type::array_end + ) + { + return next_token(); + } + + std::size_t depth = 1U; + while (next_token()) + { + auto tok = current().type(); + if (tok == ast_node_type::object_end || tok == ast_node_type::array_end) + { + --depth; + if (depth == 0U) + return next_token(); + } + else if (tok == ast_node_type::object_begin || tok == ast_node_type::array_begin) + { + ++depth; + } + // If we find the end of the document at depth of 1, we were in a JSON document that was not structurally + // enclosed -- niether { ... } nor [ ... ]. Fall back to using the document ending as the structure. + else if (tok == ast_node_type::document_end && depth == 1U) + { + return true; + } + } + return false; +} + +bool reader::impl::next_key_impl() noexcept +{ + if (!next_token()) + return false; + + if (auto tok = current().type(); + ( tok == ast_node_type::key_canonical + || tok == ast_node_type::key_escaped + || tok == ast_node_type::object_end + ) + ) + { + return true; + } + + std::size_t depth = 0U; + do + { + auto tok = current().type(); + if (tok == ast_node_type::object_end || tok == ast_node_type::array_end || tok == ast_node_type::document_end) + { + --depth; + } + else if (tok == ast_node_type::object_begin || tok == ast_node_type::array_begin) + { + ++depth; + } + + if (depth == 0U) + return next_token(); + } while (next_token()); + return false; +} + +} diff --git a/src/jsonv/reader_impl.hpp b/src/jsonv/reader_impl.hpp new file mode 100644 index 0000000..d8439a1 --- /dev/null +++ b/src/jsonv/reader_impl.hpp @@ -0,0 +1,62 @@ +/// \file +/// +/// Copyright (c) 2020 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#pragma once + +#include +#include +#include +#include + +namespace jsonv +{ + +class JSONV_LOCAL reader::impl +{ +public: + explicit impl(); + + virtual ~impl() noexcept; + + virtual bool good() const = 0; + + const ast_node& current() const; + + const path& current_path() const; + + bool next_token(); + + bool next_structure(); + + bool next_key(); + +protected: + /// Attempt to load the current token. If there is no token to load, return \c nullopt. + virtual optional load_current() const = 0; + + virtual optional load_current_path() const = 0; + + virtual bool next_token_impl() noexcept = 0; + + virtual bool next_structure_impl() noexcept; + + virtual bool next_key_impl() noexcept; + + /// Mark the cache as dirty. This should be used any time the token changes. It is automatically called from the + /// \c next_X functions. + void mark_dirty(); + +private: + mutable bool _current_dirty; + mutable optional _current; + mutable bool _current_path_dirty; + mutable optional _current_path; +}; + +} diff --git a/src/jsonv/reader_impl_parse_index.cpp b/src/jsonv/reader_impl_parse_index.cpp new file mode 100644 index 0000000..1d61ee2 --- /dev/null +++ b/src/jsonv/reader_impl_parse_index.cpp @@ -0,0 +1,135 @@ +/// \file +/// +/// Copyright (c) 2020 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#include + +#include "detail/overload.hpp" +#include "reader_impl_parse_index.hpp" + +namespace jsonv +{ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// reader::impl_parse_index // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +reader::impl_parse_index::impl_parse_index(parse_index source) : + _index(std::move(source)), + _current(_index.begin()) +{ } + +reader::impl_parse_index::impl_parse_index(string_view source, const parse_options& options) : + impl_parse_index(parse_index::parse(source, options)) +{ } + +reader::impl_parse_index::impl_parse_index(string_view source) : + impl_parse_index(parse_index::parse(source)) +{ } + +reader::impl_parse_index::~impl_parse_index() noexcept = default; + +bool reader::impl_parse_index::good() const +{ + return _current != _index.end(); +} + +optional reader::impl_parse_index::load_current() const +{ + if (good()) + return *_current; + else + return nullopt; +} + +static path build_current_path(parse_index::const_iterator begin, parse_index::const_iterator current) +{ + if (begin == current) + return path(); + + auto end = current; + ++end; + + std::vector> elem_stack; + + for (auto iter = begin; iter != end; ++iter) + { + const auto& node = *iter; + auto tok = node.type(); + + if (!elem_stack.empty() && elem_stack.back().first.type() == ast_node_type::array_begin) + ++elem_stack.back().second; + + if (tok == ast_node_type::object_begin || tok == ast_node_type::array_begin) + { + elem_stack.emplace_back(node, std::size_t(0U) - 1); + } + else if (tok == ast_node_type::object_end || tok == ast_node_type::array_end) + { + elem_stack.pop_back(); + } + else if (tok == ast_node_type::key_canonical || tok == ast_node_type::key_escaped) + { + elem_stack.back().first = node; + } + } + + path::storage_type elements; + elements.reserve(elem_stack.size()); + for (const auto& [node, idx] : elem_stack) + { + if (node.type() == ast_node_type::key_canonical || node.type() == ast_node_type::key_escaped) + { + elements.push_back(node.visit_key([](const auto& x) { return std::string(x.value()); })); + } + else if (node.type() == ast_node_type::array_begin) + { + // If we see -1, that means we haven't visited any elements of the array yet. The path at this point should + // still represent the whole array, not the first element of it. + if (idx != (std::size_t(0U) - 1)) + elements.push_back(idx); + } + } + + return path(std::move(elements)); +} + +optional reader::impl_parse_index::load_current_path() const +{ + if (good()) + return build_current_path(_index.begin(), _current); + else + return nullopt; +} + +bool reader::impl_parse_index::next_token_impl() noexcept +{ + if (_current == _index.end()) + return false; + + ++_current; + return _current != _index.end(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// reader::impl_parse_index_owning // +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +reader::impl_parse_index_owning::impl_parse_index_owning(std::string&& source) : + reader_impl_parse_index_owning_string(std::move(source)), + reader::impl_parse_index(this->source) +{ } + +reader::impl_parse_index_owning::impl_parse_index_owning(std::string&& source, const parse_options& options) : + reader_impl_parse_index_owning_string(std::move(source)), + reader::impl_parse_index(this->source, options) +{ } + +reader::impl_parse_index_owning::~impl_parse_index_owning() noexcept = default; + +} diff --git a/src/jsonv/reader_impl_parse_index.hpp b/src/jsonv/reader_impl_parse_index.hpp new file mode 100644 index 0000000..abf8044 --- /dev/null +++ b/src/jsonv/reader_impl_parse_index.hpp @@ -0,0 +1,64 @@ +/// \file +/// +/// Copyright (c) 2020 by Travis Gockel. All rights reserved. +/// +/// This program is free software: you can redistribute it and/or modify it under the terms of the Apache License +/// as published by the Apache Software Foundation, either version 2 of the License, or (at your option) any later +/// version. +/// +/// \author Travis Gockel (travis@gockelhut.com) +#pragma once + +#include +#include + +#include "reader_impl.hpp" + +namespace jsonv +{ + +class JSONV_LOCAL reader::impl_parse_index : + public reader::impl +{ +public: + explicit impl_parse_index(string_view source); + explicit impl_parse_index(string_view source, const parse_options& options); + + explicit impl_parse_index(parse_index source); + + virtual ~impl_parse_index() noexcept override; + + virtual bool good() const override; + + virtual optional load_current() const override; + + virtual optional load_current_path() const override; + + virtual bool next_token_impl() noexcept override; + +private: + parse_index _index; + parse_index::const_iterator _current; +}; + +struct JSONV_LOCAL reader_impl_parse_index_owning_string +{ + std::string source; + + explicit reader_impl_parse_index_owning_string(std::string&& source) : + source(std::move(source)) + { } +}; + +class JSONV_LOCAL reader::impl_parse_index_owning : + private reader_impl_parse_index_owning_string, + public reader::impl_parse_index +{ +public: + explicit impl_parse_index_owning(std::string&& source); + explicit impl_parse_index_owning(std::string&& source, const parse_options& options); + + virtual ~impl_parse_index_owning() noexcept override; +}; + +} diff --git a/src/jsonv/serialization.cpp b/src/jsonv/serialization.cpp index 09b6367..f8014b2 100644 --- a/src/jsonv/serialization.cpp +++ b/src/jsonv/serialization.cpp @@ -170,8 +170,6 @@ const path& extraction_error::path() const noexcept { if (_problems.empty()) { - JSONV_UNLIKELY - static const jsonv::path empty_path; return empty_path; } @@ -185,8 +183,6 @@ const std::exception_ptr& extraction_error::nested_ptr() const noexcept { if (_problems.empty()) { - JSONV_UNLIKELY - static const std::exception_ptr empty_ex; return empty_ex; }