diff --git a/.gitignore b/.gitignore index eeb9501..1b53fa2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ EvenPerfectBitScanner.Tests/bin/* EvenPerfectBitScanner.Tests/obj/* BuildDivisorTransitions/bin/* BuildDivisorTransitions/obj/* +DeviceDependentGenerator/bin/* +DeviceDependentGenerator/obj/* +DeviceDependentGenerator.Tests/bin/* +DeviceDependentGenerator.Tests/obj/* PerfectNumbers.Core.Tests/bin/* PerfectNumbers.Core.Tests/obj/* .DS_Store diff --git a/DeviceDependentGenerator.Tests/DeviceDependentGenerator.Tests.csproj b/DeviceDependentGenerator.Tests/DeviceDependentGenerator.Tests.csproj new file mode 100644 index 0000000..f2b1c05 --- /dev/null +++ b/DeviceDependentGenerator.Tests/DeviceDependentGenerator.Tests.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/DeviceDependentGenerator.Tests/DeviceDependentGeneratorTests.cs b/DeviceDependentGenerator.Tests/DeviceDependentGeneratorTests.cs new file mode 100644 index 0000000..74dadc8 --- /dev/null +++ b/DeviceDependentGenerator.Tests/DeviceDependentGeneratorTests.cs @@ -0,0 +1,110 @@ +using System.Collections.Immutable; +using FluentAssertions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace DeviceDependentGenerator.Tests; + +public sealed class DeviceDependentGeneratorTests +{ + [Fact] + public void Generates_classes_per_enum_value_and_filters_device_blocks() + { + const string attributeAndEnum = """ + using System; + + namespace PerfectNumbers.Core; + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] + public sealed class DeviceDependentTemplateAttribute : Attribute + { + public DeviceDependentTemplateAttribute(Type enumType) { } + } + + public enum CalculationDevice + { + Cpu, + Hybrid, + Gpu, + } + """; + + const string template = """ + using System; + + namespace PerfectNumbers.Core; + + [DeviceDependentTemplate(typeof(CalculationDevice))] + public static class PrimeCalculatorTemplate + { + #if DEVICE_CPU + public static string Device => "CPU"; + #elif DEVICE_HYBRID + public static string Device => "HYBRID"; + #elif DEVICE_GPU + public static string Device => "GPU"; + #else + #error Unknown device + #endif + } + """; + + CSharpCompilation compilation = CreateCompilation(attributeAndEnum, template); + + var generator = new PerfectNumbers.Core.Generators.DeviceDependentGenerator(); + var parseOptions = (CSharpParseOptions)compilation.SyntaxTrees.First().Options; + ISourceGenerator sourceGenerator = generator.AsSourceGenerator(); + GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { sourceGenerator }, parseOptions: parseOptions); + driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out ImmutableArray diagnostics); + + diagnostics.Should().BeEmpty(); + + GeneratorDriverRunResult runResult = driver.GetRunResult(); + runResult.Diagnostics.Should().BeEmpty(); + + ImmutableArray generated = runResult.Results.Single().GeneratedSources; + + string cpu = generated.Single(s => s.HintName.EndsWith("PrimeCalculator.Cpu.generated.cs", StringComparison.Ordinal)).SourceText.ToString(); + string hybrid = generated.Single(s => s.HintName.EndsWith("PrimeCalculator.Hybrid.generated.cs", StringComparison.Ordinal)).SourceText.ToString(); + string gpu = generated.Single(s => s.HintName.EndsWith("PrimeCalculator.Gpu.generated.cs", StringComparison.Ordinal)).SourceText.ToString(); + + cpu.Should().Contain("class PrimeCalculatorCpu"); + cpu.Should().Contain("=> \"CPU\""); + cpu.Should().NotContain("#if"); + cpu.Should().NotContain("#endif"); + + hybrid.Should().Contain("class PrimeCalculatorHybrid"); + hybrid.Should().Contain("=> \"HYBRID\""); + hybrid.Should().NotContain("#if"); + hybrid.Should().NotContain("#endif"); + + gpu.Should().Contain("class PrimeCalculatorGpu"); + gpu.Should().Contain("=> \"GPU\""); + gpu.Should().NotContain("#if"); + gpu.Should().NotContain("#endif"); + } + + private static CSharpCompilation CreateCompilation(params string[] sources) + { + var parseOptions = new CSharpParseOptions(LanguageVersion.CSharp12); + var trees = sources.Select((text, i) => CSharpSyntaxTree.ParseText(text, parseOptions, path: "Test" + i + ".cs")); + + var references = new[] + { + typeof(object), + typeof(Attribute), + typeof(Enumerable), + typeof(System.Runtime.GCSettings), + } + .Select(static t => MetadataReference.CreateFromFile(t.Assembly.Location)) + .DistinctBy(static r => r.Display) + .ToList(); + + return CSharpCompilation.Create( + assemblyName: "DeviceDependentGenerator.Tests.Compilation", + syntaxTrees: trees, + references: references, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + } +} diff --git a/DeviceDependentGenerator/DeviceDependentGenerator.cs b/DeviceDependentGenerator/DeviceDependentGenerator.cs new file mode 100644 index 0000000..8a2f533 --- /dev/null +++ b/DeviceDependentGenerator/DeviceDependentGenerator.cs @@ -0,0 +1,393 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; + +namespace PerfectNumbers.Core.Generators; + +[Generator(LanguageNames.CSharp)] +public sealed class DeviceDependentGenerator : IIncrementalGenerator +{ + private const string DeviceDependentTemplateAttributeMetadataName = "PerfectNumbers.Core.DeviceDependentTemplateAttribute"; + + private static readonly DiagnosticDescriptor EnumTypeMustBeEnum = new( + id: "DDG001", + title: "DeviceDependentTemplate requires an enum type", + messageFormat: "DeviceDependentTemplate requires an enum type, but '{0}' is not an enum.", + category: "DeviceDependentGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor TemplateMustBeTopLevel = new( + id: "DDG002", + title: "DeviceDependentTemplate must be top-level", + messageFormat: "DeviceDependentTemplate can only be used on a top-level class/struct (not nested types).", + category: "DeviceDependentGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor TemplateNotFoundInDeviceParse = new( + id: "DDG003", + title: "Template not found after preprocessing", + messageFormat: "Could not find template type '{0}' after preprocessing for device '{1}'.", + category: "DeviceDependentGenerator", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValuesProvider templates = context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName: DeviceDependentTemplateAttributeMetadataName, + predicate: static (node, _) => node is TypeDeclarationSyntax, + transform: static (attributeContext, cancellationToken) => TryGetTemplateInfo(attributeContext, cancellationToken)) + .Where(static info => info is not null)! + .Select(static (info, _) => info!); + + IncrementalValueProvider options = context.AnalyzerConfigOptionsProvider.Select( + static (provider, _) => new GeneratorOptions(provider.GlobalOptions)); + + context.RegisterSourceOutput(templates.Combine(options), static (productionContext, pair) => + { + GenerateForTemplate(productionContext, pair.Left, pair.Right); + }); + } + + private static TemplateInfo? TryGetTemplateInfo(GeneratorAttributeSyntaxContext attributeContext, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (attributeContext.TargetSymbol is not INamedTypeSymbol templateSymbol) + { + return null; + } + + if (attributeContext.Attributes.Length != 1) + { + return null; + } + + AttributeData attributeData = attributeContext.Attributes[0]; + if (attributeData.ConstructorArguments.Length != 1) + { + return null; + } + + if (attributeData.ConstructorArguments[0].Value is not INamedTypeSymbol enumTypeSymbol) + { + return null; + } + + if (enumTypeSymbol.TypeKind != TypeKind.Enum) + { + Location location = attributeContext.TargetNode.GetLocation(); + return TemplateInfo.CreateInvalidEnum(templateSymbol, enumTypeSymbol, location); + } + + ImmutableArray enumValues = enumTypeSymbol + .GetMembers() + .OfType() + .Where(static f => !f.IsImplicitlyDeclared && f.HasConstantValue) + .Select(static f => f.Name) + .ToImmutableArray(); + + Location attributeLocation = attributeContext.TargetNode.GetLocation(); + return TemplateInfo.CreateValid(templateSymbol, enumTypeSymbol, attributeLocation, enumValues); + } + + private static void GenerateForTemplate(SourceProductionContext context, TemplateInfo template, GeneratorOptions options) + { + if (!template.Valid) + { + context.ReportDiagnostic(Diagnostic.Create(EnumTypeMustBeEnum, template.AttributeLocation, template.EnumType.ToDisplayString())); + return; + } + + if (template.DeclaringSyntaxReference?.GetSyntax(context.CancellationToken) is not TypeDeclarationSyntax templateSyntax) + { + return; + } + + if (!IsTopLevel(templateSyntax)) + { + context.ReportDiagnostic(Diagnostic.Create(TemplateMustBeTopLevel, templateSyntax.Identifier.GetLocation())); + return; + } + + string templateName = template.Name; + string baseName = templateName.EndsWith("Template", StringComparison.Ordinal) + ? templateName.Substring(0, templateName.Length - "Template".Length) + : templateName; + + SourceText fileText = templateSyntax.SyntaxTree.GetText(context.CancellationToken); + string filePath = templateSyntax.SyntaxTree.FilePath; + CSharpParseOptions baseParseOptions = (CSharpParseOptions)templateSyntax.SyntaxTree.Options; + + foreach (string enumValueName in template.EnumValues) + { + string deviceDefine = "DEVICE_" + enumValueName.ToUpperInvariant(); + CSharpParseOptions parseOptions = WithDeviceDefine(baseParseOptions, deviceDefine); + + SyntaxTree deviceTree = CSharpSyntaxTree.ParseText( + fileText.ToString(), + parseOptions, + filePath, + encoding: Encoding.UTF8); + + CompilationUnitSyntax deviceRoot = deviceTree.GetCompilationUnitRoot(context.CancellationToken); + TypeDeclarationSyntax? deviceTemplateSyntax = FindTemplateDeclaration(deviceRoot, templateName); + if (deviceTemplateSyntax is null) + { + context.ReportDiagnostic(Diagnostic.Create(TemplateNotFoundInDeviceParse, templateSyntax.Identifier.GetLocation(), templateName, enumValueName)); + continue; + } + + string generatedTypeName = baseName + enumValueName; + TypeDeclarationSyntax generatedTypeSyntax = TransformTemplateType(deviceTemplateSyntax, generatedTypeName); + + CompilationUnitSyntax generatedRoot = BuildGeneratedFile( + externs: deviceRoot.Externs, + usings: deviceRoot.Usings, + containingNamespace: template.ContainingNamespace, + typeDeclaration: generatedTypeSyntax); + + generatedRoot = (CompilationUnitSyntax)new ConditionalDirectiveAndDisabledTextStripper().Visit(generatedRoot)!; + generatedRoot = generatedRoot.NormalizeWhitespace(); + + string generatedFileName = baseName + "." + enumValueName + ".generated.cs"; + string hintName = options.BuildHintName(filePath, generatedFileName); + context.AddSource(hintName, SourceText.From(generatedRoot.ToFullString(), Encoding.UTF8)); + } + } + + private static bool IsTopLevel(TypeDeclarationSyntax typeDeclaration) + => typeDeclaration.Parent is NamespaceDeclarationSyntax + || typeDeclaration.Parent is FileScopedNamespaceDeclarationSyntax + || typeDeclaration.Parent is CompilationUnitSyntax; + + private static CSharpParseOptions WithDeviceDefine(CSharpParseOptions baseParseOptions, string deviceDefine) + { + IEnumerable symbols = baseParseOptions.PreprocessorSymbolNames; + if (symbols.Contains(deviceDefine)) + { + return baseParseOptions; + } + + return baseParseOptions.WithPreprocessorSymbols(symbols.Concat(new[] { deviceDefine })); + } + + private static TypeDeclarationSyntax? FindTemplateDeclaration(CompilationUnitSyntax root, string templateName) + { + foreach (TypeDeclarationSyntax typeDecl in root.DescendantNodes().OfType()) + { + if (string.Equals(typeDecl.Identifier.ValueText, templateName, StringComparison.Ordinal)) + { + return typeDecl; + } + } + + return null; + } + + private static TypeDeclarationSyntax TransformTemplateType(TypeDeclarationSyntax templateType, string generatedTypeName) + { + TypeDeclarationSyntax withoutAttribute = RemoveDeviceDependentAttribute(templateType); + return withoutAttribute.WithIdentifier(SyntaxFactory.Identifier(generatedTypeName)); + } + + private static TypeDeclarationSyntax RemoveDeviceDependentAttribute(TypeDeclarationSyntax typeDeclaration) + { + if (typeDeclaration.AttributeLists.Count == 0) + { + return typeDeclaration; + } + + var newLists = new List(typeDeclaration.AttributeLists.Count); + foreach (AttributeListSyntax list in typeDeclaration.AttributeLists) + { + SeparatedSyntaxList attributes = list.Attributes; + var newAttributes = new List(attributes.Count); + foreach (AttributeSyntax attribute in attributes) + { + if (IsDeviceDependentTemplateAttribute(attribute.Name)) + { + continue; + } + + newAttributes.Add(attribute); + } + + if (newAttributes.Count == 0) + { + continue; + } + + newLists.Add(list.WithAttributes(SyntaxFactory.SeparatedList(newAttributes))); + } + + return typeDeclaration.WithAttributeLists(SyntaxFactory.List(newLists)); + } + + private static bool IsDeviceDependentTemplateAttribute(NameSyntax attributeName) + { + string text = attributeName.ToString(); + if (text.EndsWith("DeviceDependentTemplate", StringComparison.Ordinal)) + { + return true; + } + + return text.EndsWith("DeviceDependentTemplateAttribute", StringComparison.Ordinal); + } + + private static CompilationUnitSyntax BuildGeneratedFile( + SyntaxList externs, + SyntaxList usings, + string containingNamespace, + TypeDeclarationSyntax typeDeclaration) + { + MemberDeclarationSyntax member; + if (string.IsNullOrEmpty(containingNamespace)) + { + member = typeDeclaration; + } + else + { + member = SyntaxFactory + .FileScopedNamespaceDeclaration(SyntaxFactory.ParseName(containingNamespace)) + .WithMembers(SyntaxFactory.SingletonList(typeDeclaration)); + } + + return SyntaxFactory + .CompilationUnit() + .WithExterns(externs) + .WithUsings(usings) + .WithMembers(SyntaxFactory.SingletonList(member)); + } + + private sealed class TemplateInfo + { + private TemplateInfo( + INamedTypeSymbol templateType, + INamedTypeSymbol enumType, + Location attributeLocation, + ImmutableArray enumValues, + bool valid) + { + TemplateType = templateType; + EnumType = enumType; + AttributeLocation = attributeLocation; + EnumValues = enumValues; + Valid = valid; + } + + public INamedTypeSymbol TemplateType { get; } + public INamedTypeSymbol EnumType { get; } + public Location AttributeLocation { get; } + public ImmutableArray EnumValues { get; } + public bool Valid { get; } + + public string Name => TemplateType.Name; + + public string ContainingNamespace => TemplateType.ContainingNamespace?.IsGlobalNamespace == true + ? string.Empty + : TemplateType.ContainingNamespace.ToDisplayString(); + + public SyntaxReference? DeclaringSyntaxReference => TemplateType.DeclaringSyntaxReferences.FirstOrDefault(); + + public static TemplateInfo CreateInvalidEnum(INamedTypeSymbol templateType, INamedTypeSymbol enumType, Location attributeLocation) + => new(templateType, enumType, attributeLocation, enumValues: default, valid: false); + + public static TemplateInfo CreateValid( + INamedTypeSymbol templateType, + INamedTypeSymbol enumType, + Location attributeLocation, + ImmutableArray enumValues) + => new(templateType, enumType, attributeLocation, enumValues, valid: true); + } + + private readonly struct GeneratorOptions + { + private readonly AnalyzerConfigOptions globalOptions; + + public GeneratorOptions(AnalyzerConfigOptions globalOptions) + { + this.globalOptions = globalOptions; + } + + public string BuildHintName(string templateFilePath, string generatedFileName) + { + if (!globalOptions.TryGetValue("build_property.ProjectDir", out string? projectDir) || string.IsNullOrWhiteSpace(projectDir)) + { + return generatedFileName; + } + + string? templateDirectory = Path.GetDirectoryName(templateFilePath); + if (string.IsNullOrWhiteSpace(templateDirectory)) + { + return generatedFileName; + } + + string relativeDirectory = GetRelativePath(projectDir, templateDirectory); + if (relativeDirectory is "." or "") + { + return generatedFileName; + } + + relativeDirectory = relativeDirectory.Replace('\\', '/'); + return relativeDirectory.TrimEnd('/') + "/" + generatedFileName; + } + + private static string GetRelativePath(string baseDirectory, string targetDirectory) + { + if (string.IsNullOrWhiteSpace(baseDirectory) || string.IsNullOrWhiteSpace(targetDirectory)) + { + return targetDirectory; + } + + if (!baseDirectory.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) + && !baseDirectory.EndsWith(Path.AltDirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + { + baseDirectory += Path.DirectorySeparatorChar; + } + + var baseUri = new Uri(baseDirectory, UriKind.Absolute); + var targetUri = new Uri(targetDirectory, UriKind.Absolute); + string relative = Uri.UnescapeDataString(baseUri.MakeRelativeUri(targetUri).ToString()); + return relative.Replace('/', Path.DirectorySeparatorChar); + } + } + + private sealed class ConditionalDirectiveAndDisabledTextStripper : CSharpSyntaxRewriter + { + public override SyntaxToken VisitToken(SyntaxToken token) + => token.WithLeadingTrivia(Filter(token.LeadingTrivia)).WithTrailingTrivia(Filter(token.TrailingTrivia)); + + private static SyntaxTriviaList Filter(SyntaxTriviaList list) + { + var builder = ImmutableArray.CreateBuilder(list.Count); + foreach (SyntaxTrivia trivia in list) + { + SyntaxKind kind = trivia.Kind(); + if (kind is SyntaxKind.DisabledTextTrivia + or SyntaxKind.IfDirectiveTrivia + or SyntaxKind.ElifDirectiveTrivia + or SyntaxKind.ElseDirectiveTrivia + or SyntaxKind.EndIfDirectiveTrivia) + { + continue; + } + + builder.Add(trivia); + } + + return SyntaxFactory.TriviaList(builder.ToImmutable()); + } + } +} diff --git a/DeviceDependentGenerator/DeviceDependentGenerator.csproj b/DeviceDependentGenerator/DeviceDependentGenerator.csproj new file mode 100644 index 0000000..a002c94 --- /dev/null +++ b/DeviceDependentGenerator/DeviceDependentGenerator.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + latest + enable + disable + true + true + + + + + + + + diff --git a/EvenPerfectScanner.sln b/EvenPerfectScanner.sln index e727561..69823a9 100644 --- a/EvenPerfectScanner.sln +++ b/EvenPerfectScanner.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 @@ -16,42 +17,130 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvenPerfectBitScanner.Bench EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildDivisorTransitions", "BuildDivisorTransitions\BuildDivisorTransitions.csproj", "{0A936837-5082-4C0B-AC3B-840AD2E11057}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceDependentGenerator", "DeviceDependentGenerator\DeviceDependentGenerator.csproj", "{E900CE3B-0DA4-4D7A-B825-851D6227ED67}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceDependentGenerator.Tests", "DeviceDependentGenerator.Tests\DeviceDependentGenerator.Tests.csproj", "{2C632996-9606-4CB5-B046-38AB91D6F6DC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|x64.ActiveCfg = Debug|Any CPU + {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|x64.Build.0 = Debug|Any CPU + {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|x86.ActiveCfg = Debug|Any CPU + {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Debug|x86.Build.0 = Debug|Any CPU {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|Any CPU.ActiveCfg = Release|Any CPU {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|Any CPU.Build.0 = Release|Any CPU + {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|x64.ActiveCfg = Release|Any CPU + {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|x64.Build.0 = Release|Any CPU + {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|x86.ActiveCfg = Release|Any CPU + {FFD557C9-12AE-4D8D-A21A-44CEE197BFB6}.Release|x86.Build.0 = Release|Any CPU {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|x64.ActiveCfg = Debug|Any CPU + {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|x64.Build.0 = Debug|Any CPU + {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|x86.ActiveCfg = Debug|Any CPU + {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Debug|x86.Build.0 = Debug|Any CPU {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|Any CPU.Build.0 = Release|Any CPU + {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|x64.ActiveCfg = Release|Any CPU + {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|x64.Build.0 = Release|Any CPU + {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|x86.ActiveCfg = Release|Any CPU + {FA4063F7-CB3C-4A66-BE70-4C6535D90197}.Release|x86.Build.0 = Release|Any CPU {553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|x64.ActiveCfg = Debug|Any CPU + {553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|x64.Build.0 = Debug|Any CPU + {553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|x86.ActiveCfg = Debug|Any CPU + {553D1735-214B-4097-B647-D73DB2CB9ADA}.Debug|x86.Build.0 = Debug|Any CPU {553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU {553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|Any CPU.Build.0 = Release|Any CPU + {553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|x64.ActiveCfg = Release|Any CPU + {553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|x64.Build.0 = Release|Any CPU + {553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|x86.ActiveCfg = Release|Any CPU + {553D1735-214B-4097-B647-D73DB2CB9ADA}.Release|x86.Build.0 = Release|Any CPU {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|x64.ActiveCfg = Debug|Any CPU + {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|x64.Build.0 = Debug|Any CPU + {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|x86.ActiveCfg = Debug|Any CPU + {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Debug|x86.Build.0 = Debug|Any CPU {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|Any CPU.ActiveCfg = Release|Any CPU {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|Any CPU.Build.0 = Release|Any CPU + {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|x64.ActiveCfg = Release|Any CPU + {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|x64.Build.0 = Release|Any CPU + {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|x86.ActiveCfg = Release|Any CPU + {65ED9CC9-BB94-4FA5-940E-B84B3EE87DA7}.Release|x86.Build.0 = Release|Any CPU {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|x64.ActiveCfg = Debug|Any CPU + {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|x64.Build.0 = Debug|Any CPU + {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|x86.ActiveCfg = Debug|Any CPU + {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Debug|x86.Build.0 = Debug|Any CPU {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|Any CPU.ActiveCfg = Release|Any CPU {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|Any CPU.Build.0 = Release|Any CPU + {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|x64.ActiveCfg = Release|Any CPU + {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|x64.Build.0 = Release|Any CPU + {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|x86.ActiveCfg = Release|Any CPU + {B07A718C-A71C-410D-A0D7-FA1674CEB38F}.Release|x86.Build.0 = Release|Any CPU {83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|x64.ActiveCfg = Debug|Any CPU + {83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|x64.Build.0 = Debug|Any CPU + {83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|x86.ActiveCfg = Debug|Any CPU + {83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|x86.Build.0 = Debug|Any CPU {83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|Any CPU.ActiveCfg = Release|Any CPU {83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|Any CPU.Build.0 = Release|Any CPU + {83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|x64.ActiveCfg = Release|Any CPU + {83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|x64.Build.0 = Release|Any CPU + {83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|x86.ActiveCfg = Release|Any CPU + {83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|x86.Build.0 = Release|Any CPU {0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|x64.ActiveCfg = Debug|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|x64.Build.0 = Debug|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|x86.ActiveCfg = Debug|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|x86.Build.0 = Debug|Any CPU {0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|Any CPU.ActiveCfg = Release|Any CPU {0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|Any CPU.Build.0 = Release|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|x64.ActiveCfg = Release|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|x64.Build.0 = Release|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|x86.ActiveCfg = Release|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|x86.Build.0 = Release|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|x64.ActiveCfg = Debug|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|x64.Build.0 = Debug|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|x86.ActiveCfg = Debug|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Debug|x86.Build.0 = Debug|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|Any CPU.Build.0 = Release|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|x64.ActiveCfg = Release|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|x64.Build.0 = Release|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|x86.ActiveCfg = Release|Any CPU + {E900CE3B-0DA4-4D7A-B825-851D6227ED67}.Release|x86.Build.0 = Release|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|x64.ActiveCfg = Debug|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|x64.Build.0 = Debug|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|x86.ActiveCfg = Debug|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Debug|x86.Build.0 = Debug|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|Any CPU.Build.0 = Release|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|x64.ActiveCfg = Release|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|x64.Build.0 = Release|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|x86.ActiveCfg = Release|Any CPU + {2C632996-9606-4CB5-B046-38AB91D6F6DC}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/PerfectNumbers.Core/DeviceDependentTemplateAttribute.cs b/PerfectNumbers.Core/DeviceDependentTemplateAttribute.cs new file mode 100644 index 0000000..5a6b63e --- /dev/null +++ b/PerfectNumbers.Core/DeviceDependentTemplateAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace PerfectNumbers.Core; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] +public sealed class DeviceDependentTemplateAttribute : Attribute +{ + public DeviceDependentTemplateAttribute(Type enumType) + { + EnumType = enumType ?? throw new ArgumentNullException(nameof(enumType)); + } + + public Type EnumType { get; } +} + diff --git a/PerfectNumbers.Core/PerfectNumbers.Core.csproj b/PerfectNumbers.Core/PerfectNumbers.Core.csproj index 81bc8fd..20690aa 100644 --- a/PerfectNumbers.Core/PerfectNumbers.Core.csproj +++ b/PerfectNumbers.Core/PerfectNumbers.Core.csproj @@ -14,6 +14,12 @@ + + + +