-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathCodeBuilder.cs
More file actions
244 lines (217 loc) · 9.21 KB
/
Copy pathCodeBuilder.cs
File metadata and controls
244 lines (217 loc) · 9.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
namespace GDScriptInterface.SourceGenerator;
/// <summary>
/// Provides methods for generating source code based on interface mappings.
/// This class is responsible for creating method and property fields for Godot's GDScript interface bindings.
/// </summary>
internal static class CodeBuilder
{
/// <summary>
/// Generates the source code for an interface mapping and adds it to the source production context.
/// </summary>
/// <param name="context">The source production context used to add the generated source code.</param>
/// <param name="classSymbol">The named type symbol representing the class containing the interface mapping.</param>
/// <param name="iface">The named type symbol representing the target interface.</param>
/// <param name="propInfo">The resolved GDScript property information.</param>
public static void GenerateInterfaceMapping(
SourceProductionContext context,
INamedTypeSymbol classSymbol,
INamedTypeSymbol iface,
Dictionary<ITypeSymbol, GDScriptPropertyInfo> propInfo
)
{
var ns = iface.ContainingNamespace.IsGlobalNamespace
? "" // Use an empty namespace if the interface belongs to the global namespace.
: $"namespace {iface.ContainingNamespace};"; // Define the namespace based on the interface.
var className = classSymbol.Name; // Name of the class containing the interface mapping.
var source = new StringBuilder();
source.AppendLine("// <auto-generated />"); // Marker for auto-generated code.
source.AppendLine();
source.AppendLine($"using Godot;"); // Add required namespaces.
source.AppendLine($"using GDScriptInterfaceChecker;");
source.AppendLine(ns); // Add the namespace declaration.
source.AppendLine();
source.AppendLine($"public static partial class {className}"); // Declare the partial class.
source.AppendLine("{");
// Generate method fields for each ordinary method in the interface.
foreach (var method in iface.GetMembers().OfType<IMethodSymbol>())
{
if (method.MethodKind != MethodKind.Ordinary)
continue; // Skip non-ordinary methods (e.g., constructors).
source.AppendLine(GenerateMethodField(method, propInfo)); // Generate the method field.
}
// Generate getter and setter fields for each property in the interface.
foreach (var prop in iface.GetMembers().OfType<IPropertySymbol>())
{
source.AppendLine(GeneratePropertyGetter(prop, propInfo)); // Generate the property getter.
if (!prop.IsReadOnly)
source.AppendLine(GeneratePropertySetter(prop, propInfo)); // Generate the property setter if not read-only.
}
source.AppendLine("}");
context.AddSource(
$"{className}.g.cs", // File name for the generated source code.
source.ToString() // Generated source code as a string.
);
}
/// <summary>
/// Generates the source code for a property getter.
/// </summary>
/// <param name="prop">The property symbol representing the target property.</param>
/// <param name="propInfo">The resolved GDScript property information.</param>
/// <returns>A string containing the generated source code for the property getter.</returns>
private static string GeneratePropertyGetter(
IPropertySymbol prop,
Dictionary<ITypeSymbol, GDScriptPropertyInfo> propInfo
)
{
var fieldName = prop.Name.ToSnakeCase(); // Convert property name to snake_case format.
var retInfo = propInfo[prop.Type]; // Retrieve return type information from the type map.
return $$"""
public static readonly GDScriptMethodInfo {{prop.Name}}Getter =
new GDScriptMethodInfo(
Name: "@{{fieldName}}_getter",
Args: [],
DefaultArgs: [],
Flags: MethodFlags.Default,
Return:
{{Indent(GeneratePropertyInfo("", retInfo), 4)}}
);
""";
}
/// <summary>
/// Generates the source code for a property setter.
/// </summary>
/// <param name="prop">The property symbol representing the target property.</param>
/// <param name="propInfo">The resolved GDScript property information.</param>
/// <returns>A string containing the generated source code for the property setter.</returns>
private static string GeneratePropertySetter(
IPropertySymbol prop,
Dictionary<ITypeSymbol, GDScriptPropertyInfo> propInfo
)
{
var fieldName = prop.Name.ToSnakeCase(); // Convert property name to snake_case format.
var valueInfo = propInfo[prop.Type]; // Retrieve parameter type information from the type map.
var args = Indent(
GeneratePropertyInfo(
"value",
valueInfo
),
4
); // Generate indented argument information.
return $$"""
public static readonly GDScriptMethodInfo {{prop.Name}}Setter =
new GDScriptMethodInfo(
Name: "@{{fieldName}}_setter",
Args: new[]
{
{{args}}
},
DefaultArgs: [],
Flags: MethodFlags.Default,
Return:
{{Indent(GenerateNilReturn(), 4)}}
);
""";
}
/// <summary>
/// Generates the source code for a method field.
/// </summary>
/// <param name="method">The method symbol representing the target method.</param>
/// <param name="propInfo">The resolved GDScript property information.</param>
/// <returns>A string containing the generated source code for the method field.</returns>
private static string GenerateMethodField(
IMethodSymbol method,
Dictionary<ITypeSymbol, GDScriptPropertyInfo> propInfo
)
{
var fieldName = method.Name.ToSnakeCase(); // Convert method name to snake_case format.
var args = method.Parameters.Length == 0
? "" // No arguments if the method has no parameters.
: Indent(
string.Join(",\n",
method.Parameters.Select(p =>
{
var info = propInfo[p.Type];
return GeneratePropertyInfo(
p.Name.ToSnakeCase(),
info
);
})
),
4
); // Generate indented argument information.
var retInfo = propInfo[method.ReturnType]; // Retrieve return type information from the type map.
return $$"""
public static readonly GDScriptMethodInfo {{method.Name}} =
new GDScriptMethodInfo(
Name: "{{fieldName}}",
Args: new[]
{
{{args}}
},
DefaultArgs: [],
Flags: MethodFlags.Default,
Return:
{{Indent(GeneratePropertyInfo("", retInfo), 4)}}
);
""";
}
/// <summary>
/// Generates the source code for a GDScript property info object.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="propInfo">The resolved GDScript property information.</param>
/// <returns>A string containing the generated source code for the GDScript property info.</returns>
private static string GeneratePropertyInfo(
string name,
GDScriptPropertyInfo propInfo
)
{
return $$"""
new GDScriptPropertyInfo(
Name: "{{name}}",
ClassName: "{{propInfo.ClassName}}",
Type: {{VariantTypeExtensions.ToString(propInfo.VariantType)}},
Hint: {{PropertyHintExtensions.ToString(propInfo.Hint)}},
HintString: "{{propInfo.HintString}}",
Usage: {{PropertyUsageFlagsExtensions.ToString(propInfo.Usage)}}
)
""";
}
/// <summary>
/// Indents the given text by the specified level.
/// </summary>
/// <param name="text">The text to be indented.</param>
/// <param name="level">The indentation level (in multiples of 4 spaces).</param>
/// <returns>The indented text.</returns>
private static string Indent(string text, int level)
{
var pad = new string(' ', level * 4); // Create padding based on the indentation level.
return string.Join(
"\n",
text.Split('\n').Select(l => l.Length == 0 ? l : pad + l) // Add padding to each line.
);
}
/// <summary>
/// Generates the source code for a GDScript property info object representing void return type.
/// This method is used to create a default property info object with no name, class name, or type hint,
/// and with the Variant type set to <c>Variant.Type.Nil</c>.
/// </summary>
/// <returns>A string containing the generated source code for the void return type property info.</returns>
private static string GenerateNilReturn()
{
return """
new GDScriptPropertyInfo(
Name: "",
ClassName: "",
Type: Variant.Type.Nil,
Hint: PropertyHint.None,
HintString: "",
Usage: PropertyUsageFlags.Default
)
""";
}
}