Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow options for writing and parsing NaN/Infinity #641

Merged
merged 1 commit into from
May 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/dom.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Parse flags | Meaning
`kParseCommentsFlag` | Allow one-line `// ...` and multi-line `/* ... */` comments (relaxed JSON syntax).
`kParseNumbersAsStringsFlag` | Parse numerical type values as strings.
`kParseTrailingCommasFlag` | Allow trailing commas at the end of objects and arrays (relaxed JSON syntax).
`kParseNanAndInfFlag` | Allow parsing `NaN`, `Inf`, `Infinity`, `-Inf` and `-Infinity` as `double` values (relaxed JSON syntax).

By using a non-type template parameter, instead of a function parameter, C++ compiler can generate code which is optimized for specified combinations, improving speed, and reducing code size (if only using a single specialization). The downside is the flags needed to be determined in compile-time.

Expand Down
23 changes: 22 additions & 1 deletion include/rapidjson/reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "internal/meta.h"
#include "internal/stack.h"
#include "internal/strtod.h"
#include <limits>

#if defined(RAPIDJSON_SIMD) && defined(_MSC_VER)
#include <intrin.h>
Expand Down Expand Up @@ -150,6 +151,7 @@ enum ParseFlag {
kParseCommentsFlag = 32, //!< Allow one-line (//) and multi-line (/**/) comments.
kParseNumbersAsStringsFlag = 64, //!< Parse all numbers (ints/doubles) as strings.
kParseTrailingCommasFlag = 128, //!< Allow trailing commas at the end of objects and arrays.
kParseNanAndInfFlag = 256, //!< Allow parsing NaN, Inf, Infinity, -Inf and -Infinity as doubles.
kParseDefaultFlags = RAPIDJSON_PARSE_DEFAULT_FLAGS //!< Default parse flags. Can be customized by defining RAPIDJSON_PARSE_DEFAULT_FLAGS
};

Expand Down Expand Up @@ -1137,6 +1139,8 @@ class GenericReader {
(parseFlags & kParseInsituFlag) == 0> s(*this, copy.s);

size_t startOffset = s.Tell();
double d = 0.0;
bool useNanOrInf = false;

// Parse minus
bool minus = Consume(s, '-');
Expand Down Expand Up @@ -1178,12 +1182,26 @@ class GenericReader {
significandDigit++;
}
}
// Parse NaN or Infinity here
else if ((parseFlags & kParseNanAndInfFlag) && RAPIDJSON_LIKELY((s.Peek() == 'I' || s.Peek() == 'N'))) {
useNanOrInf = true;
if (RAPIDJSON_LIKELY(Consume(s, 'N') && Consume(s, 'a') && Consume(s, 'N'))) {
d = std::numeric_limits<double>::quiet_NaN();
}
else if (RAPIDJSON_LIKELY(Consume(s, 'I') && Consume(s, 'n') && Consume(s, 'f'))) {
d = (minus ? -std::numeric_limits<double>::infinity() : std::numeric_limits<double>::infinity());
if (RAPIDJSON_UNLIKELY(s.Peek() == 'i' && !(Consume(s, 'i') && Consume(s, 'n')
&& Consume(s, 'i') && Consume(s, 't') && Consume(s, 'y'))))
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
}
else
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());
}
else
RAPIDJSON_PARSE_ERROR(kParseErrorValueInvalid, s.Tell());

// Parse 64bit int
bool useDouble = false;
double d = 0.0;
if (use64bit) {
if (minus)
while (RAPIDJSON_LIKELY(s.Peek() >= '0' && s.Peek() <= '9')) {
Expand Down Expand Up @@ -1346,6 +1364,9 @@ class GenericReader {

cont = handler.Double(minus ? -d : d);
}
else if (useNanOrInf) {
cont = handler.Double(d);
}
else {
if (use64bit) {
if (minus)
Expand Down
44 changes: 39 additions & 5 deletions include/rapidjson/writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ RAPIDJSON_NAMESPACE_BEGIN
enum WriteFlag {
kWriteNoFlags = 0, //!< No flags are set.
kWriteValidateEncodingFlag = 1, //!< Validate encoding of JSON strings.
kWriteNanAndInfFlag = 2, //!< Allow writing of Inf, -Inf and NaN.
kWriteDefaultFlags = RAPIDJSON_WRITE_DEFAULT_FLAGS //!< Default write flags. Can be customized by defining RAPIDJSON_WRITE_DEFAULT_FLAGS
};

Expand Down Expand Up @@ -319,9 +320,25 @@ class Writer {
}

bool WriteDouble(double d) {
if (internal::Double(d).IsNanOrInf())
return false;

if (internal::Double(d).IsNanOrInf()) {
if (!(writeFlags & kWriteNanAndInfFlag))
return false;
if (internal::Double(d).IsNan()) {
PutReserve(*os_, 3);
PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N');
return true;
}
if (internal::Double(d).Sign()) {
PutReserve(*os_, 9);
PutUnsafe(*os_, '-');
}
else
PutReserve(*os_, 8);
PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f');
PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y');
return true;
}

char buffer[25];
char* end = internal::dtoa(d, buffer, maxDecimalPlaces_);
PutReserve(*os_, static_cast<size_t>(end - buffer));
Expand Down Expand Up @@ -489,8 +506,25 @@ inline bool Writer<StringBuffer>::WriteUint64(uint64_t u) {

template<>
inline bool Writer<StringBuffer>::WriteDouble(double d) {
if (internal::Double(d).IsNanOrInf())
return false;
if (internal::Double(d).IsNanOrInf()) {
// Note: This code path can only be reached if (RAPIDJSON_WRITE_DEFAULT_FLAGS & kWriteNanAndInfFlag).
if (!(kWriteDefaultFlags & kWriteNanAndInfFlag))
return false;
if (internal::Double(d).IsNan()) {
PutReserve(*os_, 3);
PutUnsafe(*os_, 'N'); PutUnsafe(*os_, 'a'); PutUnsafe(*os_, 'N');
return true;
}
if (internal::Double(d).Sign()) {
PutReserve(*os_, 9);
PutUnsafe(*os_, '-');
}
else
PutReserve(*os_, 8);
PutUnsafe(*os_, 'I'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'f');
PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 'n'); PutUnsafe(*os_, 'i'); PutUnsafe(*os_, 't'); PutUnsafe(*os_, 'y');
return true;
}

char *buffer = os_->Push(25);
char* end = internal::dtoa(d, buffer, maxDecimalPlaces_);
Expand Down
65 changes: 65 additions & 0 deletions test/unittest/readertest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "rapidjson/internal/itoa.h"
#include "rapidjson/memorystream.h"

#include <limits>

using namespace rapidjson;

#ifdef __GNUC__
Expand Down Expand Up @@ -1774,6 +1776,69 @@ TEST(Reader, TrailingCommaHandlerTerminationIterative) {
TestTrailingCommaHandlerTermination<kParseIterativeFlag>();
}

TEST(Reader, ParseNanAndInfinity) {
#define TEST_NAN_INF(str, x) \
{ \
{ \
StringStream s(str); \
ParseDoubleHandler h; \
Reader reader; \
ASSERT_EQ(kParseErrorNone, reader.Parse<kParseNanAndInfFlag>(s, h).Code()); \
EXPECT_EQ(1u, h.step_); \
internal::Double e(x), a(h.actual_); \
EXPECT_EQ(e.IsNan(), a.IsNan()); \
EXPECT_EQ(e.IsInf(), a.IsInf()); \
if (!e.IsNan()) \
EXPECT_EQ(e.Sign(), a.Sign()); \
} \
{ \
const char* json = "{ \"naninfdouble\": " str " } "; \
StringStream s(json); \
NumbersAsStringsHandler h(str); \
Reader reader; \
EXPECT_TRUE(reader.Parse<kParseNumbersAsStringsFlag|kParseNanAndInfFlag>(s, h)); \
} \
{ \
char* json = StrDup("{ \"naninfdouble\": " str " } "); \
InsituStringStream s(json); \
NumbersAsStringsHandler h(str); \
Reader reader; \
EXPECT_TRUE(reader.Parse<kParseInsituFlag|kParseNumbersAsStringsFlag|kParseNanAndInfFlag>(s, h)); \
free(json); \
} \
}
#define TEST_NAN_INF_ERROR(errorCode, str, errorOffset) \
{ \
int streamPos = errorOffset; \
char buffer[1001]; \
strncpy(buffer, str, 1000); \
InsituStringStream s(buffer); \
BaseReaderHandler<> h; \
Reader reader; \
EXPECT_FALSE(reader.Parse<kParseNanAndInfFlag>(s, h)); \
EXPECT_EQ(errorCode, reader.GetParseErrorCode());\
EXPECT_EQ(errorOffset, reader.GetErrorOffset());\
EXPECT_EQ(streamPos, s.Tell());\
}

double nan = std::numeric_limits<double>::quiet_NaN();
double inf = std::numeric_limits<double>::infinity();

TEST_NAN_INF("NaN", nan);
TEST_NAN_INF("-NaN", nan);
TEST_NAN_INF("Inf", inf);
TEST_NAN_INF("Infinity", inf);
TEST_NAN_INF("-Inf", -inf);
TEST_NAN_INF("-Infinity", -inf);
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "nan", 1);
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "-nan", 1);
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "NAN", 1);
TEST_NAN_INF_ERROR(kParseErrorValueInvalid, "-Infinty", 6);

#undef TEST_NAN_INF_ERROR
#undef TEST_NAN_INF
}

#ifdef __GNUC__
RAPIDJSON_DIAG_POP
#endif
Expand Down
23 changes: 19 additions & 4 deletions test/unittest/writertest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,15 @@ TEST(Writer, NaN) {
double nan = zero / zero;
EXPECT_TRUE(internal::Double(nan).IsNan());
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
EXPECT_FALSE(writer.Double(nan));

{
Writer<StringBuffer> writer(buffer);
EXPECT_FALSE(writer.Double(nan));
}
{
Writer<StringBuffer, UTF8<>, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer);
EXPECT_TRUE(writer.Double(nan));
EXPECT_STREQ("NaN", buffer.GetString());
}
GenericStringBuffer<UTF16<> > buffer2;
Writer<GenericStringBuffer<UTF16<> > > writer2(buffer2);
EXPECT_FALSE(writer2.Double(nan));
Expand All @@ -460,12 +466,21 @@ TEST(Writer, Inf) {
StringBuffer buffer;
{
Writer<StringBuffer> writer(buffer);
EXPECT_FALSE(writer.Double(inf));
EXPECT_FALSE(writer.Double(inf));
}
{
Writer<StringBuffer> writer(buffer);
EXPECT_FALSE(writer.Double(-inf));
}
{
Writer<StringBuffer, UTF8<>, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer);
EXPECT_TRUE(writer.Double(inf));
}
{
Writer<StringBuffer, UTF8<>, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer);
EXPECT_TRUE(writer.Double(-inf));
}
EXPECT_STREQ("Infinity-Infinity", buffer.GetString());
}

TEST(Writer, RawValue) {
Expand Down