JSON parsing & text generation is now enum-identifier aware.

When Parsing JSON, it will read enums either as int values, identifiers
specific to the enum type, or strings containing those identifiers.

When generating text, it will output enum identifiers by default
(this can be turned off in favor of integers, like before).

Change-Id: If28b0a1f8f27de79aff3e626f40c0c0b271c325a
Tested: on Windows and Linux
Bug: 16214968
parent 2811a3ea
...@@ -100,19 +100,21 @@ struct EnumDef; ...@@ -100,19 +100,21 @@ struct EnumDef;
// Represents any type in the IDL, which is a combination of the BaseType // Represents any type in the IDL, which is a combination of the BaseType
// and additional information for vectors/structs_. // and additional information for vectors/structs_.
struct Type { 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), : base_type(_base_type),
element(BASE_TYPE_NONE), element(BASE_TYPE_NONE),
struct_def(_sd), 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 base_type;
BaseType element; // only set if t == BASE_TYPE_VECTOR BaseType element; // only set if t == BASE_TYPE_VECTOR
StructDef *struct_def; // only set if t or element == BASE_TYPE_STRUCT 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. // Represents a parsed scalar value, it's type, and field offset.
...@@ -220,11 +222,10 @@ struct EnumVal { ...@@ -220,11 +222,10 @@ struct EnumVal {
struct EnumDef : public Definition { struct EnumDef : public Definition {
EnumDef() : is_union(false) {} EnumDef() : is_union(false) {}
StructDef *ReverseLookup(int enum_idx) { EnumVal *ReverseLookup(int enum_idx) {
assert(is_union);
for (auto it = vals.vec.begin() + 1; it != vals.vec.end(); ++it) { for (auto it = vals.vec.begin() + 1; it != vals.vec.end(); ++it) {
if ((*it)->value == enum_idx) { if ((*it)->value == enum_idx) {
return (*it)->struct_def; return *it;
} }
} }
return nullptr; return nullptr;
...@@ -294,8 +295,10 @@ class Parser { ...@@ -294,8 +295,10 @@ class Parser {
struct GeneratorOptions { struct GeneratorOptions {
bool strict_json; bool strict_json;
int indent_step; 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 // Generate text (JSON) from a given FlatBuffer, and a given Parser
......
...@@ -32,14 +32,30 @@ const char *NewLine(int indent_step) { ...@@ -32,14 +32,30 @@ const char *NewLine(int indent_step) {
return indent_step >= 0 ? "\n" : ""; 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 // Print (and its template specialization below for pointers) generate text
// for a single FlatBuffer value into JSON format. // for a single FlatBuffer value into JSON format.
// The general case for scalars: // The general case for scalars:
template<typename T> void Print(T val, Type /*type*/, int /*indent*/, template<typename T> void Print(T val, Type type, int /*indent*/,
StructDef * /*union_sd*/, StructDef * /*union_sd*/,
const GeneratorOptions & /*opts*/, const GeneratorOptions &opts,
std::string *_text) { std::string *_text) {
std::string &text = *_text; std::string &text = *_text;
if (type.enum_def && opts.output_enum_identifiers) {
auto enum_val = type.enum_def->ReverseLookup(static_cast<int>(val));
if (enum_val) {
OutputIdentifier(enum_val->name, opts, _text);
return;
}
}
text += NumToString(val); text += NumToString(val);
} }
...@@ -188,9 +204,7 @@ static void GenStruct(const StructDef &struct_def, const Table *table, ...@@ -188,9 +204,7 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
text += NewLine(opts.indent_step); text += NewLine(opts.indent_step);
} }
text.append(indent + opts.indent_step, ' '); text.append(indent + opts.indent_step, ' ');
if (opts.strict_json) text += "\""; OutputIdentifier(fd.name, opts, _text);
text += fd.name;
if (opts.strict_json) text += "\"";
text += ": "; text += ": ";
switch (fd.value.type.base_type) { switch (fd.value.type.base_type) {
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE) \ #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE) \
...@@ -210,8 +224,10 @@ static void GenStruct(const StructDef &struct_def, const Table *table, ...@@ -210,8 +224,10 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
break; break;
} }
if (fd.value.type.base_type == BASE_TYPE_UTYPE) { 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<uint8_t>(fd.value.offset, 0)); table->GetField<uint8_t>(fd.value.offset, 0));
assert(enum_val);
union_sd = enum_val->struct_def;
} }
} }
} }
......
...@@ -268,7 +268,7 @@ void Parser::ParseType(Type &type) { ...@@ -268,7 +268,7 @@ void Parser::ParseType(Type &type) {
// union element. // union element.
Error("vector of union types not supported (wrap in table first)."); 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; type.element = subtype.base_type;
Expect(']'); Expect(']');
return; return;
...@@ -359,9 +359,9 @@ void Parser::ParseAnyValue(Value &val, FieldDef *field) { ...@@ -359,9 +359,9 @@ void Parser::ParseAnyValue(Value &val, FieldDef *field) {
Error("missing type field before this union value: " + field->name); Error("missing type field before this union value: " + field->name);
auto enum_idx = atot<unsigned char>( auto enum_idx = atot<unsigned char>(
field_stack_.back().first.constant.c_str()); field_stack_.back().first.constant.c_str());
auto struct_def = val.type.enum_def->ReverseLookup(enum_idx); auto enum_val = val.type.enum_def->ReverseLookup(enum_idx);
if (!struct_def) Error("illegal type id for: " + field->name); if (!enum_val) Error("illegal type id for: " + field->name);
val.constant = NumToString(ParseTable(*struct_def)); val.constant = NumToString(ParseTable(*enum_val->struct_def));
break; break;
} }
case BASE_TYPE_STRUCT: case BASE_TYPE_STRUCT:
...@@ -546,7 +546,16 @@ bool Parser::TryTypedValue(int dtoken, ...@@ -546,7 +546,16 @@ bool Parser::TryTypedValue(int dtoken,
} }
void Parser::ParseSingleValue(Value &e) { 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), IsScalar(e.type.base_type),
e, e,
BASE_TYPE_INT) || BASE_TYPE_INT) ||
...@@ -558,19 +567,6 @@ void Parser::ParseSingleValue(Value &e) { ...@@ -558,19 +567,6 @@ void Parser::ParseSingleValue(Value &e) {
e.type.base_type == BASE_TYPE_STRING, e.type.base_type == BASE_TYPE_STRING,
e, e,
BASE_TYPE_STRING)) { 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 { } else {
Error("cannot parse value starting with: " + TokenToString(token_)); Error("cannot parse value starting with: " + TokenToString(token_));
} }
...@@ -611,6 +607,8 @@ void Parser::ParseEnum(bool is_union) { ...@@ -611,6 +607,8 @@ void Parser::ParseEnum(bool is_union) {
ParseType(enum_def.underlying_type); ParseType(enum_def.underlying_type);
if (!IsInteger(enum_def.underlying_type.base_type)) if (!IsInteger(enum_def.underlying_type.base_type))
Error("underlying enum type must be integral"); 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); ParseMetaData(enum_def);
Expect('{'); Expect('{');
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
3, 3,
4 4
], ],
test_type: 1, test_type: Monster,
test: { test: {
hp: 20 hp: 20
}, },
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
3, 3,
4 4
], ],
test_type: 1, test_type: Monster,
test: { test: {
hp: 20 hp: 20
}, },
......
...@@ -465,7 +465,8 @@ void ErrorTest() { ...@@ -465,7 +465,8 @@ void ErrorTest() {
TestError("table X { Y:int; } root_type X; { Z:", "unknown field"); 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; " TestError("struct X { Y:int; Z:int; } table W { V:X; } root_type W; "
"{ V:{ Y:1 } }", "incomplete"); "{ 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("table X { Y:byte; } root_type X; { Y:; }", "starting");
TestError("enum X:byte { Y } enum X {", "enum already"); TestError("enum X:byte { Y } enum X {", "enum already");
TestError("enum X:float {}", "underlying"); TestError("enum X:float {}", "underlying");
...@@ -482,7 +483,7 @@ void ErrorTest() { ...@@ -482,7 +483,7 @@ void ErrorTest() {
} }
// Additional parser testing not covered elsewhere. // Additional parser testing not covered elsewhere.
void TokenTest() { void ScientificTest() {
flatbuffers::Parser parser; flatbuffers::Parser parser;
// Simple schema. // Simple schema.
...@@ -496,6 +497,15 @@ void TokenTest() { ...@@ -496,6 +497,15 @@ void TokenTest() {
TEST_EQ(fabs(root[1] - 3.14159) < 0.001, true); 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*/[]) { int main(int /*argc*/, const char * /*argv*/[]) {
// Run our various test suites: // Run our various test suites:
...@@ -510,7 +520,8 @@ int main(int /*argc*/, const char * /*argv*/[]) { ...@@ -510,7 +520,8 @@ int main(int /*argc*/, const char * /*argv*/[]) {
FuzzTest2(); FuzzTest2();
ErrorTest(); ErrorTest();
TokenTest(); ScientificTest();
EnumStringsTest();
if (!testing_fails) { if (!testing_fails) {
TEST_OUTPUT_LINE("ALL TESTS PASSED"); TEST_OUTPUT_LINE("ALL TESTS PASSED");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment