Commit a2b1bfc1 authored by rouzier's avatar rouzier Committed by Wouter van Oortmerssen

[c++] Add support for boolean types in flexbuffers (#4386)

* Add support for boolean types in flexbuffers

* Simplify casting number <=> boolean

* Added comments for tests

* Add proper support for Booleans

* Bad rebase

* No special case for strings

* Removed unused test

* Simplify logic
parent f2b3705c
......@@ -76,9 +76,10 @@ enum Type {
TYPE_VECTOR_UINT4 = 23,
TYPE_VECTOR_FLOAT4 = 24,
TYPE_BLOB = 25,
TYPE_BOOL = 26,
};
inline bool IsInline(Type t) { return t <= TYPE_FLOAT; }
inline bool IsInline(Type t) { return t <= TYPE_FLOAT || t == TYPE_BOOL; }
inline bool IsTypedVectorElementType(Type t) {
return t >= TYPE_INT && t <= TYPE_STRING;
......@@ -349,6 +350,7 @@ class Reference {
Type GetType() const { return type_; }
bool IsNull() const { return type_ == TYPE_NULL; }
bool IsBool() const { return type_ == TYPE_BOOL; }
bool IsInt() const { return type_ == TYPE_INT ||
type_ == TYPE_INDIRECT_INT; }
bool IsUInt() const { return type_ == TYPE_UINT||
......@@ -363,6 +365,10 @@ class Reference {
bool IsMap() const { return type_ == TYPE_MAP; }
bool IsBlob() const { return type_ == TYPE_BLOB; }
bool AsBool() const {
return (type_ == TYPE_BOOL ? ReadUInt64(data_, parent_width_) : AsUInt64()) != 0;
}
// Reads any type as a int64_t. Never fails, does most sensible conversion.
// Truncates floats, strings are attempted to be parsed for a number,
// vectors/maps return their size. Returns 0 if all else fails.
......@@ -381,6 +387,7 @@ class Reference {
case TYPE_NULL: return 0;
case TYPE_STRING: return flatbuffers::StringToInt(AsString().c_str());
case TYPE_VECTOR: return static_cast<int64_t>(AsVector().size());
case TYPE_BOOL: return ReadInt64(data_, parent_width_);
default:
// Convert other things to int.
return 0;
......@@ -408,6 +415,7 @@ class Reference {
case TYPE_NULL: return 0;
case TYPE_STRING: return flatbuffers::StringToUInt(AsString().c_str());
case TYPE_VECTOR: return static_cast<uint64_t>(AsVector().size());
case TYPE_BOOL: return ReadUInt64(data_, parent_width_);
default:
// Convert other things to uint.
return 0;
......@@ -435,6 +443,8 @@ class Reference {
case TYPE_NULL: return 0.0;
case TYPE_STRING: return strtod(AsString().c_str(), nullptr);
case TYPE_VECTOR: return static_cast<double>(AsVector().size());
case TYPE_BOOL: return static_cast<double>(
ReadUInt64(data_, parent_width_));
default:
// Convert strings and other things to float.
return 0;
......@@ -494,6 +504,8 @@ class Reference {
s += flatbuffers::NumToString(AsDouble());
} else if (IsNull()) {
s += "null";
} else if (IsBool()) {
s += AsBool() ? "true" : "false";
} else if (IsMap()) {
s += "{ ";
auto m = AsMap();
......@@ -588,6 +600,10 @@ class Reference {
}
}
bool MutateBool(bool b) {
return type_ == TYPE_BOOL && Mutate(data_, b, parent_width_, BIT_WIDTH_8);
}
bool MutateUInt(uint64_t u) {
if (type_ == TYPE_UINT) {
return Mutate(data_, u, parent_width_, WidthU(u));
......@@ -816,7 +832,7 @@ class Builder FLATBUFFERS_FINAL_CLASS {
void Double(double f) { stack_.push_back(Value(f)); }
void Double(const char *key, double d) { Key(key); Double(d); }
void Bool(bool b) { Int(static_cast<int64_t>(b)); }
void Bool(bool b) { stack_.push_back(Value(b)); }
void Bool(const char *key, bool b) { Key(key); Bool(b); }
void IndirectInt(int64_t i) {
......@@ -1236,6 +1252,8 @@ class Builder FLATBUFFERS_FINAL_CLASS {
Value() : i_(0), type_(TYPE_NULL), min_bit_width_(BIT_WIDTH_8) {}
Value(bool b) : u_(static_cast<uint64_t>(b)), type_(TYPE_BOOL), min_bit_width_(BIT_WIDTH_8) {}
Value(int64_t i, Type t, BitWidth bw)
: i_(i), type_(t), min_bit_width_(bw) {}
Value(uint64_t u, Type t, BitWidth bw)
......@@ -1294,6 +1312,7 @@ class Builder FLATBUFFERS_FINAL_CLASS {
case TYPE_INT:
Write(val.i_, byte_width);
break;
case TYPE_BOOL:
case TYPE_UINT:
Write(val.u_, byte_width);
break;
......
......@@ -110,6 +110,7 @@ inline bool IsFloat (BaseType t) { return t == BASE_TYPE_FLOAT ||
t == BASE_TYPE_DOUBLE; }
inline bool IsLong (BaseType t) { return t == BASE_TYPE_LONG ||
t == BASE_TYPE_ULONG; }
inline bool IsBool (BaseType t) { return t == BASE_TYPE_BOOL; }
extern const char *const kTypeNames[];
extern const char kTypeSizes[];
......
......@@ -187,7 +187,8 @@ std::string Namespace::GetFullyQualifiedName(const std::string &name,
TD(Attribute, 270, "attribute") \
TD(Null, 271, "null") \
TD(Service, 272, "rpc_service") \
TD(NativeInclude, 273, "native_include")
TD(NativeInclude, 273, "native_include") \
TD(BooleanConstant, 274, "boolean constant")
#ifdef __GNUC__
__extension__ // Stop GCC complaining about trailing comma with -Wpendantic.
#endif
......@@ -395,7 +396,7 @@ CheckedError Parser::Next() {
// which simplifies our logic downstream.
if (attribute_ == "true" || attribute_ == "false") {
attribute_ = NumToString(attribute_ == "true");
token_ = kTokenIntegerConstant;
token_ = kTokenBooleanConstant;
return NoError();
}
// Check for declaration keywords:
......@@ -1343,6 +1344,11 @@ CheckedError Parser::ParseSingleValue(Value &e) {
e,
BASE_TYPE_INT,
&match));
ECHECK(TryTypedValue(kTokenBooleanConstant,
IsBool(e.type.base_type),
e,
BASE_TYPE_BOOL,
&match));
ECHECK(TryTypedValue(kTokenFloatConstant,
IsFloat(e.type.base_type),
e,
......@@ -2012,6 +2018,9 @@ CheckedError Parser::SkipAnyJsonValue() {
case kTokenFloatConstant:
EXPECT(kTokenFloatConstant);
break;
case kTokenBooleanConstant:
EXPECT(kTokenBooleanConstant);
break;
default:
return TokenError();
}
......@@ -2069,6 +2078,10 @@ CheckedError Parser::ParseFlexBufferValue(flexbuffers::Builder *builder) {
builder->Int(StringToInt(attribute_.c_str()));
EXPECT(kTokenIntegerConstant);
break;
case kTokenBooleanConstant:
builder->Bool(StringToInt(attribute_.c_str()) != 0);
EXPECT(kTokenBooleanConstant);
break;
case kTokenFloatConstant:
builder->Double(strtod(attribute_.c_str(), nullptr));
EXPECT(kTokenFloatConstant);
......
......@@ -1591,7 +1591,7 @@ void FlexBuffersTest() {
flexbuffers::BUILDER_FLAG_SHARE_KEYS_AND_STRINGS);
// Write the equivalent of:
// { vec: [ -100, "Fred", 4.0 ], bar: [ 1, 2, 3 ], foo: 100 }
// { vec: [ -100, "Fred", 4.0, false ], bar: [ 1, 2, 3 ], bar3: [ 1, 2, 3 ], foo: 100, bool: true, mymap: { foo: "Fred" } }
#ifndef FLATBUFFERS_CPP98_STL
// It's possible to do this without std::function support as well.
slb.Map([&]() {
......@@ -1601,10 +1601,12 @@ void FlexBuffersTest() {
slb.IndirectFloat(4.0f);
uint8_t blob[] = { 77 };
slb.Blob(blob, 1);
slb += false;
});
int ints[] = { 1, 2, 3 };
slb.Vector("bar", ints, 3);
slb.FixedTypedVector("bar3", ints, 3);
slb.Bool("bool", true);
slb.Double("foo", 100);
slb.Map("mymap", [&]() {
slb.String("foo", "Fred"); // Testing key and string reuse.
......@@ -1637,9 +1639,9 @@ void FlexBuffersTest() {
printf("\n");
auto map = flexbuffers::GetRoot(slb.GetBuffer()).AsMap();
TEST_EQ(map.size(), 5);
TEST_EQ(map.size(), 6);
auto vec = map["vec"].AsVector();
TEST_EQ(vec.size(), 4);
TEST_EQ(vec.size(), 5);
TEST_EQ(vec[0].AsInt64(), -100);
TEST_EQ_STR(vec[1].AsString().c_str(), "Fred");
TEST_EQ(vec[1].AsInt64(), 0); // Number parsing failed.
......@@ -1652,17 +1654,20 @@ void FlexBuffersTest() {
auto blob = vec[3].AsBlob();
TEST_EQ(blob.size(), 1);
TEST_EQ(blob.data()[0], 77);
TEST_EQ(vec[4].IsBool(), true); // Check if type is a bool
TEST_EQ(vec[4].AsBool(), false); // Check if value is false
auto tvec = map["bar"].AsTypedVector();
TEST_EQ(tvec.size(), 3);
TEST_EQ(tvec[2].AsInt8(), 3);
auto tvec3 = map["bar3"].AsFixedTypedVector();
TEST_EQ(tvec3.size(), 3);
TEST_EQ(tvec3[2].AsInt8(), 3);
TEST_EQ(map["bool"].AsBool(), true);
TEST_EQ(map["foo"].AsUInt8(), 100);
TEST_EQ(map["unknown"].IsNull(), true);
auto mymap = map["mymap"].AsMap();
// These should be equal by pointer equality, since key and value are shared.
TEST_EQ(mymap.Keys()[0].AsKey(), map.Keys()[2].AsKey());
TEST_EQ(mymap.Keys()[0].AsKey(), map.Keys()[3].AsKey());
TEST_EQ(mymap.Values()[0].AsString().c_str(), vec[1].AsString().c_str());
// We can mutate values in the buffer.
TEST_EQ(vec[0].MutateInt(-99), true);
......@@ -1673,11 +1678,14 @@ void FlexBuffersTest() {
TEST_EQ(vec[2].MutateFloat(2.0f), true);
TEST_EQ(vec[2].AsFloat(), 2.0f);
TEST_EQ(vec[2].MutateFloat(3.14159), false); // Double does not fit in float.
TEST_EQ(vec[4].AsBool(), false); // Is false before change
TEST_EQ(vec[4].MutateBool(true), true); // Can change a bool
TEST_EQ(vec[4].AsBool(), true); // Changed bool is now true
// Parse from JSON:
flatbuffers::Parser parser;
slb.Clear();
auto jsontest = "{ a: [ 123, 456.0 ], b: \"hello\" }";
auto jsontest = "{ a: [ 123, 456.0 ], b: \"hello\", c: true, d: false }";
TEST_EQ(parser.ParseFlexBuffer(jsontest, nullptr, &slb),
true);
auto jroot = flexbuffers::GetRoot(slb.GetBuffer());
......@@ -1686,6 +1694,10 @@ void FlexBuffersTest() {
TEST_EQ(jvec[0].AsInt64(), 123);
TEST_EQ(jvec[1].AsDouble(), 456.0);
TEST_EQ_STR(jmap["b"].AsString().c_str(), "hello");
TEST_EQ(jmap["c"].IsBool(), true); // Parsed correctly to a bool
TEST_EQ(jmap["c"].AsBool(), true); // Parsed correctly to true
TEST_EQ(jmap["d"].IsBool(), true); // Parsed correctly to a bool
TEST_EQ(jmap["d"].AsBool(), false); // Parsed correctly to false
// And from FlexBuffer back to JSON:
auto jsonback = jroot.ToString();
TEST_EQ_STR(jsontest, jsonback.c_str());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment