Protobufs: Added '--oneof-union' option. (#4647)

* Added '--oneof-union' option.

Used with the .proto -> .fbs converter, will translate protobuff oneofs to flatbuffer unions.
Updated proto test to check both methods of converting oneofs.

* Added '--oneof-union' option.

Used with the .proto -> .fbs converter, will translate protobuff oneofs to flatbuffer unions.
Updated proto test to check both methods of converting oneofs.

* FlatBuffers: Moved MakeCamel() into idl_parser.cpp

Removes library dependency on Java/C# generator code.
This commit is contained in:
Nik Hemmings
2018-03-05 16:45:25 +00:00
committed by Wouter van Oortmerssen
parent 77b458bee5
commit fb94af8899
10 changed files with 211 additions and 63 deletions

View File

@@ -76,6 +76,21 @@ static bool ValidateUTF8(const std::string &str) {
return true;
}
// Convert an underscore_based_indentifier in to camelCase.
// Also uppercases the first character if first is true.
std::string MakeCamel(const std::string &in, bool first) {
std::string s;
for (size_t i = 0; i < in.length(); i++) {
if (!i && first)
s += static_cast<char>(toupper(in[0]));
else if (in[i] == '_' && i + 1 < in.length())
s += static_cast<char>(toupper(in[++i]));
else
s += in[i];
}
return s;
}
void Parser::Message(const std::string &msg) {
error_ = file_being_parsed_.length() ? AbsolutePath(file_being_parsed_) : "";
// clang-format off
@@ -1467,42 +1482,29 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
NEXT();
std::string enum_name = attribute_;
EXPECT(kTokenIdentifier);
auto &enum_def = *new EnumDef();
enum_def.name = enum_name;
enum_def.file = file_being_parsed_;
enum_def.doc_comment = enum_comment;
enum_def.is_union = is_union;
enum_def.defined_namespace = current_namespace_;
if (enums_.Add(current_namespace_->GetFullyQualifiedName(enum_name),
&enum_def))
return Error("enum already exists: " + enum_name);
if (is_union) {
enum_def.underlying_type.base_type = BASE_TYPE_UTYPE;
enum_def.underlying_type.enum_def = &enum_def;
} else {
if (opts.proto_mode) {
enum_def.underlying_type.base_type = BASE_TYPE_INT;
EnumDef *enum_def;
ECHECK(StartEnum(enum_name, is_union, &enum_def));
enum_def->doc_comment = enum_comment;
if (!is_union && !opts.proto_mode) {
// Give specialized error message, since this type spec used to
// be optional in the first FlatBuffers release.
if (!Is(':')) {
return Error(
"must specify the underlying integer type for this"
" enum (e.g. \': short\', which was the default).");
} else {
// Give specialized error message, since this type spec used to
// be optional in the first FlatBuffers release.
if (!Is(':')) {
return Error(
"must specify the underlying integer type for this"
" enum (e.g. \': short\', which was the default).");
} else {
NEXT();
}
// Specify the integer type underlying this enum.
ECHECK(ParseType(enum_def.underlying_type));
if (!IsInteger(enum_def.underlying_type.base_type))
return Error("underlying enum type must be integral");
NEXT();
}
// Specify the integer type underlying this enum.
ECHECK(ParseType(enum_def->underlying_type));
if (!IsInteger(enum_def->underlying_type.base_type))
return Error("underlying enum type must be integral");
// Make this type refer back to the enum it was derived from.
enum_def.underlying_type.enum_def = &enum_def;
enum_def->underlying_type.enum_def = enum_def;
}
ECHECK(ParseMetaData(&enum_def.attributes));
ECHECK(ParseMetaData(&enum_def->attributes));
EXPECT('{');
if (is_union) enum_def.vals.Add("NONE", new EnumVal("NONE", 0));
if (is_union) enum_def->vals.Add("NONE", new EnumVal("NONE", 0));
for (;;) {
if (opts.proto_mode && attribute_ == "option") {
ECHECK(ParseProtoOption());
@@ -1520,11 +1522,12 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
std::replace(value_name.begin(), value_name.end(), '.', '_');
}
}
auto prevsize = enum_def.vals.vec.size();
auto value =
!enum_def.vals.vec.empty() ? enum_def.vals.vec.back()->value + 1 : 0;
auto prevsize = enum_def->vals.vec.size();
auto value = !enum_def->vals.vec.empty()
? enum_def->vals.vec.back()->value + 1
: 0;
auto &ev = *new EnumVal(value_name, value);
if (enum_def.vals.Add(value_name, &ev))
if (enum_def->vals.Add(value_name, &ev))
return Error("enum value already exists: " + value_name);
ev.doc_comment = value_comment;
if (is_union) {
@@ -1534,7 +1537,7 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
if (ev.union_type.base_type != BASE_TYPE_STRUCT &&
ev.union_type.base_type != BASE_TYPE_STRING)
return Error("union value type may only be table/struct/string");
enum_def.uses_type_aliases = true;
enum_def->uses_type_aliases = true;
} else {
ev.union_type = Type(BASE_TYPE_STRUCT, LookupCreateStruct(full_name));
}
@@ -1544,7 +1547,7 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
ev.value = StringToInt(attribute_.c_str());
EXPECT(kTokenIntegerConstant);
if (!opts.proto_mode && prevsize &&
enum_def.vals.vec[prevsize - 1]->value >= ev.value)
enum_def->vals.vec[prevsize - 1]->value >= ev.value)
return Error("enum values must be specified in ascending order");
}
if (is_union) {
@@ -1563,18 +1566,18 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) {
if (Is('}')) break;
}
EXPECT('}');
if (enum_def.attributes.Lookup("bit_flags")) {
for (auto it = enum_def.vals.vec.begin(); it != enum_def.vals.vec.end();
if (enum_def->attributes.Lookup("bit_flags")) {
for (auto it = enum_def->vals.vec.begin(); it != enum_def->vals.vec.end();
++it) {
if (static_cast<size_t>((*it)->value) >=
SizeOf(enum_def.underlying_type.base_type) * 8)
SizeOf(enum_def->underlying_type.base_type) * 8)
return Error("bit flag out of range of underlying integral type");
(*it)->value = 1LL << (*it)->value;
}
}
if (dest) *dest = &enum_def;
types_.Add(current_namespace_->GetFullyQualifiedName(enum_def.name),
new Type(BASE_TYPE_UNION, nullptr, &enum_def));
if (dest) *dest = enum_def;
types_.Add(current_namespace_->GetFullyQualifiedName(enum_def->name),
new Type(BASE_TYPE_UNION, nullptr, enum_def));
return NoError();
}
@@ -1864,6 +1867,24 @@ CheckedError Parser::ParseProtoDecl() {
return NoError();
}
CheckedError Parser::StartEnum(const std::string &enum_name, bool is_union,
EnumDef **dest) {
auto &enum_def = *new EnumDef();
enum_def.name = enum_name;
enum_def.file = file_being_parsed_;
enum_def.doc_comment = doc_comment_;
enum_def.is_union = is_union;
enum_def.defined_namespace = current_namespace_;
if (enums_.Add(current_namespace_->GetFullyQualifiedName(enum_name),
&enum_def))
return Error("enum already exists: " + enum_name);
enum_def.underlying_type.base_type = is_union ? BASE_TYPE_UTYPE
: BASE_TYPE_INT;
enum_def.underlying_type.enum_def = &enum_def;
if (dest) *dest = &enum_def;
return NoError();
}
CheckedError Parser::ParseProtoFields(StructDef *struct_def, bool isextend,
bool inside_oneof) {
EXPECT('{');
@@ -1910,12 +1931,19 @@ CheckedError Parser::ParseProtoFields(StructDef *struct_def, bool isextend,
}
}
StructDef *anonymous_struct = nullptr;
EnumDef *oneof_union = nullptr;
Type type;
if (IsIdent("group") || oneof) {
if (!oneof) NEXT();
auto name = "Anonymous" + NumToString(anonymous_counter++);
ECHECK(StartStruct(name, &anonymous_struct));
type = Type(BASE_TYPE_STRUCT, anonymous_struct);
if (oneof && opts.proto_oneof_union) {
auto name = MakeCamel(attribute_, true) + "Union";
ECHECK(StartEnum(name, true, &oneof_union));
type = Type(BASE_TYPE_UNION, nullptr, oneof_union);
} else {
auto name = "Anonymous" + NumToString(anonymous_counter++);
ECHECK(StartStruct(name, &anonymous_struct));
type = Type(BASE_TYPE_STRUCT, anonymous_struct);
}
} else {
ECHECK(ParseTypeFromProtoType(&type));
}
@@ -1974,6 +2002,27 @@ CheckedError Parser::ParseProtoFields(StructDef *struct_def, bool isextend,
if (anonymous_struct) {
ECHECK(ParseProtoFields(anonymous_struct, false, oneof));
if (Is(';')) NEXT();
} else if (oneof_union) {
// Parse into a temporary StructDef, then transfer fields into an
// EnumDef describing the oneof as a union.
StructDef oneof_struct;
ECHECK(ParseProtoFields(&oneof_struct, false, oneof));
if (Is(';')) NEXT();
for (auto field_it = oneof_struct.fields.vec.begin();
field_it != oneof_struct.fields.vec.end(); ++field_it) {
const auto &oneof_field = **field_it;
const auto &oneof_type = oneof_field.value.type;
if (oneof_type.base_type != BASE_TYPE_STRUCT ||
!oneof_type.struct_def || oneof_type.struct_def->fixed)
return Error("oneof '" + name +
"' cannot be mapped to a union because member '" +
oneof_field.name + "' is not a table type.");
auto enum_val = new EnumVal(oneof_type.struct_def->name,
oneof_union->vals.vec.size());
enum_val->union_type = oneof_type;
enum_val->doc_comment = oneof_field.doc_comment;
oneof_union->vals.Add(oneof_field.name, enum_val);
}
} else {
EXPECT(';');
}