clean_ryujinx/src/Ryujinx.HLE.Generators/UserServiceGenerator.cs

93 lines
3.9 KiB
C#

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ryujinx.HLE.Generators
{
[Generator]
public sealed class UserServiceGenerator : IIncrementalGenerator
{
private sealed class ServiceData : IEquatable<ServiceData>
{
public required string FullName { get; init; }
public required IReadOnlyList<(string ServiceName, string ParameterValue)> Instances { get; init; }
public override bool Equals(object obj)
=> obj is ServiceData data && Equals(data);
public bool Equals(ServiceData other)
{
return this.FullName == other.FullName && this.Instances.SequenceEqual(other.Instances);
}
public override int GetHashCode() => FullName.GetHashCode();
}
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var pipeline = context.SyntaxProvider.ForAttributeWithMetadataName("Ryujinx.HLE.HOS.Services.ServiceAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax decl && !decl.Modifiers.Any(SyntaxKind.AbstractKeyword) && !decl.Modifiers.Any(SyntaxKind.PrivateKeyword),
transform: (ctx, _) =>
{
var target = (INamedTypeSymbol)ctx.TargetSymbol;
var instances = ctx.Attributes.Select(attr =>
{
string param = attr.ConstructorArguments is [_, { IsNull: false } arg] ? arg.ToCSharpString() : null;
return ((string)attr.ConstructorArguments[0].Value, param);
});
return new ServiceData
{
FullName = target.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
Instances = instances.ToList(),
};
}
);
context.RegisterSourceOutput(pipeline.Collect(),
(ctx, data) =>
{
var generator = new CodeGenerator();
generator.AppendLine("#nullable enable");
generator.AppendLine("using System;");
generator.EnterScope("namespace Ryujinx.HLE.HOS.Services.Sm");
generator.EnterScope("partial class IUserInterface");
generator.EnterScope("public IpcService? GetServiceInstance(string name, ServiceCtx context)");
generator.EnterScope("return name switch");
foreach (var serviceImpl in data)
{
foreach (var instance in serviceImpl.Instances)
{
if (instance.ParameterValue == null)
{
generator.AppendLine($"\"{instance.ServiceName}\" => new {serviceImpl.FullName}(context),");
}
else
{
generator.AppendLine($"\"{instance.ServiceName}\" => new {serviceImpl.FullName}(context, {instance.ParameterValue}),");
}
}
}
generator.AppendLine("_ => null,");
generator.LeaveScope(";");
generator.LeaveScope();
generator.LeaveScope();
generator.LeaveScope();
generator.AppendLine("#nullable disable");
ctx.AddSource("IUserInterface.g.cs", generator.ToString());
});
}
}
}