Skip to content

Commit 4d22409

Browse files
committed
refactor operator>>(istream&, value&)
1 parent ea45843 commit 4d22409

File tree

3 files changed

+70
-18
lines changed

3 files changed

+70
-18
lines changed

include/boost/json/impl/value.ipp

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -352,22 +352,30 @@ operator>>(
352352
{
353353
using Traits = std::istream::traits_type;
354354

355+
// sentry prepares the stream for reading and finalizes it in destructor
355356
std::istream::sentry sentry(is);
356357
if( !sentry )
357358
return is;
358359

359-
unsigned char parser_buf[BOOST_JSON_STACK_BUFFER_SIZE];
360+
unsigned char parser_buf[BOOST_JSON_STACK_BUFFER_SIZE / 2];
360361
stream_parser p({}, {}, parser_buf);
361362
p.reset( jv.storage() );
362363

364+
char read_buf[BOOST_JSON_STACK_BUFFER_SIZE / 2];
365+
std::streambuf& buf = *is.rdbuf();
363366
std::ios::iostate err = std::ios::goodbit;
364367
try
365368
{
366-
std::istream::int_type c = is.rdbuf()->sgetc();
367369
while( true )
368370
{
369371
error_code ec;
370372

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

387+
// regardless of reaching EOF, we might have parsed a full JSON
388+
// document; if so, we successfully finish
379389
if( p.done() )
380390
{
381391
jv = p.release();
382392
return is;
383393
}
384394

385-
char read_buf[1];
386-
read_buf[0] = Traits::to_char_type(c);
387-
c = is.rdbuf()->snextc();
395+
// at this point we definitely have more input, specifically in
396+
// buf's internal buffer; we also definitely haven't parsed a whole
397+
// document
398+
std::streamsize available = buf.in_avail();
399+
// if this assert fails, the streambuf is buggy
400+
BOOST_ASSERT( available > 0 );
401+
402+
available = std::min(
403+
static_cast<std::size_t>(available), sizeof(read_buf) );
404+
// we read from the internal buffer of buf into our buffer
405+
available = buf.sgetn( read_buf, available );
406+
407+
std::size_t consumed = p.write_some( read_buf, available, ec );
408+
// if the parser hasn't consumed the entire input we've took from
409+
// buf, we put the remaining data back; this should succeed,
410+
// because we only read data from buf's internal buffer
411+
while( consumed++ < static_cast<std::size_t>(available) )
412+
{
413+
std::istream::int_type const status = buf.sungetc();
414+
BOOST_ASSERT( status != Traits::eof() );
415+
(void)status;
416+
}
388417

389-
p.write_some(read_buf, 1, ec);
390418
if( ec.failed() )
391419
break;
392420
}
393421
}
394422
catch(...)
395423
{
396-
is.setstate(std::ios::failbit);
397-
throw;
424+
try
425+
{
426+
is.setstate(std::ios::badbit);
427+
}
428+
// we ignore the exception, because we need to throw the original
429+
// exception instead
430+
catch( std::ios::failure const& ) { }
431+
432+
if( is.exceptions() & std::ios::badbit )
433+
throw;
398434
}
399435

400-
err |= std::ios::failbit;
401-
is.setstate(err);
436+
is.setstate(err | std::ios::failbit);
402437
return is;
403438
}
404439

include/boost/json/value.hpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3343,12 +3343,16 @@ class value
33433343
33443344
This function parses JSON from an input stream into a `value`. If
33453345
parsing fails, `std::ios_base::failbit` will be set for `is` and
3346-
`jv` will be left unchanged.<br>
3346+
`jv` will be left unchanged. Regardless of whether `skipws` flag is set
3347+
on `is`, consumes whitespace before and after JSON, because whitespace
3348+
is considered a part of JSON. Behaves as [_FormattedInputFunction_]
3349+
(https://en.cppreference.com/w/cpp/named_req/FormattedInputFunction).<br>
33473350
33483351
Note: this operator cannot assume that the stream only contains a
3349-
single JSON document, which results in **very underwhelming
3350-
performance**. If you know that your input consists of a single
3351-
JSON document, consider using @ref parse function instead.
3352+
single JSON document, which may result in **very underwhelming
3353+
performance**, if the stream isn't cooperative. If you know that your
3354+
input consists of a single JSON document, consider using @ref parse
3355+
function instead.
33523356
33533357
@return Reference to `is`.
33543358
@@ -3358,7 +3362,7 @@ class value
33583362
@par Exception Safety
33593363
Basic guarantee.
33603364
Calls to `memory_resource::allocate` may throw.
3361-
The stream may throw as described by
3365+
The stream may throw as configured by
33623366
[`std::ios::exceptions`](https://en.cppreference.com/w/cpp/io/basic_ios/exceptions).
33633367
33643368
@param is The input stream to parse from.

test/value.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2261,7 +2261,7 @@ class value_test
22612261
R"({ "x": 1
22622262
, "y": 2
22632263
, "z": [77, null, true, "qwerty uiop"]
2264-
} 12)");
2264+
}_12)");
22652265
value jv;
22662266

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

22802280
ss.clear();
22812281
ss.str("23");
@@ -2285,22 +2285,35 @@ class value_test
22852285
ss.clear();
22862286
ss.str("");
22872287
ss >> jv;
2288+
BOOST_TEST( jv == 23 );
22882289
BOOST_TEST( ss.rdstate() == (std::ios::failbit | std::ios::eofbit) );
22892290

22902291
ss.clear();
22912292
ss.str("nu");
22922293
ss >> jv;
2294+
BOOST_TEST( jv == 23 );
22932295
BOOST_TEST( ss.rdstate() == (std::ios::failbit | std::ios::eofbit) );
22942296

22952297
ss.clear();
22962298
ss.str("[1,2,3,4,]");
22972299
ss >> jv;
2300+
BOOST_TEST( jv == 23 );
22982301
BOOST_TEST( ss.rdstate() == std::ios::failbit );
22992302

23002303
{
23012304
throwing_buffer buf;
23022305
std::istream is(&buf);
2303-
BOOST_TEST_THROWS( is >> jv, std::exception );
2306+
is >> jv;
2307+
BOOST_TEST( jv == 23 );
2308+
BOOST_TEST( is.rdstate() & std::ios::badbit );
2309+
}
2310+
{
2311+
throwing_buffer buf;
2312+
std::istream is(&buf);
2313+
is.exceptions(std::ios::badbit);
2314+
BOOST_TEST_THROWS( is >> jv, std::invalid_argument );
2315+
BOOST_TEST( jv == 23 );
2316+
BOOST_TEST( is.rdstate() & std::ios::badbit );
23042317
}
23052318
}
23062319

0 commit comments

Comments
 (0)