using System.Text; using MathNet.Numerics; using MathNet.Numerics.LinearRegression; using MathNet.Numerics.Statistics; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Polaris.DirtyWorks; public class MathDotNetAddSpanVersionDemoWork { class ClassModificationInfo { public string Path { get; init; } public string FullName { get; init; } public List<(int start, string signature, string replacement)> Modifications { get; } = new(); } private readonly string _repoRoot = @"D:\GitHub\mathnet-numerics\src\Numerics"; private readonly Type[] _classesToModify = [ // typeof(NumericalDerivative), // typeof(Distance), // typeof(Beta), // typeof(BetaScaled), // typeof(Burr), // typeof(Categorical), // typeof(Cauchy), // typeof(Chi), // typeof(ChiSquared), // typeof(ContinuousUniform), // typeof(Dirichlet), // typeof(Erlang), // typeof(Exponential), // typeof(FisherSnedecor), // typeof(Gamma), // typeof(InverseGamma), // typeof(InverseGaussian), // typeof(Laplace), // typeof(Logistic), // typeof(LogNormal), // typeof(Multinomial), // typeof(Normal), // typeof(Pareto), // typeof(Rayleigh), // typeof(SkewedGeneralizedError), // typeof(SkewedGeneralizedT), // typeof(Stable), // typeof(StudentT), // typeof(Triangular), // typeof(TruncatedPareto), // typeof(Weibull), // // typeof(ExcelFunctions), // typeof(FindRoots), typeof(Fit), // typeof(Fourier), // typeof(Hartley), // typeof(Barycentric), // typeof(BulirschStoerRationalInterpolation), // typeof(CubicSpline), // typeof(LinearSpline), // typeof(LogLinear), // typeof(NevillePolynomialInterpolation), // typeof(QuadraticSpline), // typeof(StepInterpolation), typeof(SimpleRegression), // typeof(Polynomial), // typeof(ManagedFourierTransformProvider), // typeof(ManagedLinearAlgebraProvider), typeof(ArrayStatistics), typeof(Correlation), // typeof(HybridMC), typeof(SortedArrayStatistics), typeof(Generate), ]; public void Run() { var all = new List(); var classNames = new HashSet(); foreach (var type in _classesToModify) { classNames.Add(type.FullName); } LoopFileSystem(_repoRoot, all, classNames); foreach (var info in all) { if (info.Modifications.Any()) { var text = File.ReadAllText(info.Path); var delta = 0; foreach (var (start, signature, replacement) in info.Modifications) { var ss = start + delta; var m = text[ss..(ss + signature.Length)]; if (m != signature) { throw new Exception($"offset is wrong, file: {info.Path}."); } var buffer = new StringBuilder(); buffer.Append(text[..ss]); buffer.Append(replacement); buffer.Append(text[(ss + signature.Length)..]); text = buffer.ToString(); delta += replacement.Length - signature.Length; } File.WriteAllText(info.Path, text); } } } private void LoopFileSystem(string folder, List all, HashSet classNames) { foreach (var subFolder in Directory.EnumerateDirectories(folder)) { LoopFileSystem(subFolder, all, classNames); } foreach (var file in Directory.EnumerateFiles(folder)) { if (Path.GetExtension(file).ToLower() == ".cs") { try { var m = ParseCsFile(file, classNames); if (m != null) { all.Add(m); } } catch (Exception ex) { Logger.Warn(() => ex.Message); } } } } private ClassModificationInfo ParseCsFile(string file, HashSet classNames) { ClassModificationInfo result = null; var tree = CSharpSyntaxTree.ParseText(File.ReadAllText(file)); var root = tree.GetRoot(); foreach (var node in root.ChildNodes()) { // if (namespaceName != null) throw new Exception($"only single namespace[{namespaceName}] declaration is supported."); if (node is not NamespaceDeclarationSyntax nds) continue; var namespaceName = nds.Name.ToString(); foreach (var member in nds.Members) { // if (classFullName != null) throw new Exception($"only single class[{classFullName}] declaration is supported."); if (member is not ClassDeclarationSyntax cds) continue; var classFullName = $"{namespaceName}.{cds.Identifier}"; if (cds.Modifiers.All(x => !x.IsKind(SyntaxKind.PublicKeyword))) continue; if (classNames.Contains(classFullName)) { result = new ClassModificationInfo { Path = file, FullName = classFullName }; foreach (var member2 in cds.Members) { if (member2 is not MethodDeclarationSyntax mds) continue; if (mds.Modifiers.All(x => !x.IsKind(SyntaxKind.PublicKeyword))) continue; var m = ModifyMethod(mds); if (m != default) { result.Modifications.Add(m); } } } } } return result; } private (int start, string signature, string replacement) ModifyMethod(MethodDeclarationSyntax mds) { var parameterList = mds.ParameterList; var parameterListString = parameterList.ToString(); if (parameterListString.Contains("Span<")) { return default; } var arrayMethodDecl = $"{mds.Modifiers} {mds.ReturnType} {mds.Identifier}{mds.TypeParameterList?.ToString() ?? ""}{parameterList}"; List invokeStringList = []; List spanVersionList = []; var isTarget = false; foreach (var parameter in parameterList.Parameters) { if (ProcessParameter(parameter, out var a, out var b)) { isTarget = true; } invokeStringList.Add(a); spanVersionList.Add(b); } if (!isTarget) { return default; } var spanListString = $"({String.Join(", ", spanVersionList)})"; var spanMethodDecl = $"{mds.Modifiers} {mds.ReturnType} {mds.Identifier}{mds.TypeParameterList?.ToString() ?? ""}{spanListString}"; var newMethod = $"{arrayMethodDecl} => {mds.Identifier}({String.Join(", ", invokeStringList)});"; var signature = arrayMethodDecl; var replacement = $"{newMethod}{Environment.NewLine}{Environment.NewLine} {spanMethodDecl}{Environment.NewLine}"; return (mds.SpanStart, signature, replacement); } private bool ProcessParameter(ParameterSyntax parameter, out string invocation, out string spanSignature) { foreach (var node in parameter.ChildNodes()) { if (node is ArrayTypeSyntax ats) { var isTarget = false; string elementString = null; foreach (var node2 in ats.ChildNodes()) { if (node2 is ArrayRankSpecifierSyntax arss) { if (arss.Rank == 1) { isTarget = true; } } else if (node2 is PredefinedTypeSyntax pfts) { if (pfts.Keyword.Text is "double" or "float") { elementString = pfts.Keyword.Text; } } else if (node2 is IdentifierNameSyntax ins) { elementString = ins.Identifier.Text; } } if (isTarget && elementString != null) { invocation = $"{parameter.Identifier}.AsSpan()"; spanSignature = $"Span<{elementString}> {parameter.Identifier}"; return true; } } } invocation = $"{parameter.Identifier}"; spanSignature = $"{parameter}"; return false; } }