From 135da7ab347a7a6e40c0d9d0c234b0fc2d812982 Mon Sep 17 00:00:00 2001 From: Michael Thon Date: Wed, 18 May 2016 21:09:21 +0200 Subject: [PATCH] Allow options for writing and parsing NaN/Infinity This adds kWriteNanAndInfFlag to Writer to allow writing of nan, inf and -inf doubles as "NaN", "Infinity" and "-Infinity", respectively, and kParseNanAndInfFlag to Reader to allow parsing of "NaN", "Inf", "Infinity", "-Inf" and "-Infinity". This is part of issue #36, adding optional support for relaxed JSON syntax. --- doc/dom.md | 1 + include/rapidjson/reader.h | 23 ++++++++++++- include/rapidjson/writer.h | 44 +++++++++++++++++++++--- test/unittest/readertest.cpp | 65 ++++++++++++++++++++++++++++++++++++ test/unittest/writertest.cpp | 23 ++++++++++--- 5 files changed, 146 insertions(+), 10 deletions(-) diff --git a/doc/dom.md b/doc/dom.md index 60480c318..6c541fe93 100644 --- a/doc/dom.md +++ b/doc/dom.md @@ -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. diff --git a/include/rapidjson/reader.h b/include/rapidjson/reader.h index 16e2d073c..13fd12681 100644 --- a/include/rapidjson/reader.h +++ b/include/rapidjson/reader.h @@ -23,6 +23,7 @@ #include "internal/meta.h" #include "internal/stack.h" #include "internal/strtod.h" +#include #if defined(RAPIDJSON_SIMD) && defined(_MSC_VER) #include @@ -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 }; @@ -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, '-'); @@ -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::quiet_NaN(); + } + else if (RAPIDJSON_LIKELY(Consume(s, 'I') && Consume(s, 'n') && Consume(s, 'f'))) { + d = (minus ? -std::numeric_limits::infinity() : std::numeric_limits::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')) { @@ -1346,6 +1364,9 @@ class GenericReader { cont = handler.Double(minus ? -d : d); } + else if (useNanOrInf) { + cont = handler.Double(d); + } else { if (use64bit) { if (minus) diff --git a/include/rapidjson/writer.h b/include/rapidjson/writer.h index 2809f7058..82797bb3e 100644 --- a/include/rapidjson/writer.h +++ b/include/rapidjson/writer.h @@ -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 }; @@ -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(end - buffer)); @@ -489,8 +506,25 @@ inline bool Writer::WriteUint64(uint64_t u) { template<> inline bool Writer::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_); diff --git a/test/unittest/readertest.cpp b/test/unittest/readertest.cpp index 329af2a7e..69c3cc415 100644 --- a/test/unittest/readertest.cpp +++ b/test/unittest/readertest.cpp @@ -19,6 +19,8 @@ #include "rapidjson/internal/itoa.h" #include "rapidjson/memorystream.h" +#include + using namespace rapidjson; #ifdef __GNUC__ @@ -1774,6 +1776,69 @@ TEST(Reader, TrailingCommaHandlerTerminationIterative) { TestTrailingCommaHandlerTermination(); } +TEST(Reader, ParseNanAndInfinity) { +#define TEST_NAN_INF(str, x) \ + { \ + { \ + StringStream s(str); \ + ParseDoubleHandler h; \ + Reader reader; \ + ASSERT_EQ(kParseErrorNone, reader.Parse(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(s, h)); \ + } \ + { \ + char* json = StrDup("{ \"naninfdouble\": " str " } "); \ + InsituStringStream s(json); \ + NumbersAsStringsHandler h(str); \ + Reader reader; \ + EXPECT_TRUE(reader.Parse(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(s, h)); \ + EXPECT_EQ(errorCode, reader.GetParseErrorCode());\ + EXPECT_EQ(errorOffset, reader.GetErrorOffset());\ + EXPECT_EQ(streamPos, s.Tell());\ + } + + double nan = std::numeric_limits::quiet_NaN(); + double inf = std::numeric_limits::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 diff --git a/test/unittest/writertest.cpp b/test/unittest/writertest.cpp index 9c68c539a..22f428e0b 100644 --- a/test/unittest/writertest.cpp +++ b/test/unittest/writertest.cpp @@ -446,9 +446,15 @@ TEST(Writer, NaN) { double nan = zero / zero; EXPECT_TRUE(internal::Double(nan).IsNan()); StringBuffer buffer; - Writer writer(buffer); - EXPECT_FALSE(writer.Double(nan)); - + { + Writer writer(buffer); + EXPECT_FALSE(writer.Double(nan)); + } + { + Writer, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer); + EXPECT_TRUE(writer.Double(nan)); + EXPECT_STREQ("NaN", buffer.GetString()); + } GenericStringBuffer > buffer2; Writer > > writer2(buffer2); EXPECT_FALSE(writer2.Double(nan)); @@ -460,12 +466,21 @@ TEST(Writer, Inf) { StringBuffer buffer; { Writer writer(buffer); - EXPECT_FALSE(writer.Double(inf)); + EXPECT_FALSE(writer.Double(inf)); } { Writer writer(buffer); EXPECT_FALSE(writer.Double(-inf)); } + { + Writer, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer); + EXPECT_TRUE(writer.Double(inf)); + } + { + Writer, UTF8<>, CrtAllocator, kWriteNanAndInfFlag> writer(buffer); + EXPECT_TRUE(writer.Double(-inf)); + } + EXPECT_STREQ("Infinity-Infinity", buffer.GetString()); } TEST(Writer, RawValue) {