summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCasper <casperneo@uchicago.edu>2020-07-16 13:43:47 -0700
committerGitHub <noreply@github.com>2020-07-16 13:43:47 -0700
commit9ecd2e16c2adb6fe9e3cf2fa8f4e1e310d48a1c2 (patch)
treeec79c62ad00d6cad36b799e801d5d4293edf8a5c
parent33e2d807919ee70e0b353f776c0ecbccf9cba130 (diff)
downloadflatbuffers-9ecd2e16c2adb6fe9e3cf2fa8f4e1e310d48a1c2.tar.gz
flatbuffers-9ecd2e16c2adb6fe9e3cf2fa8f4e1e310d48a1c2.tar.bz2
flatbuffers-9ecd2e16c2adb6fe9e3cf2fa8f4e1e310d48a1c2.zip
Flatc parser support for nullable scalars (#6026)
* Parser support for nullable scalars * Use older C++ features * use default element * Add a test for json, flexbuffers, and null * test comments and names Co-authored-by: Casper Neo <cneo@google.com>
-rw-r--r--include/flatbuffers/idl.h4
-rw-r--r--src/idl_parser.cpp29
-rw-r--r--tests/test.cpp77
3 files changed, 108 insertions, 2 deletions
diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h
index 8c02f282..70a2b787 100644
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
@@ -296,6 +296,7 @@ struct FieldDef : public Definition {
shared(false),
native_inline(false),
flexbuffer(false),
+ nullable(false),
nested_flatbuffer(NULL),
padding(0) {}
@@ -314,6 +315,8 @@ struct FieldDef : public Definition {
bool native_inline; // Field will be defined inline (instead of as a pointer)
// for native tables if field is a struct.
bool flexbuffer; // This field contains FlexBuffer data.
+ bool nullable; // If True, this field is Null (as opposed to default
+ // valued).
StructDef *nested_flatbuffer; // This field contains nested FlatBuffer data.
size_t padding; // Bytes to always pad after this field.
};
@@ -927,6 +930,7 @@ class Parser : public ParserState {
bool SupportsAdvancedUnionFeatures() const;
bool SupportsAdvancedArrayFeatures() const;
+ bool SupportsNullableScalars() const;
Namespace *UniqueNamespace(Namespace *ns);
FLATBUFFERS_CHECKED_ERROR RecurseError();
diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp
index 6faca7ac..5ccd690d 100644
--- a/src/idl_parser.cpp
+++ b/src/idl_parser.cpp
@@ -721,7 +721,7 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
// Only cpp, js and ts supports the union vector feature so far.
if (!SupportsAdvancedUnionFeatures()) {
return Error(
- "Vectors of unions are not yet supported in all "
+ "Vectors of unions are not yet supported in at least one of "
"the specified programming languages.");
}
// For vector of union fields, add a second auto-generated vector field to
@@ -743,6 +743,19 @@ CheckedError Parser::ParseField(StructDef &struct_def) {
return Error(
"default values currently only supported for scalars in tables");
}
+
+ // Mark the nullable scalars. Note that a side effect of ParseSingleValue is
+ // fixing field->value.constant to null.
+ if (IsScalar(type.base_type)) {
+ field->nullable = (field->value.constant == "null");
+ if (field->nullable && !SupportsNullableScalars()) {
+ return Error(
+ "Nullable scalars are not yet supported in at least one the of "
+ "the specified programming languages."
+ );
+ }
+ }
+
// Append .0 if the value has not it (skip hex and scientific floats).
// This suffix needed for generated C++ code.
if (IsFloat(type.base_type)) {
@@ -1758,6 +1771,12 @@ CheckedError Parser::ParseSingleValue(const std::string *name, Value &e,
TRY_ECHECK(kTokenStringOrIdent, IsBool(in_type), BASE_TYPE_BOOL);
}
}
+ // Check for optional scalars.
+ if (!match && IsScalar(in_type) && attribute_ == "null") {
+ e.constant = "null";
+ NEXT();
+ match = true;
+ }
// Check if this could be a string/identifier enum value.
// Enum can have only true integer base type.
if (!match && IsInteger(in_type) && !IsBool(in_type) &&
@@ -1811,7 +1830,8 @@ CheckedError Parser::ParseSingleValue(const std::string *name, Value &e,
// This flag forces to check default scalar values or metadata of field.
// For JSON parser the flag should be false.
// If it is set for JSON each value will be checked twice (see ParseTable).
- if (check_now && IsScalar(match_type)) {
+ // Special case 'null' since atot can't handle that.
+ if (check_now && IsScalar(match_type) && e.constant != "null") {
// clang-format off
switch (match_type) {
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, ...) \
@@ -2236,6 +2256,11 @@ CheckedError Parser::CheckClash(std::vector<FieldDef *> &fields,
return NoError();
}
+
+bool Parser::SupportsNullableScalars() const {
+ return opts.lang_to_generate == 0; // No support yet.
+}
+
bool Parser::SupportsAdvancedUnionFeatures() const {
return opts.lang_to_generate != 0 &&
(opts.lang_to_generate &
diff --git a/tests/test.cpp b/tests/test.cpp
index b7ae7672..de86f02b 100644
--- a/tests/test.cpp
+++ b/tests/test.cpp
@@ -3416,6 +3416,81 @@ void TestEmbeddedBinarySchema() {
0);
}
+void NullableScalarsTest() {
+ // Simple schemas and a "has nullable scalar" sentinal.
+ std::vector<std::string> schemas;
+ schemas.push_back("table Monster { mana : int; }");
+ schemas.push_back("table Monster { mana : int = 42; }");
+ schemas.push_back("table Monster { mana : int = null; }");
+ schemas.push_back("table Monster { mana : long; }");
+ schemas.push_back("table Monster { mana : long = 42; }");
+ schemas.push_back("table Monster { mana : long = null; }");
+ schemas.push_back("table Monster { mana : float; }");
+ schemas.push_back("table Monster { mana : float = 42; }");
+ schemas.push_back("table Monster { mana : float = null; }");
+ schemas.push_back("table Monster { mana : double; }");
+ schemas.push_back("table Monster { mana : double = 42; }");
+ schemas.push_back("table Monster { mana : double = null; }");
+ schemas.push_back("table Monster { mana : bool; }");
+ schemas.push_back("table Monster { mana : bool = 42; }");
+ schemas.push_back("table Monster { mana : bool = null; }");
+
+ // Check the FieldDef is correctly set.
+ for (auto schema = schemas.begin(); schema < schemas.end(); schema++) {
+ const bool has_null = schema->find("null") != std::string::npos;
+ flatbuffers::Parser parser;
+ TEST_ASSERT(parser.Parse(schema->c_str()));
+ const auto *mana = parser.structs_.Lookup("Monster")->fields.Lookup("mana");
+ TEST_EQ(mana->nullable, has_null);
+ }
+
+ // Test if nullable scalars are allowed for each language.
+ const int kNumLanguages = 17;
+ for (int lang=0; lang<kNumLanguages; lang++) {
+ flatbuffers::IDLOptions opts;
+ opts.lang_to_generate |= 1 << lang;
+ for (auto schema = schemas.begin(); schema < schemas.end(); schema++) {
+ const bool has_null = schema->find("null") != std::string::npos;
+ flatbuffers::Parser parser(opts);
+ // Its not supported in any language yet so has_null means error.
+ TEST_EQ(parser.Parse(schema->c_str()), !has_null);
+ }
+ }
+}
+
+void ParseFlexbuffersFromJsonWithNullTest() {
+ // Test nulls are handled appropriately through flexbuffers to exercise other
+ // code paths of ParseSingleValue in the nullable scalars change.
+ // TODO(cneo): Json -> Flatbuffers test once some language can generate code
+ // with nullable scalars.
+ {
+ char json[] = "{\"opt_field\": 123 }";
+ flatbuffers::Parser parser;
+ flexbuffers::Builder flexbuild;
+ parser.ParseFlexBuffer(json, nullptr, &flexbuild);
+ auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
+ TEST_EQ(root.AsMap()["opt_field"].AsInt64(), 123);
+ }
+ {
+ char json[] = "{\"opt_field\": 123.4 }";
+ flatbuffers::Parser parser;
+ flexbuffers::Builder flexbuild;
+ parser.ParseFlexBuffer(json, nullptr, &flexbuild);
+ auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
+ TEST_EQ(root.AsMap()["opt_field"].AsDouble(), 123.4);
+ }
+ {
+ char json[] = "{\"opt_field\": null }";
+ flatbuffers::Parser parser;
+ flexbuffers::Builder flexbuild;
+ parser.ParseFlexBuffer(json, nullptr, &flexbuild);
+ auto root = flexbuffers::GetRoot(flexbuild.GetBuffer());
+ TEST_ASSERT(!root.AsMap().IsTheEmptyMap());
+ TEST_ASSERT(root.AsMap()["opt_field"].IsNull());
+ TEST_EQ(root.ToString(), std::string("{ opt_field: null }"));
+ }
+}
+
int FlatBufferTests() {
// clang-format off
@@ -3503,6 +3578,8 @@ int FlatBufferTests() {
TestMonsterExtraFloats();
FixedLengthArrayTest();
NativeTypeTest();
+ NullableScalarsTest();
+ ParseFlexbuffersFromJsonWithNullTest();
return 0;
}