diff --git a/doc/Jamfile b/doc/Jamfile index 36e1649b..1cc35971 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -56,9 +56,6 @@ make index.xml rule make_doxygen_xml ( targets * : sources * : properties * ) { - LIB_DIR on $(targets) = - [ path.native [ path.parent [ path.root - [ on $(sources[1]) return $(SEARCH) ] [ path.pwd ] ] ] ] ; } if [ os.name ] = NT diff --git a/doc/qbk/02_2_strings.qbk b/doc/qbk/02_2_strings.qbk index 57d797bf..9569e03a 100644 --- a/doc/qbk/02_2_strings.qbk +++ b/doc/qbk/02_2_strings.qbk @@ -22,18 +22,12 @@ operate the same way, with these variations: * New characters are -A string may be constructed initially empty without incurrent any +A string may be constructed initially empty without incurrening any memory allocations using the default storage, or a specified storage: -``` -string str1; // empty string, default storage - -string str2( make_storage() ); // empty string, pool storage -``` +[snippet_strings_1] As with `std` strings, space may be reserved upon construction. Exceptions are possible here: - - [endsect] diff --git a/doc/qbk/02_3_arrays.qbk b/doc/qbk/02_3_arrays.qbk index 48579d66..70c042f1 100644 --- a/doc/qbk/02_3_arrays.qbk +++ b/doc/qbk/02_3_arrays.qbk @@ -21,29 +21,16 @@ a __std_vector__ of __value__. Additionally: An array may be constructed empty without incurring any memory allocations using the default storage, or a specified storage: - -``` -array arr1; // empty array, default storage - -array arr2( make_storage() ); // empty array, pool storage -``` +[snippet_arrays_1] Initializer lists can be used to construct objects with initial contents. These constructors may allocate memory and throw: -``` -array arr( { "Hello", 42, true } ); -``` +[snippet_arrays_2] Alternatively, elements may be inserted after construction: -``` -array arr; - -arr.emplace_back( "Hello" ); -arr.emplace_back( 42 ); -arr.emplace_back( true ); -``` +[snippet_arrays_3] Similar to the `std` counterpart, elements may be accessed directly by their 0-based index with bounds checking using @@ -51,12 +38,7 @@ by their 0-based index with bounds checking using or without bounds checking using [link json.ref.boost__json__array.operator_lb__rb_ `operator[]`]: -``` -assert( arr[0] == "Hello" ); - -// The following line throws std::out_of_range, since the index is out of range -obj.at( 3 ) = nullptr; -``` +[snippet_arrays_4] For the complete listing of all available member functions and nested types, see the reference page for __array__. diff --git a/doc/qbk/02_4_objects.qbk b/doc/qbk/02_4_objects.qbk index 1368cb20..72bd1f98 100644 --- a/doc/qbk/02_4_objects.qbk +++ b/doc/qbk/02_4_objects.qbk @@ -29,30 +29,17 @@ modeled after __std_unordered_map__ with these unique properties: An object may be constructed empty without incurring any memory allocations using the default storage, or a specified storage: - -``` -object obj1; // empty object, default storage - -object obj2( make_storage() ); // empty object, pool storage -``` +[snippet_objects_1] Initializer lists consisting of two-element key value pairs can be used to construct objects with initial contents. These constructors may allocate memory and throw: -``` -object obj( {{"key1", "value1" }, { "key2", 42 }, { "key3", false }} ); -``` +[snippet_objects_2] Alternatively, elements may be inserted after construction: -``` -object obj; - -obj.emplace( "key1", "value1" ); -obj.emplace( "key2", 42 ); -obj.emplace( "key3", false ); -``` +[snippet_objects_3] Similar to the `std` counterpart, elements may be accessed directly by their key with bounds checking using @@ -61,16 +48,7 @@ or without bounds checking using [link json.ref.boost__json__object.operator_lb__rb_ `operator[]`] which creates a null element if the key does not already exist: -``` -object obj; - -obj["key1"] = "value1"; -obj["key2"] = 42; -obj["key3"] = false; - -// The following line throws std::out_of_range, since the key does not exist -obj.at( "key4" ); -``` +[snippet_objects_4] Internally, the container computes a hash table over the keys so that the complexity of lookups is O(1) on average. diff --git a/doc/qbk/02_6_storage.qbk b/doc/qbk/02_6_storage.qbk index a00cf7c9..f197cf78 100644 --- a/doc/qbk/02_6_storage.qbk +++ b/doc/qbk/02_6_storage.qbk @@ -74,17 +74,7 @@ always points to the default storage. When instances of library types are constructed without specifying a storage pointer, they use the default storage: -``` -value jv; // uses the default storage -storage_ptr sp; // uses the default storage -object obj( sp ); // uses the default storage - -assert( jv.storage().get() == sp.get() ); // same pointer -assert( *jv.storage() == *sp ); // deep equality - -assert( jv.storage().get() == - obj.storage().get() ); // same pointer -``` +[snippet_storage_1] The default storage is suited for general purpose operations. It allocates only what is needed, and frees memory immediately when destructors run. @@ -100,12 +90,7 @@ requests, and only deallocates when the lifetime of the storage ends. This example shows how a function accepting a string can parse the string into a value using a pool and return the result: -``` -value parse_fast( string_view s ) -{ - return parse( s, make_storage() ); -} -``` +[snippet_storage_2] In the sample code above, ownership of the pool is shared by the returned value and all of its children. The pool is destroyed only when the last @@ -130,16 +115,7 @@ appears. To make this case more efficient, we can construct the block storage using the __scoped_storage__ wrapper and use that for the storage instead: -``` -void do_rpc( string_view cmd ) -{ - scoped_storage sp; - - auto jv = parse( cmd, sp ); - - do_json( jv ); -} -``` +[snippet_storage_3] Scoped storage removes the cost of reference counting from operations on the containers that use it. @@ -221,15 +197,10 @@ In this table: [heading Exemplar] -``` -struct Storage -{ - static constexpr std::uint64_t id = 0; - static_constexpr bool need_free = true; +[snippet_storage_4] - void* allocate( std::size_t bytes, std::size_t align ); - void deallocate( void* p, std::size_t bytes, std::size_t align ); -}; -``` +[heading Models] + +* [link json.ref.boost__json__pool `pool`] [endsect] diff --git a/doc/qbk/02_7_parsing.qbk b/doc/qbk/02_7_parsing.qbk index 5032bcd0..895e3d57 100644 --- a/doc/qbk/02_7_parsing.qbk +++ b/doc/qbk/02_7_parsing.qbk @@ -44,28 +44,19 @@ The `parse` function provides a succinct interface for converting a JSON to a __value__ in a single function call that uses exceptions to indicate errors: -``` -value jv = parse( "[1,2,3,4,5]" ); -``` +[snippet_parsing_1] An overload is provided to receive an error code upon failure instead of an exception: -``` -error_code ec; -value jv = parse( "[1,2,3,4,5]", ec ); -if( ec ) - std::cout << "Parsing failed: " << ec.message() << "\n"; -``` +[snippet_parsing_2] The calls above will produce values that use the default storage. Each parse function allows an additional parameter specifying the storage to use. Here we start the parser with a new instance of __pool__ for the resulting value: -``` -value jv = parse( "[1,2,3,4,5]", make_storage() ); -``` +[snippet_parsing_3] These free functions use a temporary __parser__ instance, which allocates internal structures to assist with performance. If a @@ -82,48 +73,14 @@ is used to inform the parser that an optional final character buffer represents the end of the JSON. This example parses a complete JSON using the stateful API: -``` -parser p; - -// This must be called once before parsing every new JSON. -p.start(); - -// Write the entire character buffer, indicating -// to the parser that there is no more data. -p.finish( "[1,2,3,4,5]", 11 ); - -// Take ownership of the resulting value. -value jv = p.release(); - -// At this point the parser may be re-used by calling p.start() again. -``` +[snippet_parsing_4] Explicit parser instances may also be used to process serialized JSON incrementally. In the code that follows the JSON is represented by two consecutive buffers, and the overloads which return the error code are used instead: -``` -parser p; -error_code ec; - -// This must be called once before parsing every new JSON -p.start(); - -// Write the first part of the buffer -p.write( "[1,2,", 5, ec); - -// Write the remaining part of the character buffer, -// indicating to the parser that there is no more data. -if(! ec ) - p.finish( "3,4,5]", 6, ec ); - -// Take ownership of the resulting value. -if(! ec) - value jv = p.release(); - -// At this point the parser may be re-used by calling p.start() again. -``` +[snippet_parsing_5] [h4 Storage] @@ -134,23 +91,7 @@ __value__ using the default storage. Alternatively, the storage to use may be supplied as an argument. This example uses a scoped instance of block storage: -``` -{ - parser p; - - // Declare a new, scoped instance of the block storage - scoped_storage< pool > sp; - - // Use the scoped instance for the parsed value - p.start( sp ); - - // Write the entire JSON - p.finish( "[1,2,3,4,5]" ); - - // The value will use the instance of block storage created above - value jv = p.release(); -} -``` +[snippet_parsing_6] [h4 SAX] diff --git a/doc/qbk/02_8_serializing.qbk b/doc/qbk/02_8_serializing.qbk index 2c8b4c01..fcd554f4 100644 --- a/doc/qbk/02_8_serializing.qbk +++ b/doc/qbk/02_8_serializing.qbk @@ -41,21 +41,12 @@ serialization: To facilitate debugging and ease of output, instances of __value__ may be written to standard output streams using the stream operator: -``` -value jv = { 1, 2, 3 ,4 ,5 }; - -std::cout << jv << "\n"; - -``` +[snippet_serializing_1] The function `to_string` may be used to capture the serialized value as a string: -``` -value jv = { 1, 2, 3 ,4 ,5 }; - -string s = to_string( jv ); -``` +[snippet_serializing_2] Instead of emitting the complete serialized JSON at once, sometimes it is necessary to produce output incrementally. diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk index ecf25651..4558794d 100644 --- a/doc/qbk/main.qbk +++ b/doc/qbk/main.qbk @@ -81,6 +81,7 @@ [import ../../example/pretty.cpp] [import ../../example/validate.cpp] [import ../../include/boost/json/impl/serializer.ipp] +[import ../../test/snippets.cpp] [/-----------------------------------------------------------------------------] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0fda3aff..abc5253a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable (json-tests parser.cpp pool.cpp serializer.cpp + snippets.cpp storage.cpp storage_ptr.cpp string.cpp diff --git a/test/Jamfile b/test/Jamfile index c043c508..8786eca6 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -37,6 +37,7 @@ local SOURCES = parser.cpp pool.cpp serializer.cpp + snippets.cpp storage.cpp storage_ptr.cpp string.cpp diff --git a/test/snippets.cpp b/test/snippets.cpp new file mode 100644 index 00000000..5ad4a0e4 --- /dev/null +++ b/test/snippets.cpp @@ -0,0 +1,326 @@ +// +// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/vinniefalco/json +// + +#include + +#include +#include + +namespace boost { +namespace json { + +namespace { + +void +usingStrings() +{ + { + //[snippet_strings_1 + + string str1; // empty string, default storage + + string str2( make_storage() ); // empty string, pool storage + + //] + } +} + +//---------------------------------------------------------- + +void +usingArrays() +{ + { + //[snippet_arrays_1 + + array arr1; // empty array, default storage + + array arr2( make_storage() ); // empty array, pool storage + + //] + } + { + //[snippet_arrays_2 + + array arr( { "Hello", 42, true } ); + + //] + } + { + //[snippet_arrays_3 + + array arr; + + arr.emplace_back( "Hello" ); + arr.emplace_back( 42 ); + arr.emplace_back( true ); + + //] + + //[snippet_arrays_4 + + assert( arr[0].as_string() == "Hello" ); + + // The following line throws std::out_of_range, since the index is out of range + arr.at( 3 ) = nullptr; + + //] + } +} + +//---------------------------------------------------------- + +void +usingObjects() +{ + { + //[snippet_objects_1 + + object obj1; // empty object, default storage + + object obj2( make_storage() ); // empty object, pool storage + + //] + } + { + //[snippet_objects_2 + + object obj( {{"key1", "value1" }, { "key2", 42 }, { "key3", false }} ); + + //] + } + { + //[snippet_objects_3 + + object obj; + + obj.emplace( "key1", "value1" ); + obj.emplace( "key2", 42 ); + obj.emplace( "key3", false ); + + //] + } + { + //[snippet_objects_4 + + object obj; + + obj["key1"] = "value1"; + obj["key2"] = 42; + obj["key3"] = false; + + // The following line throws std::out_of_range, since the key does not exist + obj.at( "key4" ); + + //] + } +} + +//---------------------------------------------------------- + +void +usingStorage() +{ + { + //[snippet_storage_1 + + value jv; // uses the default storage + storage_ptr sp; // uses the default storage + object obj( sp ); // uses the default storage + + assert( jv.storage().get() == sp.get() ); // same pointer + assert( *jv.storage() == *sp ); // deep equality + + assert( jv.storage().get() == + obj.storage().get() ); // same pointer + + //] + } +} + +//[snippet_storage_2 + +value parse_fast( string_view s ) +{ + return parse( s, make_storage() ); +} + +//] + +void do_json(value const&) {} + +//[snippet_storage_3 + +void do_rpc( string_view cmd ) +{ + scoped_storage sp; + + auto jv = parse( cmd, sp ); + + do_json( jv ); +} + +//] + +//[snippet_storage_4 + +struct Storage +{ + static constexpr std::uint64_t id = 0; + static constexpr bool need_free = true; + + void* allocate( std::size_t bytes, std::size_t align ); + void deallocate( void* p, std::size_t bytes, std::size_t align ); +}; + +//] + +BOOST_JSON_STATIC_ASSERT( + is_storage::value); + +//---------------------------------------------------------- + +void +usingParsing() +{ + { + //[snippet_parsing_1 + + value jv = parse( "[1,2,3,4,5]" ); + + //] + } + { + //[snippet_parsing_2 + + error_code ec; + value jv = parse( "[1,2,3,4,5]", ec ); + if( ec ) + std::cout << "Parsing failed: " << ec.message() << "\n"; + + //] + } + { + //[snippet_parsing_3 + + value jv = parse( "[1,2,3,4,5]", make_storage() ); + + //] + } + { + //[snippet_parsing_4 + + parser p; + + // This must be called once before parsing every new JSON. + p.start(); + + // Write the entire character buffer, indicating + // to the parser that there is no more data. + p.finish( "[1,2,3,4,5]", 11 ); + + // Take ownership of the resulting value. + value jv = p.release(); + + // At this point the parser may be re-used by calling p.start() again. + + //] + } + { + //[snippet_parsing_5 + + parser p; + error_code ec; + + // This must be called once before parsing every new JSON + p.start(); + + // Write the first part of the buffer + p.write( "[1,2,", 5, ec); + + // Write the remaining part of the character buffer, + // indicating to the parser that there is no more data. + if(! ec ) + p.finish( "3,4,5]", 6, ec ); + + // Take ownership of the resulting value. + if(! ec) + value jv = p.release(); + + // At this point the parser may be re-used by calling p.start() again. + + //] + } + { + //[snippet_parsing_6 + + { + parser p; + error_code ec; + + // Declare a new, scoped instance of the block storage + scoped_storage< pool > sp; + + // Use the scoped instance for the parsed value + p.start( sp ); + + // Write the entire JSON + p.finish( "[1,2,3,4,5]", 11, ec ); + + // The value will use the instance of block storage created above + value jv = p.release(); + } + + //] + } +} + +//---------------------------------------------------------- + +void +usingSerializing() +{ + { + //[snippet_serializing_1 + + value jv = { 1, 2, 3 ,4 ,5 }; + + std::cout << jv << "\n"; + + //] + } + { + //[snippet_serializing_2 + + value jv = { 1, 2, 3 ,4 ,5 }; + + string s = to_string( jv ); + + //] + } +} + +//---------------------------------------------------------- + +} // (anon) + +class snippets_test : public beast::unit_test::suite +{ +public: + void + run() override + { + pass(); + } +}; + +BEAST_DEFINE_TESTSUITE(boost,json,snippets); + +} // json +} // boost