Browse Source

Made apigen work

Krzysztof Krysiński 1 year ago
parent
commit
4b23963819

+ 1 - 1
src/PixiEditor.AvaloniaUI.Desktop/PixiEditor.AvaloniaUI.Desktop.csproj

@@ -25,6 +25,6 @@
     </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
   </ItemGroup>
 </Project>

+ 2 - 2
src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj

@@ -36,8 +36,8 @@
         <PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
         <PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
         <PackageReference Include="Hardware.Info" Version="11.0.0" />
-        <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
-        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
+        <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.9.2" />
+        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
         <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
         <PackageReference Include="PixiEditor.ColorPicker.Models" Version="1.0.5" />
         <PackageReference Include="PixiEditor.Parser" Version="3.3.0" />

+ 7 - 2
src/PixiEditor.Extensions.WasmRuntime/ApiFunctionAttribute.cs

@@ -1,7 +1,12 @@
 namespace PixiEditor.Extensions.WasmRuntime;
 
 [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
-public class ApiFunctionAttribute(string name) : Attribute
+public class ApiFunctionAttribute : Attribute
 {
-    public string Name { get; } = name;
+    public string Name { get; }
+
+    public ApiFunctionAttribute(string name)
+    {
+        Name = name;
+    }
 }

+ 1 - 0
src/PixiEditor.Extensions.WasmRuntime/PixiEditor.Extensions.WasmRuntime.csproj

@@ -13,6 +13,7 @@
 
     <ItemGroup>
       <ProjectReference Include="..\PixiEditor.Extensions\PixiEditor.Extensions.csproj" />
+      <ProjectReference Include="..\PixiEditor.WasmApi.Gen\PixiEditor.WasmApi.Gen.csproj" OutputItemType="Analyzer"/>
     </ItemGroup>
 
 </Project>

+ 1 - 1
src/PixiEditor.Extensions.WasmRuntime/WasmExtensionInstance.cs

@@ -63,7 +63,7 @@ public partial class WasmExtensionInstance : Extension
     {
         var elementMap = (ElementMap)Api.Services.GetService(typeof(ElementMap));
         byte[] map = elementMap.Serialize();
-        var ptr = WasmMemoryUtility.WriteSpan(map);
+        var ptr = WasmMemoryUtility.WriteBytes(map);
         Instance.GetAction<int, int>("set_element_map").Invoke(ptr, map.Length);
 
         WasmMemoryUtility.Free(ptr);

+ 31 - 10
src/PixiEditor.Extensions.WasmRuntime/WasmMemoryUtility.cs

@@ -26,6 +26,37 @@ public class WasmMemoryUtility
         return Encoding.UTF8.GetString(span);
     }
 
+    public byte[] GetBytes(int offset, int length)
+    {
+        var span = memory.GetSpan<byte>(offset, length);
+        return span.ToArray();
+    }
+
+    public Span<byte> GetSpan(int offset, int length)
+    {
+        return memory.GetSpan<byte>(offset, length);
+    }
+
+    public int GetInt32(int offset)
+    {
+        return memory.ReadInt32(offset);
+    }
+
+    public int WriteSpan(Span<byte> span)
+    {
+        return WriteBytes(span.ToArray());
+    }
+
+    public int WriteBytes(byte[] bytes)
+    {
+        var length = bytes.Length;
+        var ptr = malloc.Invoke(length);
+
+        var span = memory.GetSpan<byte>(ptr, length);
+        bytes.CopyTo(span);
+        return ptr;
+    }
+
     public int WriteInt32(int value)
     {
         const int length = 4;
@@ -46,14 +77,4 @@ public class WasmMemoryUtility
     {
         free.Invoke(address);
     }
-
-    public int WriteSpan(byte[] bytes)
-    {
-        var length = bytes.Length;
-        var ptr = malloc.Invoke(length);
-
-        var span = memory.GetSpan<byte>(ptr, length);
-        bytes.CopyTo(span);
-        return ptr;
-    }
 }

+ 1 - 2
src/PixiEditor.Extensions.WasmRuntime/WindowingApi.cs

@@ -5,10 +5,9 @@ namespace PixiEditor.Extensions.WasmRuntime;
 internal class WindowingApi : ApiGroupHandler
 {
     [ApiFunction("create_popup_window")]
-    public int CreatePopupWindow(string title, byte[] bodySpan)
+    public int CreatePopupWindow(string title, Span<byte> bodySpan)
     {
         var body = LayoutBuilder.Deserialize(bodySpan, DuplicateResolutionTactic.ThrowException);
-
         var popupWindow = Api.Windowing.CreatePopupWindow(title, body.BuildNative());
 
         int handle = NativeObjectManager.AddObject(popupWindow);

+ 95 - 56
src/PixiEditor.WasmApi.Gen/ApiGenerator.cs

@@ -1,48 +1,69 @@
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Threading;
+using System.Collections.Immutable;
+using System.Diagnostics;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
+using PixiEditor.Api.Gen;
 
-namespace PixiEditor.Api.Gen;
+namespace PixiEditor.WasmApi.Gen;
 
-[Generator]
+[Generator(LanguageNames.CSharp)]
 public class ApiGenerator : IIncrementalGenerator
 {
+    private const string FullyQualifiedApiFunctionAttributeName =
+        "PixiEditor.Extensions.WasmRuntime.ApiFunctionAttribute";
+
     private const string ApiFunctionAttributeName = "ApiFunctionAttribute";
+
     public void Initialize(IncrementalGeneratorInitializationContext context)
     {
-        var methods = context.SyntaxProvider.CreateSyntaxProvider(CouldBeApiImplAsync, GetApiFunctionMethodOrNull)
+        var methods = context.SyntaxProvider.ForAttributeWithMetadataName(
+                FullyQualifiedApiFunctionAttributeName,
+                (_, _) => true,
+                GetApiFunctionMethodOrNull)
             .Where(x => x is not null)
             .Collect();
 
         context.RegisterSourceOutput(methods, GenerateLinkerCode);
     }
 
-    private void GenerateLinkerCode(SourceProductionContext ctx, ImmutableArray<IMethodSymbol?> symbols)
+    private void GenerateLinkerCode(SourceProductionContext ctx, ImmutableArray<(IMethodSymbol methodSymbol, SemanticModel SemanticModel)?> symbols)
     {
-        if (symbols.IsDefaultOrEmpty) return;
+        List<StatementSyntax> linkingMethodsCode = new List<StatementSyntax>();
 
-        List<string> linkingMethodsCode = new List<string>();
-
-        foreach (IMethodSymbol? method in symbols)
+        foreach (var symbol in symbols)
         {
-            if (method == null) continue;
+            if(!symbol.HasValue) continue;
+            if (symbol.Value.methodSymbol == null) continue;
 
-            linkingMethodsCode.Add(GenerateLinkingCodeForMethod(method));
+            linkingMethodsCode.Add(GenerateLinkingCodeForMethod(symbol.Value));
         }
 
-        ctx.AddSource($"{TargetNamespace}.{TargetClassName}.g.cs", BuildFinalCode(linkingMethodsCode));
+        // partial void LinkApiFunctions()
+        var methodDeclaration = SyntaxFactory
+            .MethodDeclaration(SyntaxFactory.ParseTypeName("void"), $"LinkApiFunctions")
+            .AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword))
+            .WithBody(SyntaxFactory.Block(linkingMethodsCode));
+
+        // internal partial class WasmExtensionInstance
+        var cDecl = SyntaxFactory
+            .ClassDeclaration("WasmExtensionInstance")
+            .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.PartialKeyword))
+            .AddMembers(methodDeclaration);
+
+        // namespace PixiEditor.Extensions.WasmRuntime
+        var nspace = SyntaxFactory
+            .NamespaceDeclaration(SyntaxFactory.ParseName("PixiEditor.Extensions.WasmRuntime"))
+            .AddMembers(cDecl);
+
+        ctx.AddSource($"WasmExtensionInstance+ApiFunctions", nspace.NormalizeWhitespace().ToFullString());
     }
 
-    private string GenerateLinkingCodeForMethod(IMethodSymbol method)
+    private StatementSyntax GenerateLinkingCodeForMethod((IMethodSymbol methodSymbol, SemanticModel SemanticModel) symbol)
     {
-        AttributeData importName = method.GetAttributes().First(x => x.NamedArguments[0].Key == "Name");
-        string name = (string)importName.NamedArguments[0].Value.Value;
+        string name = $"{symbol.methodSymbol.GetAttributes()[0].ConstructorArguments[0].ToCSharpString()}";
 
-        ImmutableArray<IParameterSymbol> arguments = method.Parameters;
+        ImmutableArray<IParameterSymbol> arguments = symbol.methodSymbol.Parameters;
 
         List<string> convertedParams = new List<string>();
         foreach (var argSymbol in arguments)
@@ -52,28 +73,70 @@ public class ApiGenerator : IIncrementalGenerator
 
         ParameterListSyntax paramList = SyntaxFactory.ParseParameterList(string.Join(",", convertedParams));
 
-        SyntaxList<StatementSyntax> statements = BuildFunctionBody(method, paramList);
+        SyntaxList<StatementSyntax> statements = new SyntaxList<StatementSyntax>();
+
+        SyntaxList<StatementSyntax> variableStatements = BuildVariableStatements(arguments);
+
+
+        statements = statements.AddRange(variableStatements);
+        statements = statements.AddRange(BuildFunctionBody(symbol));
 
         BlockSyntax body = SyntaxFactory.Block(statements);
 
-        var methodExpression = SyntaxFactory.AnonymousMethodExpression(paramList, body);
+        var parameters = SyntaxFactory.ParameterList(paramList.Parameters);
+
+        var define = SyntaxFactory.ParseStatement(
+            $"Linker.DefineFunction(\"env\", {name}, {parameters.ToFullString()} => \n{body.ToFullString()});");
+
+        return define;
+    }
+
+    private SyntaxList<StatementSyntax> BuildVariableStatements(ImmutableArray<IParameterSymbol> arguments)
+    {
+        SyntaxList<StatementSyntax> syntaxes = new SyntaxList<StatementSyntax>();
+
+        foreach (var argSymbol in arguments)
+        {
+            string lowerType = argSymbol.Type.Name;
+            bool isLengthType = TypeConversionTable.IsLengthType(argSymbol);
+            string paramsString = isLengthType ? $"{argSymbol.Name}Pointer, {argSymbol.Name}Length" : $"{argSymbol.Name}Pointer";
+            syntaxes = syntaxes.Add(SyntaxFactory.ParseStatement($"{argSymbol.Type.ToDisplayString()} {argSymbol.Name} = WasmMemoryUtility.Get{lowerType}({paramsString});"));
+        }
 
-        SyntaxFactory.ParseStatement($"Linker.DefineFunction(\"env\", {name})
+        return syntaxes;
     }
 
-    private SyntaxList<StatementSyntax> BuildFunctionBody(IMethodSymbol method, ParameterListSyntax paramList)
+    private SyntaxList<StatementSyntax> BuildFunctionBody((IMethodSymbol methodSymbol, SemanticModel SemanticModel) method)
     {
         SyntaxList<StatementSyntax> syntaxes = new SyntaxList<StatementSyntax>();
-        foreach (SyntaxReference? reference in method.DeclaringSyntaxReferences)
+        MethodBodyRewriter rewriter = new MethodBodyRewriter(method.SemanticModel);
+        foreach (SyntaxReference? reference in method.methodSymbol.DeclaringSyntaxReferences)
         {
-            if (reference.GetSyntax() is StatementSyntax statementSyntax)
-                syntaxes = syntaxes.Add(statementSyntax);
+            SyntaxNode? node = reference.GetSyntax();
+
+            if (node is not MethodDeclarationSyntax methodDeclaration)
+                continue;
+
+            var statements = methodDeclaration.Body!.Statements;
+            foreach (var statement in statements)
+            {
+                if(statement is not ReturnStatementSyntax returnStatementSyntax)
+                {
+                    var newStatement = (StatementSyntax)rewriter.Visit(statement);
+                    syntaxes = syntaxes.Add(newStatement);
+                }
+                else
+                {
+                    var returnType = method.methodSymbol.ReturnType.Name;
+                    syntaxes = syntaxes.Add(SyntaxFactory.ParseStatement($"return WasmMemoryUtility.Write{returnType}({returnStatementSyntax.Expression.ToFullString()});"));
+                }
+            }
         }
 
         return syntaxes;
     }
 
-    private bool CouldBeApiImplAsync(SyntaxNode node, CancellationToken cancellation)
+    private static bool CouldBeApiImplAsync(SyntaxNode node, CancellationToken cancellation)
     {
         if (node is not AttributeSyntax attribute)
             return false;
@@ -83,7 +146,7 @@ public class ApiGenerator : IIncrementalGenerator
         return name is "ApiFunction" or ApiFunctionAttributeName;
     }
 
-    private string? ExtractName(NameSyntax? attributeName)
+    private static string? ExtractName(NameSyntax? attributeName)
     {
         return attributeName switch
         {
@@ -93,36 +156,12 @@ public class ApiGenerator : IIncrementalGenerator
         };
     }
 
-    private IMethodSymbol? GetApiFunctionMethodOrNull(GeneratorSyntaxContext context, CancellationToken cancelToken)
+    private static (IMethodSymbol methodSymbol, SemanticModel SemanticModel)? GetApiFunctionMethodOrNull(GeneratorAttributeSyntaxContext context,
+        CancellationToken cancelToken)
     {
-        AttributeSyntax member = (AttributeSyntax)context.Node;
-
-        if (member.Parent?.Parent is not MethodDeclarationSyntax methodDeclarationSyntax)
+        if (context.TargetSymbol is not IMethodSymbol methodSymbol)
             return null;
 
-        var symbol = context.SemanticModel.GetDeclaredSymbol(member, cancelToken);
-
-        if (symbol is IMethodSymbol methodSymbol)
-        {
-            if (methodSymbol.ReceiverType == null)
-                return null;
-
-            return methodSymbol is null || !IsApiFunction(methodSymbol) ? null : methodSymbol;
-        }
-    }
-
-    private bool IsApiFunction(IMethodSymbol methodSymbol)
-    {
-        return methodSymbol.GetAttributes().Any(x => x.AttributeClass is {
-            Name: ApiFunctionAttributeName,
-            ContainingNamespace: {
-                Name: "PixiEditor.Extensions.WasmRuntime",
-                ContainingNamespace.IsGlobalNamespace: true
-        } });
+        return (methodSymbol, context.SemanticModel);
     }
 }
-
-class ApiFunction
-{
-
-}

+ 48 - 0
src/PixiEditor.WasmApi.Gen/MethodBodyRewriter.cs

@@ -0,0 +1,48 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace PixiEditor.WasmApi.Gen;
+
+public class MethodBodyRewriter : CSharpSyntaxRewriter
+{
+    public SemanticModel MethodSemanticModel { get; }
+    public MethodBodyRewriter(SemanticModel methodSemanticModel)
+    {
+        MethodSemanticModel = methodSemanticModel;
+    }
+
+    public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
+    {
+        /*var methodSymbol = MethodSemanticModel.GetSymbolInfo(node).Symbol as IMethodSymbol;
+
+        var fullyQualifiedName = methodSymbol.ToDisplayString();
+
+        var newInvocation = SyntaxFactory.ParseExpression($"{fullyQualifiedName}({string.Join(", ", node.ArgumentList.Arguments)})");
+
+        return newInvocation;*/
+
+        return base.VisitInvocationExpression(node);
+    }
+
+    public override SyntaxNode VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
+    {
+        var memberSymbol = MethodSemanticModel.GetSymbolInfo(node).Symbol;
+
+        if(memberSymbol.Kind != SymbolKind.Property && memberSymbol.Kind != SymbolKind.Field)
+        {
+            return base.VisitMemberAccessExpression(node);
+        }
+
+        if(!memberSymbol.IsStatic)
+        {
+            return base.VisitMemberAccessExpression(node);
+        }
+
+        var fullyQualifiedName = memberSymbol.ToDisplayString();
+
+        var newMemberAccess = SyntaxFactory.ParseExpression(fullyQualifiedName);
+
+        return newMemberAccess;
+    }
+}

+ 15 - 21
src/PixiEditor.WasmApi.Gen/PixiEditor.WasmApi.Gen.csproj

@@ -1,26 +1,20 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
-    <PropertyGroup>
-        <TargetFramework>netstandard2.0</TargetFramework>
-        <IsPackable>false</IsPackable>
-        <Nullable>enable</Nullable>
-        <LangVersion>latest</LangVersion>
-
-        <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
-        <IsRoslynComponent>true</IsRoslynComponent>
-
-        <RootNamespace>PixiEditor.Api.Gen</RootNamespace>
-        <PackageId>PixiEditor.WasmApi.Gen</PackageId>
-    </PropertyGroup>
-
-    <ItemGroup>
-        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
-            <PrivateAssets>all</PrivateAssets>
-            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
-        </PackageReference>
-        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2"/>
-        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2"/>
-    </ItemGroup>
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>true</ImplicitUsings>
+    <LangVersion>latest</LangVersion>
+    <IncludeBuildOutput>false</IncludeBuildOutput>
+    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
+  </PropertyGroup>
 
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all"/>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all"/>
+  </ItemGroup>
 
+  <ItemGroup>
+    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false"/>
+  </ItemGroup>
 </Project>

+ 4 - 3
src/PixiEditor.WasmApi.Gen/TypeConversionTable.cs

@@ -12,12 +12,13 @@ public static class TypeConversionTable
             return [$"int {symbol.Name}Pointer", $"int {symbol.Name}Length"];
         }
 
-        return [$"int {symbol.Name}"];
+        return [$"int {symbol.Name}Pointer"];
     }
 
-    private static bool IsLengthType(IParameterSymbol symbol)
+    public static bool IsLengthType(IParameterSymbol symbol)
     {
         return symbol.Type.Name.Equals("string", StringComparison.OrdinalIgnoreCase)
-               || symbol.Type.Name.Equals("byte[]", StringComparison.OrdinalIgnoreCase);
+               || symbol.Type.Name.Equals("byte[]", StringComparison.OrdinalIgnoreCase)
+               || symbol.Type.Name.Equals("span", StringComparison.OrdinalIgnoreCase);
     }
 }