diff --git a/src/idl_gen_general.cpp b/src/idl_gen_general.cpp index d60ecd490..ab5468bdf 100644 --- a/src/idl_gen_general.cpp +++ b/src/idl_gen_general.cpp @@ -80,6 +80,8 @@ struct LanguageParameters { const char *open_curly; const char *const_decl; const char *unsubclassable_decl; + const char *enum_decl; + const char *enum_separator; const char *inheritance_marker; const char *namespace_ident; const char *namespace_begin; @@ -99,6 +101,8 @@ LanguageParameters language_parameters[] = { " {\n", " final ", "final ", + "final class ", + ";\n", " extends ", "package ", ";", @@ -121,6 +125,8 @@ LanguageParameters language_parameters[] = { "\n{\n", " readonly ", "sealed ", + "enum ", + ",\n", " : ", "namespace ", "\n{", @@ -144,6 +150,8 @@ LanguageParameters language_parameters[] = { "\n{\n", "const ", " ", + "class ", + ";\n", "", "package ", "", @@ -181,6 +189,16 @@ static std::string GenTypeBasic(const LanguageParameters &lang, return gtypename[type.base_type * GeneratorOptions::kMAX + lang.language]; } +// Generate type to be used in user-facing API +static std::string GenTypeForUser(const LanguageParameters &lang, + const Type &type) { + if (lang.language == GeneratorOptions::kCSharp) { + if (type.enum_def != nullptr && + type.base_type != BASE_TYPE_UNION) return type.enum_def->name; + } + return GenTypeBasic(lang, type); +} + static std::string GenTypeGet(const LanguageParameters &lang, const Type &type); @@ -240,17 +258,44 @@ static std::string DestinationMask(const LanguageParameters &lang, } } -// Cast necessary to correctly read serialized unsigned values. +// Casts necessary to correctly read serialized data static std::string DestinationCast(const LanguageParameters &lang, const Type &type) { - if (lang.language == GeneratorOptions::kJava && - (type.base_type == BASE_TYPE_UINT || - (type.base_type == BASE_TYPE_VECTOR && - type.element == BASE_TYPE_UINT))) return "(long)"; + switch (lang.language) { + case GeneratorOptions::kJava: + // Cast necessary to correctly read serialized unsigned values. + if (type.base_type == BASE_TYPE_UINT || + (type.base_type == BASE_TYPE_VECTOR && + type.element == BASE_TYPE_UINT)) return "(long)"; + break; + + case GeneratorOptions::kCSharp: + // Cast from raw integral types to enum + if (type.enum_def != nullptr && + type.base_type != BASE_TYPE_UNION) return "(" + type.enum_def->name + ")"; + break; + + default: + break; + } return ""; } - +// Read value and possibly process it to get proper value +static std::string DestinationValue(const LanguageParameters &lang, + const std::string &name, + const Type &type) { + std::string type_mask = DestinationMask(lang, type, false); + // is a typecast needed? (for C# enums and unsigned values in Java) + if (type_mask.length() || + (lang.language == GeneratorOptions::kCSharp && + type.enum_def != nullptr && + type.base_type != BASE_TYPE_UNION)) { + return "(" + GenTypeBasic(lang, type) + ")(" + name + type_mask + ")"; + } else { + return name; + } +} static std::string GenDefaultValue(const Value &value) { return value.type.base_type == BASE_TYPE_BOOL @@ -269,50 +314,61 @@ static void GenEnum(const LanguageParameters &lang, EnumDef &enum_def, // to map directly to how they're used in C/C++ and file formats. // That, and Java Enums are expensive, and not universally liked. GenComment(enum_def.doc_comment, code_ptr, &lang.comment_config); - code += std::string("public ") + lang.unsubclassable_decl; - code += "class " + enum_def.name + lang.open_curly; - code += " private " + enum_def.name + "() { }\n"; + code += std::string("public ") + lang.enum_decl + enum_def.name; + if (lang.language == GeneratorOptions::kCSharp) { + code += lang.inheritance_marker + GenTypeBasic(lang, enum_def.underlying_type); + } + code += lang.open_curly; + if (lang.language == GeneratorOptions::kJava) { + code += " private " + enum_def.name + "() { }\n"; + } for (auto it = enum_def.vals.vec.begin(); it != enum_def.vals.vec.end(); ++it) { auto &ev = **it; GenComment(ev.doc_comment, code_ptr, &lang.comment_config, " "); - code += " public static"; - code += lang.const_decl; - code += GenTypeBasic(lang, enum_def.underlying_type); + if (lang.language != GeneratorOptions::kCSharp) { + code += " public static"; + code += lang.const_decl; + code += GenTypeBasic(lang, enum_def.underlying_type); + } code += " " + ev.name + " = "; - code += NumToString(ev.value) + ";\n"; + code += NumToString(ev.value); + code += lang.enum_separator; } // Generate a generate string table for enum values. - // 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; - // 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) { - code += "\n private static"; - code += lang.const_decl; - code += lang.string_type; - code += "[] names = { "; - auto val = enum_def.vals.vec.front()->value; - for (auto it = enum_def.vals.vec.begin(); - it != enum_def.vals.vec.end(); - ++it) { - while (val++ != (*it)->value) code += "\"\", "; - code += "\"" + (*it)->name + "\", "; + // We do not do that for C# where this functionality is native. + if (lang.language != GeneratorOptions::kCSharp) { + // 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; + // 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) { + code += "\n private static"; + code += lang.const_decl; + code += lang.string_type; + code += "[] names = { "; + auto val = enum_def.vals.vec.front()->value; + for (auto it = enum_def.vals.vec.begin(); + it != enum_def.vals.vec.end(); + ++it) { + while (val++ != (*it)->value) code += "\"\", "; + code += "\"" + (*it)->name + "\", "; + } + code += "};\n\n"; + code += " public static "; + 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; + code += "]; }\n"; } - code += "};\n\n"; - code += " public static "; - 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; - code += "]; }\n"; } // Close the class @@ -364,8 +420,8 @@ static void GenStructArgs(const LanguageParameters &lang, (field.value.type.struct_def->name + "_").c_str()); } else { code += ", "; - code += GenTypeBasic(lang, - DestinationType(lang, field.value.type, false)); + code += GenTypeForUser(lang, + DestinationType(lang, field.value.type, false)); code += " "; code += nameprefix; code += MakeCamel(field.name, lang.first_camel_upper); @@ -397,13 +453,7 @@ static void GenStructBody(const LanguageParameters &lang, code += " builder." + FunctionStart(lang, 'P') + "ut"; code += GenMethod(lang, field.value.type) + "("; auto argname = nameprefix + MakeCamel(field.name, lang.first_camel_upper); - std::string type_mask = DestinationMask(lang, field.value.type, false); - if (type_mask.length()) { - code += "(" + GenTypeBasic(lang, field.value.type) + ")"; - code += "(" + argname + type_mask + ")"; - } else { - code += argname; - } + code += DestinationValue(lang, argname, field.value.type); code += ");\n"; } } @@ -464,7 +514,11 @@ static void GenStruct(const LanguageParameters &lang, const Parser &parser, GenComment(field.doc_comment, code_ptr, &lang.comment_config, " "); std::string type_name = GenTypeGet(lang, field.value.type); std::string type_name_dest = - GenTypeGet(lang, DestinationType(lang, field.value.type, true)); + lang.language == GeneratorOptions::kCSharp && + field.value.type.enum_def != nullptr && + field.value.type.base_type != BASE_TYPE_UNION + ? field.value.type.enum_def->name + : GenTypeGet(lang, DestinationType(lang, field.value.type, true)); std::string dest_mask = DestinationMask(lang, field.value.type, true); std::string dest_cast = DestinationCast(lang, field.value.type); std::string method_start = " public " + type_name_dest + " " + @@ -612,8 +666,8 @@ static void GenStruct(const LanguageParameters &lang, const Parser &parser, auto &field = **it; if (field.deprecated) continue; code += ",\n "; - code += GenTypeBasic(lang, - DestinationType(lang, field.value.type, false)); + code += GenTypeForUser(lang, + DestinationType(lang, field.value.type, false)); code += " "; code += field.name; // Java doesn't have defaults, which means this method must always @@ -661,20 +715,14 @@ static void GenStruct(const LanguageParameters &lang, const Parser &parser, code += " public static void " + FunctionStart(lang, 'A') + "dd"; code += MakeCamel(field.name); code += "(FlatBufferBuilder builder, "; - code += GenTypeBasic(lang, - DestinationType(lang, field.value.type, false)); + code += GenTypeForUser(lang, + DestinationType(lang, field.value.type, false)); auto argname = MakeCamel(field.name, false); if (!IsScalar(field.value.type.base_type)) argname += "Offset"; code += " " + argname + ") { builder." + FunctionStart(lang, 'A') + "dd"; code += GenMethod(lang, field.value.type) + "("; code += NumToString(it - struct_def.fields.vec.begin()) + ", "; - std::string type_mask = DestinationMask(lang, field.value.type, false); - if (type_mask.length()) { - code += "(" + GenTypeBasic(lang, field.value.type) + ")"; - code += "(" + argname + type_mask + ")"; - } else { - code += argname; - } + code += DestinationValue(lang, argname, field.value.type); code += ", " + GenDefaultValue(field.value); code += "); }\n"; if (field.value.type.base_type == BASE_TYPE_VECTOR) { diff --git a/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs b/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs index 1db1352ed..a16232ee8 100644 --- a/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs +++ b/tests/FlatBuffers.Test/FlatBuffersExampleTests.cs @@ -72,7 +72,7 @@ namespace FlatBuffers.Test Monster.AddHp(fbb, (short)80); Monster.AddName(fbb, str); Monster.AddInventory(fbb, inv); - Monster.AddTestType(fbb, (byte)1); + Monster.AddTestType(fbb, Any.Monster); Monster.AddTest(fbb, mon2); Monster.AddTest4(fbb, test4); Monster.AddTestarrayofstring(fbb, testArrayOfString); @@ -111,7 +111,7 @@ namespace FlatBuffers.Test Assert.AreEqual((short)5, t.A()); Assert.AreEqual((sbyte)6, t.B()); - Assert.AreEqual((byte)Any.Monster, monster.TestType()); + Assert.AreEqual(Any.Monster, monster.TestType()); var monster2 = new Monster(); Assert.IsTrue(monster.Test(monster2) != null); @@ -148,10 +148,10 @@ namespace FlatBuffers.Test public void TestEnums() { - Assert.AreEqual(Color.Name(Color.Red), "Red"); - Assert.AreEqual(Color.Name(Color.Blue), "Blue"); - Assert.AreEqual(Any.Name(Any.NONE), "NONE"); - Assert.AreEqual(Any.Name(Any.Monster), "Monster"); + Assert.AreEqual("Red", Color.Red.ToString()); + Assert.AreEqual("Blue", Color.Blue.ToString()); + Assert.AreEqual("NONE", Any.NONE.ToString()); + Assert.AreEqual("Monster", Any.Monster.ToString()); } } } diff --git a/tests/MyGame/Example/Any.cs b/tests/MyGame/Example/Any.cs index af5b2c3f9..b7a5b3614 100644 --- a/tests/MyGame/Example/Any.cs +++ b/tests/MyGame/Example/Any.cs @@ -3,15 +3,10 @@ namespace MyGame.Example { -public sealed class Any +public enum Any : byte { - private Any() { } - public static readonly byte NONE = 0; - public static readonly byte Monster = 1; - - private static readonly string[] names = { "NONE", "Monster", }; - - public static string Name(int e) { return names[e]; } + NONE = 0, + Monster = 1, }; diff --git a/tests/MyGame/Example/Color.cs b/tests/MyGame/Example/Color.cs index c3c764741..7843733e3 100644 --- a/tests/MyGame/Example/Color.cs +++ b/tests/MyGame/Example/Color.cs @@ -3,16 +3,11 @@ namespace MyGame.Example { -public sealed class Color +public enum Color : sbyte { - private Color() { } - public static readonly sbyte Red = 1; - public static readonly sbyte Green = 2; - public static readonly sbyte Blue = 8; - - private static readonly string[] names = { "Red", "Green", "", "", "", "", "", "Blue", }; - - public static string Name(int e) { return names[e - Red]; } + Red = 1, + Green = 2, + Blue = 8, }; diff --git a/tests/MyGame/Example/Monster.cs b/tests/MyGame/Example/Monster.cs index 7a882055f..343be87b9 100644 --- a/tests/MyGame/Example/Monster.cs +++ b/tests/MyGame/Example/Monster.cs @@ -18,8 +18,8 @@ public sealed class Monster : Table { public string Name() { int o = __offset(10); return o != 0 ? __string(o + bb_pos) : null; } public byte Inventory(int j) { int o = __offset(14); return o != 0 ? bb.Get(__vector(o) + j * 1) : (byte)0; } public int InventoryLength() { int o = __offset(14); return o != 0 ? __vector_len(o) : 0; } - public sbyte Color() { int o = __offset(16); return o != 0 ? bb.GetSbyte(o + bb_pos) : (sbyte)8; } - public byte TestType() { int o = __offset(18); return o != 0 ? bb.Get(o + bb_pos) : (byte)0; } + public Color Color() { int o = __offset(16); return o != 0 ? (Color)bb.GetSbyte(o + bb_pos) : (Color)8; } + public Any TestType() { int o = __offset(18); return o != 0 ? (Any)bb.Get(o + bb_pos) : (Any)0; } public Table Test(Table obj) { int o = __offset(20); return o != 0 ? __union(obj, o) : null; } public Test Test4(int j) { return Test4(new Test(), j); } public Test Test4(Test obj, int j) { int o = __offset(22); return o != 0 ? obj.__init(__vector(o) + j * 4, bb) : null; } @@ -55,8 +55,8 @@ public sealed class Monster : Table { public static void AddInventory(FlatBufferBuilder builder, int inventoryOffset) { builder.AddOffset(5, inventoryOffset, 0); } public static int CreateInventoryVector(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); for (int i = data.Length - 1; i >= 0; i--) builder.AddByte(data[i]); return builder.EndVector(); } public static void StartInventoryVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(1, numElems, 1); } - public static void AddColor(FlatBufferBuilder builder, sbyte color) { builder.AddSbyte(6, color, 8); } - public static void AddTestType(FlatBufferBuilder builder, byte testType) { builder.AddByte(7, testType, 0); } + public static void AddColor(FlatBufferBuilder builder, Color color) { builder.AddSbyte(6, (sbyte)(color), 8); } + public static void AddTestType(FlatBufferBuilder builder, Any testType) { builder.AddByte(7, (byte)(testType), 0); } public static void AddTest(FlatBufferBuilder builder, int testOffset) { builder.AddOffset(8, testOffset, 0); } public static void AddTest4(FlatBufferBuilder builder, int test4Offset) { builder.AddOffset(9, test4Offset, 0); } public static void StartTest4Vector(FlatBufferBuilder builder, int numElems) { builder.StartVector(4, numElems, 2); } diff --git a/tests/MyGame/Example/Vec3.cs b/tests/MyGame/Example/Vec3.cs index bd0acebf5..01aac19ae 100644 --- a/tests/MyGame/Example/Vec3.cs +++ b/tests/MyGame/Example/Vec3.cs @@ -12,11 +12,11 @@ public sealed class Vec3 : Struct { public float Y() { return bb.GetFloat(bb_pos + 4); } public float Z() { return bb.GetFloat(bb_pos + 8); } public double Test1() { return bb.GetDouble(bb_pos + 16); } - public sbyte Test2() { return bb.GetSbyte(bb_pos + 24); } + public Color Test2() { return (Color)bb.GetSbyte(bb_pos + 24); } public Test Test3() { return Test3(new Test()); } public Test Test3(Test obj) { return obj.__init(bb_pos + 26, bb); } - public static int CreateVec3(FlatBufferBuilder builder, float X, float Y, float Z, double Test1, sbyte Test2, short Test_A, sbyte Test_B) { + public static int CreateVec3(FlatBufferBuilder builder, float X, float Y, float Z, double Test1, Color Test2, short Test_A, sbyte Test_B) { builder.Prep(16, 32); builder.Pad(2); builder.Prep(2, 4); @@ -24,7 +24,7 @@ public sealed class Vec3 : Struct { builder.PutSbyte(Test_B); builder.PutShort(Test_A); builder.Pad(1); - builder.PutSbyte(Test2); + builder.PutSbyte((sbyte)(Test2)); builder.PutDouble(Test1); builder.Pad(4); builder.PutFloat(Z);