Skip to content

Commit 3420111

Browse files
committed
feat: csharp ordered map
1 parent 7e310f8 commit 3420111

File tree

8 files changed

+372
-1
lines changed

8 files changed

+372
-1
lines changed

cmd/protoc-gen-csharp-tableau-loader/messager.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/iancoleman/strcase"
88
"github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper"
9+
"github.com/tableauio/loader/internal/options"
910
"github.com/tableauio/tableau/proto/tableaupb"
1011
"google.golang.org/protobuf/compiler/protogen"
1112
"google.golang.org/protobuf/proto"
@@ -50,9 +51,14 @@ func generateFileContent(gen *protogen.Plugin, file *protogen.File, g *protogen.
5051
// genMessage generates a message definition.
5152
func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protogen.Message) {
5253
messagerName := string(message.Desc.Name())
54+
messagerFullName := string(message.Desc.FullName())
5355

5456
g.P(" public class ", messagerName, " : Messager, IMessagerName")
5557
g.P(" {")
58+
// type definitions
59+
if options.NeedGenOrderedMap(message.Desc, options.LangCS) {
60+
genOrderedMapTypeDef(gen, g, message.Desc, 1, nil, messagerFullName)
61+
}
5662
g.P(" private Protoconf.", messagerName, " Data_ = new Protoconf.", messagerName, "();")
5763
g.P()
5864
g.P(" public static string Name() => Protoconf.", messagerName, ".Descriptor.Name;")
@@ -68,8 +74,23 @@ func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, message *protog
6874
g.P(" }")
6975
g.P()
7076
g.P(" public ref readonly Protoconf.", messagerName, " Data() => ref Data_;")
77+
78+
if options.NeedGenOrderedMap(message.Desc, options.LangCS) || options.NeedGenIndex(message.Desc, options.LangCS) {
79+
g.P()
80+
g.P(" protected override bool ProcessAfterLoad()")
81+
g.P(" {")
82+
if options.NeedGenOrderedMap(message.Desc, options.LangCS) {
83+
genOrderedMapLoader(gen, g, message.Desc, 1, messagerFullName)
84+
}
85+
g.P(" return true;")
86+
g.P(" }")
87+
}
88+
7189
// syntactic sugar for accessing map items
7290
genMapGetters(gen, g, message.Desc, 1, nil, messagerName)
91+
if options.NeedGenOrderedMap(message.Desc, options.LangCS) {
92+
genOrderedMapGetters(gen, g, message.Desc, 1, nil, messagerFullName)
93+
}
7394
g.P(" }")
7495
}
7596

@@ -106,6 +127,19 @@ func genMapGetters(gen *protogen.Plugin, g *protogen.GeneratedFile, md protorefl
106127
}
107128
}
108129

130+
func getNextLevelMapFD(fd protoreflect.FieldDescriptor) protoreflect.FieldDescriptor {
131+
if fd.Kind() == protoreflect.MessageKind {
132+
md := fd.Message()
133+
for i := 0; i < md.Fields().Len(); i++ {
134+
fd := md.Fields().Get(i)
135+
if fd.IsMap() {
136+
return fd
137+
}
138+
}
139+
}
140+
return nil
141+
}
142+
109143
func parseMapValueType(fd protoreflect.FieldDescriptor) string {
110144
return helper.ParseCsharpType(fd.MapValue())
111145
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/iancoleman/strcase"
8+
"github.com/tableauio/loader/cmd/protoc-gen-csharp-tableau-loader/helper"
9+
"github.com/tableauio/loader/internal/options"
10+
"google.golang.org/protobuf/compiler/protogen"
11+
"google.golang.org/protobuf/reflect/protoreflect"
12+
)
13+
14+
const orderedMapSuffix = "_OrderedMap"
15+
const orderedMapValueSuffix = "_OrderedMapValue"
16+
17+
func genOrderedMapTypeDef(gen *protogen.Plugin, g *protogen.GeneratedFile, md protoreflect.MessageDescriptor, depth int, keys []helper.MapKey, messagerFullName string) {
18+
if depth == 1 && !options.NeedGenOrderedMap(md, options.LangCS) {
19+
return
20+
}
21+
for i := 0; i < md.Fields().Len(); i++ {
22+
fd := md.Fields().Get(i)
23+
if fd.IsMap() {
24+
if depth == 1 {
25+
g.P(" // OrderedMap types.")
26+
}
27+
nextKeys := helper.AddMapKey(gen, fd, keys)
28+
keyType := nextKeys[len(nextKeys)-1].Type
29+
30+
if fd.MapValue().Kind() == protoreflect.MessageKind {
31+
genOrderedMapTypeDef(gen, g, fd.MapValue().Message(), depth+1, nextKeys, messagerFullName)
32+
}
33+
34+
prefix := parseOrderedMapPrefix(fd, messagerFullName)
35+
orderedMap := prefix + orderedMapSuffix
36+
orderedMapValue := prefix + orderedMapValueSuffix
37+
38+
nextMapFD := getNextLevelMapFD(fd.MapValue())
39+
if nextMapFD != nil {
40+
currValueType := helper.ParseCsharpType(fd.MapValue())
41+
nextPrefix := parseOrderedMapPrefix(nextMapFD, messagerFullName)
42+
nextOrderedMap := nextPrefix + orderedMapSuffix
43+
g.P(" public class ", orderedMapValue, " : Tuple<", nextOrderedMap, ", ", currValueType, "?>")
44+
g.P(" {")
45+
g.P(" public ", orderedMapValue, "(", nextOrderedMap, " item1, ", currValueType, "? item2) : base(item1, item2) { }")
46+
g.P(" }")
47+
g.P(" public class ", orderedMap, " : SortedDictionary<", keyType, ", ", orderedMapValue, "> { }")
48+
g.P()
49+
} else {
50+
g.P(" public class ", orderedMap, " : SortedDictionary<", keyType, ", ", parseMapValueType(fd), "> { }")
51+
g.P()
52+
}
53+
if depth == 1 {
54+
g.P(" private ", orderedMap, " OrderedMap = new ", orderedMap, "();")
55+
g.P()
56+
}
57+
break
58+
}
59+
}
60+
}
61+
62+
func genOrderedMapLoader(gen *protogen.Plugin, g *protogen.GeneratedFile, md protoreflect.MessageDescriptor, depth int, messagerFullName string) {
63+
if depth == 1 {
64+
g.P(" // OrderedMap init.")
65+
g.P(" OrderedMap.Clear();")
66+
}
67+
for i := 0; i < md.Fields().Len(); i++ {
68+
fd := md.Fields().Get(i)
69+
if fd.IsMap() {
70+
prefix := parseOrderedMapPrefix(fd, messagerFullName)
71+
// orderedMap := prefix + orderedMapSuffix
72+
orderedMapValue := prefix + orderedMapValueSuffix
73+
keyName := fmt.Sprintf("key%d", depth)
74+
valueName := fmt.Sprintf("value%d", depth)
75+
76+
tmpOrderedMapName := fmt.Sprintf("ordered_map%d", depth)
77+
78+
prevContainer := fmt.Sprintf("value%d", depth-1)
79+
prevTmpOrderedMapName := fmt.Sprintf("ordered_map%d", depth-1)
80+
if depth == 1 {
81+
prevContainer = "Data_"
82+
prevTmpOrderedMapName = "OrderedMap"
83+
}
84+
g.P(strings.Repeat(" ", depth+2), "foreach (var (", keyName, ", ", valueName, ") in ", prevContainer, ".", strcase.ToCamel(string(fd.Name())), ")")
85+
g.P(strings.Repeat(" ", depth+2), "{")
86+
nextMapFD := getNextLevelMapFD(fd.MapValue())
87+
if nextMapFD != nil {
88+
nextPrefix := parseOrderedMapPrefix(nextMapFD, messagerFullName)
89+
nextOrderedMap := nextPrefix + orderedMapSuffix
90+
g.P(strings.Repeat(" ", depth+3), "var ", tmpOrderedMapName, " = new ", nextOrderedMap, "();")
91+
}
92+
if fd.MapValue().Kind() == protoreflect.MessageKind {
93+
genOrderedMapLoader(gen, g, fd.MapValue().Message(), depth+1, messagerFullName)
94+
}
95+
96+
if nextMapFD != nil {
97+
g.P(strings.Repeat(" ", depth+3), prevTmpOrderedMapName, "[", keyName, "] = new ", orderedMapValue, "(", tmpOrderedMapName, ", ", valueName, ");")
98+
} else {
99+
g.P(strings.Repeat(" ", depth+3), prevTmpOrderedMapName, "[", keyName, "] = ", valueName, ";")
100+
}
101+
g.P(strings.Repeat(" ", depth+2), "}")
102+
break
103+
}
104+
}
105+
}
106+
107+
func genOrderedMapGetters(gen *protogen.Plugin, g *protogen.GeneratedFile, md protoreflect.MessageDescriptor, depth int, keys []helper.MapKey, messagerFullName string) {
108+
if depth == 1 && !options.NeedGenOrderedMap(md, options.LangCS) {
109+
return
110+
}
111+
genGetterName := func(depth int) string {
112+
getter := "GetOrderedMap"
113+
if depth > 1 {
114+
getter = fmt.Sprintf("GetOrderedMap%v", depth-1)
115+
}
116+
return getter
117+
}
118+
for i := 0; i < md.Fields().Len(); i++ {
119+
fd := md.Fields().Get(i)
120+
if fd.IsMap() {
121+
g.P()
122+
if depth == 1 {
123+
g.P(" // OrderedMap accessors.")
124+
}
125+
getter := genGetterName(depth)
126+
prefix := parseOrderedMapPrefix(fd, messagerFullName)
127+
orderedMap := prefix + orderedMapSuffix
128+
129+
if depth == 1 {
130+
g.P(" public ref readonly ", orderedMap, " ", getter, "()")
131+
g.P(" {")
132+
g.P(" return ref OrderedMap;")
133+
} else {
134+
g.P(" public ", orderedMap, "? ", getter, "(", helper.GenGetParams(keys), ")")
135+
g.P(" {")
136+
lastKeyName := keys[len(keys)-1].Name
137+
if depth == 2 {
138+
g.P(" if (OrderedMap.TryGetValue(", lastKeyName, ", out var value))")
139+
} else {
140+
prevKeys := keys[:len(keys)-1]
141+
prevGetter := genGetterName(depth - 1)
142+
g.P(" var conf = ", prevGetter, "(", helper.GenGetArguments(prevKeys), ");")
143+
g.P(" if (conf != null && conf.TryGetValue(", lastKeyName, ", out var value))")
144+
}
145+
g.P(" {")
146+
g.P(" return value.Item1;")
147+
g.P(" }")
148+
g.P(" return null;")
149+
}
150+
g.P(" }")
151+
152+
keys = helper.AddMapKey(gen, fd, keys)
153+
if fd.MapValue().Kind() == protoreflect.MessageKind {
154+
genOrderedMapGetters(gen, g, fd.MapValue().Message(), depth+1, keys, messagerFullName)
155+
}
156+
break
157+
}
158+
}
159+
}
160+
161+
func parseOrderedMapPrefix(mapFd protoreflect.FieldDescriptor, messagerFullName string) string {
162+
if mapFd.MapValue().Kind() == protoreflect.MessageKind {
163+
localMsgProtoName := strings.TrimPrefix(string(mapFd.MapValue().Message().FullName()), messagerFullName+".")
164+
return strings.ReplaceAll(localMsgProtoName, ".", "_")
165+
}
166+
return mapFd.MapValue().Kind().String()
167+
}

internal/options/options.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type Language = string
2121
const (
2222
LangCPP Language = "cpp"
2323
LangGO Language = "go"
24+
LangCS Language = "cs"
2425
)
2526

2627
func NeedGenOrderedMap(md protoreflect.MessageDescriptor, lang Language) bool {

test/csharp-tableau-loader/Program.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,27 @@ static void Main(string[] args)
2626
{
2727
Console.WriteLine($"HeroConf: {heroConf.Data()}");
2828
Console.WriteLine($"HeroConf Load duration: {heroConf.GetStats().Duration.TotalMilliseconds} ms");
29+
// Traverse top-level OrderedMap (HeroOrderedMap)
30+
var heroOrderedMap = heroConf.GetOrderedMap();
31+
if (heroOrderedMap != null)
32+
{
33+
Console.WriteLine("Hero OrderedMap:");
34+
foreach (var heroPair in heroOrderedMap)
35+
{
36+
Console.WriteLine($"Hero: {heroPair.Key}");
37+
Console.WriteLine($" - Hero Data: {heroPair.Value.Item2}");
38+
// Traverse nested Attr OrderedMap
39+
var attrOrderedMap = heroPair.Value.Item1;
40+
if (attrOrderedMap != null && attrOrderedMap.Count > 0)
41+
{
42+
Console.WriteLine(" Attributes:");
43+
foreach (var attrPair in attrOrderedMap)
44+
{
45+
Console.WriteLine($" - {attrPair.Key}: {attrPair.Value}");
46+
}
47+
}
48+
}
49+
}
2950
}
3051

3152
var itemConf = hub.Get<Tableau.ItemConf>();

test/csharp-tableau-loader/tableau/HeroConf.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@ namespace Tableau
1212
{
1313
public class HeroConf : Messager, IMessagerName
1414
{
15+
// OrderedMap types.
16+
public class Hero_Attr_OrderedMap : SortedDictionary<string, Protoconf.HeroConf.Types.Hero.Types.Attr> { }
17+
18+
public class Hero_OrderedMapValue : Tuple<Hero_Attr_OrderedMap, Protoconf.HeroConf.Types.Hero?>
19+
{
20+
public Hero_OrderedMapValue(Hero_Attr_OrderedMap item1, Protoconf.HeroConf.Types.Hero? item2) : base(item1, item2) { }
21+
}
22+
public class Hero_OrderedMap : SortedDictionary<string, Hero_OrderedMapValue> { }
23+
24+
private Hero_OrderedMap OrderedMap = new Hero_OrderedMap();
25+
1526
private Protoconf.HeroConf Data_ = new Protoconf.HeroConf();
1627

1728
public static string Name() => Protoconf.HeroConf.Descriptor.Name;
@@ -28,6 +39,22 @@ public override bool Load(string dir, Format fmt, LoadOptions? options = null)
2839

2940
public ref readonly Protoconf.HeroConf Data() => ref Data_;
3041

42+
protected override bool ProcessAfterLoad()
43+
{
44+
// OrderedMap init.
45+
OrderedMap.Clear();
46+
foreach (var (key1, value1) in Data_.HeroMap)
47+
{
48+
var ordered_map1 = new Hero_Attr_OrderedMap();
49+
foreach (var (key2, value2) in value1.AttrMap)
50+
{
51+
ordered_map1[key2] = value2;
52+
}
53+
OrderedMap[key1] = new Hero_OrderedMapValue(ordered_map1, value1);
54+
}
55+
return true;
56+
}
57+
3158
public Protoconf.HeroConf.Types.Hero? Get1(string name)
3259
{
3360
if (Data_.HeroMap.TryGetValue(name, out var val))
@@ -46,6 +73,21 @@ public override bool Load(string dir, Format fmt, LoadOptions? options = null)
4673
}
4774
return null;
4875
}
76+
77+
// OrderedMap accessors.
78+
public ref readonly Hero_OrderedMap GetOrderedMap()
79+
{
80+
return ref OrderedMap;
81+
}
82+
83+
public Hero_Attr_OrderedMap? GetOrderedMap1(string name)
84+
{
85+
if (OrderedMap.TryGetValue(name, out var value))
86+
{
87+
return value.Item1;
88+
}
89+
return null;
90+
}
4991
}
5092

5193
public class HeroBaseConf : Messager, IMessagerName

test/csharp-tableau-loader/tableau/ItemConf.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ namespace Tableau
1212
{
1313
public class ItemConf : Messager, IMessagerName
1414
{
15+
// OrderedMap types.
16+
public class Item_OrderedMap : SortedDictionary<uint, Protoconf.ItemConf.Types.Item> { }
17+
18+
private Item_OrderedMap OrderedMap = new Item_OrderedMap();
19+
1520
private Protoconf.ItemConf Data_ = new Protoconf.ItemConf();
1621

1722
public static string Name() => Protoconf.ItemConf.Descriptor.Name;
@@ -28,6 +33,17 @@ public override bool Load(string dir, Format fmt, LoadOptions? options = null)
2833

2934
public ref readonly Protoconf.ItemConf Data() => ref Data_;
3035

36+
protected override bool ProcessAfterLoad()
37+
{
38+
// OrderedMap init.
39+
OrderedMap.Clear();
40+
foreach (var (key1, value1) in Data_.ItemMap)
41+
{
42+
OrderedMap[key1] = value1;
43+
}
44+
return true;
45+
}
46+
3147
public Protoconf.ItemConf.Types.Item? Get1(uint id)
3248
{
3349
if (Data_.ItemMap.TryGetValue(id, out var val))
@@ -36,6 +52,12 @@ public override bool Load(string dir, Format fmt, LoadOptions? options = null)
3652
}
3753
return null;
3854
}
55+
56+
// OrderedMap accessors.
57+
public ref readonly Item_OrderedMap GetOrderedMap()
58+
{
59+
return ref OrderedMap;
60+
}
3961
}
4062

4163

0 commit comments

Comments
 (0)