From a2b1bfc1075e8e5ae7b347a04cbb796524f2c5c9 Mon Sep 17 00:00:00 2001 From: rouzier Date: Fri, 4 Aug 2017 11:04:28 -0400 Subject: [PATCH] [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 --- include/flatbuffers/flexbuffers.h | 23 +++++++++++++++++++++-- include/flatbuffers/idl.h | 1 + src/idl_parser.cpp | 17 +++++++++++++++-- tests/test.cpp | 22 +++++++++++++++++----- 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/include/flatbuffers/flexbuffers.h b/include/flatbuffers/flexbuffers.h index 4b722babe..fa22a8e39 100644 --- a/include/flatbuffers/flexbuffers.h +++ b/include/flatbuffers/flexbuffers.h @@ -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(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(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(AsVector().size()); + case TYPE_BOOL: return static_cast( + 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(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(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; diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index cd37b9cc6..2385f471b 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -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[]; diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index b8356316b..b27493945 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -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); diff --git a/tests/test.cpp b/tests/test.cpp index 90d8a7c07..a8c6faa9e 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -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());