1
+ using FFXIVClientStructs . InteropGenerator ;
2
+ using FFXIVClientStructs . InteropSourceGenerators . Extensions ;
3
+ using FFXIVClientStructs . InteropSourceGenerators . Models ;
4
+ using LanguageExt ;
5
+ using Microsoft . CodeAnalysis ;
6
+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
7
+ using static LanguageExt . Prelude ;
8
+ using static FFXIVClientStructs . InteropSourceGenerators . DiagnosticDescriptors ;
9
+
10
+ namespace FFXIVClientStructs . InteropSourceGenerators ;
11
+
12
+ [ Generator ]
13
+ internal sealed class FixedStringGenerator : IIncrementalGenerator
14
+ {
15
+ private const string AttributeName = "FFXIVClientStructs.Interop.Attributes.FixedStringAttribute" ;
16
+
17
+ public void Initialize ( IncrementalGeneratorInitializationContext context )
18
+ {
19
+ IncrementalValuesProvider < ( Validation < DiagnosticInfo , StructInfo > StructInfo , Validation < DiagnosticInfo , FixedStringInfo > FixedStringInfo ) > structAndFixedStringInfos =
20
+ context . SyntaxProvider
21
+ . ForAttributeWithMetadataName (
22
+ AttributeName ,
23
+ static ( node , _ ) => node is VariableDeclaratorSyntax
24
+ {
25
+ Parent : VariableDeclarationSyntax
26
+ {
27
+ Parent : FieldDeclarationSyntax
28
+ {
29
+ Parent : StructDeclarationSyntax , AttributeLists . Count : > 0
30
+ }
31
+ }
32
+ } ,
33
+ static ( context , _ ) =>
34
+ {
35
+ StructDeclarationSyntax structSyntax = ( StructDeclarationSyntax ) context . TargetNode . Parent ! . Parent ! . Parent ! ;
36
+
37
+ IFieldSymbol fieldSymbol = ( IFieldSymbol ) context . TargetSymbol ;
38
+
39
+ return ( Struct : StructInfo . GetFromSyntax ( structSyntax ) ,
40
+ Info : FixedStringInfo . GetFromRoslyn ( fieldSymbol ) ) ;
41
+ } ) ;
42
+
43
+ // group by struct
44
+ IncrementalValuesProvider < ( Validation < DiagnosticInfo , StructInfo > StructInfo , Validation < DiagnosticInfo , Seq < FixedStringInfo > > FixedStringInfos ) > groupedStructInfoWithFixedStringInfos =
45
+ structAndFixedStringInfos . TupleGroupByValidation ( ) ;
46
+
47
+ // make sure caching is working
48
+ IncrementalValuesProvider < Validation < DiagnosticInfo , StructWithFixedStringInfos > > structWithFixedInfos =
49
+ groupedStructInfoWithFixedStringInfos . Select ( static ( item , _ ) =>
50
+ ( item . StructInfo , item . FixedStringInfos ) . Apply ( static ( si , fsi ) =>
51
+ new StructWithFixedStringInfos ( si , fsi ) )
52
+ ) ;
53
+
54
+ context . RegisterSourceOutput ( structWithFixedInfos , ( sourceContext , item ) =>
55
+ {
56
+ item . Match (
57
+ Fail : diagnosticInfos =>
58
+ {
59
+ diagnosticInfos . Iter ( dInfo => sourceContext . ReportDiagnostic ( dInfo . ToDiagnostic ( ) ) ) ;
60
+ } ,
61
+ Succ : structWithFixedInfo =>
62
+ {
63
+ sourceContext . AddSource ( structWithFixedInfo . GetFileName ( ) , structWithFixedInfo . RenderSource ( ) ) ;
64
+ } ) ;
65
+ } ) ;
66
+ }
67
+
68
+ internal sealed record FixedStringInfo ( string FieldName , int MaxLength , string PropertyName )
69
+ {
70
+ public static Validation < DiagnosticInfo , FixedStringInfo > GetFromRoslyn ( IFieldSymbol fieldSymbol )
71
+ {
72
+ Validation < DiagnosticInfo , IFieldSymbol > validSymbol =
73
+ ( fieldSymbol . IsFixedSizeBuffer
74
+ ? Success < DiagnosticInfo , IFieldSymbol > ( fieldSymbol )
75
+ : Fail < DiagnosticInfo , IFieldSymbol > (
76
+ DiagnosticInfo . Create ( FixedSizedAttributeOnInvalidField ,
77
+ fieldSymbol ) ) )
78
+ . Bind ( symbol =>
79
+ {
80
+ IPointerTypeSymbol pointerType = ( symbol . Type as IPointerTypeSymbol ) ! ; // we know its a pointer
81
+ ITypeSymbol pointedToType = pointerType . PointedAtType ;
82
+ if ( pointedToType . SpecialType != SpecialType . System_Byte )
83
+ return Fail < DiagnosticInfo , IFieldSymbol > (
84
+ DiagnosticInfo . Create ( FixedSizedAttributeOnInvalidField ,
85
+ fieldSymbol ) ) ;
86
+
87
+ return Success < DiagnosticInfo , IFieldSymbol > ( fieldSymbol ) ;
88
+ } ) ;
89
+ Option < AttributeData > attribute = fieldSymbol . GetFirstAttributeDataByTypeName ( AttributeName ) ;
90
+
91
+ Validation < DiagnosticInfo , string > validPropertyName = attribute . GetValidAttributeArgument < string > ( "PropertyName" , 0 , AttributeName , fieldSymbol ) ;
92
+
93
+ return ( validSymbol , validPropertyName ) . Apply ( ( symbol , propertyName ) =>
94
+ new FixedStringInfo ( symbol . Name , symbol . FixedSize , string . IsNullOrEmpty ( propertyName ) ? $ "{ symbol . Name } String" : propertyName ) ) ;
95
+ }
96
+
97
+ public void RenderFixedString ( IndentedStringBuilder builder )
98
+ {
99
+ builder . AppendLine ( $ "public string { PropertyName } {{ get {{ fixed (byte* p = { FieldName } ) {{ var str = Encoding.UTF8.GetString(p, { MaxLength } ); var nullIdx = str.IndexOf('\0 '); return nullIdx >= 0 ? str[..nullIdx] : str; }} }} }}") ;
100
+ }
101
+ }
102
+
103
+ private sealed record StructWithFixedStringInfos ( StructInfo StructInfo , Seq < FixedStringInfo > FixedStringInfos )
104
+ {
105
+ public string RenderSource ( )
106
+ {
107
+ IndentedStringBuilder builder = new ( ) ;
108
+
109
+ builder . AppendLine ( "using System.Runtime.CompilerServices;" ) ;
110
+ builder . AppendLine ( "using System.Text;" ) ;
111
+
112
+ StructInfo . RenderStart ( builder ) ;
113
+
114
+ foreach ( FixedStringInfo fsi in FixedStringInfos )
115
+ fsi . RenderFixedString ( builder ) ;
116
+
117
+ StructInfo . RenderEnd ( builder ) ;
118
+
119
+ return builder . ToString ( ) ;
120
+ }
121
+
122
+ public string GetFileName ( )
123
+ {
124
+ return $ "{ StructInfo . Namespace } .{ StructInfo . Name } .FixedStrings.g.cs";
125
+ }
126
+ }
127
+ }
0 commit comments