Browse Source

Add AST source generator projects

ast-source-generator
Siegfried Pammer 6 months ago
parent
commit
f4d89087a2
  1. 2
      Directory.Packages.props
  2. 13
      ICSharpCode.Decompiler.Generators.Attributes/DecompilerAstNodeAttribute.cs
  3. 7
      ICSharpCode.Decompiler.Generators.Attributes/ICSharpCode.Decompiler.Generators.Attributes.csproj
  4. 289
      ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs
  5. 8
      ICSharpCode.Decompiler.Generators/GeneratorAttributes.cs
  6. 25
      ICSharpCode.Decompiler.Generators/ICSharpCode.Decompiler.Generators.csproj
  7. 3
      ICSharpCode.Decompiler.Generators/IsExternalInit.cs
  8. 32
      ICSharpCode.Decompiler.Generators/RoslynHelpers.cs
  9. 125
      ICSharpCode.Decompiler.Generators/TreeTraversal.cs
  10. 3
      ICSharpCode.Decompiler/packages.lock.json
  11. 12
      ILSpy.sln

2
Directory.Packages.props

@ -15,8 +15,10 @@ @@ -15,8 +15,10 @@
<PackageVersion Include="K4os.Compression.LZ4" Version="1.3.8" />
<PackageVersion Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<PackageVersion Include="McMaster.Extensions.Hosting.CommandLine" Version="4.1.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.13.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.13.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.13.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Converter.Xml" Version="1.1.0-beta2-22171-02" />
<PackageVersion Include="Microsoft.DiaSymReader" Version="1.4.0" />
<PackageVersion Include="Microsoft.DiaSymReader.Native" Version="17.0.0-beta1.21524.1" />

13
ICSharpCode.Decompiler.Generators.Attributes/DecompilerAstNodeAttribute.cs

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
using System;
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
public sealed class DecompilerAstNodeAttribute : Attribute
{
public DecompilerAstNodeAttribute(bool hasNullNode) { }
}
public sealed class ExcludeFromMatchAttribute : Attribute
{
}
}

7
ICSharpCode.Decompiler.Generators.Attributes/ICSharpCode.Decompiler.Generators.Attributes.csproj

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

289
ICSharpCode.Decompiler.Generators/DecompilerSyntaxTreeGenerator.cs

@ -0,0 +1,289 @@ @@ -0,0 +1,289 @@
using System.Collections;
using System.Collections.Immutable;
using System.Text;
using ICSharpCode.Decompiler.CSharp.Syntax;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace ICSharpCode.Decompiler.Generators;
[Generator]
internal class DecompilerSyntaxTreeGenerator : IIncrementalGenerator
{
record AstNodeAdditions(string NodeName, bool NeedsAcceptImpls, bool NeedsVisitor, bool NeedsNullNode, int NullNodeBaseCtorParamCount, bool IsTypeNode, string VisitMethodName, string VisitMethodParamType, EquatableArray<(string Member, string TypeName, bool RecursiveMatch, bool MatchAny)>? MembersToMatch);
AstNodeAdditions GetAstNodeAdditions(GeneratorAttributeSyntaxContext context, CancellationToken ct)
{
var targetSymbol = (INamedTypeSymbol)context.TargetSymbol;
var attribute = context.Attributes.SingleOrDefault(ad => ad.AttributeClass?.Name == "DecompilerAstNodeAttribute")!;
var (visitMethodName, paramTypeName) = targetSymbol.Name switch {
"ErrorExpression" => ("ErrorNode", "AstNode"),
string s when s.Contains("AstType") => (s.Replace("AstType", "Type"), s),
_ => (targetSymbol.Name, targetSymbol.Name),
};
List<(string Member, string TypeName, bool RecursiveMatch, bool MatchAny)>? membersToMatch = null;
if (!targetSymbol.MemberNames.Contains("DoMatch"))
{
membersToMatch = new();
var astNodeType = (INamedTypeSymbol)context.SemanticModel.GetSpeculativeSymbolInfo(context.TargetNode.Span.Start, SyntaxFactory.ParseTypeName("AstNode"), SpeculativeBindingOption.BindAsTypeOrNamespace).Symbol!;
if (targetSymbol.BaseType!.MemberNames.Contains("MatchAttributesAndModifiers"))
membersToMatch.Add(("MatchAttributesAndModifiers", null!, false, false));
foreach (var m in targetSymbol.GetMembers())
{
if (m is not IPropertySymbol property || property.IsIndexer || property.IsOverride)
continue;
if (property.GetAttributes().Any(a => a.AttributeClass?.Name == nameof(ExcludeFromMatchAttribute)))
continue;
if (property.Type.MetadataName is "CSharpTokenNode" or "TextLocation")
continue;
switch (property.Type)
{
case INamedTypeSymbol named when named.IsDerivedFrom(astNodeType) || named.MetadataName == "AstNodeCollection`1":
membersToMatch.Add((property.Name, named.Name, true, false));
break;
case INamedTypeSymbol { TypeKind: TypeKind.Enum } named when named.GetMembers().Any(_ => _.Name == "Any"):
membersToMatch.Add((property.Name, named.Name, false, true));
break;
default:
membersToMatch.Add((property.Name, property.Type.Name, false, false));
break;
}
}
}
return new(targetSymbol.Name, !targetSymbol.MemberNames.Contains("AcceptVisitor"),
NeedsVisitor: !targetSymbol.IsAbstract && targetSymbol.BaseType!.IsAbstract,
NeedsNullNode: (bool)attribute.ConstructorArguments[0].Value!,
NullNodeBaseCtorParamCount: targetSymbol.InstanceConstructors.Min(m => m.Parameters.Length),
IsTypeNode: targetSymbol.Name == "AstType" || targetSymbol.BaseType?.Name == "AstType",
visitMethodName, paramTypeName, membersToMatch?.ToEquatableArray());
}
void WriteGeneratedMembers(SourceProductionContext context, AstNodeAdditions source)
{
var builder = new StringBuilder();
builder.AppendLine("namespace ICSharpCode.Decompiler.CSharp.Syntax;");
builder.AppendLine();
builder.AppendLine("#nullable enable");
builder.AppendLine();
builder.AppendLine($"partial class {source.NodeName}");
builder.AppendLine("{");
if (source.NeedsNullNode)
{
bool needsNew = source.NodeName != "AstNode";
builder.AppendLine($" {(needsNew ? "new " : "")}public static readonly {source.NodeName} Null = new Null{source.NodeName}();");
builder.AppendLine($@"
sealed class Null{source.NodeName} : {source.NodeName}
{{
public override NodeType NodeType => NodeType.Unknown;
public override bool IsNull => true;
public override void AcceptVisitor(IAstVisitor visitor)
{{
visitor.VisitNullNode(this);
}}
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{{
return visitor.VisitNullNode(this);
}}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{{
return visitor.VisitNullNode(this, data);
}}
protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
{{
return other == null || other.IsNull;
}}");
if (source.IsTypeNode)
{
builder.AppendLine(
$@"
public override Decompiler.TypeSystem.ITypeReference ToTypeReference(Resolver.NameLookupMode lookupMode, Decompiler.TypeSystem.InterningProvider? interningProvider = null)
{{
return Decompiler.TypeSystem.SpecialType.UnknownType;
}}"
);
}
if (source.NullNodeBaseCtorParamCount > 0)
{
builder.AppendLine($@"
public Null{source.NodeName}() : base({string.Join(", ", Enumerable.Repeat("default", source.NullNodeBaseCtorParamCount))}) {{ }}");
}
builder.AppendLine($@"
}}
");
}
if (source.NeedsAcceptImpls && source.NeedsVisitor)
{
builder.Append($@" public override void AcceptVisitor(IAstVisitor visitor)
{{
visitor.Visit{source.NodeName}(this);
}}
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{{
return visitor.Visit{source.NodeName}(this);
}}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{{
return visitor.Visit{source.NodeName}(this, data);
}}
");
}
if (source.MembersToMatch != null)
{
builder.Append($@" protected internal override bool DoMatch(AstNode? other, PatternMatching.Match match)
{{
return other is {source.NodeName} o && !o.IsNull");
foreach (var (member, typeName, recursive, hasAny) in source.MembersToMatch)
{
if (member == "MatchAttributesAndModifiers")
{
builder.Append($"\r\n\t\t\t&& this.MatchAttributesAndModifiers(o, match)");
}
else if (recursive)
{
builder.Append($"\r\n\t\t\t&& this.{member}.DoMatch(o.{member}, match)");
}
else if (hasAny)
{
builder.Append($"\r\n\t\t\t&& (this.{member} == {typeName}.Any || this.{member} == o.{member})");
}
else
{
builder.Append($"\r\n\t\t\t&& this.{member} == o.{member}");
}
}
builder.Append(@";
}
");
}
builder.AppendLine("}");
context.AddSource(source.NodeName + ".g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
}
private void WriteVisitors(SourceProductionContext context, ImmutableArray<AstNodeAdditions> source)
{
var builder = new StringBuilder();
builder.AppendLine("namespace ICSharpCode.Decompiler.CSharp.Syntax;");
source = source
.Concat([new("NullNode", false, true, false, 0, false, "NullNode", "AstNode", null), new("PatternPlaceholder", false, true, false, 0, false, "PatternPlaceholder", "AstNode", null)])
.ToImmutableArray();
WriteInterface("IAstVisitor", "void", "");
WriteInterface("IAstVisitor<out S>", "S", "");
WriteInterface("IAstVisitor<in T, out S>", "S", ", T data");
context.AddSource("IAstVisitor.g.cs", SourceText.From(builder.ToString(), Encoding.UTF8));
void WriteInterface(string name, string ret, string param)
{
builder.AppendLine($"public interface {name}");
builder.AppendLine("{");
foreach (var type in source.OrderBy(t => t.VisitMethodName))
{
if (!type.NeedsVisitor)
continue;
string extParams, paramName;
if (type.VisitMethodName == "PatternPlaceholder")
{
paramName = "placeholder";
extParams = ", PatternMatching.Pattern pattern" + param;
}
else
{
paramName = char.ToLowerInvariant(type.VisitMethodName[0]) + type.VisitMethodName.Substring(1);
extParams = param;
}
builder.AppendLine($"\t{ret} Visit{type.VisitMethodName}({type.VisitMethodParamType} {paramName}{extParams});");
}
builder.AppendLine("}");
}
}
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var astNodeAdditions = context.SyntaxProvider.ForAttributeWithMetadataName(
"ICSharpCode.Decompiler.CSharp.Syntax.DecompilerAstNodeAttribute",
(n, ct) => n is ClassDeclarationSyntax,
GetAstNodeAdditions);
var visitorMembers = astNodeAdditions.Collect();
context.RegisterSourceOutput(astNodeAdditions, WriteGeneratedMembers);
context.RegisterSourceOutput(visitorMembers, WriteVisitors);
}
}
readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IEnumerable<T>
where T : IEquatable<T>
{
readonly T[] array;
public EquatableArray(T[] array)
{
this.array = array ?? throw new ArgumentNullException(nameof(array));
}
public bool Equals(EquatableArray<T> other)
{
return other.array.AsSpan().SequenceEqual(this.array);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)array).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return array.GetEnumerator();
}
}
static class EquatableArrayExtensions
{
public static EquatableArray<T> ToEquatableArray<T>(this List<T> array) where T : IEquatable<T>
{
return new EquatableArray<T>(array.ToArray());
}
}

8
ICSharpCode.Decompiler.Generators/GeneratorAttributes.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
using System;
namespace ICSharpCode.Decompiler.CSharp.Syntax;
public class DecompilerAstNodeAttribute(bool hasNullNode) : Attribute
{
public bool HasNullNode { get; } = hasNullNode;
}

25
ICSharpCode.Decompiler.Generators/ICSharpCode.Decompiler.Generators.csproj

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<LangVersion>12</LangVersion>
</PropertyGroup>
<PropertyGroup>
<RoslynVersion>4.8.0</RoslynVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ICSharpCode.Decompiler.Generators.Attributes\ICSharpCode.Decompiler.Generators.Attributes.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Common" VersionOverride="$(RoslynVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" VersionOverride="$(RoslynVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" VersionOverride="$(RoslynVersion)" />
</ItemGroup>
</Project>

3
ICSharpCode.Decompiler.Generators/IsExternalInit.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace System.Runtime.CompilerServices;
class IsExternalInit { }

32
ICSharpCode.Decompiler.Generators/RoslynHelpers.cs

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
using Microsoft.CodeAnalysis;
namespace ICSharpCode.Decompiler.Generators;
public static class RoslynHelpers
{
public static IEnumerable<INamedTypeSymbol> GetTopLevelTypes(this IAssemblySymbol assembly)
{
foreach (var ns in TreeTraversal.PreOrder(assembly.GlobalNamespace, ns => ns.GetNamespaceMembers()))
{
foreach (var t in ns.GetTypeMembers())
{
yield return t;
}
}
}
public static bool IsDerivedFrom(this INamedTypeSymbol type, INamedTypeSymbol baseType)
{
INamedTypeSymbol? t = type;
while (t != null)
{
if (SymbolEqualityComparer.Default.Equals(t, baseType))
return true;
t = t.BaseType;
}
return false;
}
}

125
ICSharpCode.Decompiler.Generators/TreeTraversal.cs

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
#nullable enable
// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
namespace ICSharpCode.Decompiler.Generators;
/// <summary>
/// Static helper methods for traversing trees.
/// </summary>
internal static class TreeTraversal
{
/// <summary>
/// Converts a tree data structure into a flat list by traversing it in pre-order.
/// </summary>
/// <param name="root">The root element of the tree.</param>
/// <param name="recursion">The function that gets the children of an element.</param>
/// <returns>Iterator that enumerates the tree structure in pre-order.</returns>
public static IEnumerable<T> PreOrder<T>(T root, Func<T, IEnumerable<T>?> recursion)
{
return PreOrder(new T[] { root }, recursion);
}
/// <summary>
/// Converts a tree data structure into a flat list by traversing it in pre-order.
/// </summary>
/// <param name="input">The root elements of the forest.</param>
/// <param name="recursion">The function that gets the children of an element.</param>
/// <returns>Iterator that enumerates the tree structure in pre-order.</returns>
public static IEnumerable<T> PreOrder<T>(IEnumerable<T> input, Func<T, IEnumerable<T>?> recursion)
{
Stack<IEnumerator<T>> stack = new Stack<IEnumerator<T>>();
try
{
stack.Push(input.GetEnumerator());
while (stack.Count > 0)
{
while (stack.Peek().MoveNext())
{
T element = stack.Peek().Current;
yield return element;
IEnumerable<T>? children = recursion(element);
if (children != null)
{
stack.Push(children.GetEnumerator());
}
}
stack.Pop().Dispose();
}
}
finally
{
while (stack.Count > 0)
{
stack.Pop().Dispose();
}
}
}
/// <summary>
/// Converts a tree data structure into a flat list by traversing it in post-order.
/// </summary>
/// <param name="root">The root element of the tree.</param>
/// <param name="recursion">The function that gets the children of an element.</param>
/// <returns>Iterator that enumerates the tree structure in post-order.</returns>
public static IEnumerable<T> PostOrder<T>(T root, Func<T, IEnumerable<T>?> recursion)
{
return PostOrder(new T[] { root }, recursion);
}
/// <summary>
/// Converts a tree data structure into a flat list by traversing it in post-order.
/// </summary>
/// <param name="input">The root elements of the forest.</param>
/// <param name="recursion">The function that gets the children of an element.</param>
/// <returns>Iterator that enumerates the tree structure in post-order.</returns>
public static IEnumerable<T> PostOrder<T>(IEnumerable<T> input, Func<T, IEnumerable<T>?> recursion)
{
Stack<IEnumerator<T>> stack = new Stack<IEnumerator<T>>();
try
{
stack.Push(input.GetEnumerator());
while (stack.Count > 0)
{
while (stack.Peek().MoveNext())
{
T element = stack.Peek().Current;
IEnumerable<T>? children = recursion(element);
if (children != null)
{
stack.Push(children.GetEnumerator());
}
else
{
yield return element;
}
}
stack.Pop().Dispose();
if (stack.Count > 0)
yield return stack.Peek().Current;
}
}
finally
{
while (stack.Count > 0)
{
stack.Pop().Dispose();
}
}
}
}

3
ICSharpCode.Decompiler/packages.lock.json

@ -91,6 +91,9 @@ @@ -91,6 +91,9 @@
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
},
"icsharpcode.decompiler.generators.attributes": {
"type": "Project"
}
}
}

12
ILSpy.sln

@ -43,6 +43,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution @@ -43,6 +43,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ICSharpCode.Decompiler.Generators", "ICSharpCode.Decompiler.Generators\ICSharpCode.Decompiler.Generators.csproj", "{0792B524-622B-4B9D-8027-3531ED6A42EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICSharpCode.Decompiler.Generators.Attributes", "ICSharpCode.Decompiler.Generators.Attributes\ICSharpCode.Decompiler.Generators.Attributes.csproj", "{02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -101,6 +105,14 @@ Global @@ -101,6 +105,14 @@ Global
{81A30182-3378-4952-8880-F44822390040}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81A30182-3378-4952-8880-F44822390040}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81A30182-3378-4952-8880-F44822390040}.Release|Any CPU.Build.0 = Release|Any CPU
{0792B524-622B-4B9D-8027-3531ED6A42EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0792B524-622B-4B9D-8027-3531ED6A42EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0792B524-622B-4B9D-8027-3531ED6A42EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0792B524-622B-4B9D-8027-3531ED6A42EB}.Release|Any CPU.Build.0 = Release|Any CPU
{02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02FE14EC-EEA7-4902-BCBF-0F95FB90C9B3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Loading…
Cancel
Save