diff --git a/include/flatbuffers/code_generators.h b/include/flatbuffers/code_generators.h index c2ed707aeeb..3128f17a754 100644 --- a/include/flatbuffers/code_generators.h +++ b/include/flatbuffers/code_generators.h @@ -53,6 +53,11 @@ class CodeWriter { value_map_[key] = value; } + std::string GetValue(const std::string &key) const { + const auto it = value_map_.find(key); + return it == value_map_.end() ? "" : it->second; + } + // Appends the given text to the generated code as well as a newline // character. Any text within {{ and }} delimeters is replaced by values // previously stored in the CodeWriter by calling SetValue above. The newline diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 8299fe0cf81..359cc68019a 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -123,6 +123,13 @@ inline bool IsLong (BaseType t) { return t == BASE_TYPE_LONG || inline bool IsBool (BaseType t) { return t == BASE_TYPE_BOOL; } inline bool IsOneByte(BaseType t) { return t >= BASE_TYPE_UTYPE && t <= BASE_TYPE_UCHAR; } + +inline bool IsUnsigned(BaseType t) { + return (t == BASE_TYPE_UTYPE) || (t == BASE_TYPE_UCHAR) || + (t == BASE_TYPE_USHORT) || (t == BASE_TYPE_UINT) || + (t == BASE_TYPE_ULONG); +} + // clang-format on extern const char *const kTypeNames[]; @@ -327,52 +334,96 @@ inline size_t InlineAlignment(const Type &type) { return IsStruct(type) ? type.struct_def->minalign : SizeOf(type.base_type); } -struct EnumVal { - EnumVal(const std::string &_name, int64_t _val) : name(_name), value(_val) {} - EnumVal() : value(0) {} +struct EnumDef; +struct EnumValBuilder; +struct EnumVal { Offset Serialize(FlatBufferBuilder *builder, const Parser &parser) const; bool Deserialize(const Parser &parser, const reflection::EnumVal *val); + + uint64_t GetAsUInt64() const { return static_cast(value); } + int64_t GetAsInt64() const { return value; } bool IsZero() const { return 0 == value; } bool IsNonZero() const { return !IsZero(); } std::string name; std::vector doc_comment; - int64_t value; Type union_type; + + private: + friend EnumDef; + friend EnumValBuilder; + friend bool operator==(const EnumVal &lhs, const EnumVal &rhs); + + EnumVal(const std::string &_name, int64_t _val) : name(_name), value(_val) {} + EnumVal() : value(0) {} + + int64_t value; }; struct EnumDef : public Definition { EnumDef() : is_union(false), uses_multiple_type_instances(false) {} - EnumVal *ReverseLookup(int64_t enum_idx, bool skip_union_default = true) { - for (auto it = Vals().begin() + - static_cast(is_union && skip_union_default); - it != Vals().end(); ++it) { - if ((*it)->value == enum_idx) { return *it; } - } - return nullptr; - } - - Offset Serialize(FlatBufferBuilder *builder, const Parser &parser) const; + Offset Serialize(FlatBufferBuilder *builder, + const Parser &parser) const; bool Deserialize(Parser &parser, const reflection::Enum *values); + template void ChangeEnumValue(EnumVal *ev, T new_val); + void SortByValue(); + void RemoveDuplicates(); + + std::string AllFlags() const; + const EnumVal *MinValue() const; + const EnumVal *MaxValue() const; + // Returns the number of integer steps from v1 to v2. + uint64_t Distance(const EnumVal *v1, const EnumVal *v2) const; + // Returns the number of integer steps from Min to Max. + uint64_t Distance() const { return Distance(MinValue(), MaxValue()); } + + EnumVal *ReverseLookup(int64_t enum_idx, + bool skip_union_default = false) const; + EnumVal *FindByValue(const std::string &constant) const; + + std::string ToString(const EnumVal &ev) const { + return IsUInt64() ? NumToString(ev.GetAsUInt64()) + : NumToString(ev.GetAsInt64()); + } + size_t size() const { return vals.vec.size(); } const std::vector &Vals() const { + FLATBUFFERS_ASSERT(false == vals.vec.empty()); return vals.vec; } - SymbolTable vals; + const EnumVal *Lookup(const std::string &enum_name) const { + return vals.Lookup(enum_name); + } + bool is_union; // Type is a union which uses type aliases where at least one type is // available under two different names. bool uses_multiple_type_instances; Type underlying_type; + + private: + bool IsUInt64() const { + return (BASE_TYPE_ULONG == underlying_type.base_type); + } + + friend EnumValBuilder; + SymbolTable vals; }; +inline bool operator==(const EnumVal &lhs, const EnumVal &rhs) { + return lhs.value == rhs.value; +} +inline bool operator!=(const EnumVal &lhs, const EnumVal &rhs) { + return !(lhs == rhs); +} + inline bool EqualByName(const Type &a, const Type &b) { return a.base_type == b.base_type && a.element == b.element && (a.struct_def == b.struct_def || diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index 268c436ebd5..d42ebe3ef7d 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -28,6 +28,23 @@ namespace flatbuffers { // Pedantic warning free version of toupper(). inline char ToUpper(char c) { return static_cast(::toupper(c)); } +// Make numerical literal with type-suffix. +// This function is only needed for C++! Other languages do not need it. +static inline std::string NumToStringCpp(std::string val, BaseType type) { + // Avoid issues with -2147483648, -9223372036854775808. + switch (type) { + case BASE_TYPE_INT: + return (val != "-2147483648") ? val : ("(-2147483647 - 1)"); + case BASE_TYPE_ULONG: return (val == "0") ? val : (val + "ULL"); + case BASE_TYPE_LONG: + if (val == "-9223372036854775808") + return "(-9223372036854775807LL - 1LL)"; + else + return (val == "0") ? val : (val + "LL"); + default: return val; + } +} + static std::string GeneratedFileName(const std::string &path, const std::string &file_name) { return path + file_name + "_generated.h"; @@ -789,7 +806,7 @@ class CppGenerator : public BaseGenerator { code_.SetValue("NUM_FIELDS", NumToString(num_fields)); std::vector names; std::vector types; - bool consecutive_enum_from_zero = true; + if (struct_def) { for (auto it = struct_def->fields.vec.begin(); it != struct_def->fields.vec.end(); ++it) { @@ -804,9 +821,6 @@ class CppGenerator : public BaseGenerator { names.push_back(Name(ev)); types.push_back(enum_def->is_union ? ev.union_type : Type(enum_def->underlying_type)); - if (static_cast(it - enum_def->Vals().begin()) != ev.value) { - consecutive_enum_from_zero = false; - } } } std::string ts; @@ -851,12 +865,16 @@ class CppGenerator : public BaseGenerator { ns += "\"" + *it + "\""; } std::string vs; + const auto consecutive_enum_from_zero = + enum_def && enum_def->MinValue()->IsZero() && + ((enum_def->size() - 1) == enum_def->Distance()); if (enum_def && !consecutive_enum_from_zero) { for (auto it = enum_def->Vals().begin(); it != enum_def->Vals().end(); ++it) { const auto &ev = **it; if (!vs.empty()) vs += ", "; - vs += NumToString(ev.value); + vs += NumToStringCpp(enum_def->ToString(ev), + enum_def->underlying_type.base_type); } } else if (struct_def && struct_def->fixed) { for (auto it = struct_def->fields.vec.begin(); @@ -883,6 +901,7 @@ class CppGenerator : public BaseGenerator { code_ += " };"; } if (!vs.empty()) { + // Problem with uint64_t values greater than 9223372036854775807ULL. code_ += " static const int64_t values[] = { {{VALUES}} };"; } auto has_names = @@ -907,6 +926,7 @@ class CppGenerator : public BaseGenerator { // Generate an enum declaration, // an enum string lookup table, // and an enum array of values + void GenEnum(const EnumDef &enum_def) { code_.SetValue("ENUM_NAME", Name(enum_def)); code_.SetValue("BASE_TYPE", GenTypeBasic(enum_def.underlying_type, false)); @@ -914,24 +934,31 @@ class CppGenerator : public BaseGenerator { GenComment(enum_def.doc_comment); code_ += GenEnumDecl(enum_def) + "\\"; - if (parser_.opts.scoped_enums) code_ += " : {{BASE_TYPE}}\\"; + // MSVC doesn't support int64/uint64 enum without explicitly declared enum + // type. The value 4611686018427387904ULL is truncated to zero with warning: + // "warning C4309: 'initializing': truncation of constant value". + auto add_type = parser_.opts.scoped_enums; + add_type |= (enum_def.underlying_type.base_type == BASE_TYPE_LONG); + add_type |= (enum_def.underlying_type.base_type == BASE_TYPE_ULONG); + if (add_type) code_ += " : {{BASE_TYPE}}\\"; code_ += " {"; - int64_t anyv = 0; - const EnumVal *minv = nullptr, *maxv = nullptr; for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { const auto &ev = **it; - - GenComment(ev.doc_comment, " "); + if (!ev.doc_comment.empty()) { + auto prefix = code_.GetValue("SEP") + " "; + GenComment(ev.doc_comment, prefix.c_str()); + code_.SetValue("SEP", ""); + } code_.SetValue("KEY", GenEnumValDecl(enum_def, Name(ev))); - code_.SetValue("VALUE", NumToString(ev.value)); + code_.SetValue("VALUE", + NumToStringCpp(enum_def.ToString(ev), + enum_def.underlying_type.base_type)); code_ += "{{SEP}} {{KEY}} = {{VALUE}}\\"; code_.SetValue("SEP", ",\n"); - - minv = !minv || minv->value > ev.value ? &ev : minv; - maxv = !maxv || maxv->value < ev.value ? &ev : maxv; - anyv |= ev.value; } + const EnumVal *minv = enum_def.MinValue(); + const EnumVal *maxv = enum_def.MaxValue(); if (parser_.opts.scoped_enums || parser_.opts.prefixed_enums) { FLATBUFFERS_ASSERT(minv && maxv); @@ -943,7 +970,9 @@ class CppGenerator : public BaseGenerator { code_ += "{{SEP}} {{KEY}} = {{VALUE}}\\"; code_.SetValue("KEY", GenEnumValDecl(enum_def, "ANY")); - code_.SetValue("VALUE", NumToString(anyv)); + code_.SetValue("VALUE", + NumToStringCpp(enum_def.AllFlags(), + enum_def.underlying_type.base_type)); code_ += "{{SEP}} {{KEY}} = {{VALUE}}\\"; } else { // MIN & MAX are useless for bit_flags code_.SetValue("KEY", GenEnumValDecl(enum_def, "MIN")); @@ -984,22 +1013,23 @@ class CppGenerator : public BaseGenerator { // Problem is, if values are very sparse that could generate really big // tables. Ideally in that case we generate a map lookup instead, but for // the moment we simply don't output a table at all. - auto range = - enum_def.vals.vec.back()->value - enum_def.vals.vec.front()->value + 1; + auto range = enum_def.Distance(); // Average distance between values above which we consider a table // "too sparse". Change at will. - static const int kMaxSparseness = 5; - if (range / static_cast(enum_def.vals.vec.size()) < - kMaxSparseness) { + static const uint64_t kMaxSparseness = 5; + if (range / static_cast(enum_def.size()) < kMaxSparseness) { code_ += "inline const char * const *EnumNames{{ENUM_NAME}}() {"; code_ += " static const char * const names[] = {"; - auto val = enum_def.Vals().front()->value; + auto val = enum_def.Vals().front(); for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { - const auto &ev = **it; - while (val++ != ev.value) { code_ += " \"\","; } - code_ += " \"" + Name(ev) + "\","; + auto ev = *it; + for (auto k = enum_def.Distance(val, ev); k > 1; --k) { + code_ += " \"\","; + } + val = ev; + code_ += " \"" + Name(*ev) + "\","; } code_ += " nullptr"; code_ += " };"; @@ -1010,14 +1040,13 @@ class CppGenerator : public BaseGenerator { code_ += "inline const char *EnumName{{ENUM_NAME}}({{ENUM_NAME}} e) {"; - code_ += " if (e < " + - GetEnumValUse(enum_def, *enum_def.vals.vec.front()) + - " || e > " + GetEnumValUse(enum_def, *enum_def.vals.vec.back()) + + code_ += " if (e < " + GetEnumValUse(enum_def, *enum_def.MinValue()) + + " || e > " + GetEnumValUse(enum_def, *enum_def.MaxValue()) + ") return \"\";"; code_ += " const size_t index = static_cast(e)\\"; - if (enum_def.vals.vec.front()->value) { - auto vals = GetEnumValUse(enum_def, *enum_def.vals.vec.front()); + if (enum_def.MinValue()->IsNonZero()) { + auto vals = GetEnumValUse(enum_def, *enum_def.MinValue()); code_ += " - static_cast(" + vals + ")\\"; } code_ += ";"; @@ -1067,8 +1096,8 @@ class CppGenerator : public BaseGenerator { if (parser_.opts.generate_object_based_api && enum_def.is_union) { // Generate a union type code_.SetValue("NAME", Name(enum_def)); - code_.SetValue("NONE", - GetEnumValUse(enum_def, *enum_def.vals.Lookup("NONE"))); + FLATBUFFERS_ASSERT(enum_def.Lookup("NONE")); + code_.SetValue("NONE", GetEnumValUse(enum_def, *enum_def.Lookup("NONE"))); code_ += "struct {{NAME}}Union {"; code_ += " {{NAME}} type;"; @@ -1363,8 +1392,8 @@ class CppGenerator : public BaseGenerator { code_ += ""; // Union Reset() function. - code_.SetValue("NONE", - GetEnumValUse(enum_def, *enum_def.vals.Lookup("NONE"))); + FLATBUFFERS_ASSERT(enum_def.Lookup("NONE")); + code_.SetValue("NONE", GetEnumValUse(enum_def, *enum_def.Lookup("NONE"))); code_ += "inline void {{ENUM_NAME}}Union::Reset() {"; code_ += " switch (type) {"; @@ -1430,18 +1459,19 @@ class CppGenerator : public BaseGenerator { if (IsFloat(field.value.type.base_type)) return float_const_gen_.GenFloatConstant(field); else - return field.value.constant; + return NumToStringCpp(field.value.constant, field.value.type.base_type); } std::string GetDefaultScalarValue(const FieldDef &field, bool is_ctor) { if (field.value.type.enum_def && IsScalar(field.value.type.base_type)) { - auto ev = field.value.type.enum_def->ReverseLookup( - StringToInt(field.value.constant.c_str()), false); + auto ev = field.value.type.enum_def->FindByValue(field.value.constant); if (ev) { return WrapInNameSpace(field.value.type.enum_def->defined_namespace, GetEnumValUse(*field.value.type.enum_def, *ev)); } else { - return GenUnderlyingCast(field, true, field.value.constant); + return GenUnderlyingCast( + field, true, + NumToStringCpp(field.value.constant, field.value.type.base_type)); } } else if (field.value.type.base_type == BASE_TYPE_BOOL) { return field.value.constant == "0" ? "false" : "true"; diff --git a/src/idl_gen_dart.cpp b/src/idl_gen_dart.cpp index 2141eb389ee..c97681b775c 100644 --- a/src/idl_gen_dart.cpp +++ b/src/idl_gen_dart.cpp @@ -242,9 +242,9 @@ class DartGenerator : public BaseGenerator { // holes. if (!is_bit_flags) { code += " static const int minValue = " + - NumToString(enum_def.vals.vec.front()->value) + ";\n"; + enum_def.ToString(*enum_def.MinValue()) + ";\n"; code += " static const int maxValue = " + - NumToString(enum_def.vals.vec.back()->value) + ";\n"; + enum_def.ToString(*enum_def.MaxValue()) + ";\n"; } code += @@ -259,13 +259,13 @@ class DartGenerator : public BaseGenerator { GenDocComment(ev.doc_comment, &code, "", " "); } code += " static const " + name + " " + ev.name + " = "; - code += "const " + name + "._(" + NumToString(ev.value) + ");\n"; + code += "const " + name + "._(" + enum_def.ToString(ev) + ");\n"; } code += " static get values => {"; for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { auto &ev = **it; - code += NumToString(ev.value) + ": " + ev.name + ","; + code += enum_def.ToString(ev) + ": " + ev.name + ","; } code += "};\n\n"; @@ -507,7 +507,7 @@ class DartGenerator : public BaseGenerator { auto &ev = **en_it; auto enum_name = NamespaceAliasFromUnionType(ev.name); - code += " case " + NumToString(ev.value) + ": return " + + code += " case " + enum_def.ToString(ev) + ": return " + enum_name + ".reader.vTableGet(_bc, _bcOffset, " + NumToString(field.value.offset) + ", null);\n"; } diff --git a/src/idl_gen_fbs.cpp b/src/idl_gen_fbs.cpp index 5a85a955d68..e5f3723cbcd 100644 --- a/src/idl_gen_fbs.cpp +++ b/src/idl_gen_fbs.cpp @@ -102,7 +102,7 @@ std::string GenerateFBS(const Parser &parser, const std::string &file_name) { if (enum_def.is_union) schema += " " + GenType(ev.union_type) + ",\n"; else - schema += " " + ev.name + " = " + NumToString(ev.value) + ",\n"; + schema += " " + ev.name + " = " + enum_def.ToString(ev) + ",\n"; } schema += "}\n\n"; } diff --git a/src/idl_gen_general.cpp b/src/idl_gen_general.cpp index fe793bf3bc8..d4338e3ad55 100644 --- a/src/idl_gen_general.cpp +++ b/src/idl_gen_general.cpp @@ -439,21 +439,12 @@ class GeneralGenerator : public BaseGenerator { } std::string GenEnumDefaultValue(const FieldDef &field) const { - auto& value = field.value; - auto enum_def = value.type.enum_def; - auto vec = enum_def->vals.vec; - auto default_value = StringToInt(value.constant.c_str()); - - auto result = value.constant; - for (auto it = vec.begin(); it != vec.end(); ++it) { - auto enum_val = **it; - if (enum_val.value == default_value) { - result = WrapInNameSpace(*enum_def) + "." + enum_val.name; - break; - } - } - - return result; + auto &value = field.value; + FLATBUFFERS_ASSERT(value.type.enum_def); + auto &enum_def = *value.type.enum_def; + auto enum_val = enum_def.FindByValue(value.constant); + return enum_val ? (WrapInNameSpace(enum_def) + "." + enum_val->name) + : value.constant; } std::string GenDefaultValue(const FieldDef &field, bool enableLangOverrides) const { @@ -552,7 +543,7 @@ class GeneralGenerator : public BaseGenerator { code += GenTypeBasic(enum_def.underlying_type, false); } code += " " + ev.name + " = "; - code += NumToString(ev.value); + code += enum_def.ToString(ev); code += lang_.enum_separator; } @@ -562,21 +553,22 @@ class GeneralGenerator : public BaseGenerator { // Problem is, if values are very sparse that could generate really big // tables. Ideally in that case we generate a map lookup instead, but for // the moment we simply don't output a table at all. - auto range = enum_def.vals.vec.back()->value - - enum_def.vals.vec.front()->value + 1; + auto range = enum_def.Distance(); // Average distance between values above which we consider a table // "too sparse". Change at will. - static const int kMaxSparseness = 5; - if (range / static_cast(enum_def.vals.vec.size()) < - kMaxSparseness) { + static const uint64_t kMaxSparseness = 5; + if (range / static_cast(enum_def.size()) < kMaxSparseness) { code += "\n public static"; code += lang_.const_decl; code += lang_.string_type; code += "[] names = { "; - auto val = enum_def.Vals().front()->value; + auto val = enum_def.Vals().front(); for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { - while (val++ != (*it)->value) code += "\"\", "; + auto ev = *it; + for (auto k = enum_def.Distance(val, ev); k > 1; --k) + code += "\"\", "; + val = ev; code += "\"" + (*it)->name + "\", "; } code += "};\n\n"; @@ -584,8 +576,8 @@ class GeneralGenerator : public BaseGenerator { code += lang_.string_type; code += " " + MakeCamel("name", lang_.first_camel_upper); code += "(int e) { return names[e"; - if (enum_def.vals.vec.front()->value) - code += " - " + enum_def.vals.vec.front()->name; + if (enum_def.MinValue()->IsNonZero()) + code += " - " + enum_def.MinValue()->name; code += "]; }\n"; } } diff --git a/src/idl_gen_go.cpp b/src/idl_gen_go.cpp index 412d1e820de..f6119cb3f8a 100644 --- a/src/idl_gen_go.cpp +++ b/src/idl_gen_go.cpp @@ -162,7 +162,7 @@ class GoGenerator : public BaseGenerator { code += " "; code += GetEnumTypeName(enum_def); code += " = "; - code += NumToString(ev.value) + "\n"; + code += enum_def.ToString(ev) + "\n"; } // End enum code. diff --git a/src/idl_gen_js_ts.cpp b/src/idl_gen_js_ts.cpp index fea46208b5a..880bd3921a2 100644 --- a/src/idl_gen_js_ts.cpp +++ b/src/idl_gen_js_ts.cpp @@ -359,13 +359,13 @@ class JsTsGenerator : public BaseGenerator { // Generate mapping between EnumName: EnumValue(int) if (reverse) { - code += " " + NumToString(ev.value); + code += " " + enum_def.ToString(ev); code += lang_.language == IDLOptions::kTs ? "= " : ": "; code += "'" + ev.name + "'"; } else { code += " " + ev.name; code += lang_.language == IDLOptions::kTs ? "= " : ": "; - code += NumToString(ev.value); + code += enum_def.ToString(ev); } code += (it + 1) != enum_def.Vals().end() ? ",\n" : "\n"; @@ -431,8 +431,7 @@ class JsTsGenerator : public BaseGenerator { std::string GenDefaultValue(const Value &value, const std::string &context) { if (value.type.enum_def) { - if (auto val = value.type.enum_def->ReverseLookup( - StringToInt(value.constant.c_str()), false)) { + if (auto val = value.type.enum_def->FindByValue(value.constant)) { if (lang_.language == IDLOptions::kTs) { return GenPrefixedTypeName(WrapInNameSpace(*value.type.enum_def), value.type.enum_def->file) + diff --git a/src/idl_gen_lobster.cpp b/src/idl_gen_lobster.cpp index fe23d970db9..b2e8f423668 100644 --- a/src/idl_gen_lobster.cpp +++ b/src/idl_gen_lobster.cpp @@ -263,7 +263,7 @@ class LobsterGenerator : public BaseGenerator { auto &ev = **it; GenComment(ev.doc_comment, code_ptr, nullptr, " "); code += " " + enum_def.name + "_" + NormalizedName(ev) + " = " + - NumToString(ev.value); + enum_def.ToString(ev); if (it + 1 != enum_def.Vals().end()) code += ","; code += "\n"; } diff --git a/src/idl_gen_lua.cpp b/src/idl_gen_lua.cpp index b26f9070daf..deb66c9c53f 100644 --- a/src/idl_gen_lua.cpp +++ b/src/idl_gen_lua.cpp @@ -111,8 +111,8 @@ namespace lua { // A single enum member. void EnumMember(const EnumDef &enum_def, const EnumVal &ev, std::string *code_ptr) { std::string &code = *code_ptr; - code += std::string(Indent) + NormalizedName(ev) + " = " + NumToString(ev.value) + ",\n"; - (void)enum_def; + code += std::string(Indent) + NormalizedName(ev) + " = " + + enum_def.ToString(ev) + ",\n"; } // End enum code. diff --git a/src/idl_gen_php.cpp b/src/idl_gen_php.cpp index a4b9a4694e2..669b15505ab 100644 --- a/src/idl_gen_php.cpp +++ b/src/idl_gen_php.cpp @@ -125,8 +125,7 @@ class PhpGenerator : public BaseGenerator { code += Indent + "const "; code += ev.name; code += " = "; - code += NumToString(ev.value) + ";\n"; - (void)enum_def; + code += enum_def.ToString(ev) + ";\n"; } // End enum code. @@ -875,8 +874,7 @@ class PhpGenerator : public BaseGenerator { std::string GenDefaultValue(const Value &value) { if (value.type.enum_def) { - if (auto val = value.type.enum_def->ReverseLookup( - StringToInt(value.constant.c_str()), false)) { + if (auto val = value.type.enum_def->FindByValue(value.constant)) { return WrapInNameSpace(*value.type.enum_def) + "::" + val->name; } } diff --git a/src/idl_gen_python.cpp b/src/idl_gen_python.cpp index f2688e4e133..556869ee953 100644 --- a/src/idl_gen_python.cpp +++ b/src/idl_gen_python.cpp @@ -118,8 +118,7 @@ class PythonGenerator : public BaseGenerator { code += Indent; code += NormalizedName(ev); code += " = "; - code += NumToString(ev.value) + "\n"; - (void)enum_def; + code += enum_def.ToString(ev) + "\n"; } // End enum code. diff --git a/src/idl_gen_rust.cpp b/src/idl_gen_rust.cpp index 23fd34a76fb..cdd5d843986 100644 --- a/src/idl_gen_rust.cpp +++ b/src/idl_gen_rust.cpp @@ -589,20 +589,17 @@ class RustGenerator : public BaseGenerator { code_ += "#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]"; code_ += "pub enum " + Name(enum_def) + " {"; - int64_t anyv = 0; - const EnumVal *minv = nullptr, *maxv = nullptr; for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { const auto &ev = **it; GenComment(ev.doc_comment, " "); code_.SetValue("KEY", Name(ev)); - code_.SetValue("VALUE", NumToString(ev.value)); + code_.SetValue("VALUE", enum_def.ToString(ev)); code_ += " {{KEY}} = {{VALUE}},"; - - minv = !minv || minv->value > ev.value ? &ev : minv; - maxv = !maxv || maxv->value < ev.value ? &ev : maxv; - anyv |= ev.value; } + const EnumVal *minv = enum_def.MinValue(); + const EnumVal *maxv = enum_def.MaxValue(); + FLATBUFFERS_ASSERT(minv && maxv); code_ += ""; code_ += "}"; @@ -611,8 +608,8 @@ class RustGenerator : public BaseGenerator { code_.SetValue("ENUM_NAME", Name(enum_def)); code_.SetValue("ENUM_NAME_SNAKE", MakeSnakeCase(Name(enum_def))); code_.SetValue("ENUM_NAME_CAPS", MakeUpper(MakeSnakeCase(Name(enum_def)))); - code_.SetValue("ENUM_MIN_BASE_VALUE", NumToString(minv->value)); - code_.SetValue("ENUM_MAX_BASE_VALUE", NumToString(maxv->value)); + code_.SetValue("ENUM_MIN_BASE_VALUE", enum_def.ToString(*minv)); + code_.SetValue("ENUM_MAX_BASE_VALUE", enum_def.ToString(*maxv)); // Generate enum constants, and impls for Follow, EndianScalar, and Push. code_ += "const ENUM_MIN_{{ENUM_NAME_CAPS}}: {{BASE_TYPE}} = \\"; @@ -671,34 +668,36 @@ class RustGenerator : public BaseGenerator { // Problem is, if values are very sparse that could generate really big // tables. Ideally in that case we generate a map lookup instead, but for // the moment we simply don't output a table at all. - auto range = - enum_def.vals.vec.back()->value - enum_def.vals.vec.front()->value + 1; + auto range = enum_def.Distance(); // Average distance between values above which we consider a table // "too sparse". Change at will. - static const int kMaxSparseness = 5; - if (range / static_cast(enum_def.vals.vec.size()) < - kMaxSparseness) { + static const uint64_t kMaxSparseness = 5; + if (range / static_cast(enum_def.size()) < kMaxSparseness) { code_ += "#[allow(non_camel_case_types)]"; code_ += "const ENUM_NAMES_{{ENUM_NAME_CAPS}}:[&'static str; " + - NumToString(range) + "] = ["; + NumToString(range + 1) + "] = ["; - auto val = enum_def.Vals().front()->value; + auto val = enum_def.Vals().front(); for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { - const auto &ev = **it; - while (val++ != ev.value) { code_ += " \"\","; } - auto suffix = *it != enum_def.vals.vec.back() ? "," : ""; - code_ += " \"" + Name(ev) + "\"" + suffix; + auto ev = *it; + for (auto k = enum_def.Distance(val, ev); k > 1; --k) { + code_ += " \"\","; + } + val = ev; + auto suffix = *it != enum_def.Vals().back() ? "," : ""; + code_ += " \"" + Name(*ev) + "\"" + suffix; } code_ += "];"; code_ += ""; - code_ += "pub fn enum_name_{{ENUM_NAME_SNAKE}}(e: {{ENUM_NAME}}) -> " - "&'static str {"; + code_ += + "pub fn enum_name_{{ENUM_NAME_SNAKE}}(e: {{ENUM_NAME}}) -> " + "&'static str {"; code_ += " let index = e as {{BASE_TYPE}}\\"; - if (enum_def.vals.vec.front()->value) { - auto vals = GetEnumValUse(enum_def, *enum_def.vals.vec.front()); + if (enum_def.MinValue()->IsNonZero()) { + auto vals = GetEnumValUse(enum_def, *enum_def.MinValue()); code_ += " - " + vals + " as {{BASE_TYPE}}\\"; } code_ += ";"; @@ -735,8 +734,7 @@ class RustGenerator : public BaseGenerator { } case ftUnionKey: case ftEnumKey: { - auto ev = field.value.type.enum_def->ReverseLookup( - StringToInt(field.value.constant.c_str()), false); + auto ev = field.value.type.enum_def->FindByValue(field.value.constant); assert(ev); return WrapInNameSpace(field.value.type.enum_def->defined_namespace, GetEnumValUse(*field.value.type.enum_def, *ev)); diff --git a/src/idl_gen_text.cpp b/src/idl_gen_text.cpp index 4c61ff99cec..eeb34ffbb48 100644 --- a/src/idl_gen_text.cpp +++ b/src/idl_gen_text.cpp @@ -51,10 +51,10 @@ bool Print(T val, Type type, int /*indent*/, Type * /*union_type*/, const IDLOptions &opts, std::string *_text) { std::string &text = *_text; if (type.enum_def && opts.output_enum_identifiers) { - auto enum_val = type.enum_def->ReverseLookup(static_cast(val)); - if (enum_val) { + auto ev = type.enum_def->ReverseLookup(static_cast(val)); + if (ev) { text += "\""; - text += enum_val->name; + text += ev->name; text += "\""; return true; } @@ -251,7 +251,7 @@ static bool GenStruct(const StructDef &struct_def, const Table *table, } if (fd.value.type.base_type == BASE_TYPE_UTYPE) { auto enum_val = fd.value.type.enum_def->ReverseLookup( - table->GetField(fd.value.offset, 0)); + table->GetField(fd.value.offset, 0), true); union_type = enum_val ? &enum_val->union_type : nullptr; } } diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index e26aa550815..c732fed21b8 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -676,14 +677,6 @@ CheckedError Parser::ParseField(StructDef &struct_def) { return Error( "default values currently only supported for scalars in tables"); } - if (type.enum_def && - !type.enum_def->is_union && - !type.enum_def->attributes.Lookup("bit_flags") && - !type.enum_def->ReverseLookup(StringToInt( - field->value.constant.c_str()))) { - return Error("default value of " + field->value.constant + " for field " + - name + " is not part of enum " + type.enum_def->name); - } // 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)) { @@ -699,14 +692,26 @@ CheckedError Parser::ParseField(StructDef &struct_def) { field->value.constant += ".0"; } } - - if (type.enum_def && IsScalar(type.base_type) && !struct_def.fixed && - !type.enum_def->attributes.Lookup("bit_flags") && - !type.enum_def->ReverseLookup(StringToInt( - field->value.constant.c_str()))) - Warning("enum " + type.enum_def->name + - " does not have a declaration for this field\'s default of " + - field->value.constant); + if (type.enum_def) { + // The type.base_type can only be scalar, union or vector. + // Table, struct or string can't have enum_def. + // Default value of union and vector in NONE, NULL translated to "0". + FLATBUFFERS_ASSERT(IsInteger(type.base_type) || + (type.base_type == BASE_TYPE_UNION) || + (type.base_type == BASE_TYPE_VECTOR)); + if (type.base_type == BASE_TYPE_VECTOR) { + // Vector can't use initialization list. + FLATBUFFERS_ASSERT(field->value.constant == "0"); + } else { + // All unions should have the NONE ("0") enum value. + auto in_enum = type.enum_def->attributes.Lookup("bit_flags") || + type.enum_def->FindByValue(field->value.constant); + if (false == in_enum) + return Error("default value of " + field->value.constant + + " for field " + name + " is not part of enum " + + type.enum_def->name); + } + } field->doc_comment = dc; ECHECK(ParseMetaData(&field->attributes)); @@ -917,7 +922,7 @@ CheckedError Parser::ParseAnyValue(Value &val, FieldDef *field, } else { ECHECK(atot(constant.c_str(), *this, &enum_idx)); } - auto enum_val = val.type.enum_def->ReverseLookup(enum_idx); + auto enum_val = val.type.enum_def->ReverseLookup(enum_idx, true); if (!enum_val) return Error("illegal type id for: " + field->name); if (enum_val->union_type.base_type == BASE_TYPE_STRUCT) { ECHECK(ParseTable(*enum_val->union_type.struct_def, &val.constant, @@ -1338,42 +1343,33 @@ CheckedError Parser::TryTypedValue(const std::string *name, int dtoken, CheckedError Parser::ParseEnumFromString(const Type &type, std::string *result) { - int64_t i64 = 0; - // Parse one or more enum identifiers, separated by spaces. - const char *next = attribute_.c_str(); - do { - const char *divider = strchr(next, ' '); - std::string word; - if (divider) { - word = std::string(next, divider); - next = divider + strspn(divider, " "); + const auto base_type = + type.enum_def ? type.enum_def->underlying_type.base_type : type.base_type; + if (!IsInteger(base_type)) return Error("not a valid value for this field"); + uint64_t u64 = 0; + for (size_t pos = 0; pos != std::string::npos;) { + const auto delim = attribute_.find_first_of(' ', pos); + const auto last = (std::string::npos == delim); + auto word = attribute_.substr(pos, !last ? delim - pos : std::string::npos); + pos = !last ? delim + 1 : std::string::npos; + const EnumVal *ev = nullptr; + if (type.enum_def) { + ev = type.enum_def->Lookup(word); } else { - word = next; - next += word.length(); - } - if (type.enum_def) { // The field has an enum type - auto enum_val = type.enum_def->vals.Lookup(word); - if (!enum_val) - return Error("unknown enum value: " + word + - ", for enum: " + type.enum_def->name); - i64 |= enum_val->value; - } else { // No enum type, probably integral field. - if (!IsInteger(type.base_type)) - return Error("not a valid value for this field: " + word); - // TODO: could check if its a valid number constant here. - const char *dot = strrchr(word.c_str(), '.'); - if (!dot) + auto dot = word.find_first_of('.'); + if (std::string::npos == dot) return Error("enum values need to be qualified by an enum type"); - std::string enum_def_str(word.c_str(), dot); - std::string enum_val_str(dot + 1, word.c_str() + word.length()); - auto enum_def = LookupEnum(enum_def_str); + auto enum_def_str = word.substr(0, dot); + const auto enum_def = LookupEnum(enum_def_str); if (!enum_def) return Error("unknown enum: " + enum_def_str); - auto enum_val = enum_def->vals.Lookup(enum_val_str); - if (!enum_val) return Error("unknown enum value: " + enum_val_str); - i64 |= enum_val->value; + auto enum_val_str = word.substr(dot + 1); + ev = enum_def->Lookup(enum_val_str); } - } while (*next); - *result = NumToString(i64); + if (!ev) return Error("unknown enum value: " + word); + u64 |= ev->GetAsUInt64(); + } + *result = IsUnsigned(base_type) ? NumToString(u64) + : NumToString(static_cast(u64)); return NoError(); } @@ -1618,7 +1614,204 @@ StructDef *Parser::LookupCreateStruct(const std::string &name, return struct_def; } -CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) { +const EnumVal *EnumDef::MinValue() const { + return vals.vec.empty() ? nullptr : vals.vec.front(); +} +const EnumVal *EnumDef::MaxValue() const { + return vals.vec.empty() ? nullptr : vals.vec.back(); +} + +template static uint64_t EnumDistanceImpl(T e1, T e2) { + if (e1 < e2) { std::swap(e1, e2); } // use std for scalars + // Signed overflow may occur, use unsigned calculation. + // The unsigned overflow is well-defined by C++ standard (modulo 2^n). + return static_cast(e1) - static_cast(e2); +} + +uint64_t EnumDef::Distance(const EnumVal *v1, const EnumVal *v2) const { + return IsUInt64() ? EnumDistanceImpl(v1->GetAsUInt64(), v2->GetAsUInt64()) + : EnumDistanceImpl(v1->GetAsInt64(), v2->GetAsInt64()); +} + +std::string EnumDef::AllFlags() const { + FLATBUFFERS_ASSERT(attributes.Lookup("bit_flags")); + uint64_t u64 = 0; + for (auto it = Vals().begin(); it != Vals().end(); ++it) { + u64 |= (*it)->GetAsUInt64(); + } + return IsUInt64() ? NumToString(u64) : NumToString(static_cast(u64)); +} + +EnumVal *EnumDef::ReverseLookup(int64_t enum_idx, + bool skip_union_default) const { + auto skip_first = static_cast(is_union && skip_union_default); + for (auto it = Vals().begin() + skip_first; it != Vals().end(); ++it) { + if ((*it)->GetAsInt64() == enum_idx) { return *it; } + } + return nullptr; +} + +EnumVal *EnumDef::FindByValue(const std::string &constant) const { + int64_t i64; + auto done = false; + if (IsUInt64()) { + uint64_t u64; // avoid reinterpret_cast of pointers + done = StringToNumber(constant.c_str(), &u64); + i64 = static_cast(u64); + } else { + done = StringToNumber(constant.c_str(), &i64); + } + FLATBUFFERS_ASSERT(done); + if (!done) return nullptr; + return ReverseLookup(i64, false); +} + +void EnumDef::SortByValue() { + auto &v = vals.vec; + if (IsUInt64()) + std::sort(v.begin(), v.end(), [](const EnumVal *e1, const EnumVal *e2) { + return e1->GetAsUInt64() < e2->GetAsUInt64(); + }); + else + std::sort(v.begin(), v.end(), [](const EnumVal *e1, const EnumVal *e2) { + return e1->GetAsInt64() < e2->GetAsInt64(); + }); +} + +void EnumDef::RemoveDuplicates() { + // This method depends form SymbolTable implementation! + // 1) vals.vec - owner (raw pointer) + // 2) vals.dict - access map + auto first = vals.vec.begin(); + auto last = vals.vec.end(); + if (first == last) return; + auto result = first; + while (++first != last) { + if ((*result)->value != (*first)->value) { + *(++result) = *first; + } else { + auto ev = *first; + for (auto it = vals.dict.begin(); it != vals.dict.end(); ++it) { + if (it->second == ev) it->second = *result; // reassign + } + delete ev; // delete enum value + *first = nullptr; + } + } + vals.vec.erase(++result, last); +} + +template void EnumDef::ChangeEnumValue(EnumVal *ev, T new_value) { + ev->value = static_cast(new_value); +} + +namespace EnumHelper { +template struct EnumValType { typedef int64_t type; }; +template<> struct EnumValType { typedef uint64_t type; }; +} // namespace EnumHelper + +struct EnumValBuilder { + EnumVal *CreateEnumerator(const std::string &ev_name) { + FLATBUFFERS_ASSERT(!temp); + auto first = enum_def.vals.vec.empty(); + user_value = first; + temp = new EnumVal(ev_name, first ? 0 : enum_def.vals.vec.back()->value); + return temp; + } + + EnumVal *CreateEnumerator(const std::string &ev_name, int64_t val) { + FLATBUFFERS_ASSERT(!temp); + user_value = true; + temp = new EnumVal(ev_name, val); + return temp; + } + + FLATBUFFERS_CHECKED_ERROR AcceptEnumerator(const std::string &name) { + FLATBUFFERS_ASSERT(temp); + ECHECK(ValidateValue(&temp->value, false == user_value)); + FLATBUFFERS_ASSERT((temp->union_type.enum_def == nullptr) || + (temp->union_type.enum_def == &enum_def)); + auto not_unique = enum_def.vals.Add(name, temp); + temp = nullptr; + if (not_unique) return parser.Error("enum value already exists: " + name); + return NoError(); + } + + FLATBUFFERS_CHECKED_ERROR AcceptEnumerator() { + return AcceptEnumerator(temp->name); + } + + FLATBUFFERS_CHECKED_ERROR AssignEnumeratorValue(const std::string &value) { + user_value = true; + auto fit = false; + auto ascending = false; + if (enum_def.IsUInt64()) { + uint64_t u64; + fit = StringToNumber(value.c_str(), &u64); + ascending = u64 > temp->GetAsUInt64(); + temp->value = static_cast(u64); // well-defined since C++20. + } else { + int64_t i64; + fit = StringToNumber(value.c_str(), &i64); + ascending = i64 > temp->GetAsInt64(); + temp->value = i64; + } + if (!fit) return parser.Error("enum value does not fit, \"" + value + "\""); + if (!ascending && strict_ascending && !enum_def.vals.vec.empty()) + return parser.Error("enum values must be specified in ascending order"); + return NoError(); + } + + template + inline FLATBUFFERS_CHECKED_ERROR ValidateImpl(int64_t *ev, int m) { + typedef typename EnumHelper::EnumValType::type T; // int64_t or uint64_t + static_assert(sizeof(T) == sizeof(int64_t), "invalid EnumValType"); + const auto v = static_cast(*ev); + auto up = static_cast((flatbuffers::numeric_limits::max)()); + auto dn = static_cast((flatbuffers::numeric_limits::lowest)()); + if (v < dn || v > (up - m)) { + return parser.Error("enum value does not fit, \"" + NumToString(v) + + (m ? " + 1\"" : "\"") + " out of " + + TypeToIntervalString()); + } + *ev = static_cast(v + m); // well-defined since C++20. + return NoError(); + } + + FLATBUFFERS_CHECKED_ERROR ValidateValue(int64_t *ev, bool next) { + // clang-format off + switch (enum_def.underlying_type.base_type) { + #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, \ + PTYPE, RTYPE) \ + case BASE_TYPE_##ENUM: { \ + if (!IsInteger(BASE_TYPE_##ENUM)) break; \ + return ValidateImpl(ev, next ? 1 : 0); \ + } + FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD); + #undef FLATBUFFERS_TD + default: break; + } + // clang-format on + return parser.Error("fatal: invalid enum underlying type"); + } + + EnumValBuilder(Parser &_parser, EnumDef &_enum_def, bool strict_order = true) + : parser(_parser), + enum_def(_enum_def), + temp(nullptr), + strict_ascending(strict_order), + user_value(false) {} + + ~EnumValBuilder() { delete temp; } + + Parser &parser; + EnumDef &enum_def; + EnumVal *temp; + const bool strict_ascending; + bool user_value; +}; + +CheckedError Parser::ParseEnum(const bool is_union, EnumDef **dest) { std::vector enum_comment = doc_comment_; NEXT(); std::string enum_name = attribute_; @@ -1645,33 +1838,38 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) { enum_def->underlying_type.enum_def = enum_def; } ECHECK(ParseMetaData(&enum_def->attributes)); + const auto underlying_type = enum_def->underlying_type.base_type; + if (enum_def->attributes.Lookup("bit_flags") && + !IsUnsigned(underlying_type)) { + // todo: Convert to the Error in the future? + Warning("underlying type of bit_flags enum must be unsigned"); + } + // Protobuf allows them to be specified in any order, so sort afterwards. + const auto strict_ascending = (false == opts.proto_mode); + EnumValBuilder evb(*this, *enum_def, strict_ascending); EXPECT('{'); - if (is_union) enum_def->vals.Add("NONE", new EnumVal("NONE", 0)); - std::set> union_types; - for (;;) { + // A lot of code generatos expect that an enum is not-empty. + if ((is_union || Is('}')) && !opts.proto_mode) { + evb.CreateEnumerator("NONE"); + ECHECK(evb.AcceptEnumerator()); + } + std::set> union_types; + while (!Is('}')) { if (opts.proto_mode && attribute_ == "option") { ECHECK(ParseProtoOption()); } else { - auto value_name = attribute_; - auto full_name = value_name; - std::vector value_comment = doc_comment_; + auto &ev = *evb.CreateEnumerator(attribute_); + auto full_name = ev.name; + ev.doc_comment = doc_comment_; EXPECT(kTokenIdentifier); if (is_union) { - ECHECK(ParseNamespacing(&full_name, &value_name)); + ECHECK(ParseNamespacing(&full_name, &ev.name)); if (opts.union_value_namespacing) { // Since we can't namespace the actual enum identifiers, turn // namespace parts into part of the identifier. - value_name = full_name; - std::replace(value_name.begin(), value_name.end(), '.', '_'); + ev.name = full_name; + std::replace(ev.name.begin(), ev.name.end(), '.', '_'); } - } - auto prevsize = enum_def->vals.vec.size(); - auto prevvalue = prevsize > 0 ? enum_def->vals.vec.back()->value : 0; - auto &ev = *new EnumVal(value_name, 0); - if (enum_def->vals.Add(value_name, &ev)) - return Error("enum value already exists: " + value_name); - ev.doc_comment = value_comment; - if (is_union) { if (Is(':')) { NEXT(); ECHECK(ParseType(ev.union_type)); @@ -1682,51 +1880,22 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) { ev.union_type = Type(BASE_TYPE_STRUCT, LookupCreateStruct(full_name)); } if (!enum_def->uses_multiple_type_instances) { - auto union_type_key = std::make_pair(ev.union_type.base_type, ev.union_type.struct_def); - if (union_types.count(union_type_key) > 0) { - enum_def->uses_multiple_type_instances = true; - } else { - union_types.insert(union_type_key); - } + auto ins = union_types.insert(std::make_pair( + ev.union_type.base_type, ev.union_type.struct_def)); + enum_def->uses_multiple_type_instances = (false == ins.second); } } + if (Is('=')) { NEXT(); - ECHECK(atot(attribute_.c_str(), *this, &ev.value)); + ECHECK(evb.AssignEnumeratorValue(attribute_)); EXPECT(kTokenIntegerConstant); - if (!opts.proto_mode && prevsize && - enum_def->vals.vec[prevsize - 1]->value >= ev.value) - return Error("enum values must be specified in ascending order"); - } else if (prevsize == 0) { - // already set to zero - } else if (prevvalue != flatbuffers::numeric_limits::max()) { - ev.value = prevvalue + 1; - } else { - return Error("enum value overflows"); + } else if (false == strict_ascending) { + // The opts.proto_mode flag is active. + return Error("Protobuf mode doesn't allow implicit enum values."); } - // Check that value fits into the underlying type. - switch (enum_def->underlying_type.base_type) { - // clang-format off - #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, \ - PTYPE, RTYPE) \ - case BASE_TYPE_##ENUM: { \ - int64_t min_value = static_cast( \ - flatbuffers::numeric_limits::lowest()); \ - int64_t max_value = static_cast( \ - flatbuffers::numeric_limits::max()); \ - if (ev.value < min_value || ev.value > max_value) { \ - return Error( \ - "enum value does not fit [" + NumToString(min_value) + \ - "; " + NumToString(max_value) + "]"); \ - } \ - break; \ - } - FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD); - #undef FLATBUFFERS_TD - default: break; - // clang-format on - } + ECHECK(evb.AcceptEnumerator()); if (opts.proto_mode && Is('[')) { NEXT(); @@ -1737,18 +1906,31 @@ CheckedError Parser::ParseEnum(bool is_union, EnumDef **dest) { } if (!Is(opts.proto_mode ? ';' : ',')) break; NEXT(); - if (Is('}')) break; } EXPECT('}'); + + // At this point, the enum can be empty if input is invalid proto-file. + if (!enum_def->size()) + return Error("incomplete enum declaration, values not found"); + if (enum_def->attributes.Lookup("bit_flags")) { - for (auto it = enum_def->vals.vec.begin(); it != enum_def->vals.vec.end(); + const auto base_width = static_cast(8 * SizeOf(underlying_type)); + for (auto it = enum_def->Vals().begin(); it != enum_def->Vals().end(); ++it) { - if (static_cast((*it)->value) >= - SizeOf(enum_def->underlying_type.base_type) * 8) + auto ev = *it; + const auto u = ev->GetAsUInt64(); + // Stop manipulations with the sign. + if (!IsUnsigned(underlying_type) && u == (base_width - 1)) + return Error("underlying type of bit_flags enum must be unsigned"); + if (u >= base_width) return Error("bit flag out of range of underlying integral type"); - (*it)->value = 1LL << (*it)->value; + enum_def->ChangeEnumValue(ev, 1ULL << u); } } + + if (false == strict_ascending) + enum_def->SortByValue(); // Must be sorted to use MinValue/MaxValue. + if (dest) *dest = enum_def; types_.Add(current_namespace_->GetFullyQualifiedName(enum_def->name), new Type(BASE_TYPE_UNION, nullptr, enum_def)); @@ -1982,10 +2164,6 @@ CheckedError Parser::ParseNamespace() { return NoError(); } -static bool compareEnumVals(const EnumVal *a, const EnumVal *b) { - return a->value < b->value; -} - // Best effort parsing of .proto declarations, with the aim to turn them // in the closest corresponding FlatBuffer equivalent. // We parse everything as identifiers instead of keywords, since we don't @@ -2031,25 +2209,8 @@ CheckedError Parser::ParseProtoDecl() { EnumDef *enum_def; ECHECK(ParseEnum(false, &enum_def)); if (Is(';')) NEXT(); - // Protobuf allows them to be specified in any order, so sort afterwards. - auto &v = enum_def->vals.vec; - std::sort(v.begin(), v.end(), compareEnumVals); - // 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) { - auto ref = it[-1]; - auto ev = it[0]; - for (auto dit = enum_def->vals.dict.begin(); - dit != enum_def->vals.dict.end(); ++dit) { - if (dit->second == ev) dit->second = ref; // reassign - } - delete ev; // delete enum value - it = v.erase(it); - } else { - ++it; - } - } + enum_def->RemoveDuplicates(); } else if (IsIdent("syntax")) { // Skip these. NEXT(); EXPECT('='); @@ -2219,11 +2380,11 @@ CheckedError Parser::ParseProtoFields(StructDef *struct_def, bool isextend, 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); + EnumValBuilder evb(*this, *oneof_union); + auto ev = evb.CreateEnumerator(oneof_type.struct_def->name); + ev->union_type = oneof_type; + ev->doc_comment = oneof_field.doc_comment; + ECHECK(evb.AcceptEnumerator(oneof_field.name)); } } else { EXPECT(';'); @@ -3205,9 +3366,9 @@ std::string Parser::ConformTo(const Parser &base) { for (auto evit = enum_def.Vals().begin(); evit != enum_def.Vals().end(); ++evit) { auto &enum_val = **evit; - auto enum_val_base = enum_def_base->vals.Lookup(enum_val.name); + auto enum_val_base = enum_def_base->Lookup(enum_val.name); if (enum_val_base) { - if (enum_val.value != enum_val_base->value) + if (enum_val != *enum_val_base) return "values differ for enum: " + enum_val.name; } } diff --git a/tests/test.cpp b/tests/test.cpp index e0232300ba7..c8d4577d119 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1297,7 +1297,6 @@ void ErrorTest() { TestError("enum X:float {}", "underlying"); TestError("enum X:byte { Y, Y }", "value already"); TestError("enum X:byte { Y=2, Z=1 }", "ascending"); - TestError("enum X:byte (bit_flags) { Y=8 }", "bit flag out"); TestError("table X { Y:int; } table X {", "datatype already"); TestError("struct X (force_align: 7) { Y:int; }", "force_align"); TestError("struct X {}", "size 0"); @@ -1416,6 +1415,13 @@ void EnumStringsTest() { "root_type T;" "{ F:[ \"E.C\", \"E.A E.B E.C\" ] }"), true); + // unsigned bit_flags + flatbuffers::Parser parser3; + TEST_EQ( + parser3.Parse("enum E:uint16 (bit_flags) { F0, F07=7, F08, F14=14, F15 }" + " table T { F: E = \"F15 F08\"; }" + "root_type T;"), + true); } void EnumNamesTest() { @@ -1436,9 +1442,10 @@ void EnumNamesTest() { void EnumOutOfRangeTest() { TestError("enum X:byte { Y = 128 }", "enum value does not fit"); TestError("enum X:byte { Y = -129 }", "enum value does not fit"); - TestError("enum X:byte { Y = 127, Z }", "enum value does not fit"); + TestError("enum X:byte { Y = 126, Z0, Z1 }", "enum value does not fit"); TestError("enum X:ubyte { Y = -1 }", "enum value does not fit"); TestError("enum X:ubyte { Y = 256 }", "enum value does not fit"); + TestError("enum X:ubyte { Y = 255, Z }", "enum value does not fit"); // Unions begin with an implicit "NONE = 0". TestError("table Y{} union X { Y = -1 }", "enum values must be specified in ascending order"); @@ -1448,12 +1455,13 @@ void EnumOutOfRangeTest() { TestError("enum X:int { Y = 2147483648 }", "enum value does not fit"); TestError("enum X:uint { Y = -1 }", "enum value does not fit"); TestError("enum X:uint { Y = 4294967297 }", "enum value does not fit"); - TestError("enum X:long { Y = 9223372036854775808 }", "constant does not fit"); - TestError("enum X:long { Y = 9223372036854775807, Z }", "enum value overflows"); - TestError("enum X:ulong { Y = -1 }", "enum value does not fit"); - // TODO: these are perfectly valid constants that shouldn't fail - TestError("enum X:ulong { Y = 13835058055282163712 }", "constant does not fit"); - TestError("enum X:ulong { Y = 18446744073709551615 }", "constant does not fit"); + TestError("enum X:long { Y = 9223372036854775808 }", "does not fit"); + TestError("enum X:long { Y = 9223372036854775807, Z }", "enum value does not fit"); + TestError("enum X:ulong { Y = -1 }", "does not fit"); + TestError("enum X:ubyte (bit_flags) { Y=8 }", "bit flag out"); + TestError("enum X:byte (bit_flags) { Y=7 }", "must be unsigned"); // -128 + // bit_flgs out of range + TestError("enum X:ubyte (bit_flags) { Y0,Y1,Y2,Y3,Y4,Y5,Y6,Y7,Y8 }", "out of range"); } void EnumValueTest() { @@ -1472,6 +1480,15 @@ void EnumValueTest() { // Generate json with defaults and check. TEST_EQ(TestValue(nullptr, "E", "enum E:int { Z, V=5 }"), 0); TEST_EQ(TestValue(nullptr, "E=V", "enum E:int { Z, V=5 }"), 5); + // u84 test + TEST_EQ(TestValue(nullptr, "E=V", + "enum E:ulong { V = 13835058055282163712 }"), + 13835058055282163712ULL); + TEST_EQ(TestValue(nullptr, "E=V", + "enum E:ulong { V = 18446744073709551615 }"), + 18446744073709551615ULL); + // Assign non-enum value to enum field. Is it right? + TEST_EQ(TestValue("{ Y:7 }", "E", "enum E:int { V = 0 }"), 7); } void IntegerOutOfRangeTest() { @@ -2694,7 +2711,7 @@ int main(int /*argc*/, const char * /*argv*/ []) { std::string req_locale; if (flatbuffers::ReadEnvironmentVariable("FLATBUFFERS_TEST_LOCALE", - &req_locale)) { + &req_locale)) { TEST_OUTPUT_LINE("The environment variable FLATBUFFERS_TEST_LOCALE=%s", req_locale.c_str()); req_locale = flatbuffers::RemoveStringQuotes(req_locale);