BadScript 2
Loading...
Searching...
No Matches
BadInteropApiModelBuilder.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Collections.Immutable;
4using System.Linq;
5
7
8using Microsoft.CodeAnalysis;
9
11
13{
14 private static readonly DiagnosticDescriptor s_CanNotConvertTypeDiagnostic = new DiagnosticDescriptor(
15 "BAS0001",
16 "Can not convert type",
17 "Can not convert type {0}",
18 "BadScript2.Interop.Generator",
19 DiagnosticSeverity.Error,
20 true
21 );
22
23 private static readonly DiagnosticDescriptor s_CanNotStringifyDefaultValue = new DiagnosticDescriptor(
24 "BAS0002",
25 "Can not stringify default value",
26 "Can not stringify default value {0}",
27 "BadScript2.Interop.Generator",
28 DiagnosticSeverity.Error,
29 true
30 );
31
32 private readonly List<Diagnostic> m_Diagnostics = new List<Diagnostic>();
33
34 private void AddDiagnostic(Diagnostic diagnostic)
35 {
36 m_Diagnostics.Add(diagnostic);
37 }
38
39 private IEnumerable<IMethodSymbol> FindMethods(INamedTypeSymbol api)
40 {
41 IEnumerable<IMethodSymbol> methods = api.GetMembers().Where(x => x is IMethodSymbol).Cast<IMethodSymbol>();
42 foreach (IMethodSymbol method in methods)
43 {
44 AttributeData? attribute = method.GetInteropMethodAttribute();
45 if (attribute != null)
46 {
47 yield return method;
48 }
49 }
50 }
51
52 public ApiModel GenerateModel(INamedTypeSymbol api)
53 {
54 IEnumerable<IMethodSymbol> methods = FindMethods(api);
55 AttributeData? apiAttribute = api.GetInteropApiAttribute();
56 if (apiAttribute == null)
57 {
58 throw new Exception("BadInteropApiAttribute not found");
59 }
60
61 string apiName = api.Name;
62 bool constructorPrivate = false;
63 if (apiAttribute.ConstructorArguments.Length > 0)
64 {
65 apiName = apiAttribute.ConstructorArguments[0].Value?.ToString() ?? apiName;
66 bool? priv = apiAttribute.ConstructorArguments[1].Value as bool?;
67 constructorPrivate = apiAttribute.ConstructorArguments.Length > 1 && priv != null && priv.Value;
68 }
69
70 MethodModel[] methodModels = GenerateMethodModels(methods).ToArray();
71 Diagnostic[] diagnostics = Array.Empty<Diagnostic>();
72 if (m_Diagnostics.Count != 0)
73 {
74 diagnostics = m_Diagnostics.ToArray();
75 m_Diagnostics.Clear();
76 }
77
78 return new ApiModel(
79 api.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted))!,
80 api.Name,
81 methodModels,
82 apiName,
83 constructorPrivate,
84 diagnostics
85 );
86 }
87
88 private string EscapeDescription(string str)
89 {
90 return str.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n");
91 }
92
93 private IEnumerable<ParameterModel> GenerateParameterModel(IMethodSymbol method)
94 {
95 foreach (IParameterSymbol symbol in method.Parameters.Where(x => x.Ordinal >= 0).OrderBy(x => x.Ordinal))
96 {
97 //if parameter is first parameter and its of type BadScript2.Runtime.BadExecutionContext
98 if (symbol.Ordinal == 0 && symbol.Type.ToDisplayString() == "BadScript2.Runtime.BadExecutionContext")
99 {
100 yield return new ParameterModel(true);
101 }
102 else
103 {
104 AttributeData? attribute = symbol.GetParameterAttribute();
105 string? name = symbol.Name;
106 string? description = null;
107 bool isNullable = symbol.NullableAnnotation == NullableAnnotation.Annotated;
108 string type = ConvertType(symbol.Type, true, symbol);
109
110 if (attribute != null)
111 {
112 ImmutableArray<TypedConstant> cargs = attribute.ConstructorArguments;
113 name = cargs.Length > 0 ? cargs[0].Value?.ToString() ?? name : name;
114 description = cargs.Length > 1 ? cargs[1].Value?.ToString() : null;
115 if (description != null)
116 {
117 description = EscapeDescription(description);
118 }
119
120 type = cargs.Length > 2 ? cargs[2].Value?.ToString() ?? type : type;
121 }
122
123 bool hasDefaultValue = symbol.HasExplicitDefaultValue;
124 string? defaultValue = null;
125 if (hasDefaultValue)
126 {
127 defaultValue = StringifyDefaultValue(symbol.ExplicitDefaultValue, symbol);
128 }
129
130 bool isRestArgs = symbol.IsParams;
131
132 yield return new ParameterModel(false, hasDefaultValue, defaultValue, name, description, type, symbol.Type.ToDisplayString(), isNullable, isRestArgs);
133 }
134 }
135 }
136
137 private string StringifyDefaultValue(object? obj, ISymbol symbol)
138 {
139 switch (obj)
140 {
141 case string str:
142 return $"\"{str}\"";
143 case char c:
144 return $"'{c}'";
145 case bool b:
146 return b.ToString().ToLower();
147 case float f:
148 return $"{f}f";
149 case double d:
150 return $"{d}d";
151 case decimal m:
152 return $"{m}m";
153 case int i:
154 return i.ToString();
155 case long l:
156 return $"{l}L";
157 case uint u:
158 return $"{u}u";
159 case ulong ul:
160 return $"{ul}ul";
161 case short s:
162 return $"{s}s";
163 case ushort us:
164 return $"{us}us";
165 case byte by:
166 return $"{by}b";
167 case sbyte sb:
168 return $"{sb}sb";
169 case Enum e:
170 return $"{e.GetType().Name}.{e}";
171 case Type t:
172 return $"typeof({t.Name})";
173 case null:
174 return "null";
175 default:
176 {
177 AddDiagnostic(symbol.CreateDiagnostic(s_CanNotStringifyDefaultValue, obj));
178
179 return "null";
180 }
181 }
182 }
183
184 private IEnumerable<MethodModel> GenerateMethodModels(IEnumerable<IMethodSymbol> symbols)
185 {
186 foreach (IMethodSymbol symbol in symbols)
187 {
188 AttributeData? attribute = symbol.GetInteropMethodAttribute();
189
190 if (attribute == null)
191 {
192 continue;
193 }
194
195 ImmutableArray<TypedConstant> cargs = attribute.ConstructorArguments;
196
197 //The api name, if its not provided, use the method name
198 string name = cargs.Length > 0 ? cargs[0].Value?.ToString() ?? symbol.Name : symbol.Name;
199
200 //The description, if its not provided, use null
201 string description = EscapeDescription(cargs.Length > 1 ? cargs[1].Value?.ToString() ?? string.Empty : string.Empty);
202
203 //Check if the symbol is a void return
204 bool isVoidReturn = symbol.ReturnsVoid;
205 AttributeData? returnAttribute = symbol.GetReturnTypeAttribute();
206 string returnDescription = string.Empty;
207 bool allowNativeReturn = false;
208 if (returnAttribute != null)
209 {
210 if (returnAttribute.ConstructorArguments.Length > 0)
211 {
212 returnDescription = EscapeDescription(returnAttribute.ConstructorArguments[0].Value?.ToString() ?? string.Empty);
213 }
214
215 if (returnAttribute.ConstructorArguments.Length > 1)
216 {
217 allowNativeReturn = (bool)returnAttribute.ConstructorArguments[1].Value!;
218 }
219 }
220
221
222 //The return type, if its not provided, use the symbol's return type
223 string returnType = isVoidReturn ? "any" : ConvertType(symbol.ReturnType, allowNativeReturn, symbol);
224
225
226 MethodModel model = new MethodModel(
227 symbol.Name,
228 name,
229 returnType,
230 description,
231 GenerateParameterModel(symbol).ToArray(),
232 isVoidReturn,
233 returnDescription,
234 allowNativeReturn
235 );
236
237 yield return model;
238 }
239 }
240
241 private string ConvertType(ITypeSymbol type, bool allowAny, ISymbol sourceSymbol)
242 {
243 if (type.NullableAnnotation == NullableAnnotation.Annotated)
244 {
245 //Unwrap nullable value types
246 type = type.WithNullableAnnotation(NullableAnnotation.NotAnnotated);
247 }
248
249 //If type is string, return "string"
250 if (type.SpecialType == SpecialType.System_String)
251 {
252 return "string";
253 }
254
255 //if type is bool return "bool"
256 if (type.SpecialType == SpecialType.System_Boolean)
257 {
258 return "bool";
259 }
260
261 //if type is numeric type return "num"
262 if (type.SpecialType == SpecialType.System_Byte ||
263 type.SpecialType == SpecialType.System_SByte ||
264 type.SpecialType == SpecialType.System_Int16 ||
265 type.SpecialType == SpecialType.System_UInt16 ||
266 type.SpecialType == SpecialType.System_Int32 ||
267 type.SpecialType == SpecialType.System_UInt32 ||
268 type.SpecialType == SpecialType.System_Int64 ||
269 type.SpecialType == SpecialType.System_UInt64 ||
270 type.SpecialType == SpecialType.System_Single ||
271 type.SpecialType == SpecialType.System_Double ||
272 type.SpecialType == SpecialType.System_Decimal)
273 {
274 return "num";
275 }
276
277 //if type is array or list or ilist, return "array"
278 if (type is IArrayTypeSymbol ||
279 type.AllInterfaces.Any(x => x.ToDisplayString() == "System.Collections.Generic.IList<T>") ||
280 type.AllInterfaces.Any(x => x.ToDisplayString() == "System.Collections.IList<T>"))
281 {
282 return "Array";
283 }
284
285 //if type is BadObject, return "any"
286 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.BadObject")
287 {
288 return "any";
289 }
290
291 if (type.ToDisplayString() == "BadScript2.Interop.Common.Task.BadTask")
292 {
293 return "Task";
294 }
295
296 //If type is BadTable, return "Table"
297 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.BadTable")
298 {
299 return "Table";
300 }
301
302 //If type is BadArray return "Array"
303 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.BadArray")
304 {
305 return "Array";
306 }
307
308 //If type is BadFunction return "Function"
309 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Functions.BadFunction")
310 {
311 return "Function";
312 }
313
314 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Types.BadClassPrototype")
315 {
316 return "Type";
317 }
318
319 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Error.BadRuntimeError")
320 {
321 return "Error";
322 }
323
324 //If Type is IBadBoolean or BadBoolean return "bool"
325 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.IBadBoolean" || type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.BadBoolean")
326 {
327 return "bool";
328 }
329
330 //If Type is IBadNumber or BadNumber return "num"
331 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.IBadNumber" || type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.BadNumber")
332 {
333 return "num";
334 }
335
336 //If Type is IBadString or BadString return "string"
337 if (type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.IBadString" || type.ToDisplayString() == "BadScript2.Runtime.Objects.Native.BadString")
338 {
339 return "string";
340 }
341
342 //If Type is BadScope return "Scope"
343 if (type.ToDisplayString() == "BadScript2.Runtime.BadScope")
344 {
345 return "Scope";
346 }
347
348 if (allowAny)
349 {
350 return "any";
351 }
352
353 AddDiagnostic(sourceSymbol.CreateDiagnostic(s_CanNotConvertTypeDiagnostic, type.ToDisplayString()));
354
355 return "any";
356 }
357}
IEnumerable< ParameterModel > GenerateParameterModel(IMethodSymbol method)
IEnumerable< IMethodSymbol > FindMethods(INamedTypeSymbol api)
IEnumerable< MethodModel > GenerateMethodModels(IEnumerable< IMethodSymbol > symbols)
string ConvertType(ITypeSymbol type, bool allowAny, ISymbol sourceSymbol)