Skip to content

Commit

Permalink
parse overload for std::istream
Browse files Browse the repository at this point in the history
  • Loading branch information
grisumbras committed Nov 5, 2022
1 parent 9f20452 commit 3404427
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 2 deletions.
3 changes: 3 additions & 0 deletions include/boost/json/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ enum class error
/// A string is too large
string_too_large,

/// error occured when trying to read input
input_error,

//
// generic errors
//
Expand Down
2 changes: 2 additions & 0 deletions include/boost/json/impl/error.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ case error::object_too_large: return "object too large";
case error::array_too_large: return "array too large";
case error::key_too_large: return "key too large";
case error::string_too_large: return "string too large";
case error::input_error: return "input error";

case error::exception: return "got exception";
case error::test_failure: return "test failure";
Expand Down Expand Up @@ -91,6 +92,7 @@ case error::object_too_large:
case error::array_too_large:
case error::key_too_large:
case error::string_too_large:
case error::input_error:
return condition::parse_error;

case error::missing_slash:
Expand Down
69 changes: 69 additions & 0 deletions include/boost/json/impl/parse.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <boost/json/parser.hpp>
#include <boost/json/detail/except.hpp>

#include <istream>

BOOST_JSON_NS_BEGIN

value
Expand Down Expand Up @@ -62,6 +64,73 @@ parse(
return jv;
}

value
parse(
std::istream& is,
error_code& ec,
storage_ptr sp,
parse_options const& opt)
{
unsigned char parser_buffer[BOOST_JSON_STACK_BUFFER_SIZE / 2];
stream_parser p(storage_ptr(), opt, parser_buffer);
p.reset(std::move(sp));

char read_buffer[BOOST_JSON_STACK_BUFFER_SIZE / 2];
while( true )
{
if( is.rdstate() & std::ios::eofbit )
{
p.finish(ec);
if( ec.failed() )
return nullptr;
break;
}

if( is.rdstate() != std::ios::goodbit )
{
BOOST_JSON_FAIL( ec, error::input_error );
return nullptr;
}

is.read(read_buffer, sizeof(read_buffer));
auto const consumed = is.gcount();

p.write(read_buffer, consumed, ec);
if( ec.failed() )
return nullptr;
}

return p.release();
}

value
parse(
std::istream& is,
std::error_code& ec,
storage_ptr sp,
parse_options const& opt)
{
error_code jec;
value result = parse(is, jec, std::move(sp), opt);
ec = jec;
return result;
}

value
parse(
std::istream& is,
storage_ptr sp,
parse_options const& opt)
{
error_code ec;
auto jv = parse(
is, ec, std::move(sp), opt);
if(ec)
detail::throw_system_error(ec,
BOOST_CURRENT_LOCATION);
return jv;
}

BOOST_JSON_NS_END

#endif
90 changes: 89 additions & 1 deletion include/boost/json/parse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ parse(
parse_options const& opt = {});
/** @} */

/** Parse a string of JSON into a @ref value.
/** Return parsed JSON as a @ref value.
This function parses an entire string in one
step to produce a complete JSON object, returned
Expand Down Expand Up @@ -111,6 +111,94 @@ parse(
storage_ptr sp = {},
parse_options const& opt = {});

/** Return parsed JSON as a @ref value.
This function reads data from an input stream and parses it to produce a
complete JSON entity, returned as a @ref value. If the stream does not
contain a complete serialized JSON, or contains extra non-whitespace data,
an error occurs. In this case the returned value will be `null`, using the
default memory resource.
@par Complexity
Linear in the size of consumed input.
@par Exception Safety
Basic guarantee.
Calls to `memory_resource::allocate` may throw.
The stream may throw as described by
[`std::ios::exceptions`](https://en.cppreference.com/w/cpp/io/basic_ios/exceptions).
@return A value representing the parsed JSON,
or a `null` if any error occurred.
@param is The stream to read from.
@param ec Set to the error, if any occurred.
@param sp The memory resource that the new value and all of its elements
will use. If this parameter is omitted, the default memory resource
is used.
@param opt The options for the parser. If this parameter is omitted, the
parser will accept only standard JSON.
@see @ref parse_options, @ref stream_parser, @ref value::operator>>.
*/
/** @{ */
BOOST_JSON_DECL
value
parse(
std::istream& is,
error_code& ec,
storage_ptr sp = {},
parse_options const& opt = {});

BOOST_JSON_DECL
value
parse(
std::istream& is,
std::error_code& ec,
storage_ptr sp = {},
parse_options const& opt = {});
/** @} */

/** Return parsed JSON as a @ref value.
This function reads data from an input stream and parses it to produce a
complete JSON entity, returned as a @ref value. If the stream does not
contain a complete serialized JSON, or contains extra non-whitespace data,
an exception is thrown.
@par Complexity
Linear in the size of consumed input.
@par Exception Safety
Basic guarantee.
Throws @ref system_error on failed parse.
Calls to `memory_resource::allocate` may throw.
The stream may throw as described by
[`std::ios::exceptions`](https://en.cppreference.com/w/cpp/io/basic_ios/exceptions).
@return A value representing the parsed JSON upon success.
@param is The stream to read from.
@param sp The memory resource that the new value and all of its elements
will use. If this parameter is omitted, the default memory resource
is used.
@param opt The options for the parser. If this parameter is omitted, the
parser will accept only standard JSON.
@see @ref parse_options, @ref stream_parser, @ref value::operator>>.
*/
BOOST_JSON_DECL
value
parse(
std::istream& is,
storage_ptr sp = {},
parse_options const& opt = {});

BOOST_JSON_NS_END

#endif
9 changes: 8 additions & 1 deletion include/boost/json/value.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3343,7 +3343,12 @@ 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.
`jv` will be left unchanged.<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.
@return Reference to `is`.
Expand All @@ -3359,6 +3364,8 @@ class value
@param is The input stream to parse from.
@param jv The value to parse into.
@see @ref parse.
*/
BOOST_JSON_DECL
friend
Expand Down
1 change: 1 addition & 0 deletions test/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class error_test
check(condition::parse_error, error::array_too_large);
check(condition::parse_error, error::key_too_large);
check(condition::parse_error, error::string_too_large);
check(condition::parse_error, error::input_error);

check(condition::pointer_parse_error, error::missing_slash);
check(condition::pointer_parse_error, error::invalid_escape);
Expand Down
26 changes: 26 additions & 0 deletions test/parse.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//
// Copyright (c) 2019 Vinnie Falco ([email protected])
// Copyright (c) 2022 Dmitry Arkhipov ([email protected])
//
// 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)
Expand Down Expand Up @@ -42,6 +43,12 @@ class parse_test
return;
BOOST_TEST(
serialize(jv) == s);

std::stringstream ss(s);
auto jv2 = parse(ss, ec);
if(! BOOST_TEST(! ec))
return;
BOOST_TEST( jv == jv2 );
}

template <class ErrorCode>
Expand All @@ -52,6 +59,12 @@ class parse_test
auto jv = parse(s, ec);
BOOST_TEST(ec);
BOOST_TEST(hasLocation(ec));

ec = {};
std::stringstream ss(s);
auto jv2 = parse(ss, ec);
BOOST_TEST(ec);
BOOST_TEST(hasLocation(ec));
}

void
Expand All @@ -73,6 +86,7 @@ class parse_test
{
good("null");
good("[1,2,3]");
good("17");
bad ("[1,2,3] #");
bad ("555415214748364655415E2147483646");
bad ("9.88874836020e-2147483640");
Expand Down Expand Up @@ -178,12 +192,24 @@ class parse_test
BOOST_TEST(arr == array{123});
}

void
testIstream()
{
std::stringstream ss("null");
parse(ss); // does not throw

ss.clear();
ss.setstate(std::ios::failbit);
BOOST_TEST_THROWS( parse(ss), system_error );
}

void
run()
{
testParse();
testMemoryUsage();
testIssue726();
testIstream();
}
};

Expand Down

0 comments on commit 3404427

Please sign in to comment.