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