diff --git a/docs/html/md__compiler.html b/docs/html/md__compiler.html
index 9ab104fe9..aa13d825a 100644
--- a/docs/html/md__compiler.html
+++ b/docs/html/md__compiler.html
@@ -84,7 +84,7 @@ $(document).ready(function(){initNavTree('md__compiler.html','');});
--gen-mutable : Generate additional non-const accessors for mutating FlatBuffers in-place.
--gen-onefile : Generate single output file (useful for C#)
--raw-binary : Allow binaries without a file_indentifier to be read. This may crash flatc given a mismatched schema.
---proto: Expect input files to be .proto files (protocol buffers). Output the corresponding .fbs file. Currently supports: package, message, enum, nested declarations. Does not support, but will skip without error: import, option. Does not support, will generate error: service, extend, extensions, oneof, group, custom options.
+--proto: Expect input files to be .proto files (protocol buffers). Output the corresponding .fbs file. Currently supports: package, message, enum, nested declarations, import (use -I for paths), extend, oneof, group. Does not support, but will skip without error: option, service, extensions, and most everything else.
--schema: Serialize schemas instead of JSON (use with -b). This will output a binary version of the specified schema that itself corresponds to the reflection/reflection.fbs schema. Loading this binary file is the basis for reflection functionality.
diff --git a/docs/source/Compiler.md b/docs/source/Compiler.md
index ebcdd70d6..88bd88bb2 100755
--- a/docs/source/Compiler.md
+++ b/docs/source/Compiler.md
@@ -71,10 +71,10 @@ be generated for each file processed:
- `--proto`: Expect input files to be .proto files (protocol buffers).
Output the corresponding .fbs file.
- Currently supports: `package`, `message`, `enum`, nested declarations.
- Does not support, but will skip without error: `import`, `option`.
- Does not support, will generate error: `service`, `extend`, `extensions`,
- `oneof`, `group`, custom options.
+ Currently supports: `package`, `message`, `enum`, nested declarations,
+ `import` (use `-I` for paths), `extend`, `oneof`, `group`.
+ Does not support, but will skip without error: `option`, `service`,
+ `extensions`, and most everything else.
- `--schema`: Serialize schemas instead of JSON (use with -b). This will
output a binary version of the specified schema that itself corresponds
diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h
index 44154ebac..4b0dd818c 100644
--- a/include/flatbuffers/idl.h
+++ b/include/flatbuffers/idl.h
@@ -125,6 +125,11 @@ struct Type {
enum_def(_ed)
{}
+ bool operator==(const Type &o) {
+ return base_type == o.base_type && element == o.element &&
+ struct_def == o.struct_def && enum_def == o.enum_def;
+ }
+
Type VectorType() const { return Type(element, struct_def, enum_def); }
Offset Serialize(FlatBufferBuilder *builder) const;
@@ -163,6 +168,17 @@ template class SymbolTable {
return false;
}
+ void Move(const std::string &oldname, const std::string &newname) {
+ auto it = dict.find(oldname);
+ if (it != dict.end()) {
+ auto obj = it->second;
+ dict.erase(it);
+ dict[newname] = obj;
+ } else {
+ assert(false);
+ }
+ }
+
T *Lookup(const std::string &name) const {
auto it = dict.find(name);
return it == dict.end() ? nullptr : it->second;
@@ -178,6 +194,13 @@ template class SymbolTable {
// A name space, as set in the schema.
struct Namespace {
std::vector components;
+
+ // Given a (potentally unqualified) name, return the "fully qualified" name
+ // which has a full namespaced descriptor.
+ // With max_components you can request less than the number of components
+ // the current namespace has.
+ std::string GetFullyQualifiedName(const std::string &name,
+ size_t max_components = 1000) const;
};
// Base class for all definition types (fields, structs_, enums_).
@@ -293,7 +316,8 @@ class Parser {
cursor_(nullptr),
line_(1),
proto_mode_(proto_mode),
- strict_json_(strict_json) {
+ strict_json_(strict_json),
+ anonymous_counter(0) {
// Just in case none are declared:
namespaces_.push_back(new Namespace());
known_attributes_.insert("deprecated");
@@ -331,12 +355,6 @@ class Parser {
// Mark all definitions as already having code generated.
void MarkGenerated();
- // Given a (potentally unqualified) name, return the "fully qualified" name
- // which has a full namespaced descriptor. If the parser has no current
- // namespace context, or if the name passed is partially qualified the input
- // is simply returned.
- std::string GetFullyQualifiedName(const std::string &name) const;
-
// Get the files recursively included by the given file. The returned
// container will have at least the given file.
std::set GetIncludedFilesRecursive(
@@ -351,6 +369,7 @@ class Parser {
void Next();
bool IsNext(int t);
void Expect(int t);
+ std::string TokenToStringId(int t);
EnumDef *LookupEnum(const std::string &id);
void ParseNamespacing(std::string *id, std::string *last);
void ParseTypeIdent(Type &type);
@@ -369,12 +388,19 @@ class Parser {
void ParseHash(Value &e, FieldDef* field);
void ParseSingleValue(Value &e);
int64_t ParseIntegerFromString(Type &type);
- StructDef *LookupCreateStruct(const std::string &name);
- void ParseEnum(bool is_union);
+ StructDef *LookupCreateStruct(const std::string &name,
+ bool create_if_new = true,
+ bool definition = false);
+ EnumDef &ParseEnum(bool is_union);
void ParseNamespace();
- StructDef &StartStruct();
+ StructDef &StartStruct(const std::string &name);
void ParseDecl();
+ void ParseProtoFields(StructDef *struct_def, bool isextend,
+ bool inside_oneof);
+ void ParseProtoOption();
+ void ParseProtoKey();
void ParseProtoDecl();
+ void ParseProtoCurliesOrIdent();
Type ParseTypeFromProtoType();
public:
@@ -405,6 +431,8 @@ class Parser {
std::vector struct_stack_;
std::set known_attributes_;
+
+ int anonymous_counter;
};
// Utility functions for multiple generators:
diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp
index acdc63040..84efee695 100644
--- a/src/idl_gen_cpp.cpp
+++ b/src/idl_gen_cpp.cpp
@@ -287,8 +287,8 @@ static void GenTable(const Parser &parser, StructDef &struct_def,
}
auto nested = field.attributes.Lookup("nested_flatbuffer");
if (nested) {
- std::string qualified_name = parser.GetFullyQualifiedName(
- nested->constant);
+ std::string qualified_name =
+ parser.namespaces_.back()->GetFullyQualifiedName(nested->constant);
auto nested_root = parser.structs_.Lookup(qualified_name);
assert(nested_root); // Guaranteed to exist by parser.
(void)nested_root;
@@ -719,7 +719,8 @@ std::string GenerateCPP(const Parser &parser,
// Generate convenient global helper functions:
if (parser.root_struct_def_) {
auto &name = parser.root_struct_def_->name;
- std::string qualified_name = parser.GetFullyQualifiedName(name);
+ std::string qualified_name =
+ parser.namespaces_.back()->GetFullyQualifiedName(name);
std::string cpp_qualified_name = TranslateNameSpace(qualified_name);
// The root datatype accessor:
diff --git a/src/idl_gen_fbs.cpp b/src/idl_gen_fbs.cpp
index 270fd2517..a55d42a88 100644
--- a/src/idl_gen_fbs.cpp
+++ b/src/idl_gen_fbs.cpp
@@ -24,19 +24,49 @@ namespace flatbuffers {
static std::string GenType(const Type &type) {
switch (type.base_type) {
- case BASE_TYPE_STRUCT: return type.struct_def->name;
- case BASE_TYPE_UNION: return type.enum_def->name;
- case BASE_TYPE_VECTOR: return "[" + GenType(type.VectorType()) + "]";
- default: return kTypeNames[type.base_type];
+ case BASE_TYPE_STRUCT:
+ return type.struct_def->defined_namespace->GetFullyQualifiedName(
+ type.struct_def->name);
+ case BASE_TYPE_UNION:
+ return type.enum_def->defined_namespace->GetFullyQualifiedName(
+ type.enum_def->name);
+ case BASE_TYPE_VECTOR:
+ return "[" + GenType(type.VectorType()) + "]";
+ default:
+ return kTypeNames[type.base_type];
}
}
+static void GenNameSpace(const Namespace &name_space, std::string *_schema,
+ const Namespace **last_namespace) {
+ if (*last_namespace == &name_space) return;
+ *last_namespace = &name_space;
+ auto &schema = *_schema;
+ schema += "namespace ";
+ for (auto it = name_space.components.begin();
+ it != name_space.components.end(); ++it) {
+ if (it != name_space.components.begin()) schema += ".";
+ schema += *it;
+ }
+ schema += ";\n\n";
+}
+
// Generate a flatbuffer schema from the Parser's internal representation.
std::string GenerateFBS(const Parser &parser, const std::string &file_name,
const GeneratorOptions &opts) {
+ // Proto namespaces may clash with table names, so we have to prefix all:
+ for (auto it = parser.namespaces_.begin(); it != parser.namespaces_.end();
+ ++it) {
+ for (auto comp = (*it)->components.begin(); comp != (*it)->components.end();
+ ++comp) {
+ (*comp) = "_" + (*comp);
+ }
+ }
+
std::string schema;
schema += "// Generated from " + file_name + ".proto\n\n";
if (opts.include_dependence_headers) {
+ #ifdef FBS_GEN_INCLUDES // TODO: currently all in one file.
int num_includes = 0;
for (auto it = parser.included_files_.begin();
it != parser.included_files_.end(); ++it) {
@@ -48,19 +78,14 @@ std::string GenerateFBS(const Parser &parser, const std::string &file_name,
}
}
if (num_includes) schema += "\n";
+ #endif
}
- schema += "namespace ";
- auto name_space = parser.namespaces_.back();
- for (auto it = name_space->components.begin();
- it != name_space->components.end(); ++it) {
- if (it != name_space->components.begin()) schema += ".";
- schema += *it;
- }
- schema += ";\n\n";
// Generate code for all the enum declarations.
+ const Namespace *last_namespace = nullptr;
for (auto enum_def_it = parser.enums_.vec.begin();
enum_def_it != parser.enums_.vec.end(); ++enum_def_it) {
EnumDef &enum_def = **enum_def_it;
+ GenNameSpace(*enum_def.defined_namespace, &schema, &last_namespace);
GenComment(enum_def.doc_comment, &schema, nullptr);
schema += "enum " + enum_def.name + " : ";
schema += GenType(enum_def.underlying_type) + " {\n";
@@ -76,6 +101,7 @@ std::string GenerateFBS(const Parser &parser, const std::string &file_name,
for (auto it = parser.structs_.vec.begin();
it != parser.structs_.vec.end(); ++it) {
StructDef &struct_def = **it;
+ GenNameSpace(*struct_def.defined_namespace, &schema, &last_namespace);
GenComment(struct_def.doc_comment, &schema, nullptr);
schema += "table " + struct_def.name + " {\n";
for (auto field_it = struct_def.fields.vec.begin();
diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp
index c7c9d6d09..6b3631d39 100644
--- a/src/idl_parser.cpp
+++ b/src/idl_parser.cpp
@@ -77,6 +77,27 @@ template<> inline Offset atot>(const char *s) {
return Offset(atoi(s));
}
+std::string Namespace::GetFullyQualifiedName(const std::string &name,
+ size_t max_components) const {
+ // Early exit if we don't have a defined namespace.
+ if (components.size() == 0 || !max_components) {
+ return name;
+ }
+ std::stringstream stream;
+ for (size_t i = 0; i < std::min(components.size(), max_components);
+ i++) {
+ if (i) {
+ stream << ".";
+ }
+ stream << components[i];
+ }
+
+ stream << "." << name;
+ return stream.str();
+}
+
+
+
// Declare tokens we'll use. Single character tokens are represented by their
// ascii character code (e.g. '{'), others above 256.
#define FLATBUFFERS_GEN_TOKENS(TD) \
@@ -127,6 +148,10 @@ static std::string TokenToString(int t) {
}
}
+std::string Parser::TokenToStringId(int t) {
+ return TokenToString(t) + (t == kTokenIdentifier ? ": " + attribute_ : "");
+}
+
// Parses exactly nibbles worth of hex digits into a number, or error.
int64_t Parser::ParseHexNum(int nibbles) {
for (int i = 0; i < nibbles; i++)
@@ -142,6 +167,7 @@ int64_t Parser::ParseHexNum(int nibbles) {
void Parser::Next() {
doc_comment_.clear();
bool seen_newline = false;
+ attribute_.clear();
for (;;) {
char c = *cursor_++;
token_ = c;
@@ -156,8 +182,8 @@ void Parser::Next() {
Error("floating point constant can\'t start with \".\"");
break;
case '\"':
- attribute_ = "";
- while (*cursor_ != '\"') {
+ case '\'':
+ while (*cursor_ != c) {
if (*cursor_ < ' ' && *cursor_ >= 0)
Error("illegal character in string constant");
if (*cursor_ == '\\') {
@@ -169,6 +195,7 @@ void Parser::Next() {
case 'b': attribute_ += '\b'; cursor_++; break;
case 'f': attribute_ += '\f'; cursor_++; break;
case '\"': attribute_ += '\"'; cursor_++; break;
+ case '\'': attribute_ += '\''; cursor_++; break;
case '\\': attribute_ += '\\'; cursor_++; break;
case '/': attribute_ += '/'; cursor_++; break;
case 'x': { // Not in the JSON standard
@@ -200,6 +227,15 @@ void Parser::Next() {
doc_comment_.push_back(std::string(start + 1, cursor_));
}
break;
+ } else if (*cursor_ == '*') {
+ cursor_++;
+ // TODO: make nested.
+ while (*cursor_ != '*' || cursor_[1] != '/') {
+ if (!*cursor_) Error("end of file in comment");
+ cursor_++;
+ }
+ cursor_ += 2;
+ break;
}
// fall thru
default:
@@ -209,7 +245,6 @@ void Parser::Next() {
while (isalnum(static_cast(*cursor_)) ||
*cursor_ == '_')
cursor_++;
- attribute_.clear();
attribute_.append(start, cursor_);
// First, see if it is a type keyword from the table of types:
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, \
@@ -249,10 +284,20 @@ void Parser::Next() {
return;
} else if (isdigit(static_cast(c)) || c == '-') {
const char *start = cursor_ - 1;
+ if (c == '0' && (*cursor_ == 'x' || *cursor_ == 'X')) {
+ cursor_++;
+ while (isxdigit(static_cast(*cursor_))) cursor_++;
+ attribute_.append(start + 2, cursor_);
+ attribute_ = NumToString(StringToInt(attribute_.c_str(), 16));
+ token_ = kTokenIntegerConstant;
+ return;
+ }
while (isdigit(static_cast(*cursor_))) cursor_++;
- if (*cursor_ == '.') {
- cursor_++;
- while (isdigit(static_cast(*cursor_))) cursor_++;
+ if (*cursor_ == '.' || *cursor_ == 'e' || *cursor_ == 'E') {
+ if (*cursor_ == '.') {
+ cursor_++;
+ while (isdigit(static_cast(*cursor_))) cursor_++;
+ }
// See if this float has a scientific notation suffix. Both JSON
// and C++ (through strtod() we use) have the same format:
if (*cursor_ == 'e' || *cursor_ == 'E') {
@@ -264,7 +309,6 @@ void Parser::Next() {
} else {
token_ = kTokenIntegerConstant;
}
- attribute_.clear();
attribute_.append(start, cursor_);
return;
}
@@ -288,7 +332,7 @@ bool Parser::IsNext(int t) {
void Parser::Expect(int t) {
if (t != token_) {
Error("expecting: " + TokenToString(t) + " instead got: " +
- TokenToString(token_));
+ TokenToStringId(token_));
}
Next();
}
@@ -303,10 +347,13 @@ void Parser::ParseNamespacing(std::string *id, std::string *last) {
}
EnumDef *Parser::LookupEnum(const std::string &id) {
- auto ed = enums_.Lookup(GetFullyQualifiedName(id));
- // id may simply not have a namespace at all, so check that too.
- if (!ed) ed = enums_.Lookup(id);
- return ed;
+ // Search thru parent namespaces.
+ for (int components = static_cast(namespaces_.back()->components.size());
+ components >= 0; components--) {
+ auto ed = enums_.Lookup(namespaces_.back()->GetFullyQualifiedName(id, components));
+ if (ed) return ed;
+ }
+ return nullptr;
}
void Parser::ParseTypeIdent(Type &type) {
@@ -354,8 +401,7 @@ void Parser::ParseType(Type &type) {
}
}
-FieldDef &Parser::AddField(StructDef &struct_def,
- const std::string &name,
+FieldDef &Parser::AddField(StructDef &struct_def, const std::string &name,
const Type &type) {
auto &field = *new FieldDef();
field.value.offset =
@@ -796,28 +842,53 @@ void Parser::ParseSingleValue(Value &e) {
e,
BASE_TYPE_STRING)) {
} else {
- Error("cannot parse value starting with: " + TokenToString(token_));
+ Error("cannot parse value starting with: " + TokenToStringId(token_));
}
}
-StructDef *Parser::LookupCreateStruct(const std::string &name) {
- std::string qualified_name = GetFullyQualifiedName(name);
- auto struct_def = structs_.Lookup(qualified_name);
- // Unqualified names may simply have no namespace at all, so try that too.
- if (!struct_def) struct_def = structs_.Lookup(name);
- if (!struct_def) {
- // Rather than failing, we create a "pre declared" StructDef, due to
- // circular references, and check for errors at the end of parsing.
+StructDef *Parser::LookupCreateStruct(const std::string &name,
+ bool create_if_new, bool definition) {
+ std::string qualified_name = namespaces_.back()->GetFullyQualifiedName(name);
+ auto struct_def = structs_.Lookup(name);
+ if (struct_def && struct_def->predecl) {
+ if (definition) {
+ struct_def->defined_namespace = namespaces_.back();
+ structs_.Move(name, qualified_name);
+ }
+ return struct_def;
+ }
+ struct_def = structs_.Lookup(qualified_name);
+ if (!definition) {
+ // Search thru parent namespaces.
+ for (size_t components = namespaces_.back()->components.size();
+ components && !struct_def; components--) {
+ struct_def = structs_.Lookup(
+ namespaces_.back()->GetFullyQualifiedName(name, components - 1));
+ }
+ }
+ if (!struct_def && create_if_new) {
struct_def = new StructDef();
- structs_.Add(qualified_name, struct_def);
- struct_def->name = name;
- struct_def->predecl = true;
- struct_def->defined_namespace = namespaces_.back();
+ if (definition) {
+ structs_.Add(qualified_name, struct_def);
+ struct_def->name = name;
+ struct_def->defined_namespace = namespaces_.back();
+ } else {
+ // Not a definition.
+ // Rather than failing, we create a "pre declared" StructDef, due to
+ // circular references, and check for errors at the end of parsing.
+ // It is defined in the root namespace, since we don't know what the
+ // final namespace will be.
+ // TODO: maybe safer to use special namespace?
+ structs_.Add(name, struct_def);
+ struct_def->name = name;
+ struct_def->defined_namespace = new Namespace();
+ namespaces_.insert(namespaces_.begin(), struct_def->defined_namespace);
+ }
}
return struct_def;
}
-void Parser::ParseEnum(bool is_union) {
+EnumDef &Parser::ParseEnum(bool is_union) {
std::vector enum_comment = doc_comment_;
Next();
std::string enum_name = attribute_;
@@ -828,14 +899,15 @@ void Parser::ParseEnum(bool is_union) {
enum_def.doc_comment = enum_comment;
enum_def.is_union = is_union;
enum_def.defined_namespace = namespaces_.back();
- if (enums_.Add(GetFullyQualifiedName(enum_name), &enum_def))
+ if (enums_.Add(namespaces_.back()->GetFullyQualifiedName(enum_name),
+ &enum_def))
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 (proto_mode_) {
- enum_def.underlying_type.base_type = BASE_TYPE_SHORT;
+ enum_def.underlying_type.base_type = BASE_TYPE_INT;
} else {
// Give specialized error message, since this type spec used to
// be optional in the first FlatBuffers release.
@@ -853,27 +925,37 @@ void Parser::ParseEnum(bool is_union) {
Expect('{');
if (is_union) enum_def.vals.Add("NONE", new EnumVal("NONE", 0));
do {
- auto value_name = attribute_;
- auto full_name = value_name;
- std::vector value_comment = doc_comment_;
- Expect(kTokenIdentifier);
- if (is_union) ParseNamespacing(&full_name, &value_name);
- auto prevsize = enum_def.vals.vec.size();
- auto value = enum_def.vals.vec.size()
- ? enum_def.vals.vec.back()->value + 1
- : 0;
- auto &ev = *new EnumVal(value_name, value);
- if (enum_def.vals.Add(value_name, &ev))
- Error("enum value already exists: " + value_name);
- ev.doc_comment = value_comment;
- if (is_union) {
- ev.struct_def = LookupCreateStruct(full_name);
- }
- if (IsNext('=')) {
- ev.value = atoi(attribute_.c_str());
- Expect(kTokenIntegerConstant);
- if (prevsize && enum_def.vals.vec[prevsize - 1]->value >= ev.value)
- Error("enum values must be specified in ascending order");
+ if (proto_mode_ && attribute_ == "option") {
+ ParseProtoOption();
+ } else {
+ auto value_name = attribute_;
+ auto full_name = value_name;
+ std::vector value_comment = doc_comment_;
+ Expect(kTokenIdentifier);
+ if (is_union) ParseNamespacing(&full_name, &value_name);
+ auto prevsize = enum_def.vals.vec.size();
+ auto value = enum_def.vals.vec.size()
+ ? enum_def.vals.vec.back()->value + 1
+ : 0;
+ auto &ev = *new EnumVal(value_name, value);
+ if (enum_def.vals.Add(value_name, &ev))
+ Error("enum value already exists: " + value_name);
+ ev.doc_comment = value_comment;
+ if (is_union) {
+ ev.struct_def = LookupCreateStruct(full_name);
+ }
+ if (IsNext('=')) {
+ ev.value = atoi(attribute_.c_str());
+ Expect(kTokenIntegerConstant);
+ if (!proto_mode_ && prevsize &&
+ enum_def.vals.vec[prevsize - 1]->value >= ev.value)
+ Error("enum values must be specified in ascending order");
+ }
+ if (proto_mode_ && IsNext('[')) {
+ // ignore attributes on enums.
+ while (token_ != ']') Next();
+ Next();
+ }
}
} while (IsNext(proto_mode_ ? ';' : ',') && token_ != '}');
Expect('}');
@@ -886,12 +968,11 @@ void Parser::ParseEnum(bool is_union) {
(*it)->value = 1LL << (*it)->value;
}
}
+ return enum_def;
}
-StructDef &Parser::StartStruct() {
- std::string name = attribute_;
- Expect(kTokenIdentifier);
- auto &struct_def = *LookupCreateStruct(name);
+StructDef &Parser::StartStruct(const std::string &name) {
+ auto &struct_def = *LookupCreateStruct(name, true, true);
if (!struct_def.predecl) Error("datatype already exists: " + name);
struct_def.predecl = false;
struct_def.name = name;
@@ -906,7 +987,9 @@ void Parser::ParseDecl() {
std::vector dc = doc_comment_;
bool fixed = IsNext(kTokenStruct);
if (!fixed) Expect(kTokenTable);
- auto &struct_def = StartStruct();
+ std::string name = attribute_;
+ Expect(kTokenIdentifier);
+ auto &struct_def = StartStruct(name);
struct_def.doc_comment = dc;
struct_def.fixed = fixed;
ParseMetaData(struct_def);
@@ -986,30 +1069,11 @@ void Parser::ParseDecl() {
}
bool Parser::SetRootType(const char *name) {
- root_struct_def_ = structs_.Lookup(GetFullyQualifiedName(name));
+ root_struct_def_ = structs_.Lookup(
+ namespaces_.back()->GetFullyQualifiedName(name));
return root_struct_def_ != nullptr;
}
-std::string Parser::GetFullyQualifiedName(const std::string &name) const {
- Namespace *ns = namespaces_.back();
-
- // Early exit if we don't have a defined namespace, or if the name is already
- // partially qualified
- if (ns->components.size() == 0 || name.find(".") != std::string::npos) {
- return name;
- }
- std::stringstream stream;
- for (size_t i = 0; i != ns->components.size(); ++i) {
- if (i != 0) {
- stream << ".";
- }
- stream << ns->components[i];
- }
-
- stream << "." << name;
- return stream.str();
-}
-
void Parser::MarkGenerated() {
// Since the Parser object retains definitions across files, we must
// ensure we only output code for definitions once, in the file they are first
@@ -1029,10 +1093,12 @@ void Parser::ParseNamespace() {
Next();
auto ns = new Namespace();
namespaces_.push_back(ns);
- for (;;) {
- ns->components.push_back(attribute_);
- Expect(kTokenIdentifier);
- if (!IsNext('.')) break;
+ if (token_ != ';') {
+ for (;;) {
+ ns->components.push_back(attribute_);
+ Expect(kTokenIdentifier);
+ if (!IsNext('.')) break;
+ }
}
Expect(';');
}
@@ -1042,85 +1108,225 @@ void Parser::ParseNamespace() {
// We parse everything as identifiers instead of keywords, since we don't
// want protobuf keywords to become invalid identifiers in FlatBuffers.
void Parser::ParseProtoDecl() {
+ bool isextend = attribute_ == "extend";
if (attribute_ == "package") {
// These are identical in syntax to FlatBuffer's namespace decl.
ParseNamespace();
- } else if (attribute_ == "message") {
+ } else if (attribute_ == "message" || isextend) {
std::vector struct_comment = doc_comment_;
Next();
- auto &struct_def = StartStruct();
- struct_def.doc_comment = struct_comment;
- Expect('{');
- while (token_ != '}') {
- if (attribute_ == "message" || attribute_ == "enum") {
- // Nested declarations.
- ParseProtoDecl();
- } else {
- std::vector field_comment = doc_comment_;
- // Parse the qualifier.
- bool required = false;
- bool repeated = false;
+ StructDef *struct_def = nullptr;
+ if (isextend) {
+ IsNext('.'); // qualified names may start with a . ?
+ auto id = attribute_;
+ Expect(kTokenIdentifier);
+ ParseNamespacing(&id, nullptr);
+ struct_def = LookupCreateStruct(id, false);
+ if (!struct_def) Error("cannot extend unknown message type: " + id);
+ } else {
+ std::string name = attribute_;
+ Expect(kTokenIdentifier);
+ struct_def = &StartStruct(name);
+ // Since message definitions can be nested, we create a new namespace.
+ auto ns = new Namespace();
+ // Copy of current namespace.
+ *ns = *namespaces_.back();
+ // But with current message name.
+ ns->components.push_back(name);
+ namespaces_.push_back(ns);
+ }
+ struct_def->doc_comment = struct_comment;
+ ParseProtoFields(struct_def, isextend, false);
+ if (!isextend) {
+ // We have to remove the nested namespace, but we can't just throw it
+ // away, so put it at the beginning of the vector.
+ auto ns = namespaces_.back();
+ namespaces_.pop_back();
+ namespaces_.insert(namespaces_.begin(), ns);
+ }
+ IsNext(';');
+ } else if (attribute_ == "enum") {
+ // These are almost the same, just with different terminator:
+ auto &enum_def = ParseEnum(false);
+ IsNext(';');
+ // Protobuf allows them to be specified in any order, so sort afterwards.
+ auto &v = enum_def.vals.vec;
+ std::sort(v.begin(), v.end(), [](const EnumVal *a, const EnumVal *b) {
+ return a->value < b->value;
+ });
+ // Temp: remove any duplicates, as .fbs files can't handle them.
+ for (auto it = v.begin(); it != v.end(); ) {
+ if (it != v.begin() && it[0]->value == it[-1]->value) it = v.erase(it);
+ else ++it;
+ }
+ } else if (attribute_ == "syntax") { // Skip these.
+ Next();
+ Expect('=');
+ Expect(kTokenStringConstant);
+ Expect(';');
+ } else if (attribute_ == "option") { // Skip these.
+ ParseProtoOption();
+ Expect(';');
+ } else if (attribute_ == "service") { // Skip these.
+ Next();
+ Expect(kTokenIdentifier);
+ ParseProtoCurliesOrIdent();
+ } else {
+ Error("don\'t know how to parse .proto declaration starting with " +
+ TokenToStringId(token_));
+ }
+}
+
+void Parser::ParseProtoFields(StructDef *struct_def, bool isextend,
+ bool inside_oneof) {
+ Expect('{');
+ while (token_ != '}') {
+ if (attribute_ == "message" || attribute_ == "extend" ||
+ attribute_ == "enum") {
+ // Nested declarations.
+ ParseProtoDecl();
+ } else if (attribute_ == "extensions") { // Skip these.
+ Next();
+ Expect(kTokenIntegerConstant);
+ if (IsNext(kTokenIdentifier)) { // to
+ Next(); // num
+ }
+ Expect(';');
+ } else if (attribute_ == "option") { // Skip these.
+ ParseProtoOption();
+ Expect(';');
+ } else if (attribute_ == "reserved") { // Skip these.
+ Next();
+ Expect(kTokenIntegerConstant);
+ while (IsNext(',')) Expect(kTokenIntegerConstant);
+ Expect(';');
+ } else {
+ std::vector field_comment = doc_comment_;
+ // Parse the qualifier.
+ bool required = false;
+ bool repeated = false;
+ bool oneof = false;
+ if (!inside_oneof) {
if (attribute_ == "optional") {
// This is the default.
+ Expect(kTokenIdentifier);
} else if (attribute_ == "required") {
required = true;
+ Expect(kTokenIdentifier);
} else if (attribute_ == "repeated") {
repeated = true;
+ Expect(kTokenIdentifier);
+ } else if (attribute_ == "oneof") {
+ oneof = true;
+ Expect(kTokenIdentifier);
} else {
- Error("expecting optional/required/repeated, got: " + attribute_);
+ // can't error, proto3 allows decls without any of the above.
}
- Type type = ParseTypeFromProtoType();
- // Repeated elements get mapped to a vector.
- if (repeated) {
- type.element = type.base_type;
- type.base_type = BASE_TYPE_VECTOR;
- }
- std::string name = attribute_;
+ }
+ StructDef *anonymous_struct = nullptr;
+ Type type;
+ if (attribute_ == "group" || oneof) {
+ if (!oneof) Expect(kTokenIdentifier);
+ auto name = "Anonymous" + NumToString(anonymous_counter++);
+ anonymous_struct = &StartStruct(name);
+ type = Type(BASE_TYPE_STRUCT, anonymous_struct);
+ } else {
+ type = ParseTypeFromProtoType();
+ }
+ // Repeated elements get mapped to a vector.
+ if (repeated) {
+ type.element = type.base_type;
+ type.base_type = BASE_TYPE_VECTOR;
+ }
+ std::string name = attribute_;
+ // Protos may use our keywords "attribute" & "namespace" as an identifier.
+ if (IsNext(kTokenAttribute) || IsNext(kTokenNameSpace)) {
+ // TODO: simpler to just not make these keywords?
+ name += "_"; // Have to make it not a keyword.
+ } else {
Expect(kTokenIdentifier);
+ }
+ if (!oneof) {
// Parse the field id. Since we're just translating schemas, not
// any kind of binary compatibility, we can safely ignore these, and
// assign our own.
Expect('=');
Expect(kTokenIntegerConstant);
- auto &field = AddField(struct_def, name, type);
- field.doc_comment = field_comment;
- if (!IsScalar(type.base_type)) field.required = required;
- // See if there's a default specified.
- if (IsNext('[')) {
- if (attribute_ != "default") Error("\'default\' expected");
- Next();
+ }
+ FieldDef *existing_field = nullptr;
+ if (isextend) {
+ // We allow a field to be re-defined when extending.
+ // TODO: are there situations where that is problematic?
+ existing_field = struct_def->fields.Lookup(name);
+ }
+ auto &field = existing_field
+ ? *existing_field
+ : AddField(*struct_def, name, type);
+ field.doc_comment = field_comment;
+ if (!IsScalar(type.base_type)) field.required = required;
+ // See if there's a default specified.
+ if (IsNext('[')) {
+ do {
+ auto key = attribute_;
+ ParseProtoKey();
Expect('=');
- field.value.constant = attribute_;
- Next();
- Expect(']');
- }
+ auto val = attribute_;
+ ParseProtoCurliesOrIdent();
+ if (key == "default") {
+ // Temp: skip non-numeric defaults (enums).
+ auto numeric = strpbrk(val.c_str(), "0123456789-+.");
+ if (IsScalar(type.base_type) && numeric == val.c_str())
+ field.value.constant = val;
+ } else if (key == "deprecated") {
+ field.deprecated = val == "true";
+ }
+ } while (IsNext(','));
+ Expect(']');
+ }
+ if (anonymous_struct) {
+ ParseProtoFields(anonymous_struct, false, oneof);
+ IsNext(';');
+ } else {
Expect(';');
}
}
- Next();
- } else if (attribute_ == "enum") {
- // These are almost the same, just with different terminator:
- ParseEnum(false);
- } else if (attribute_ == "import") {
- Next();
- included_files_[attribute_] = true;
- Expect(kTokenStringConstant);
- Expect(';');
- } else if (attribute_ == "option") { // Skip these.
- Next();
- Expect(kTokenIdentifier);
- Expect('=');
- Next(); // Any single token.
- Expect(';');
- } else {
- Error("don\'t know how to parse .proto declaration starting with " +
- attribute_);
}
+ Next();
+}
+
+void Parser::ParseProtoKey() {
+ if (token_ == '(') {
+ Next();
+ // Skip "(a.b)" style custom attributes.
+ while (token_ == '.' || token_ == kTokenIdentifier) Next();
+ Expect(')');
+ while (IsNext('.')) Expect(kTokenIdentifier);
+ } else {
+ Expect(kTokenIdentifier);
+ }
+}
+
+void Parser::ParseProtoCurliesOrIdent() {
+ if (IsNext('{')) {
+ for (int nesting = 1; nesting; ) {
+ if (token_ == '{') nesting++;
+ else if (token_ == '}') nesting--;
+ Next();
+ }
+ } else {
+ Next(); // Any single token.
+ }
+}
+
+void Parser::ParseProtoOption() {
+ Next();
+ ParseProtoKey();
+ Expect('=');
+ ParseProtoCurliesOrIdent();
}
// Parse a protobuf type, and map it to the corresponding FlatBuffer one.
Type Parser::ParseTypeFromProtoType() {
- Expect(kTokenIdentifier);
struct type_lookup { const char *proto_type; BaseType fb_type; };
static type_lookup lookup[] = {
{ "float", BASE_TYPE_FLOAT }, { "double", BASE_TYPE_DOUBLE },
@@ -1142,6 +1348,7 @@ Type Parser::ParseTypeFromProtoType() {
return type;
}
}
+ IsNext('.'); // qualified names may start with a . ?
ParseTypeIdent(type);
return type;
}
@@ -1162,47 +1369,60 @@ bool Parser::Parse(const char *source, const char **include_paths,
line_ = 1;
error_.clear();
builder_.Clear();
+ // Start with a blank namespace just in case this file doesn't have one.
+ namespaces_.push_back(new Namespace());
try {
Next();
- // Includes must come first:
- while (IsNext(kTokenInclude)) {
- auto name = attribute_;
- Expect(kTokenStringConstant);
- // Look for the file in include_paths.
- std::string filepath;
- for (auto paths = include_paths; paths && *paths; paths++) {
- filepath = flatbuffers::ConCatPathFileName(*paths, name);
- if(FileExists(filepath.c_str())) break;
- }
- if (filepath.empty())
- Error("unable to locate include file: " + name);
- if (source_filename)
- files_included_per_file_[source_filename].insert(filepath);
- if (included_files_.find(filepath) == included_files_.end()) {
- // We found an include file that we have not parsed yet.
- // Load it and parse it.
- std::string contents;
- if (!LoadFile(filepath.c_str(), true, &contents))
- Error("unable to load include file: " + name);
- if (!Parse(contents.c_str(), include_paths, filepath.c_str())) {
- // Any errors, we're done.
- return false;
+ // Includes must come before type declarations:
+ for (;;) {
+ // Parse pre-include proto statements if any:
+ if (proto_mode_ &&
+ (attribute_ == "option" || attribute_ == "syntax" ||
+ attribute_ == "package")) {
+ ParseProtoDecl();
+ } else if (IsNext(kTokenInclude) ||
+ (proto_mode_ &&
+ attribute_ == "import" &&
+ IsNext(kTokenIdentifier))) {
+ if (proto_mode_ && attribute_ == "public") Next();
+ auto name = attribute_;
+ Expect(kTokenStringConstant);
+ // Look for the file in include_paths.
+ std::string filepath;
+ for (auto paths = include_paths; paths && *paths; paths++) {
+ filepath = flatbuffers::ConCatPathFileName(*paths, name);
+ if(FileExists(filepath.c_str())) break;
}
- // We do not want to output code for any included files:
- MarkGenerated();
- // This is the easiest way to continue this file after an include:
- // instead of saving and restoring all the state, we simply start the
- // file anew. This will cause it to encounter the same include statement
- // again, but this time it will skip it, because it was entered into
- // included_files_.
- // This is recursive, but only go as deep as the number of include
- // statements.
- return Parse(source, include_paths, source_filename);
+ if (filepath.empty())
+ Error("unable to locate include file: " + name);
+ if (source_filename)
+ files_included_per_file_[source_filename].insert(filepath);
+ if (included_files_.find(filepath) == included_files_.end()) {
+ // We found an include file that we have not parsed yet.
+ // Load it and parse it.
+ std::string contents;
+ if (!LoadFile(filepath.c_str(), true, &contents))
+ Error("unable to load include file: " + name);
+ if (!Parse(contents.c_str(), include_paths, filepath.c_str())) {
+ // Any errors, we're done.
+ return false;
+ }
+ // We do not want to output code for any included files:
+ MarkGenerated();
+ // This is the easiest way to continue this file after an include:
+ // instead of saving and restoring all the state, we simply start the
+ // file anew. This will cause it to encounter the same include statement
+ // again, but this time it will skip it, because it was entered into
+ // included_files_.
+ // This is recursive, but only go as deep as the number of include
+ // statements.
+ return Parse(source, include_paths, source_filename);
+ }
+ Expect(';');
+ } else {
+ break;
}
- Expect(';');
}
- // Start with a blank namespace just in case this file doesn't have one.
- namespaces_.push_back(new Namespace());
// Now parse all other kinds of declarations:
while (token_ != kTokenEof) {
if (proto_mode_) {
@@ -1257,8 +1477,9 @@ bool Parser::Parse(const char *source, const char **include_paths,
}
}
for (auto it = structs_.vec.begin(); it != structs_.vec.end(); ++it) {
- if ((*it)->predecl)
+ if ((*it)->predecl) {
Error("type referenced but not defined: " + (*it)->name);
+ }
}
for (auto it = enums_.vec.begin(); it != enums_.vec.end(); ++it) {
auto &enum_def = **it;
diff --git a/tests/MyGame/Example/Monster.java b/tests/MyGame/Example/Monster.java
index 19b356deb..9f9759010 100644
--- a/tests/MyGame/Example/Monster.java
+++ b/tests/MyGame/Example/Monster.java
@@ -7,6 +7,7 @@ import java.lang.*;
import java.util.*;
import com.google.flatbuffers.*;
+@SuppressWarnings("unused")
public final class Monster extends Table {
public static Monster getRootAsMonster(ByteBuffer _bb) { return getRootAsMonster(_bb, new Monster()); }
public static Monster getRootAsMonster(ByteBuffer _bb, Monster obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__init(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
diff --git a/tests/MyGame/Example/Stat.java b/tests/MyGame/Example/Stat.java
index 673729e19..73f956216 100644
--- a/tests/MyGame/Example/Stat.java
+++ b/tests/MyGame/Example/Stat.java
@@ -7,6 +7,7 @@ import java.lang.*;
import java.util.*;
import com.google.flatbuffers.*;
+@SuppressWarnings("unused")
public final class Stat extends Table {
public static Stat getRootAsStat(ByteBuffer _bb) { return getRootAsStat(_bb, new Stat()); }
public static Stat getRootAsStat(ByteBuffer _bb, Stat obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__init(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
diff --git a/tests/MyGame/Example/Test.java b/tests/MyGame/Example/Test.java
index c4cd6aa9a..ffd7394f6 100644
--- a/tests/MyGame/Example/Test.java
+++ b/tests/MyGame/Example/Test.java
@@ -7,6 +7,7 @@ import java.lang.*;
import java.util.*;
import com.google.flatbuffers.*;
+@SuppressWarnings("unused")
public final class Test extends Struct {
public Test __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; return this; }
diff --git a/tests/MyGame/Example/TestSimpleTableWithEnum.java b/tests/MyGame/Example/TestSimpleTableWithEnum.java
index d5d63de7c..3000cab47 100644
--- a/tests/MyGame/Example/TestSimpleTableWithEnum.java
+++ b/tests/MyGame/Example/TestSimpleTableWithEnum.java
@@ -7,6 +7,7 @@ import java.lang.*;
import java.util.*;
import com.google.flatbuffers.*;
+@SuppressWarnings("unused")
public final class TestSimpleTableWithEnum extends Table {
public static TestSimpleTableWithEnum getRootAsTestSimpleTableWithEnum(ByteBuffer _bb) { return getRootAsTestSimpleTableWithEnum(_bb, new TestSimpleTableWithEnum()); }
public static TestSimpleTableWithEnum getRootAsTestSimpleTableWithEnum(ByteBuffer _bb, TestSimpleTableWithEnum obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__init(_bb.getInt(_bb.position()) + _bb.position(), _bb)); }
diff --git a/tests/MyGame/Example/Vec3.java b/tests/MyGame/Example/Vec3.java
index 457f57d77..86edb6dc5 100644
--- a/tests/MyGame/Example/Vec3.java
+++ b/tests/MyGame/Example/Vec3.java
@@ -7,6 +7,7 @@ import java.lang.*;
import java.util.*;
import com.google.flatbuffers.*;
+@SuppressWarnings("unused")
public final class Vec3 extends Struct {
public Vec3 __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; return this; }
diff --git a/tests/prototest/imported.proto b/tests/prototest/imported.proto
new file mode 100644
index 000000000..5b43e4b34
--- /dev/null
+++ b/tests/prototest/imported.proto
@@ -0,0 +1,5 @@
+package proto.test;
+
+message ImportedMessage {
+ optional int32 a = 26;
+}
diff --git a/tests/prototest/test.golden b/tests/prototest/test.golden
index 7082ad2d8..2c744137e 100644
--- a/tests/prototest/test.golden
+++ b/tests/prototest/test.golden
@@ -1,14 +1,22 @@
// Generated from test.proto
-namespace proto.test;
+namespace _proto._test;
/// Enum doc comment.
-enum ProtoEnum : short {
+enum ProtoEnum : int {
FOO = 1,
/// Enum 2nd value doc comment misaligned.
BAR = 5,
}
+namespace _proto._test;
+
+table ImportedMessage {
+ a:int;
+}
+
+namespace _proto._test;
+
/// 2nd table doc comment with
/// many lines.
table ProtoMessage {
@@ -29,10 +37,13 @@ table ProtoMessage {
/// lines
l:string (required);
m:string;
- n:OtherMessage;
+ n:_proto._test._ProtoMessage.OtherMessage;
o:[string];
+ z:_proto._test.ImportedMessage;
}
+namespace _proto._test._ProtoMessage;
+
table OtherMessage {
a:double;
/// doc comment for b.
diff --git a/tests/prototest/test.proto b/tests/prototest/test.proto
index af5b66d75..913b56462 100644
--- a/tests/prototest/test.proto
+++ b/tests/prototest/test.proto
@@ -1,9 +1,9 @@
// Sample .proto file that we can translate to the corresponding .fbs.
-package proto.test;
-
option some_option = is_ignored;
-import "some_other_schema.proto";
+import "imported.proto";
+
+package proto.test;
/// Enum doc comment.
enum ProtoEnum {
@@ -41,4 +41,5 @@ message ProtoMessage {
optional bytes m = 11;
optional OtherMessage n = 12;
repeated string o = 14;
+ optional ImportedMessage z = 16;
}
diff --git a/tests/test.cpp b/tests/test.cpp
index be008180e..8612d6eec 100644
--- a/tests/test.cpp
+++ b/tests/test.cpp
@@ -428,7 +428,8 @@ void ParseProtoTest() {
// Parse proto.
flatbuffers::Parser parser(false, true);
- TEST_EQ(parser.Parse(protofile.c_str(), nullptr), true);
+ const char *include_directories[] = { "tests/prototest", nullptr };
+ TEST_EQ(parser.Parse(protofile.c_str(), include_directories), true);
// Generate fbs.
flatbuffers::GeneratorOptions opts;