Skip to content

Commit

Permalink
refactor operator>>(istream&, value&)
Browse files Browse the repository at this point in the history
  • Loading branch information
grisumbras committed Nov 6, 2022
1 parent ea45843 commit 4d22409
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 18 deletions.
55 changes: 45 additions & 10 deletions include/boost/json/impl/value.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -352,22 +352,30 @@ operator>>(
{
using Traits = std::istream::traits_type;

// sentry prepares the stream for reading and finalizes it in destructor
std::istream::sentry sentry(is);
if( !sentry )
return is;

unsigned char parser_buf[BOOST_JSON_STACK_BUFFER_SIZE];
unsigned char parser_buf[BOOST_JSON_STACK_BUFFER_SIZE / 2];
stream_parser p({}, {}, parser_buf);
p.reset( jv.storage() );

char read_buf[BOOST_JSON_STACK_BUFFER_SIZE / 2];
std::streambuf& buf = *is.rdbuf();
std::ios::iostate err = std::ios::goodbit;
try
{
std::istream::int_type c = is.rdbuf()->sgetc();
while( true )
{
error_code ec;

// we peek the buffer; this either makes sure that there's no
// more input, or makes sure there's something in the internal
// buffer (so in_avail will return a positive number)
std::istream::int_type c = is.rdbuf()->sgetc();
// if we indeed reached EOF, we check if we parsed a full JSON
// document; if not, we error out
if( Traits::eq_int_type(c, Traits::eof()) )
{
err |= std::ios::eofbit;
Expand All @@ -376,29 +384,56 @@ operator>>(
break;
}

// regardless of reaching EOF, we might have parsed a full JSON
// document; if so, we successfully finish
if( p.done() )
{
jv = p.release();
return is;
}

char read_buf[1];
read_buf[0] = Traits::to_char_type(c);
c = is.rdbuf()->snextc();
// at this point we definitely have more input, specifically in
// buf's internal buffer; we also definitely haven't parsed a whole
// document
std::streamsize available = buf.in_avail();
// if this assert fails, the streambuf is buggy
BOOST_ASSERT( available > 0 );

available = std::min(
static_cast<std::size_t>(available), sizeof(read_buf) );
// we read from the internal buffer of buf into our buffer
available = buf.sgetn( read_buf, available );

std::size_t consumed = p.write_some( read_buf, available, ec );
// if the parser hasn't consumed the entire input we've took from
// buf, we put the remaining data back; this should succeed,
// because we only read data from buf's internal buffer
while( consumed++ < static_cast<std::size_t>(available) )
{
std::istream::int_type const status = buf.sungetc();
BOOST_ASSERT( status != Traits::eof() );
(void)status;
}

p.write_some(read_buf, 1, ec);
if( ec.failed() )
break;
}
}
catch(...)
{
is.setstate(std::ios::failbit);
throw;
try
{
is.setstate(std::ios::badbit);
}
// we ignore the exception, because we need to throw the original
// exception instead
catch( std::ios::failure const& ) { }

if( is.exceptions() & std::ios::badbit )
throw;
}

err |= std::ios::failbit;
is.setstate(err);
is.setstate(err | std::ios::failbit);
return is;
}

Expand Down
14 changes: 9 additions & 5 deletions include/boost/json/value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3343,12 +3343,16 @@ class value
This function parses JSON from an input stream into a `value`. If
parsing fails, `std::ios_base::failbit` will be set for `is` and
`jv` will be left unchanged.<br>
`jv` will be left unchanged. Regardless of whether `skipws` flag is set
on `is`, consumes whitespace before and after JSON, because whitespace
is considered a part of JSON. Behaves as [_FormattedInputFunction_]
(https://en.cppreference.com/w/cpp/named_req/FormattedInputFunction).<br>
Note: this operator cannot assume that the stream only contains a
single JSON document, which results in **very underwhelming
performance**. If you know that your input consists of a single
JSON document, consider using @ref parse function instead.
single JSON document, which may result in **very underwhelming
performance**, if the stream isn't cooperative. If you know that your
input consists of a single JSON document, consider using @ref parse
function instead.
@return Reference to `is`.
Expand All @@ -3358,7 +3362,7 @@ class value
@par Exception Safety
Basic guarantee.
Calls to `memory_resource::allocate` may throw.
The stream may throw as described by
The stream may throw as configured by
[`std::ios::exceptions`](https://en.cppreference.com/w/cpp/io/basic_ios/exceptions).
@param is The input stream to parse from.
Expand Down
19 changes: 16 additions & 3 deletions test/value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2261,7 +2261,7 @@ class value_test
R"({ "x": 1
, "y": 2
, "z": [77, null, true, "qwerty uiop"]
} 12)");
}_12)");
value jv;

ss >> jv;
Expand All @@ -2275,7 +2275,7 @@ class value_test
// check we didn't consume any extra characters
std::string s;
std::getline(ss, s);
BOOST_TEST( s == " 12" );
BOOST_TEST( s == "_12" );

ss.clear();
ss.str("23");
Expand All @@ -2285,22 +2285,35 @@ class value_test
ss.clear();
ss.str("");
ss >> jv;
BOOST_TEST( jv == 23 );
BOOST_TEST( ss.rdstate() == (std::ios::failbit | std::ios::eofbit) );

ss.clear();
ss.str("nu");
ss >> jv;
BOOST_TEST( jv == 23 );
BOOST_TEST( ss.rdstate() == (std::ios::failbit | std::ios::eofbit) );

ss.clear();
ss.str("[1,2,3,4,]");
ss >> jv;
BOOST_TEST( jv == 23 );
BOOST_TEST( ss.rdstate() == std::ios::failbit );

{
throwing_buffer buf;
std::istream is(&buf);
BOOST_TEST_THROWS( is >> jv, std::exception );
is >> jv;
BOOST_TEST( jv == 23 );
BOOST_TEST( is.rdstate() & std::ios::badbit );
}
{
throwing_buffer buf;
std::istream is(&buf);
is.exceptions(std::ios::badbit);
BOOST_TEST_THROWS( is >> jv, std::invalid_argument );
BOOST_TEST( jv == 23 );
BOOST_TEST( is.rdstate() & std::ios::badbit );
}
}

Expand Down

0 comments on commit 4d22409

Please sign in to comment.