mirror of
https://github.com/Stone-Red-Code/YesNt-Interpreter.git
synced 2026-09-04 00:56:31 +02:00
574 lines
23 KiB
C#
574 lines
23 KiB
C#
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace YesNt.Interpreter.Generator;
|
|
|
|
[Generator]
|
|
public sealed class StatementRegistryGenerator : IIncrementalGenerator
|
|
{
|
|
private const string StatementAttributeName = "YesNt.Interpreter.Attributes.StatementAttribute";
|
|
private const string StaticStatementAttributeName = "YesNt.Interpreter.Attributes.StaticStatementAttribute";
|
|
|
|
public void Initialize(IncrementalGeneratorInitializationContext context)
|
|
{
|
|
context.RegisterSourceOutput(
|
|
context.CompilationProvider,
|
|
Execute);
|
|
}
|
|
|
|
private static void Execute(SourceProductionContext context, Compilation compilation)
|
|
{
|
|
List<MethodRegistration> statementMethods = [];
|
|
List<MethodRegistration> staticStatementMethods = [];
|
|
|
|
CollectMethods(compilation.Assembly.GlobalNamespace, statementMethods, staticStatementMethods);
|
|
|
|
INamedTypeSymbol? statementAttrSymbol = compilation.GetTypeByMetadataName(StatementAttributeName);
|
|
INamedTypeSymbol? staticStatementAttrSymbol = compilation.GetTypeByMetadataName(StaticStatementAttributeName);
|
|
|
|
string infoSource = GenerateInformationClassesSource(statementAttrSymbol, staticStatementAttrSymbol, compilation);
|
|
context.AddSource("GeneratedStatementInformation.g.cs", infoSource);
|
|
|
|
string registrySource = GenerateRegistrySource(statementMethods, staticStatementMethods);
|
|
context.AddSource("GeneratedStatementRegistry.g.cs", registrySource);
|
|
}
|
|
|
|
private static void CollectMethods(
|
|
INamespaceSymbol namespaceSymbol,
|
|
List<MethodRegistration> statementMethods,
|
|
List<MethodRegistration> staticStatementMethods)
|
|
{
|
|
foreach (INamespaceSymbol childNamespace in namespaceSymbol.GetNamespaceMembers())
|
|
{
|
|
CollectMethods(childNamespace, statementMethods, staticStatementMethods);
|
|
}
|
|
|
|
foreach (INamedTypeSymbol type in namespaceSymbol.GetTypeMembers())
|
|
{
|
|
CollectMethods(type, statementMethods, staticStatementMethods);
|
|
}
|
|
}
|
|
|
|
private static void CollectMethods(
|
|
INamedTypeSymbol typeSymbol,
|
|
List<MethodRegistration> statementMethods,
|
|
List<MethodRegistration> staticStatementMethods)
|
|
{
|
|
foreach (ISymbol member in typeSymbol.GetMembers())
|
|
{
|
|
if (member is IMethodSymbol method && method.MethodKind == MethodKind.Ordinary)
|
|
{
|
|
foreach (AttributeData attribute in method.GetAttributes())
|
|
{
|
|
string? attributeName = attribute.AttributeClass?.ToDisplayString();
|
|
if (attributeName == StatementAttributeName)
|
|
{
|
|
statementMethods.Add(new MethodRegistration(typeSymbol, method, attribute));
|
|
}
|
|
else if (attributeName == StaticStatementAttributeName)
|
|
{
|
|
staticStatementMethods.Add(new MethodRegistration(typeSymbol, method, attribute));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (INamedTypeSymbol nestedType in typeSymbol.GetTypeMembers())
|
|
{
|
|
CollectMethods(nestedType, statementMethods, staticStatementMethods);
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Information class generation
|
|
// -------------------------------------------------------------------------
|
|
|
|
private static string GenerateInformationClassesSource(
|
|
INamedTypeSymbol? statementAttrSymbol,
|
|
INamedTypeSymbol? staticStatementAttrSymbol,
|
|
Compilation compilation)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
_ = sb.AppendLine("// <auto-generated />");
|
|
_ = sb.AppendLine("#nullable enable");
|
|
_ = sb.AppendLine();
|
|
_ = sb.AppendLine("namespace YesNt.Interpreter.Runtime;");
|
|
_ = sb.AppendLine();
|
|
|
|
if (statementAttrSymbol is not null)
|
|
{
|
|
EmitInformationClass(sb, statementAttrSymbol, "StatementInformation", compilation);
|
|
}
|
|
|
|
if (staticStatementAttrSymbol is not null)
|
|
{
|
|
EmitInformationClass(sb, staticStatementAttrSymbol, "StaticStatementInformation", compilation);
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static void EmitInformationClass(
|
|
StringBuilder sb,
|
|
INamedTypeSymbol attributeSymbol,
|
|
string className,
|
|
Compilation compilation)
|
|
{
|
|
List<IMethodSymbol> ctors = attributeSymbol.Constructors
|
|
.Where(c => !c.IsImplicitlyDeclared)
|
|
.OrderByDescending(c => c.Parameters.Length)
|
|
.ToList();
|
|
|
|
// All parameters across all ctors that are covered by a ctor (get-only properties).
|
|
HashSet<string> ctorParamNames = new HashSet<string>(
|
|
ctors.SelectMany(c => c.Parameters).Select(p => p.Name),
|
|
StringComparer.OrdinalIgnoreCase);
|
|
|
|
// Settable properties NOT covered by any ctor parameter.
|
|
List<IPropertySymbol> settableProps = attributeSymbol
|
|
.GetMembers()
|
|
.OfType<IPropertySymbol>()
|
|
.Where(p => !p.IsStatic
|
|
&& p.DeclaredAccessibility == Accessibility.Public
|
|
&& !ctorParamNames.Contains(p.Name))
|
|
.ToList();
|
|
|
|
// All properties ever assigned by any ctor, deduplicated by name.
|
|
// We need a get-only property for each of them.
|
|
var ctorProps = ctors
|
|
.SelectMany(c => c.Parameters)
|
|
.Select(p => {
|
|
string propName = ResolvePropertyName(attributeSymbol, p);
|
|
IPropertySymbol? propSymbol = attributeSymbol
|
|
.GetMembers()
|
|
.OfType<IPropertySymbol>()
|
|
.FirstOrDefault(sym => string.Equals(sym.Name, propName, StringComparison.OrdinalIgnoreCase));
|
|
return (PropName: propName, PropType: p.Type, PropSymbol: propSymbol);
|
|
})
|
|
.GroupBy(x => x.PropName, StringComparer.OrdinalIgnoreCase)
|
|
.Select(g => g.First())
|
|
.ToList();
|
|
|
|
// ---- class declaration ----
|
|
_ = sb.Append(GetFormattedComment(attributeSymbol, attributeSymbol, className, $"Information about the {className} class.", ""));
|
|
_ = sb.AppendLine($"public sealed class {className}");
|
|
_ = sb.AppendLine("{");
|
|
|
|
// ---- one constructor per attribute constructor ----
|
|
foreach (IMethodSymbol ctor in ctors)
|
|
{
|
|
if (ctor.Parameters.Length == 0)
|
|
{
|
|
_ = sb.Append(GetFormattedComment(ctor, attributeSymbol, className, $"Initializes a new instance of the <see cref=\"{className}\"/> class.", " "));
|
|
_ = sb.AppendLine($" public {className}() {{ }}");
|
|
_ = sb.AppendLine();
|
|
continue;
|
|
}
|
|
|
|
string ctorParams = string.Join(", ",
|
|
ctor.Parameters.Select(p => $"{GlobalType(p.Type)} {p.Name}"));
|
|
|
|
_ = sb.Append(GetFormattedComment(ctor, attributeSymbol, className, $"Initializes a new instance of the <see cref=\"{className}\"/> class.", " "));
|
|
_ = sb.AppendLine($" public {className}({ctorParams})");
|
|
_ = sb.AppendLine(" {");
|
|
|
|
foreach (IParameterSymbol p in ctor.Parameters)
|
|
{
|
|
string propName = ResolvePropertyName(attributeSymbol, p);
|
|
_ = sb.AppendLine($" {propName} = {p.Name};");
|
|
}
|
|
|
|
_ = sb.AppendLine(" }");
|
|
_ = sb.AppendLine();
|
|
}
|
|
|
|
// ---- get-only properties sourced from ctor parameters ----
|
|
foreach (var ctorProp in ctorProps)
|
|
{
|
|
if (ctorProp.PropSymbol is not null)
|
|
{
|
|
_ = sb.Append(GetFormattedComment(ctorProp.PropSymbol, attributeSymbol, className, $"Gets the {ctorProp.PropName} property.", " "));
|
|
}
|
|
else
|
|
{
|
|
_ = sb.AppendLine(" /// <summary>");
|
|
_ = sb.AppendLine($" /// Gets the {ctorProp.PropName} property.");
|
|
_ = sb.AppendLine(" /// </summary>");
|
|
}
|
|
_ = sb.AppendLine($" public {GlobalType(ctorProp.PropType)} {ctorProp.PropName} {{ get; }}");
|
|
}
|
|
|
|
// ---- settable properties (named-argument style) ----
|
|
foreach (IPropertySymbol prop in settableProps)
|
|
{
|
|
string defaultClause = GetDefaultClause(prop, compilation);
|
|
_ = sb.Append(GetFormattedComment(prop, attributeSymbol, className, $"Gets or sets the {prop.Name} property.", " "));
|
|
_ = sb.AppendLine($" public {GlobalType(prop.Type)} {prop.Name} {{ get; init; }}{defaultClause}");
|
|
}
|
|
|
|
_ = sb.AppendLine("}");
|
|
_ = sb.AppendLine();
|
|
}
|
|
|
|
private static string GetFormattedComment(
|
|
ISymbol symbol,
|
|
INamedTypeSymbol attributeSymbol,
|
|
string className,
|
|
string fallbackSummary,
|
|
string indent = " ")
|
|
{
|
|
string? xml = symbol.GetDocumentationCommentXml();
|
|
if (!string.IsNullOrEmpty(xml))
|
|
{
|
|
xml = xml!.Replace(attributeSymbol.Name, className);
|
|
}
|
|
|
|
string formatted = FormatXmlComment(xml, indent);
|
|
if (!string.IsNullOrWhiteSpace(formatted))
|
|
{
|
|
return formatted;
|
|
}
|
|
|
|
// Fallback
|
|
var sb = new StringBuilder();
|
|
_ = sb.AppendLine($"{indent}/// <summary>");
|
|
_ = sb.AppendLine($"{indent}/// {fallbackSummary}");
|
|
_ = sb.AppendLine($"{indent}/// </summary>");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string FormatXmlComment(string? xml, string indent)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(xml))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
try
|
|
{
|
|
var element = System.Xml.Linq.XElement.Parse(xml!);
|
|
var sb = new StringBuilder();
|
|
foreach (var child in element.Elements())
|
|
{
|
|
string nodeXml = child.ToString();
|
|
string[] lines = nodeXml.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
|
|
foreach (string line in lines)
|
|
{
|
|
string trimmedLine = line.Trim();
|
|
if (trimmedLine.Length > 0)
|
|
{
|
|
_ = sb.AppendLine($"{indent}/// {trimmedLine}");
|
|
}
|
|
}
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
catch
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
// Finds the attribute property that corresponds to a constructor parameter.
|
|
private static string ResolvePropertyName(INamedTypeSymbol attributeSymbol, IParameterSymbol parameter)
|
|
{
|
|
IPropertySymbol? match = attributeSymbol
|
|
.GetMembers()
|
|
.OfType<IPropertySymbol>()
|
|
.FirstOrDefault(p => string.Equals(p.Name, parameter.Name, StringComparison.OrdinalIgnoreCase));
|
|
|
|
return match?.Name ?? ToPascalCase(parameter.Name);
|
|
}
|
|
|
|
private static string GlobalType(ITypeSymbol type)
|
|
{
|
|
// Primitive aliases don't have a global:: form — emit the C# keyword instead.
|
|
string keyword = type.SpecialType switch
|
|
{
|
|
SpecialType.System_String => "string",
|
|
SpecialType.System_Boolean => "bool",
|
|
SpecialType.System_Byte => "byte",
|
|
SpecialType.System_SByte => "sbyte",
|
|
SpecialType.System_Int16 => "short",
|
|
SpecialType.System_UInt16 => "ushort",
|
|
SpecialType.System_Int32 => "int",
|
|
SpecialType.System_UInt32 => "uint",
|
|
SpecialType.System_Int64 => "long",
|
|
SpecialType.System_UInt64 => "ulong",
|
|
SpecialType.System_Single => "float",
|
|
SpecialType.System_Double => "double",
|
|
SpecialType.System_Decimal => "decimal",
|
|
SpecialType.System_Char => "char",
|
|
SpecialType.System_Object => "object",
|
|
_ => ""
|
|
};
|
|
|
|
if (keyword != "")
|
|
{
|
|
// Preserve nullability annotation (e.g. string?)
|
|
return type.NullableAnnotation == NullableAnnotation.Annotated
|
|
? keyword + "?"
|
|
: keyword;
|
|
}
|
|
|
|
return $"global::{type.ToDisplayString()}";
|
|
}
|
|
|
|
private static string ToPascalCase(string name)
|
|
{
|
|
return string.IsNullOrEmpty(name) ? name : char.ToUpperInvariant(name[0]) + name.Substring(1);
|
|
}
|
|
|
|
private static string GetDefaultClause(
|
|
IPropertySymbol prop,
|
|
Compilation compilation)
|
|
{
|
|
foreach (SyntaxReference syntaxRef in prop.DeclaringSyntaxReferences)
|
|
{
|
|
if (syntaxRef.GetSyntax() is not Microsoft.CodeAnalysis.CSharp.Syntax.PropertyDeclarationSyntax syntax)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (syntax.Initializer?.Value is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SemanticModel model = compilation.GetSemanticModel(syntax.SyntaxTree);
|
|
ExpressionSyntax expr = syntax.Initializer.Value;
|
|
|
|
// 1. Try constant evaluation first (SAFE)
|
|
Optional<object?> constant = model.GetConstantValue(expr);
|
|
if (constant.HasValue)
|
|
{
|
|
return $" = {ToLiteral(constant.Value!, prop.Type)};";
|
|
}
|
|
|
|
// 2. Try symbol resolution (enum fields etc.)
|
|
ISymbol? symbolInfo = model.GetSymbolInfo(expr).Symbol;
|
|
|
|
if (symbolInfo is IFieldSymbol field)
|
|
{
|
|
string typeName = GlobalType(field.ContainingType);
|
|
return $" = {typeName}.{field.Name};";
|
|
}
|
|
|
|
// 3. fallback: raw expression (last resort)
|
|
return $" = {expr};";
|
|
}
|
|
|
|
// fallback defaults
|
|
return prop.Type.IsReferenceType || prop.NullableAnnotation == NullableAnnotation.Annotated
|
|
? " = null!;"
|
|
: prop.Type.SpecialType switch
|
|
{
|
|
SpecialType.System_Boolean => " = false;",
|
|
SpecialType.System_Int32 => " = 0;",
|
|
SpecialType.System_String => " = \"\";",
|
|
_ => ""
|
|
};
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Registry source generation (unchanged from original)
|
|
// -------------------------------------------------------------------------
|
|
|
|
private static string GenerateRegistrySource(
|
|
List<MethodRegistration> statementMethods,
|
|
List<MethodRegistration> staticStatementMethods)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
_ = sb.AppendLine("// <auto-generated />");
|
|
_ = sb.AppendLine("#nullable enable");
|
|
_ = sb.AppendLine("using System;");
|
|
_ = sb.AppendLine("using System.Collections.Generic;");
|
|
_ = sb.AppendLine();
|
|
_ = sb.AppendLine("namespace YesNt.Interpreter.Runtime;");
|
|
_ = sb.AppendLine();
|
|
_ = sb.AppendLine("internal static class GeneratedStatementRegistry");
|
|
_ = sb.AppendLine("{");
|
|
_ = sb.AppendLine(" internal static void Register(");
|
|
_ = sb.AppendLine(" RuntimeInformation runtimeInfo,");
|
|
_ = sb.AppendLine(" out Dictionary<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>> statements,");
|
|
_ = sb.AppendLine(" out List<KeyValuePair<global::YesNt.Interpreter.Runtime.StaticStatementInformation, Action>> staticStatements)");
|
|
_ = sb.AppendLine(" {");
|
|
|
|
List<INamedTypeSymbol> allTypes = statementMethods
|
|
.Concat(staticStatementMethods)
|
|
.Select(x => x.ContainingType)
|
|
.GroupBy(x => x, SymbolEqualityComparer.Default)
|
|
.Select(g => g.First())
|
|
.OrderBy(x => x.ToDisplayString())
|
|
.ToList();
|
|
|
|
Dictionary<INamedTypeSymbol, string> instanceNames = new Dictionary<INamedTypeSymbol, string>(SymbolEqualityComparer.Default);
|
|
int index = 0;
|
|
foreach (INamedTypeSymbol type in allTypes)
|
|
{
|
|
string instanceName = $"instance{index++}";
|
|
instanceNames[type] = instanceName;
|
|
_ = sb.AppendLine($" var {instanceName} = new global::{type.ToDisplayString()}();");
|
|
_ = sb.AppendLine($" {instanceName}.RuntimeInfo = runtimeInfo;");
|
|
}
|
|
|
|
_ = sb.AppendLine(" var statementEntries = new List<KeyValuePair<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>>>();");
|
|
|
|
foreach (MethodRegistration method in statementMethods
|
|
.OrderBy(x => x.ContainingType.ToDisplayString())
|
|
.ThenBy(x => x.Method.Name))
|
|
{
|
|
string instanceName = instanceNames[method.ContainingType];
|
|
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Runtime.StatementInformation", method.Attribute);
|
|
_ = sb.AppendLine($" statementEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));");
|
|
}
|
|
|
|
_ = sb.AppendLine(" var staticEntries = new List<KeyValuePair<global::YesNt.Interpreter.Runtime.StaticStatementInformation, Action>>();");
|
|
|
|
foreach (MethodRegistration method in staticStatementMethods
|
|
.OrderBy(x => x.ContainingType.ToDisplayString())
|
|
.ThenBy(x => x.Method.Name))
|
|
{
|
|
string instanceName = instanceNames[method.ContainingType];
|
|
string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Runtime.StaticStatementInformation", method.Attribute);
|
|
_ = sb.AppendLine($" staticEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));");
|
|
}
|
|
|
|
_ = sb.AppendLine(" statementEntries.Sort((a, b) =>");
|
|
_ = sb.AppendLine(" {");
|
|
_ = sb.AppendLine(" int cmp = a.Key.Priority.CompareTo(b.Key.Priority);");
|
|
_ = sb.AppendLine(" return cmp != 0 ? cmp : b.Key.Name.Length.CompareTo(a.Key.Name.Length);");
|
|
_ = sb.AppendLine(" });");
|
|
_ = sb.AppendLine(" statements = new Dictionary<global::YesNt.Interpreter.Runtime.StatementInformation, Action<string>>();");
|
|
_ = sb.AppendLine(" foreach (var entry in statementEntries)");
|
|
_ = sb.AppendLine(" statements.Add(entry.Key, entry.Value);");
|
|
_ = sb.AppendLine();
|
|
_ = sb.AppendLine(" staticEntries.Sort((a, b) => a.Key.Priority.CompareTo(b.Key.Priority));");
|
|
_ = sb.AppendLine(" staticStatements = staticEntries;");
|
|
_ = sb.AppendLine(" }");
|
|
_ = sb.AppendLine("}");
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static string BuildAttributeCreation(string attributeTypeName, AttributeData attribute)
|
|
{
|
|
string ctorArgs = string.Join(", ",
|
|
attribute.ConstructorArguments.Select(ToLiteral));
|
|
|
|
string creation = $"new {attributeTypeName}({ctorArgs})";
|
|
|
|
if (attribute.NamedArguments.Length == 0)
|
|
{
|
|
return creation;
|
|
}
|
|
|
|
string namedArgs = string.Join(", ",
|
|
attribute.NamedArguments.Select(arg => $"{arg.Key} = {ToLiteral(arg.Value)}"));
|
|
|
|
return $"{creation} {{ {namedArgs} }}";
|
|
}
|
|
|
|
private static string ToLiteral(TypedConstant constant)
|
|
{
|
|
if (constant.IsNull)
|
|
{
|
|
return "null!";
|
|
}
|
|
|
|
if (constant.Type is null)
|
|
{
|
|
return "null!";
|
|
}
|
|
|
|
if (constant.Kind == TypedConstantKind.Enum)
|
|
{
|
|
string enumType = $"global::{constant.Type.ToDisplayString()}";
|
|
object value = constant.Value!;
|
|
return $"({enumType}){Convert.ToInt64(value)}";
|
|
}
|
|
|
|
return constant.Type.SpecialType switch
|
|
{
|
|
SpecialType.System_String => "\"" + EscapeString((string)constant.Value!) + "\"",
|
|
SpecialType.System_Char => "'" + EscapeChar((char)constant.Value!) + "'",
|
|
SpecialType.System_Boolean => (bool)constant.Value! ? "true" : "false",
|
|
SpecialType.System_Int32 => ((int)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
SpecialType.System_Int64 => ((long)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture) + "L",
|
|
SpecialType.System_Single => ((float)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture) + "f",
|
|
SpecialType.System_Double => ((double)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
_ => constant.Value!.ToString() ?? "null!"
|
|
};
|
|
}
|
|
|
|
private static string ToLiteral(object value, ITypeSymbol type)
|
|
{
|
|
if (value is null)
|
|
{
|
|
return "null!";
|
|
}
|
|
|
|
if (type.TypeKind == TypeKind.Enum)
|
|
{
|
|
string enumType = GlobalType(type);
|
|
|
|
// value is already boxed enum OR underlying integral type
|
|
long underlying = Convert.ToInt64(value);
|
|
|
|
// fallback: cast
|
|
return $"({enumType}){underlying}";
|
|
}
|
|
|
|
return type.SpecialType switch
|
|
{
|
|
SpecialType.System_String => $"\"{EscapeString((string)value)}\"",
|
|
SpecialType.System_Char => $"'{EscapeChar((char)value)}'",
|
|
SpecialType.System_Boolean => (bool)value ? "true" : "false",
|
|
SpecialType.System_Int32 => ((int)value).ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
SpecialType.System_Int64 => ((long)value).ToString(System.Globalization.CultureInfo.InvariantCulture) + "L",
|
|
SpecialType.System_Single => ((float)value).ToString(System.Globalization.CultureInfo.InvariantCulture) + "f",
|
|
SpecialType.System_Double => ((double)value).ToString(System.Globalization.CultureInfo.InvariantCulture),
|
|
SpecialType.System_Decimal => ((decimal)value).ToString(System.Globalization.CultureInfo.InvariantCulture) + "m",
|
|
_ => value.ToString() ?? "null!"
|
|
};
|
|
}
|
|
|
|
private static string EscapeString(string value)
|
|
{
|
|
return value
|
|
.Replace("\\", "\\\\")
|
|
.Replace("\"", "\\\"")
|
|
.Replace("\r", "\\r")
|
|
.Replace("\n", "\\n")
|
|
.Replace("\t", "\\t");
|
|
}
|
|
|
|
private static string EscapeChar(char value)
|
|
{
|
|
return value switch
|
|
{
|
|
'\\' => "\\\\",
|
|
'\'' => "\\'",
|
|
'\r' => "\\r",
|
|
'\n' => "\\n",
|
|
'\t' => "\\t",
|
|
_ => value.ToString()
|
|
};
|
|
}
|
|
|
|
private sealed class MethodRegistration(INamedTypeSymbol containingType, IMethodSymbol method, AttributeData attribute)
|
|
{
|
|
public INamedTypeSymbol ContainingType { get; } = containingType;
|
|
|
|
public IMethodSymbol Method { get; } = method;
|
|
|
|
public AttributeData Attribute { get; } = attribute;
|
|
}
|
|
} |