From d2c7de637610e6ead10b06ef54b669f0db586d5d Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 13 Aug 2020 10:59:18 -0700 Subject: [PATCH] Add value_builder --- bench/bench.cpp | 4 +- doc/qbk/02_7_parsing.qbk | 4 +- doc/qbk/quickref.xml | 1 + example/pretty.cpp | 2 +- include/boost/json/error.hpp | 6 - include/boost/json/impl/error.ipp | 1 - include/boost/json/impl/parser.ipp | 557 +------------------- include/boost/json/impl/value_builder.ipp | 610 ++++++++++++++++++++++ include/boost/json/parser.hpp | 195 +------ include/boost/json/src.hpp | 1 + include/boost/json/value_builder.hpp | 555 ++++++++++++++++++++ test/Jamfile | 3 +- test/error.cpp | 1 - test/parser.cpp | 35 +- test/snippets.cpp | 34 +- test/value_builder.cpp | 43 ++ 16 files changed, 1270 insertions(+), 782 deletions(-) create mode 100644 include/boost/json/impl/value_builder.ipp create mode 100644 include/boost/json/value_builder.hpp create mode 100644 test/value_builder.cpp diff --git a/bench/bench.cpp b/bench/bench.cpp index a9c59ce7..0fde8363 100644 --- a/bench/bench.cpp +++ b/bench/bench.cpp @@ -274,7 +274,7 @@ public: parser p; while(repeat--) { - p.start(); + p.reset(); error_code ec; p.write(s.data(), s.size(), ec); if(! ec) @@ -341,7 +341,7 @@ public: while(repeat--) { monotonic_resource mr; - p.start(&mr); + p.reset(&mr); error_code ec; p.write(s.data(), s.size(), ec); if(! ec) diff --git a/doc/qbk/02_7_parsing.qbk b/doc/qbk/02_7_parsing.qbk index eaf5b4ec..59428bbb 100644 --- a/doc/qbk/02_7_parsing.qbk +++ b/doc/qbk/02_7_parsing.qbk @@ -58,7 +58,7 @@ failure instead of an exception: The calls above will produce values that use the default memory resource. Each parse function allows an additional parameter -specifying the memory resource to use. Here we start the parser with +specifying the memory resource to use. Here we reset the parser with a new instance of __monotonic_resource__ for the resulting value: [snippet_parsing_3] @@ -90,7 +90,7 @@ code are used instead: [h4 Allocators] When -[link json.ref.boost__json__parser.start `start`] +[link json.ref.boost__json__parser.reset `reset`] is called with no arguments, the parser constructs the resulting __value__ using the default memory resource. A __storage_ptr__ can also be passed to the function, and the resulting __value__ will diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 6ca7ef78..4712ee6f 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -31,6 +31,7 @@ storage_ptr string value + value_builder diff --git a/example/pretty.cpp b/example/pretty.cpp index bee60395..be4c7469 100644 --- a/example/pretty.cpp +++ b/example/pretty.cpp @@ -27,7 +27,7 @@ parse_file( char const* filename ) { file f( filename, "r" ); json::parser p; - p.start(); + p.reset(); do { char buf[4096]; diff --git a/include/boost/json/error.hpp b/include/boost/json/error.hpp index ae093415..e8c92e2f 100644 --- a/include/boost/json/error.hpp +++ b/include/boost/json/error.hpp @@ -58,11 +58,9 @@ using std::generic_category; */ enum class error { - //---------------------------------- // // parse errors // - //---------------------------------- /// syntax error syntax = 1, @@ -100,10 +98,6 @@ enum class error /// illegal trailing surrogate illegal_trailing_surrogate, - /** The parser needs to be started. - */ - need_start, - /// expected comma expected_comma, diff --git a/include/boost/json/impl/error.ipp b/include/boost/json/impl/error.ipp index 2b61fc7c..f4f1d941 100644 --- a/include/boost/json/impl/error.ipp +++ b/include/boost/json/impl/error.ipp @@ -44,7 +44,6 @@ case error::illegal_escape_char: return "illegal character in escape sequence"; case error::illegal_extra_digits: return "illegal extra digits in number"; case error::illegal_leading_surrogate: return "illegal leading surrogate"; case error::illegal_trailing_surrogate: return "illegal trailing surrogate"; -case error::need_start: return "parser needs start"; case error::expected_comma: return "expected comma"; case error::expected_colon: return "expected colon"; diff --git a/include/boost/json/impl/parser.ipp b/include/boost/json/impl/parser.ipp index 71a495a4..37bd2ec7 100644 --- a/include/boost/json/impl/parser.ipp +++ b/include/boost/json/impl/parser.ipp @@ -20,139 +20,6 @@ namespace boost { namespace json { -//---------------------------------------------------------- - -/* - -Stack Layout: - ... denotes 0 or more - <> denotes empty storage - -array - saved_state - std::size_t - state - value... - - -object - saved_state - std::size_t - state - value_type... - - -key - (chars)... - std::size_t -*/ - -enum class parser::state : char -{ - need_start, // start() not called yet - begin, // we have a storage_ptr - - // These states indicate what is - // currently at top of the stack. - - top, // top value, constructed if lev_.count==1 - arr, // empty array value - obj, // empty object value - key, // complete key -}; - -void -parser:: -destroy() noexcept -{ - if(key_size_ > 0) - { - // remove partial key - BOOST_ASSERT( - lev_.st == state::obj); - BOOST_ASSERT( - str_size_ == 0); - rs_.subtract(key_size_); - key_size_ = 0; - } - else if(str_size_ > 0) - { - // remove partial string - rs_.subtract(str_size_); - str_size_ = 0; - } - // unwind the rest - do - { - switch(lev_.st) - { - case state::need_start: - BOOST_ASSERT( - rs_.empty()); - break; - - case state::begin: - BOOST_ASSERT( - rs_.empty()); - break; - - case state::top: - if(lev_.count > 0) - { - BOOST_ASSERT( - lev_.count == 1); - auto ua = - pop_array(); - BOOST_ASSERT( - ua.size() == 1); - BOOST_ASSERT( - rs_.empty()); - } - else - { - // never parsed a value - rs_.subtract( - sizeof(value)); - BOOST_ASSERT( - rs_.empty()); - } - break; - - case state::arr: - { - pop_array(); - rs_.subtract(lev_.align); - pop(lev_); - break; - } - - case state::obj: - { - pop_object(); - rs_.subtract(lev_.align); - pop(lev_); - break; - } - - case state::key: - { - std::uint32_t key_size; - pop(key_size); - pop_chars(key_size); - lev_.st = state::obj; - break; - } - } - } - while(! rs_.empty()); -} - -parser:: -~parser() -{ - destroy(); -} - parser:: parser() noexcept : parser( @@ -182,40 +49,30 @@ parser( storage_ptr sp, const parse_options& opt) noexcept : basic_parser(opt) - , rs_(std::move(sp)) + , vb_(std::move(sp)) { - lev_.st = state::need_start; } void parser:: -reserve( - std::size_t bytes) noexcept +reserve(std::size_t n) { - rs_.reserve(bytes); + vb_.reserve(n); } void parser:: -start(storage_ptr sp) noexcept +reset(storage_ptr sp) noexcept { - clear(); - sp_ = std::move(sp); - lev_.st = state::begin; + vb_.reset(sp); } void parser:: clear() noexcept { - destroy(); - rs_.clear(); + vb_.clear(); basic_parser::reset(); - lev_.count = 0; - key_size_ = 0; - str_size_ = 0; - lev_.st = state::need_start; - sp_ = {}; } std::size_t @@ -227,7 +84,7 @@ write( { auto const n = basic_parser::write_some( - *this, true, data, size, ec); + vb_, true, data, size, ec); if(! ec && n < size) ec = error::extra_data; return n; @@ -253,7 +110,7 @@ parser:: finish(error_code& ec) { basic_parser::write_some( - *this, false, nullptr, 0, ec); + vb_, false, nullptr, 0, ec); } void @@ -267,403 +124,13 @@ value parser:: release() { + /* if(! is_complete()) BOOST_THROW_EXCEPTION( std::logic_error( "no value")); - BOOST_ASSERT(lev_.count == 1); - BOOST_ASSERT(depth() == 0); - auto ua = pop_array(); - BOOST_ASSERT(rs_.empty()); - union U - { - value v; - U(){} - ~U(){} - }; - U u; - ua.relocate(&u.v); - basic_parser::reset(); - lev_.st = state::need_start; - sp_ = {}; - return pilfer(u.v); -} - -//---------------------------------------------------------- - -template -void -parser:: -push(T const& t) -{ - std::memcpy( - rs_.push(sizeof(T)), - &t, sizeof(T)); -} - -void -parser:: -push_chars(string_view s) -{ - std::memcpy( - rs_.push(s.size()), - s.data(), s.size()); -} - -template -void -parser:: -emplace_object( - Args&&... args) -{ - union U - { - object::value_type v; - U(){} - ~U(){} - }; - U u; - // perform stack reallocation up-front - // VFALCO This is more than we need - rs_.prepare(sizeof(object::value_type)); - std::uint32_t key_size; - pop(key_size); - auto const key = - pop_chars(key_size); - lev_.st = state::obj; - BOOST_ASSERT((rs_.top() % - alignof(object::value_type)) == 0); - ::new(rs_.behind( - sizeof(object::value_type))) - object::value_type( - key, std::forward(args)...); - rs_.add_unchecked(sizeof(u.v)); - ++lev_.count; -} - -template -void -parser:: -emplace_array(Args&&... args) -{ - // prevent splits from exceptions - rs_.prepare(sizeof(value)); - BOOST_ASSERT((rs_.top() % - alignof(value)) == 0); - ::new(rs_.behind(sizeof(value))) value( - std::forward(args)...); - rs_.add_unchecked(sizeof(value)); - ++lev_.count; -} - -template -bool -parser:: -emplace( - error_code& ec, - Args&&... args) -{ - if(lev_.st == state::key) - { - if(lev_.count < - object::max_size()) - { - emplace_object(std::forward< - Args>(args)...); - return true; - } - ec = error::object_too_large; - return false; - } - if(lev_.count < - array::max_size()) - { - emplace_array(std::forward< - Args>(args)...); - return true; - } - ec = error::array_too_large; - return false; -} - -template -void -parser:: -pop(T& t) -{ - std::memcpy(&t, - rs_.pop(sizeof(T)), - sizeof(T)); -} - -detail::unchecked_object -parser:: -pop_object() noexcept -{ - rs_.subtract(sizeof( - object::value_type)); - if(lev_.count == 0) - return { nullptr, 0, sp_ }; - auto const n = lev_.count * sizeof( - object::value_type); - return { reinterpret_cast< - object::value_type*>(rs_.pop(n)), - lev_.count, sp_ }; -} - -detail::unchecked_array -parser:: -pop_array() noexcept -{ - rs_.subtract(sizeof(value)); - if(lev_.count == 0) - return { nullptr, 0, sp_ }; - auto const n = - lev_.count * sizeof(value); - return { reinterpret_cast( - rs_.pop(n)), lev_.count, sp_ }; -} - -string_view -parser:: -pop_chars( - std::size_t size) noexcept -{ - return { - reinterpret_cast( - rs_.pop(size)), size }; -} - -//---------------------------------------------------------- - -bool -parser:: -on_document_begin( - error_code& ec) -{ - if(lev_.st == state::need_start) - { - ec = error::need_start; - return false; - } - - lev_.count = 0; - lev_.align = 0; - key_size_ = 0; - str_size_ = 0; - - // The top level `value` is kept - // inside a notional 1-element array. - rs_.add(sizeof(value)); - lev_.st = state::top; - - return true; -} - -bool -parser:: -on_document_end(error_code&) -{ - BOOST_ASSERT(lev_.count == 1); - return true; -} - -bool -parser:: -on_object_begin(error_code&) -{ - // prevent splits from exceptions - rs_.prepare( - sizeof(level) + - sizeof(object::value_type) + - alignof(object::value_type) - 1); - push(lev_); - lev_.align = detail::align_to< - object::value_type>(rs_); - rs_.add(sizeof( - object::value_type)); - lev_.count = 0; - lev_.st = state::obj; - return true; -} - -bool -parser:: -on_object_end( - error_code& ec) -{ - BOOST_ASSERT( - lev_.st == state::obj); - auto uo = pop_object(); - rs_.subtract(lev_.align); - pop(lev_); - return emplace( - ec, std::move(uo)); -} - -bool -parser:: -on_array_begin(error_code&) -{ - // prevent splits from exceptions - rs_.prepare( - sizeof(level) + - sizeof(value) + - alignof(value) - 1); - push(lev_); - lev_.align = - detail::align_to(rs_); - rs_.add(sizeof(value)); - lev_.count = 0; - lev_.st = state::arr; - return true; -} - -bool -parser:: -on_array_end( - error_code& ec) -{ - BOOST_ASSERT( - lev_.st == state::arr); - auto ua = pop_array(); - rs_.subtract(lev_.align); - pop(lev_); - return emplace( - ec, std::move(ua)); -} - -bool -parser:: -on_key_part( - string_view s, - error_code& ec) -{ - if( s.size() > - string::max_size() - key_size_) - { - ec = error::key_too_large; - return false; - } - push_chars(s); - key_size_ += static_cast< - std::uint32_t>(s.size()); - return true; -} - -bool -parser:: -on_key( - string_view s, - error_code& ec) -{ - BOOST_ASSERT( - lev_.st == state::obj); - if(! on_key_part(s, ec)) - return false; - push(key_size_); - key_size_ = 0; - lev_.st = state::key; - return true; -} - -bool -parser:: -on_string_part( - string_view s, - error_code& ec) -{ - if( s.size() > - string::max_size() - str_size_) - { - ec = error::string_too_large; - return false; - } - push_chars(s); - str_size_ += static_cast< - std::uint32_t>(s.size()); - return true; -} - -bool -parser:: -on_string( - string_view s, - error_code& ec) -{ - if( s.size() > - string::max_size() - str_size_) - { - ec = error::string_too_large; - return false; - } - if(str_size_ == 0) - { - // fast path - return emplace(ec, s, sp_); - } - - string str(sp_); - auto const sv = - pop_chars(str_size_); - str_size_ = 0; - str.reserve( - sv.size() + s.size()); - std::memcpy( - str.data(), - sv.data(), sv.size()); - std::memcpy( - str.data() + sv.size(), - s.data(), s.size()); - str.grow(sv.size() + s.size()); - return emplace( - ec, std::move(str), sp_); -} - -bool -parser:: -on_int64( - int64_t i, - string_view, - error_code& ec) -{ - return emplace(ec, i, sp_); -} - -bool -parser:: -on_uint64( - uint64_t u, - string_view, - error_code& ec) -{ - return emplace(ec, u, sp_); -} - -bool -parser:: -on_double( - double d, - string_view, - error_code& ec) -{ - return emplace(ec, d, sp_); -} - -bool -parser:: -on_bool( - bool b, - error_code& ec) -{ - return emplace(ec, b, sp_); -} - -bool -parser:: -on_null(error_code& ec) -{ - return emplace(ec, nullptr, sp_); + */ + return vb_.release(); } //---------------------------------------------------------- @@ -675,7 +142,7 @@ parse( storage_ptr sp) { parser p; - p.start(std::move(sp)); + p.reset(std::move(sp)); p.write( s.data(), s.size(), diff --git a/include/boost/json/impl/value_builder.ipp b/include/boost/json/impl/value_builder.ipp new file mode 100644 index 00000000..e4bf32b3 --- /dev/null +++ b/include/boost/json/impl/value_builder.ipp @@ -0,0 +1,610 @@ +// +// 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/cppalliance/json +// + +#ifndef BOOST_JSON_IMPL_VALUE_BUILDER_IPP +#define BOOST_JSON_IMPL_VALUE_BUILDER_IPP + +#include +#include +#include +#include + +namespace boost { +namespace json { + +//---------------------------------------------------------- + +/* + +Stack Layout: + ... denotes 0 or more + <> denotes empty storage + +array + saved_state + std::size_t + state + value... + + +object + saved_state + std::size_t + state + value_type... + + +key + (chars)... + std::size_t +*/ + +enum class value_builder::state : char +{ + need_reset, // reset() not called yet + begin, // we have a storage_ptr + + // These states indicate what is + // currently at top of the stack. + + top, // top value, constructed if lev_.count==1 + arr, // empty array value + obj, // empty object value + key, // complete key +}; + +value_builder:: +~value_builder() +{ + destroy(); +} + +value_builder:: +value_builder( + storage_ptr sp) noexcept + : rs_(std::move(sp)) +{ + lev_.st = state::need_reset; +} + +void +value_builder:: +reserve(std::size_t n) +{ +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif + rs_.reserve(n); +#ifndef BOOST_NO_EXCEPTIONS + } + catch(std::bad_alloc const&) + { + // squelch the exception, per contract + } +#endif +} + +void +value_builder:: +reset(storage_ptr sp) noexcept +{ + clear(); + sp_ = std::move(sp); + lev_.st = state::begin; +} + +value +value_builder:: +release() +{ + // An exception here means that the value + // was not properly constructed. For example, + // an array or object was not closed, or + // there was no top level value. + if( lev_.st != state::top || + lev_.count != 1) + BOOST_THROW_EXCEPTION( + std::logic_error( + "no value")); + auto ua = pop_array(); + BOOST_ASSERT(rs_.empty()); + union U + { + value v; + U(){} + ~U(){} + }; + U u; + ua.relocate(&u.v); + lev_.st = state::need_reset; + sp_ = {}; + return pilfer(u.v); +} + +void +value_builder:: +clear() noexcept +{ + destroy(); + rs_.clear(); + lev_.count = 0; + key_size_ = 0; + str_size_ = 0; + lev_.st = state::need_reset; + sp_ = {}; +} + +//---------------------------------------------------------- + +bool +value_builder:: +on_document_begin( + error_code&) +{ + // reset() must be called before + // building every new top level value. + BOOST_ASSERT(lev_.st == state::begin); + + lev_.count = 0; + lev_.align = 0; + key_size_ = 0; + str_size_ = 0; + + // The top level `value` is kept + // inside a notional 1-element array. + rs_.add(sizeof(value)); + lev_.st = state::top; + + return true; +} + +bool +value_builder:: +on_document_end(error_code&) +{ + // If this goes off, then an + // array or object was never finished. + BOOST_ASSERT(lev_.st == state::top); + BOOST_ASSERT(lev_.count == 1); + return true; +} + +bool +value_builder:: +on_object_begin(error_code&) +{ + // prevent splits from exceptions + rs_.prepare( + sizeof(level) + + sizeof(object::value_type) + + alignof(object::value_type) - 1); + push(lev_); + lev_.align = detail::align_to< + object::value_type>(rs_); + rs_.add(sizeof( + object::value_type)); + lev_.count = 0; + lev_.st = state::obj; + return true; +} + +bool +value_builder:: +on_object_end( + error_code& ec) +{ + BOOST_ASSERT( + lev_.st == state::obj); + auto uo = pop_object(); + rs_.subtract(lev_.align); + pop(lev_); + return emplace( + ec, std::move(uo)); +} + +bool +value_builder:: +on_array_begin(error_code&) +{ + // prevent splits from exceptions + rs_.prepare( + sizeof(level) + + sizeof(value) + + alignof(value) - 1); + push(lev_); + lev_.align = + detail::align_to(rs_); + rs_.add(sizeof(value)); + lev_.count = 0; + lev_.st = state::arr; + return true; +} + +bool +value_builder:: +on_array_end( + error_code& ec) +{ + BOOST_ASSERT( + lev_.st == state::arr); + auto ua = pop_array(); + rs_.subtract(lev_.align); + pop(lev_); + return emplace( + ec, std::move(ua)); +} + +bool +value_builder:: +on_key_part( + string_view s, + error_code& ec) +{ + if( s.size() > + string::max_size() - key_size_) + { + ec = error::key_too_large; + return false; + } + push_chars(s); + key_size_ += static_cast< + std::uint32_t>(s.size()); + return true; +} + +bool +value_builder:: +on_key( + string_view s, + error_code& ec) +{ + BOOST_ASSERT( + lev_.st == state::obj); + if(! on_key_part(s, ec)) + return false; + push(key_size_); + key_size_ = 0; + lev_.st = state::key; + return true; +} + +bool +value_builder:: +on_string_part( + string_view s, + error_code& ec) +{ + if( s.size() > + string::max_size() - str_size_) + { + ec = error::string_too_large; + return false; + } + push_chars(s); + str_size_ += static_cast< + std::uint32_t>(s.size()); + return true; +} + +bool +value_builder:: +on_string( + string_view s, + error_code& ec) +{ + if( s.size() > + string::max_size() - str_size_) + { + ec = error::string_too_large; + return false; + } + if(str_size_ == 0) + { + // fast path + return emplace(ec, s, sp_); + } + + string str(sp_); + auto const sv = + pop_chars(str_size_); + str_size_ = 0; + str.reserve( + sv.size() + s.size()); + std::memcpy( + str.data(), + sv.data(), sv.size()); + std::memcpy( + str.data() + sv.size(), + s.data(), s.size()); + str.grow(sv.size() + s.size()); + return emplace( + ec, std::move(str), sp_); +} + +bool +value_builder:: +on_int64( + int64_t i, + string_view, + error_code& ec) +{ + return emplace(ec, i, sp_); +} + +bool +value_builder:: +on_uint64( + uint64_t u, + string_view, + error_code& ec) +{ + return emplace(ec, u, sp_); +} + +bool +value_builder:: +on_double( + double d, + string_view, + error_code& ec) +{ + return emplace(ec, d, sp_); +} + +bool +value_builder:: +on_bool( + bool b, + error_code& ec) +{ + return emplace(ec, b, sp_); +} + +bool +value_builder:: +on_null(error_code& ec) +{ + return emplace(ec, nullptr, sp_); +} + +//---------------------------------------------------------- + +void +value_builder:: +destroy() noexcept +{ + if(key_size_ > 0) + { + // remove partial key + BOOST_ASSERT( + lev_.st == state::obj); + BOOST_ASSERT( + str_size_ == 0); + rs_.subtract(key_size_); + key_size_ = 0; + } + else if(str_size_ > 0) + { + // remove partial string + rs_.subtract(str_size_); + str_size_ = 0; + } + // unwind the rest + do + { + switch(lev_.st) + { + case state::need_reset: + BOOST_ASSERT( + rs_.empty()); + break; + + case state::begin: + BOOST_ASSERT( + rs_.empty()); + break; + + case state::top: + if(lev_.count > 0) + { + BOOST_ASSERT( + lev_.count == 1); + auto ua = + pop_array(); + BOOST_ASSERT( + ua.size() == 1); + BOOST_ASSERT( + rs_.empty()); + } + else + { + // never parsed a value + rs_.subtract( + sizeof(value)); + BOOST_ASSERT( + rs_.empty()); + } + break; + + case state::arr: + { + pop_array(); + rs_.subtract(lev_.align); + pop(lev_); + break; + } + + case state::obj: + { + pop_object(); + rs_.subtract(lev_.align); + pop(lev_); + break; + } + + case state::key: + { + std::uint32_t key_size; + pop(key_size); + pop_chars(key_size); + lev_.st = state::obj; + break; + } + } + } + while(! rs_.empty()); +} + +template +void +value_builder:: +push(T const& t) +{ + std::memcpy( + rs_.push(sizeof(T)), + &t, sizeof(T)); +} + +void +value_builder:: +push_chars(string_view s) +{ + std::memcpy( + rs_.push(s.size()), + s.data(), s.size()); +} + +template +void +value_builder:: +emplace_object( + Args&&... args) +{ + union U + { + object::value_type v; + U(){} + ~U(){} + }; + U u; + // perform stack reallocation up-front + // VFALCO This is more than we need + rs_.prepare(sizeof(object::value_type)); + std::uint32_t key_size; + pop(key_size); + auto const key = pop_chars(key_size); + lev_.st = state::obj; + BOOST_ASSERT((rs_.top() % + alignof(object::value_type)) == 0); + ::new(rs_.behind( + sizeof(object::value_type))) + object::value_type( + key, std::forward(args)...); + rs_.add_unchecked(sizeof(u.v)); + ++lev_.count; +} + +template +void +value_builder:: +emplace_array(Args&&... args) +{ + // prevent splits from exceptions + rs_.prepare(sizeof(value)); + BOOST_ASSERT((rs_.top() % + alignof(value)) == 0); + ::new(rs_.behind(sizeof(value))) value( + std::forward(args)...); + rs_.add_unchecked(sizeof(value)); + ++lev_.count; +} + +template +bool +value_builder:: +emplace( + error_code& ec, + Args&&... args) +{ + if(lev_.st == state::key) + { + if(lev_.count < + object::max_size()) + { + emplace_object(std::forward< + Args>(args)...); + return true; + } + ec = error::object_too_large; + return false; + } + if(lev_.count < + array::max_size()) + { + emplace_array(std::forward< + Args>(args)...); + return true; + } + ec = error::array_too_large; + return false; +} + +template +void +value_builder:: +pop(T& t) +{ + std::memcpy(&t, + rs_.pop(sizeof(T)), + sizeof(T)); +} + +detail::unchecked_object +value_builder:: +pop_object() noexcept +{ + rs_.subtract(sizeof( + object::value_type)); + if(lev_.count == 0) + return { nullptr, 0, sp_ }; + auto const n = lev_.count * sizeof( + object::value_type); + return { reinterpret_cast< + object::value_type*>(rs_.pop(n)), + lev_.count, sp_ }; +} + +detail::unchecked_array +value_builder:: +pop_array() noexcept +{ + rs_.subtract(sizeof(value)); + if(lev_.count == 0) + return { nullptr, 0, sp_ }; + auto const n = + lev_.count * sizeof(value); + return { reinterpret_cast( + rs_.pop(n)), lev_.count, sp_ }; +} + +string_view +value_builder:: +pop_chars( + std::size_t size) noexcept +{ + return { + reinterpret_cast( + rs_.pop(size)), size }; +} + +} // json +} // boost + +#endif diff --git a/include/boost/json/parser.hpp b/include/boost/json/parser.hpp index 259115bb..34487b74 100644 --- a/include/boost/json/parser.hpp +++ b/include/boost/json/parser.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -34,7 +35,7 @@ namespace json { @par Usage - Before parsing a new JSON, the function @ref start + Before parsing a new JSON, the function @ref reset must be called, optionally passing the storage pointer to be used by the @ref value container into which the parsed results are stored. After the @@ -70,24 +71,7 @@ namespace json { */ class parser : public basic_parser { - friend class basic_parser; - enum class state : char; - struct level - { - std::uint32_t count; - char align; - state st; - }; - - storage_ptr sp_; - detail::raw_stack rs_; - std::uint32_t key_size_ = 0; - std::uint32_t str_size_ = 0; - level lev_; - - inline - void - destroy() noexcept; + value_builder vb_; public: /** Destructor. @@ -95,9 +79,7 @@ public: All dynamically allocated memory, including any partial parsing results, is freed. */ - BOOST_JSON_DECL - virtual - ~parser(); + ~parser() = default; /** Default constructor. @@ -107,7 +89,7 @@ public: @note Before any JSON can be parsed, the function - @ref start must be called. + @ref reset must be called. */ BOOST_JSON_DECL parser() noexcept; @@ -120,7 +102,7 @@ public: @note Before any JSON can be parsed, the function - @ref start must be called. + @ref reset must be called.
@@ -135,14 +117,13 @@ public: explicit parser(storage_ptr sp) noexcept; - /** Constructor. Constructs a parser using the specified options. @note Before any JSON can be parsed, the function - @ref start must be called. + @ref reset must be called. @param opt The options for the parser. */ @@ -158,7 +139,7 @@ public: @note Before any JSON can be parsed, the function - @ref start must be called. + @ref reset must be called.
@@ -185,7 +166,7 @@ public: @par Exception Safety - No-throw guarantee. + Strong guarantee. @param n The number of bytes to reserve. A good choices is `C * sizeof(value)` where @@ -194,7 +175,7 @@ public: */ BOOST_JSON_DECL void - reserve(std::size_t n) noexcept; + reserve(std::size_t n); /** Start parsing JSON incrementally. @@ -208,7 +189,7 @@ public: */ BOOST_JSON_DECL void - start(storage_ptr sp = {}) noexcept; + reset(storage_ptr sp = {}) noexcept; /** Parse JSON incrementally. @@ -355,7 +336,7 @@ public: @note After this call, it is necessary to call - @ref start to parse a new JSON incrementally. + @ref reset to parse a new JSON incrementally. */ BOOST_JSON_DECL void @@ -375,158 +356,6 @@ public: BOOST_JSON_DECL value release(); - -private: - template - void - push(T const& t); - - inline - void - push_chars(string_view s); - - template - void - emplace_object( - Args&&... args); - - template - void - emplace_array( - Args&&... args); - - template - bool - emplace( - error_code& ec, - Args&&... args); - - template - void - pop(T& t); - - inline - detail::unchecked_object - pop_object() noexcept; - - inline - detail::unchecked_array - pop_array() noexcept; - - inline - string_view - pop_chars( - std::size_t size) noexcept; - - inline - bool - on_document_begin( - error_code& ec); - - inline - bool - on_document_end( - error_code& ec); - - inline - bool - on_object_begin( - error_code& ec); - - inline - bool - on_object_end( - error_code& ec); - - inline - bool - on_array_begin( - error_code& ec); - - inline - bool - on_array_end( - error_code& ec); - - inline - bool - on_key_part( - string_view s, - error_code& ec); - - inline - bool - on_key( - string_view s, - error_code& ec); - - inline - bool - on_string_part( - string_view s, - error_code& ec); - - inline - bool - on_string( - string_view s, - error_code& ec); - - inline - bool - on_number_part( - string_view, - error_code&) - { - return true; - } - - inline - bool - on_int64( - int64_t i, - string_view, - error_code& ec); - - inline - bool - on_uint64( - uint64_t u, - string_view, - error_code& ec); - - inline - bool - on_double( - double d, - string_view, - error_code& ec); - - inline - bool - on_bool( - bool b, - error_code& ec); - - inline - bool - on_null(error_code&); - - bool - on_comment_part( - string_view, - error_code&) - { - return true; - } - - bool - on_comment( - string_view, - error_code&) - { - return true; - } }; //---------------------------------------------------------- diff --git a/include/boost/json/src.hpp b/include/boost/json/src.hpp index 847ded22..a7e0aa2f 100644 --- a/include/boost/json/src.hpp +++ b/include/boost/json/src.hpp @@ -36,6 +36,7 @@ in a translation unit of the program. #include #include #include +#include #include #include diff --git a/include/boost/json/value_builder.hpp b/include/boost/json/value_builder.hpp new file mode 100644 index 00000000..c62df10f --- /dev/null +++ b/include/boost/json/value_builder.hpp @@ -0,0 +1,555 @@ +// +// 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/cppalliance/json +// + +#ifndef BOOST_JSON_VALUE_BUILDER_HPP +#define BOOST_JSON_VALUE_BUILDER_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace json { + +//---------------------------------------------------------- + +/** A factory for building a value DOM. + + A value builder implements an algorithm for + efficiently constructing a @ref value from an + external source (provided by the caller). + + The builder uses a dynamically allocated internal + storage to hold portions of the document, allowing + complete objects and arrays to be constructed using + a single allocation when their contents are + eventually known. This internal storage is reused + when creating multiple values with the same builder. + + To use the builder construct it with an optionally + specified memory resource to use for the internal + storage. Then call @ref reset once before building + each complete DOM, optionally specifying the + memory resource to use for the resulting @ref value. + + The functions @ref on_document_begin and + @ref on_document_end must be called exactly once + at the beginning and at the end of construction. + The remaining event handling functions are called + according to their descriptions to build the document. +*/ +class value_builder +{ + enum class state : char; + struct level + { + std::uint32_t count; + char align; + state st; + }; + + storage_ptr sp_; + detail::raw_stack rs_; + std::uint32_t key_size_ = 0; + std::uint32_t str_size_ = 0; + level lev_; + +public: + /** Destructor. + + All dynamically allocated memory, including + any partially built results, is freed. + */ + BOOST_JSON_DECL + ~value_builder(); + + /** Constructor. + + Constructs a empty builder using the default + memory resource, or the optionally specified + @ref storage_ptr, to allocate intermediate storage. + + @note + Before any @ref value can be built, + the function @ref start must be called. + +
+ + The `sp` parameter is only used to + allocate intermediate storage; it will not be used + for the @ref value returned by @ref release. + + @param sp The @ref storage_ptr to use for + intermediate storage allocations. + */ + BOOST_JSON_DECL + explicit + value_builder(storage_ptr sp = {}) noexcept; + + /** Reserve internal storage space. + + This function reserves space for `n` bytes + in the parser's internal temporary storage. + The request is only a hint to the + implementation. + + @par Exception Safety + + Strong guarantee. + + @param n The number of bytes to reserve. A + good choices is `C * sizeof(value)` where + `C` is the total number of @ref value elements + in a typical parsed JSON. + */ + BOOST_JSON_DECL + void + reserve(std::size_t n); + + /** Prepare the builder for a new value. + + This function must be called before building + a new @ref value. Any previously existing full + or partial values are destroyed, but internal + dynamically allocated memory is preserved which + may be reused to build new values. + + @param sp A pointer to the @ref memory_resource + to use for the resulting value. The builder will + acquire shared ownership of the memory resource. + */ + BOOST_JSON_DECL + void + reset(storage_ptr sp = {}) noexcept; + + /** Return the parsed JSON as a @ref value. + + If @ref is_complete() returns `true`, then the + parsed value is returned. Otherwise an + exception is thrown. + + @throw std::logic_error `! is_complete()` + + @return The parsed value. Ownership of this + value is transferred to the caller. + */ + BOOST_JSON_DECL + value + release(); + + /** Discard all parsed JSON results. + + This function destroys all intermediate parsing + results, while preserving dynamically allocated + internal memory which may be reused on a + subsequent parse. + + @note + + After this call, it is necessary to call + @ref start to parse a new JSON incrementally. + */ + BOOST_JSON_DECL + void + clear() noexcept; + + //-------------------------------------------- + + /** Begin building a new value. + + This function must be called exactly once + after calling @ref reset, before any other + event functions are invoked. + + @return `true` on success. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_document_begin( + error_code& ec); + + /** Finish building a new value. + + This function must be called exactly once + before calling @ref release, and after all + event functions have been called. + + @return `true` on success. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_document_end( + error_code& ec); + + /** Begin building an object. + + This instructs the builder to begin building + a new JSON object, either as the top-level + element of the resulting value, or as the + next element of the current object or array + being built. + + @return `true` on success. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_object_begin( + error_code& ec); + + /** Finish building an object. + + This event function instructs the builder that + the object currently being built, which was created + by the last call to @ref on_object_begin, is finished. + + @return `true` on success. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_object_end( + error_code& ec); + + /** Begin building an array. + + This instructs the builder to begin building + a new JSON array, either as the top-level + element of the resulting value, or as the + next element of the current object or array + being built. + + @return `true` on success. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_array_begin( + error_code& ec); + + /** Finish building an array. + + This function instructs the builder that the + array currently being built, which was created + by the last call to @ref on_array_begin, is finished. + + @return `true` on success. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_array_end( + error_code& ec); + + /** Continue creating a key. + + This function appends the specified characters + to the key being built as the next element of + a currently open object. If a key is not currently + being built, the behavior is undefined. + + @return `true` on success. + + @param s The characters to append. This may be empty. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_key_part( + string_view s, + error_code& ec); + + /** Finish creating a key. + + This function appends the specified characters + to the key being built as the next element of + a currently open object, and finishes construction + of the key. If a key is not currently being built, + the behavior is undefined. + + @return `true` on success. + + @param s The characters to append. This may be empty. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_key( + string_view s, + error_code& ec); + + /** Begin or continue creating a string. + + This function appends the specified characters + to the string being built. If a string is not + currently being built, then a new empty string + is started. + + @return `true` on success. + + @param s The characters to append. This may be empty. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_string_part( + string_view s, + error_code& ec); + + /** Create a string or finish creating a string. + + This function appends the specified characters + to the string being built. If a string is not + currently being built, then a new string is created + with the specified characters. + + @return `true` on success. + + @param s The characters to append. This may be empty. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_string( + string_view s, + error_code& ec); + + /** Begin building a number from a string. + + This instructs the builder to begin building + a new JSON number, either as the top-level + element of the resulting value, or as the + next element of the current object or array + being built. + + @note This function has no effect and always + returns `true`. + + @return `true` on success. + + @param s The characters to append. This may be empty. + + @param ec Set to the error, if any occurred. + */ + bool + on_number_part( + string_view s, + error_code& ec) + { + (void)s; + (void)ec; + return true; + } + + /** Build a number. + + This function builds a number from the specified + value and adds it as the top-level element of the + resulting value, or as the next element of the + current object or array being built. + + @return `true` on success. + + @param i The integer to build. + + @param s The characters to append. This value is ignored. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_int64( + int64_t i, + string_view s, + error_code& ec); + + /** Build a number. + + This function builds a number from the specified + value and adds it as the top-level element of the + resulting value, or as the next element of the + current object or array being built. + + @return `true` on success. + + @param i The unsigned integer to build. + + @param s The characters to append. This value is ignored. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_uint64( + uint64_t u, + string_view s, + error_code& ec); + + /** Build a number. + + This function builds a number from the specified + value and adds it as the top-level element of the + resulting value, or as the next element of the + current object or array being built. + + @return `true` on success. + + @param i The floating point number to build. + + @param s The characters to append. This value is ignored. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_double( + double d, + string_view s, + error_code& ec); + + /** Build a boolean. + + This function builds a boolean from the specified + value and adds it as the top-level element of the + resulting value, or as the next element of the + current object or array being built. + + @return `true` on success. + + @param b The boolean to build. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_bool( + bool b, + error_code& ec); + + /** Build a null. + + This function builds a null from the specified + value and adds it as the top-level element of the + resulting value, or as the next element of the + current object or array being built. + + @return `true` on success. + + @param ec Set to the error, if any occurred. + */ + BOOST_JSON_DECL + bool + on_null(error_code& ec); + + /** Specify part of comment. + + This function has no effect and always returns `true`. + + @param s The characters to append. This value is ignored. + + @param ec Set to the error, if any occurred. + */ + bool + on_comment_part( + string_view s, + error_code& ec) + { + (void)s; + (void)ec; + return true; + } + + /** Specify a comment. + + This function has no effect and always returns `true`. + + @param s The characters to append. This value is ignored. + + @param ec Set to the error, if any occurred. + */ + bool + on_comment( + string_view s, + error_code& ec) + { + (void)s; + (void)ec; + return true; + } + +private: + inline + void + destroy() noexcept; + + template + void + push(T const& t); + + inline + void + push_chars(string_view s); + + template + void + emplace_object( + Args&&... args); + + template + void + emplace_array( + Args&&... args); + + template + bool + emplace( + error_code& ec, + Args&&... args); + + template + void + pop(T& t); + + inline + detail::unchecked_object + pop_object() noexcept; + + inline + detail::unchecked_array + pop_array() noexcept; + + inline + string_view + pop_chars( + std::size_t size) noexcept; +}; + +} // json +} // boost + +#ifdef BOOST_JSON_HEADER_ONLY +#include +#endif + +#endif diff --git a/test/Jamfile b/test/Jamfile index 7bf0f907..8b327e4e 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -43,8 +43,9 @@ local SOURCES = string.cpp string_view.cpp system_error.cpp - value_from.cpp value.cpp + value_builder.cpp + value_from.cpp value_to.cpp value_ref.cpp ryu/d2s_intrinsics_test.cpp diff --git a/test/error.cpp b/test/error.cpp index 8837494c..cb92c719 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -87,7 +87,6 @@ public: check(condition::assign_error, error::integer_overflow); check(condition::assign_error, error::not_exact); - check(error::need_start); check(error::test_failure); } }; diff --git a/test/parser.cpp b/test/parser.cpp index 5364c3ff..5e561e17 100644 --- a/test/parser.cpp +++ b/test/parser.cpp @@ -38,7 +38,7 @@ public: { parser p(po); error_code ec; - p.start(std::move(sp)); + p.reset(std::move(sp)); p.write(s.data(), s.size(), ec); if(BOOST_TEST(! ec)) p.finish(ec); @@ -108,7 +108,7 @@ public: mr.fail_max = 0; parser p(po); error_code ec; - p.start(&mr); + p.reset(&mr); p.write(s.data(), i, ec); if(BOOST_TEST(! ec)) p.write( @@ -265,7 +265,7 @@ public: auto const N = js.size() / 2; error_code ec; parser p; - p.start(); + p.reset(); p.write(js.data(), N, ec); if(BOOST_TEST(! ec)) { @@ -298,7 +298,7 @@ public: BOOST_TEST_CHECKPOINT(); error_code ec; parser p; - p.start(); + p.reset(); p.write(s.data(), s.size(), ec); if(BOOST_TEST(! ec)) p.finish(ec); @@ -595,7 +595,7 @@ public: { error_code ec; parser p; - p.start(); + p.reset(); BOOST_TEST( p.depth() == 0); BOOST_TEST( @@ -667,7 +667,7 @@ public: { error_code ec; parser p; - p.start(); + p.reset(); BOOST_TEST( p.depth() == 0); BOOST_TEST( @@ -690,19 +690,10 @@ public: p.reserve(1024); } - // need start error - { - parser p; - error_code ec; - p.write("", 0, ec); - BOOST_TEST( - ec == error::need_start); - } - // destroy after start { parser p; - p.start(); + p.reset(); } // release before done @@ -724,13 +715,13 @@ public: { { parser p; - p.start(); + p.reset(); BOOST_TEST(p.write( "null", 4) == 4); } { parser p; - p.start(); + p.reset(); BOOST_TEST_THROWS( p.write("x", 1), system_error); @@ -852,7 +843,7 @@ R"xx({ make_counted_resource(); parser p(sp); error_code ec; - p.start(); + p.reset(); p.write(in.data(), in.size(), ec); if(BOOST_TEST(! ec)) p.finish(ec); @@ -862,7 +853,7 @@ R"xx({ } try { - p.start(); + p.reset(); p.write(in.data(), in.size()); p.finish(); BOOST_TEST(to_string(p.release()) == out); @@ -967,7 +958,7 @@ R"xx({ { parser p; error_code ec; - p.start(); + p.reset(); p.write("nullx", 5, ec); BOOST_TEST(ec == error::extra_data); @@ -976,7 +967,7 @@ R"xx({ { parser p; error_code ec; - p.start(); + p.reset(); p.write("[123,", 5, ec); if(BOOST_TEST(! ec)) p.finish(ec); diff --git a/test/snippets.cpp b/test/snippets.cpp index 8bf9294e..55c51b86 100644 --- a/test/snippets.cpp +++ b/test/snippets.cpp @@ -735,7 +735,7 @@ usingParsing() parser p; // This must be called once before parsing every new JSON. - p.start(); + p.reset(); // Write the entire character buffer, indicating // to the parser that there is no more data. @@ -745,7 +745,7 @@ usingParsing() // Take ownership of the resulting value. value jv = p.release(); - // At this point the parser may be re-used by calling p.start() again. + // At this point the parser may be re-used by calling p.reset() again. //] } @@ -756,7 +756,7 @@ usingParsing() error_code ec; // This must be called once before parsing every new JSON - p.start(); + p.reset(); // Write the first part of the buffer p.write( "[1,2,", 5, ec); @@ -772,7 +772,7 @@ usingParsing() if(! ec) value jv = p.release(); - // At this point the parser may be re-used by calling p.start() again. + // At this point the parser may be re-used by calling p.reset() again. //] } @@ -786,7 +786,7 @@ usingParsing() monotonic_resource mr; // Use the monotonic resource for the parsed value - p.start( &mr ); + p.reset( &mr ); // Write the entire JSON p.write( "[1,2,3,4,5]", 11, ec ); @@ -812,7 +812,7 @@ usingParsing() // Fully parses a valid JSON string and // extracts the resulting value - p.start( sp ); + p.reset( sp ); p.write( "[true, false, 1, 0]", 19, ec ); p.finish( ec ); @@ -823,7 +823,7 @@ usingParsing() // The intermediate storage that was used // for the last value will be reused here. - p.start( sp ); + p.reset( sp ); p.write( "[null]", 6, ec ); p.finish( ec ); @@ -845,7 +845,7 @@ usingParsing() parser p; error_code ec; - p.start(); + p.reset(); p.write( good.data(), good.size(), ec ); // Valid JSON @@ -853,7 +853,7 @@ usingParsing() ec.clear(); - p.start(); + p.reset(); p.write( bad.data(), bad.size(), ec ); // Error, trailing commas are not permitted @@ -868,29 +868,27 @@ usingParsing() string_view comment = "/* example comment */[1, 2, 3]"; // Comments are not permitted by default - parser standard; + parser p1; parse_options po; po.allow_comments = true; - + // Constructs a parser that will treat // comments as whitespace. - parser extended(po); + parser p2(po); error_code ec; - standard.start(); - standard.write( comment.data(), - comment.size(), ec ); + p1.reset(); + p1.write( comment.data(), comment.size(), ec ); // Error, invalid JSON assert( ec ); ec.clear(); - extended.start(); - extended.write( comment.data(), - comment.size(), ec ); + p2.reset(); + p2.write( comment.data(), comment.size(), ec ); // Ok, comments are permitted assert( ! ec ); diff --git a/test/value_builder.cpp b/test/value_builder.cpp new file mode 100644 index 00000000..d2157d5a --- /dev/null +++ b/test/value_builder.cpp @@ -0,0 +1,43 @@ +// +// 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/BeastLounge +// + +// Test that header file is self-contained. +#include + +#include "test_suite.hpp" + +namespace boost { +namespace json { + +class value_builder_test +{ +public: + void + testBuilder() + { + error_code ec; + value_builder vb; + vb.reset(); + vb.on_document_begin(ec); + vb.on_null(ec); + vb.on_document_end(ec); + vb.release(); + } + + void + run() + { + testBuilder(); + } +}; + +TEST_SUITE(value_builder_test, "boost.json.value_builder"); + +} // json +} // boost