diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index d42c88218..53657d73d 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -100,19 +100,21 @@ struct EnumDef; // Represents any type in the IDL, which is a combination of the BaseType // and additional information for vectors/structs_. struct Type { - explicit Type(BaseType _base_type = BASE_TYPE_NONE, StructDef *_sd = nullptr) + explicit Type(BaseType _base_type = BASE_TYPE_NONE, + StructDef *_sd = nullptr, EnumDef *_ed = nullptr) : base_type(_base_type), element(BASE_TYPE_NONE), struct_def(_sd), - enum_def(nullptr) + enum_def(_ed) {} - Type VectorType() const { return Type(element, struct_def); } + Type VectorType() const { return Type(element, struct_def, enum_def); } BaseType base_type; BaseType element; // only set if t == BASE_TYPE_VECTOR StructDef *struct_def; // only set if t or element == BASE_TYPE_STRUCT - EnumDef *enum_def; // only set if t == BASE_TYPE_UNION / BASE_TYPE_UTYPE + EnumDef *enum_def; // set if t == BASE_TYPE_UNION / BASE_TYPE_UTYPE, + // or for an integral type derived from an enum. }; // Represents a parsed scalar value, it's type, and field offset. @@ -220,11 +222,10 @@ struct EnumVal { struct EnumDef : public Definition { EnumDef() : is_union(false) {} - StructDef *ReverseLookup(int enum_idx) { - assert(is_union); + EnumVal *ReverseLookup(int enum_idx) { for (auto it = vals.vec.begin() + 1; it != vals.vec.end(); ++it) { if ((*it)->value == enum_idx) { - return (*it)->struct_def; + return *it; } } return nullptr; @@ -294,8 +295,10 @@ class Parser { struct GeneratorOptions { bool strict_json; int indent_step; + bool output_enum_identifiers; - GeneratorOptions() : strict_json(false), indent_step(2) {} + GeneratorOptions() : strict_json(false), indent_step(2), + output_enum_identifiers(true) {} }; // Generate text (JSON) from a given FlatBuffer, and a given Parser diff --git a/src/idl_gen_text.cpp b/src/idl_gen_text.cpp index 370607240..e8759259f 100644 --- a/src/idl_gen_text.cpp +++ b/src/idl_gen_text.cpp @@ -32,14 +32,30 @@ const char *NewLine(int indent_step) { return indent_step >= 0 ? "\n" : ""; } +// Output an identifier with or without quotes depending on strictness. +void OutputIdentifier(const std::string &name, const GeneratorOptions &opts, + std::string *_text) { + std::string &text = *_text; + if (opts.strict_json) text += "\""; + text += name; + if (opts.strict_json) text += "\""; +} + // Print (and its template specialization below for pointers) generate text // for a single FlatBuffer value into JSON format. // The general case for scalars: -template void Print(T val, Type /*type*/, int /*indent*/, +template void Print(T val, Type type, int /*indent*/, StructDef * /*union_sd*/, - const GeneratorOptions & /*opts*/, + const GeneratorOptions &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) { + OutputIdentifier(enum_val->name, opts, _text); + return; + } + } text += NumToString(val); } @@ -188,9 +204,7 @@ static void GenStruct(const StructDef &struct_def, const Table *table, text += NewLine(opts.indent_step); } text.append(indent + opts.indent_step, ' '); - if (opts.strict_json) text += "\""; - text += fd.name; - if (opts.strict_json) text += "\""; + OutputIdentifier(fd.name, opts, _text); text += ": "; switch (fd.value.type.base_type) { #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE) \ @@ -210,8 +224,10 @@ static void GenStruct(const StructDef &struct_def, const Table *table, break; } if (fd.value.type.base_type == BASE_TYPE_UTYPE) { - union_sd = fd.value.type.enum_def->ReverseLookup( + auto enum_val = fd.value.type.enum_def->ReverseLookup( table->GetField(fd.value.offset, 0)); + assert(enum_val); + union_sd = enum_val->struct_def; } } } diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index 4a372b8e7..4d9c4cf25 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -268,7 +268,7 @@ void Parser::ParseType(Type &type) { // union element. Error("vector of union types not supported (wrap in table first)."); } - type = Type(BASE_TYPE_VECTOR, subtype.struct_def); + type = Type(BASE_TYPE_VECTOR, subtype.struct_def, subtype.enum_def); type.element = subtype.base_type; Expect(']'); return; @@ -359,9 +359,9 @@ void Parser::ParseAnyValue(Value &val, FieldDef *field) { Error("missing type field before this union value: " + field->name); auto enum_idx = atot( field_stack_.back().first.constant.c_str()); - auto struct_def = val.type.enum_def->ReverseLookup(enum_idx); - if (!struct_def) Error("illegal type id for: " + field->name); - val.constant = NumToString(ParseTable(*struct_def)); + auto enum_val = val.type.enum_def->ReverseLookup(enum_idx); + if (!enum_val) Error("illegal type id for: " + field->name); + val.constant = NumToString(ParseTable(*enum_val->struct_def)); break; } case BASE_TYPE_STRUCT: @@ -546,7 +546,16 @@ bool Parser::TryTypedValue(int dtoken, } void Parser::ParseSingleValue(Value &e) { - if (TryTypedValue(kTokenIntegerConstant, + // First check if derived from an enum, to allow strings/identifier values: + if (e.type.enum_def && (token_ == kTokenIdentifier || + token_ == kTokenStringConstant)) { + auto enum_val = e.type.enum_def->vals.Lookup(attribute_); + if (!enum_val) + Error("unknown enum value: " + attribute_ + + ", for enum: " + e.type.enum_def->name); + e.constant = NumToString(enum_val->value); + Next(); + } else if (TryTypedValue(kTokenIntegerConstant, IsScalar(e.type.base_type), e, BASE_TYPE_INT) || @@ -558,19 +567,6 @@ void Parser::ParseSingleValue(Value &e) { e.type.base_type == BASE_TYPE_STRING, e, BASE_TYPE_STRING)) { - } else if (token_ == kTokenIdentifier) { - for (auto it = enums_.vec.begin(); it != enums_.vec.end(); ++it) { - auto ev = (*it)->vals.Lookup(attribute_); - if (ev) { - attribute_ = NumToString(ev->value); - TryTypedValue(kTokenIdentifier, - IsInteger(e.type.base_type), - e, - BASE_TYPE_INT); - return; - } - } - Error("not valid enum value: " + attribute_); } else { Error("cannot parse value starting with: " + TokenToString(token_)); } @@ -611,6 +607,8 @@ void Parser::ParseEnum(bool is_union) { ParseType(enum_def.underlying_type); if (!IsInteger(enum_def.underlying_type.base_type)) 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; } ParseMetaData(enum_def); Expect('{'); diff --git a/tests/monsterdata_test.golden b/tests/monsterdata_test.golden index 26d53041f..c905fe4e5 100644 --- a/tests/monsterdata_test.golden +++ b/tests/monsterdata_test.golden @@ -19,7 +19,7 @@ 3, 4 ], - test_type: 1, + test_type: Monster, test: { hp: 20 }, diff --git a/tests/monsterdata_test.json b/tests/monsterdata_test.json index 26d53041f..c905fe4e5 100755 --- a/tests/monsterdata_test.json +++ b/tests/monsterdata_test.json @@ -19,7 +19,7 @@ 3, 4 ], - test_type: 1, + test_type: Monster, test: { hp: 20 }, diff --git a/tests/test.cpp b/tests/test.cpp index b859a5e88..96b144dbc 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -465,7 +465,8 @@ void ErrorTest() { TestError("table X { Y:int; } root_type X; { Z:", "unknown field"); TestError("struct X { Y:int; Z:int; } table W { V:X; } root_type W; " "{ V:{ Y:1 } }", "incomplete"); - TestError("table X { Y:byte; } root_type X; { Y:U }", "valid enum"); + TestError("enum E:byte { A } table X { Y:E; } root_type X; { Y:U }", + "unknown enum value"); TestError("table X { Y:byte; } root_type X; { Y:; }", "starting"); TestError("enum X:byte { Y } enum X {", "enum already"); TestError("enum X:float {}", "underlying"); @@ -482,7 +483,7 @@ void ErrorTest() { } // Additional parser testing not covered elsewhere. -void TokenTest() { +void ScientificTest() { flatbuffers::Parser parser; // Simple schema. @@ -496,6 +497,15 @@ void TokenTest() { TEST_EQ(fabs(root[1] - 3.14159) < 0.001, true); } +void EnumStringsTest() { + flatbuffers::Parser parser; + + TEST_EQ(parser.Parse("enum E:byte { A, B, C } table T { F:[E]; } root_type T;" + "{ F:[ A, B, \"C\" ] }"), true); +} + + + int main(int /*argc*/, const char * /*argv*/[]) { // Run our various test suites: @@ -510,7 +520,8 @@ int main(int /*argc*/, const char * /*argv*/[]) { FuzzTest2(); ErrorTest(); - TokenTest(); + ScientificTest(); + EnumStringsTest(); if (!testing_fails) { TEST_OUTPUT_LINE("ALL TESTS PASSED");