mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-12-11 21:45:16 +00:00
[Support] Make JSON handle doubles and int64s losslessly
Summary: This patch adds a new "integer" ValueType, and renames Number -> Double. This allows us to preserve the full precision of int64_t when parsing integers from the wire, or constructing from an integer. The API is unchanged, other than giving asInteger() a clearer contract. In addition, always output doubles with enough precision that parsing will reconstruct the same double. Reviewers: simon_tatham Subscribers: llvm-commits Differential Revision: https://reviews.llvm.org/D46209 llvm-svn: 336541
This commit is contained in:
parent
440d5c5ef9
commit
d7c4c3ec0b
@ -204,7 +204,7 @@ inline bool operator!=(const Array &L, const Array &R) { return !(L == R); }
|
|||||||
/// Each Value is one of the JSON kinds:
|
/// Each Value is one of the JSON kinds:
|
||||||
/// null (nullptr_t)
|
/// null (nullptr_t)
|
||||||
/// boolean (bool)
|
/// boolean (bool)
|
||||||
/// number (double)
|
/// number (double or int64)
|
||||||
/// string (StringRef)
|
/// string (StringRef)
|
||||||
/// array (json::Array)
|
/// array (json::Array)
|
||||||
/// object (json::Object)
|
/// object (json::Object)
|
||||||
@ -226,7 +226,7 @@ inline bool operator!=(const Array &L, const Array &R) { return !(L == R); }
|
|||||||
/// fromJSON(const json::Value&, T&)->bool
|
/// fromJSON(const json::Value&, T&)->bool
|
||||||
/// Deserializers are provided for:
|
/// Deserializers are provided for:
|
||||||
/// - bool
|
/// - bool
|
||||||
/// - int
|
/// - int and int64_t
|
||||||
/// - double
|
/// - double
|
||||||
/// - std::string
|
/// - std::string
|
||||||
/// - vector<T>, where T is deserializable
|
/// - vector<T>, where T is deserializable
|
||||||
@ -254,6 +254,8 @@ public:
|
|||||||
enum Kind {
|
enum Kind {
|
||||||
Null,
|
Null,
|
||||||
Boolean,
|
Boolean,
|
||||||
|
/// Number values can store both int64s and doubles at full precision,
|
||||||
|
/// depending on what they were constructed/parsed from.
|
||||||
Number,
|
Number,
|
||||||
String,
|
String,
|
||||||
Array,
|
Array,
|
||||||
@ -281,24 +283,36 @@ public:
|
|||||||
Value(llvm::StringRef V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
|
Value(llvm::StringRef V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
|
||||||
Value(const char *V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
|
Value(const char *V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
|
||||||
Value(std::nullptr_t) : Type(T_Null) {}
|
Value(std::nullptr_t) : Type(T_Null) {}
|
||||||
// Prevent implicit conversions to boolean.
|
// Boolean (disallow implicit conversions).
|
||||||
template <typename T, typename = typename std::enable_if<
|
// (The last template parameter is a dummy to keep templates distinct.)
|
||||||
std::is_same<T, bool>::value>::type>
|
template <
|
||||||
|
typename T,
|
||||||
|
typename = typename std::enable_if<std::is_same<T, bool>::value>::type,
|
||||||
|
bool = false>
|
||||||
Value(T B) : Type(T_Boolean) {
|
Value(T B) : Type(T_Boolean) {
|
||||||
create<bool>(B);
|
create<bool>(B);
|
||||||
}
|
}
|
||||||
// Numbers: arithmetic types that are not boolean.
|
// Integers (except boolean). Must be non-narrowing convertible to int64_t.
|
||||||
template <
|
template <
|
||||||
typename T,
|
typename T,
|
||||||
typename = typename std::enable_if<std::is_arithmetic<T>::value>::type,
|
typename = typename std::enable_if<std::is_integral<T>::value>::type,
|
||||||
typename = typename std::enable_if<!std::is_same<T, bool>::value>::type>
|
typename = typename std::enable_if<!std::is_same<T, bool>::value>::type>
|
||||||
Value(T D) : Type(T_Number) {
|
Value(T I) : Type(T_Integer) {
|
||||||
create<double>(D);
|
create<int64_t>(int64_t{I});
|
||||||
|
}
|
||||||
|
// Floating point. Must be non-narrowing convertible to double.
|
||||||
|
template <typename T,
|
||||||
|
typename =
|
||||||
|
typename std::enable_if<std::is_floating_point<T>::value>::type,
|
||||||
|
double * = nullptr>
|
||||||
|
Value(T D) : Type(T_Double) {
|
||||||
|
create<double>(double{D});
|
||||||
}
|
}
|
||||||
// Serializable types: with a toJSON(const T&)->Value function, found by ADL.
|
// Serializable types: with a toJSON(const T&)->Value function, found by ADL.
|
||||||
template <typename T,
|
template <typename T,
|
||||||
typename = typename std::enable_if<std::is_same<
|
typename = typename std::enable_if<std::is_same<
|
||||||
Value, decltype(toJSON(*(const T *)nullptr))>::value>>
|
Value, decltype(toJSON(*(const T *)nullptr))>::value>,
|
||||||
|
Value * = nullptr>
|
||||||
Value(const T &V) : Value(toJSON(V)) {}
|
Value(const T &V) : Value(toJSON(V)) {}
|
||||||
|
|
||||||
Value &operator=(const Value &M) {
|
Value &operator=(const Value &M) {
|
||||||
@ -319,7 +333,8 @@ public:
|
|||||||
return Null;
|
return Null;
|
||||||
case T_Boolean:
|
case T_Boolean:
|
||||||
return Boolean;
|
return Boolean;
|
||||||
case T_Number:
|
case T_Double:
|
||||||
|
case T_Integer:
|
||||||
return Number;
|
return Number;
|
||||||
case T_String:
|
case T_String:
|
||||||
case T_StringRef:
|
case T_StringRef:
|
||||||
@ -344,12 +359,17 @@ public:
|
|||||||
return llvm::None;
|
return llvm::None;
|
||||||
}
|
}
|
||||||
llvm::Optional<double> getAsNumber() const {
|
llvm::Optional<double> getAsNumber() const {
|
||||||
if (LLVM_LIKELY(Type == T_Number))
|
if (LLVM_LIKELY(Type == T_Double))
|
||||||
return as<double>();
|
return as<double>();
|
||||||
|
if (LLVM_LIKELY(Type == T_Integer))
|
||||||
|
return as<int64_t>();
|
||||||
return llvm::None;
|
return llvm::None;
|
||||||
}
|
}
|
||||||
|
// Succeeds if the Value is a Number, and exactly representable as int64_t.
|
||||||
llvm::Optional<int64_t> getAsInteger() const {
|
llvm::Optional<int64_t> getAsInteger() const {
|
||||||
if (LLVM_LIKELY(Type == T_Number)) {
|
if (LLVM_LIKELY(Type == T_Integer))
|
||||||
|
return as<int64_t>();
|
||||||
|
if (LLVM_LIKELY(Type == T_Double)) {
|
||||||
double D = as<double>();
|
double D = as<double>();
|
||||||
if (LLVM_LIKELY(std::modf(D, &D) == 0.0 &&
|
if (LLVM_LIKELY(std::modf(D, &D) == 0.0 &&
|
||||||
D >= double(std::numeric_limits<int64_t>::min()) &&
|
D >= double(std::numeric_limits<int64_t>::min()) &&
|
||||||
@ -407,9 +427,8 @@ private:
|
|||||||
enum ValueType : char {
|
enum ValueType : char {
|
||||||
T_Null,
|
T_Null,
|
||||||
T_Boolean,
|
T_Boolean,
|
||||||
// FIXME: splitting Number into Double and Integer would allow us to
|
T_Double,
|
||||||
// round-trip 64-bit integers.
|
T_Integer,
|
||||||
T_Number,
|
|
||||||
T_StringRef,
|
T_StringRef,
|
||||||
T_String,
|
T_String,
|
||||||
T_Object,
|
T_Object,
|
||||||
@ -417,7 +436,7 @@ private:
|
|||||||
};
|
};
|
||||||
// All members mutable, see moveFrom().
|
// All members mutable, see moveFrom().
|
||||||
mutable ValueType Type;
|
mutable ValueType Type;
|
||||||
mutable llvm::AlignedCharArrayUnion<bool, double, llvm::StringRef,
|
mutable llvm::AlignedCharArrayUnion<bool, double, int64_t, llvm::StringRef,
|
||||||
std::string, json::Array, json::Object>
|
std::string, json::Array, json::Object>
|
||||||
Union;
|
Union;
|
||||||
};
|
};
|
||||||
@ -505,6 +524,13 @@ inline bool fromJSON(const Value &E, int &Out) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
inline bool fromJSON(const Value &E, int64_t &Out) {
|
||||||
|
if (auto S = E.getAsInteger()) {
|
||||||
|
Out = *S;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
inline bool fromJSON(const Value &E, double &Out) {
|
inline bool fromJSON(const Value &E, double &Out) {
|
||||||
if (auto S = E.getAsNumber()) {
|
if (auto S = E.getAsNumber()) {
|
||||||
Out = *S;
|
Out = *S;
|
||||||
|
@ -104,7 +104,8 @@ void Value::copyFrom(const Value &M) {
|
|||||||
switch (Type) {
|
switch (Type) {
|
||||||
case T_Null:
|
case T_Null:
|
||||||
case T_Boolean:
|
case T_Boolean:
|
||||||
case T_Number:
|
case T_Double:
|
||||||
|
case T_Integer:
|
||||||
memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
|
memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
|
||||||
break;
|
break;
|
||||||
case T_StringRef:
|
case T_StringRef:
|
||||||
@ -127,7 +128,8 @@ void Value::moveFrom(const Value &&M) {
|
|||||||
switch (Type) {
|
switch (Type) {
|
||||||
case T_Null:
|
case T_Null:
|
||||||
case T_Boolean:
|
case T_Boolean:
|
||||||
case T_Number:
|
case T_Double:
|
||||||
|
case T_Integer:
|
||||||
memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
|
memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
|
||||||
break;
|
break;
|
||||||
case T_StringRef:
|
case T_StringRef:
|
||||||
@ -152,7 +154,8 @@ void Value::destroy() {
|
|||||||
switch (Type) {
|
switch (Type) {
|
||||||
case T_Null:
|
case T_Null:
|
||||||
case T_Boolean:
|
case T_Boolean:
|
||||||
case T_Number:
|
case T_Double:
|
||||||
|
case T_Integer:
|
||||||
break;
|
break;
|
||||||
case T_StringRef:
|
case T_StringRef:
|
||||||
as<StringRef>().~StringRef();
|
as<StringRef>().~StringRef();
|
||||||
@ -217,7 +220,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// On invalid syntax, parseX() functions return false and set Err.
|
// On invalid syntax, parseX() functions return false and set Err.
|
||||||
bool parseNumber(char First, double &Out);
|
bool parseNumber(char First, Value &Out);
|
||||||
bool parseString(std::string &Out);
|
bool parseString(std::string &Out);
|
||||||
bool parseUnicode(std::string &Out);
|
bool parseUnicode(std::string &Out);
|
||||||
bool parseError(const char *Msg); // always returns false
|
bool parseError(const char *Msg); // always returns false
|
||||||
@ -317,25 +320,28 @@ bool Parser::parseValue(Value &Out) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if (isNumber(C)) {
|
if (isNumber(C))
|
||||||
double Num;
|
return parseNumber(C, Out);
|
||||||
if (parseNumber(C, Num)) {
|
|
||||||
Out = Num;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parseError("Invalid JSON value");
|
return parseError("Invalid JSON value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Parser::parseNumber(char First, double &Out) {
|
bool Parser::parseNumber(char First, Value &Out) {
|
||||||
|
// Read the number into a string. (Must be null-terminated for strto*).
|
||||||
SmallString<24> S;
|
SmallString<24> S;
|
||||||
S.push_back(First);
|
S.push_back(First);
|
||||||
while (isNumber(peek()))
|
while (isNumber(peek()))
|
||||||
S.push_back(next());
|
S.push_back(next());
|
||||||
char *End;
|
char *End;
|
||||||
|
// Try first to parse as integer, and if so preserve full 64 bits.
|
||||||
|
// strtoll returns long long >= 64 bits, so check it's in range too.
|
||||||
|
auto I = std::strtoll(S.c_str(), &End, 10);
|
||||||
|
if (End == S.end() && I >= std::numeric_limits<int64_t>::min() &&
|
||||||
|
I <= std::numeric_limits<int64_t>::max()) {
|
||||||
|
Out = int64_t(I);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// If it's not an integer
|
||||||
Out = std::strtod(S.c_str(), &End);
|
Out = std::strtod(S.c_str(), &End);
|
||||||
return End == S.end() || parseError("Invalid JSON value (number?)");
|
return End == S.end() || parseError("Invalid JSON value (number?)");
|
||||||
}
|
}
|
||||||
@ -558,8 +564,12 @@ void llvm::json::Value::print(raw_ostream &OS, const Indenter &I) const {
|
|||||||
case T_Boolean:
|
case T_Boolean:
|
||||||
OS << (as<bool>() ? "true" : "false");
|
OS << (as<bool>() ? "true" : "false");
|
||||||
break;
|
break;
|
||||||
case T_Number:
|
case T_Double:
|
||||||
OS << format("%g", as<double>());
|
OS << format("%.*g", std::numeric_limits<double>::max_digits10,
|
||||||
|
as<double>());
|
||||||
|
break;
|
||||||
|
case T_Integer:
|
||||||
|
OS << as<int64_t>();
|
||||||
break;
|
break;
|
||||||
case T_StringRef:
|
case T_StringRef:
|
||||||
quote(OS, as<StringRef>());
|
quote(OS, as<StringRef>());
|
||||||
|
@ -227,6 +227,66 @@ TEST(JSONTest, Inspection) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify special integer handling - we try to preserve exact int64 values.
|
||||||
|
TEST(JSONTest, Integers) {
|
||||||
|
struct {
|
||||||
|
const char *Desc;
|
||||||
|
Value Val;
|
||||||
|
const char *Str;
|
||||||
|
llvm::Optional<int64_t> AsInt;
|
||||||
|
llvm::Optional<double> AsNumber;
|
||||||
|
} TestCases[] = {
|
||||||
|
{
|
||||||
|
"Non-integer. Stored as double, not convertible.",
|
||||||
|
double{1.5},
|
||||||
|
"1.5",
|
||||||
|
llvm::None,
|
||||||
|
1.5,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Integer, not exact double. Stored as int64, convertible.",
|
||||||
|
int64_t{0x4000000000000001},
|
||||||
|
"4611686018427387905",
|
||||||
|
int64_t{0x4000000000000001},
|
||||||
|
double{0x4000000000000000},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Negative integer, not exact double. Stored as int64, convertible.",
|
||||||
|
int64_t{-0x4000000000000001},
|
||||||
|
"-4611686018427387905",
|
||||||
|
int64_t{-0x4000000000000001},
|
||||||
|
double{-0x4000000000000000},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Dynamically exact integer. Stored as double, convertible.",
|
||||||
|
double{0x6000000000000000},
|
||||||
|
"6.9175290276410819e+18",
|
||||||
|
int64_t{0x6000000000000000},
|
||||||
|
double{0x6000000000000000},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Dynamically integer, >64 bits. Stored as double, not convertible.",
|
||||||
|
1.5 * double{0x8000000000000000},
|
||||||
|
"1.3835058055282164e+19",
|
||||||
|
llvm::None,
|
||||||
|
1.5 * double{0x8000000000000000},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (const auto &T : TestCases) {
|
||||||
|
EXPECT_EQ(T.Str, s(T.Val)) << T.Desc;
|
||||||
|
llvm::Expected<Value> Doc = parse(T.Str);
|
||||||
|
EXPECT_TRUE(!!Doc) << T.Desc;
|
||||||
|
EXPECT_EQ(Doc->getAsInteger(), T.AsInt) << T.Desc;
|
||||||
|
EXPECT_EQ(Doc->getAsNumber(), T.AsNumber) << T.Desc;
|
||||||
|
EXPECT_EQ(T.Val, *Doc) << T.Desc;
|
||||||
|
EXPECT_EQ(T.Str, s(*Doc)) << T.Desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Sample struct with typical JSON-mapping rules.
|
// Sample struct with typical JSON-mapping rules.
|
||||||
struct CustomStruct {
|
struct CustomStruct {
|
||||||
CustomStruct() : B(false) {}
|
CustomStruct() : B(false) {}
|
||||||
|
Loading…
Reference in New Issue
Block a user