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 @@
+
+
+
+