Skip to content

Commit

Permalink
Incremental and parse options
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Dec 18, 2024
1 parent 5d9dbeb commit 9d5b0aa
Show file tree
Hide file tree
Showing 13 changed files with 541 additions and 77 deletions.
9 changes: 3 additions & 6 deletions sandbox/ConsoleApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
using MasterMemory;
using System.Linq;
using MessagePack;
using System;
using System.IO;
using System.Buffers;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Text;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;

// IValidatableを実装すると検証対象になる
[MemoryTable("quest_master"), MessagePackObject(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MasterMemory.GeneratorCore
{
public static class CodeGenerator
{
public static GenerationContext CreateGenerationContext(ClassDeclarationSyntax classDecl)
public static GenerationContext CreateGenerationContext(TypeDeclarationSyntax classDecl)
{
var root = classDecl.SyntaxTree.GetRoot();

Expand Down Expand Up @@ -63,11 +63,11 @@ public static GenerationContext CreateGenerationContext(ClassDeclarationSyntax c
{
context.UsingStrings = usingStrings;
context.OriginalClassDeclaration = classDecl;
// context.InputFilePath = filePath;
return context;
}

return null!; // if null????
// If primary key not found, validate from another place.
throw new InvalidOperationException("PrimaryKey not found.");
}


Expand Down
27 changes: 13 additions & 14 deletions src/MasterMemory.SourceGenerator/GeneratorCore/GenerationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,32 @@
namespace MasterMemory.GeneratorCore
{

public class GenerationContext
public record GenerationContext
{
public string ClassName { get; set; }
public string MemoryTableName { get; set; }
public string[] UsingStrings { get; set; }
public EquatableArray<string> UsingStrings { get; set; }
public PrimaryKey PrimaryKey { get; set; }
public SecondaryKey[] SecondaryKeys { get; set; }
public EquatableArray<SecondaryKey> SecondaryKeys { get; set; }

public string InputFilePath { get; set; }
public ClassDeclarationSyntax OriginalClassDeclaration { get; set; }

public Property[] Properties { get; set; }
public KeyBase[] Keys => new KeyBase[] { PrimaryKey }.Concat(SecondaryKeys).ToArray();
// public string InputFilePath { get; set; }
public IgnoreEquality<TypeDeclarationSyntax> OriginalClassDeclaration { get; set; }

public EquatableArray<Property> Properties { get; set; }
public EquatableArray<KeyBase> Keys => new KeyBase[] { PrimaryKey }.Concat(SecondaryKeys).ToArray();
}

public class Property
public record Property
{
public string Type { get; set; }
public string Name { get; set; }
}

public abstract class KeyBase
public abstract record KeyBase
{
public bool IsNonUnique { get; set; }
public string StringComparisonOption { get; set; }
public KeyProperty[] Properties { get; set; }
public EquatableArray<KeyProperty> Properties { get; set; }
public abstract string SelectorName { get; }
public abstract string TableName { get; }
public abstract bool IsPrimary { get; }
Expand Down Expand Up @@ -198,22 +197,22 @@ public bool CanInlineBinarySearch
}
}

public class PrimaryKey : KeyBase
public record PrimaryKey : KeyBase
{
public override string SelectorName => "primaryIndexSelector";
public override string TableName => "data";
public override bool IsPrimary => true;
}

public class SecondaryKey : KeyBase
public record SecondaryKey : KeyBase
{
public int IndexNo { get; set; }
public override string SelectorName => $"secondaryIndex{IndexNo}Selector";
public override string TableName => $"secondaryIndex{IndexNo}";
public override bool IsPrimary => false;
}

public class KeyProperty
public record KeyProperty
{
public int KeyOrder { get; set; }
public string Name { get; set; }
Expand Down
95 changes: 52 additions & 43 deletions src/MasterMemory.SourceGenerator/MasterMemoryGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,65 +1,74 @@
using MasterMemory.GeneratorCore;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace MasterMemory.SourceGenerator;

[Generator(LanguageNames.CSharp)]
public partial class MasterMemoryGenerator : IIncrementalGenerator
{
//[Option("i", "Input file directory(search recursive).")]
//string inputDirectory,

// [Option("o", "Output file directory.")]string outputDirectory,

// [Option("n", "Namespace of generated files.")]string usingNamespace,

// [Option("p", "Prefix of class names.")]string prefixClassName = "",

// [Option("c", "Add immutable constructor to MemoryTable class.")]bool addImmutableConstructor = false,

// [Option("t", "Return null if key not found on unique find method.")]bool returnNullIfKeyNotFound = false,

// [Option("f", "Overwrite generated files if the content is unchanged.")]bool forceOverwrite = false)

public void Initialize(IncrementalGeneratorInitializationContext context)
{
// TODO:...
// Input and Output is no needed.
// prefix should be configurable?(to assemblyattribute)
// remove immutableconstructor feature
// returnnull
context.RegisterPostInitializationOutput(MasterMemoryGeneratorOptions.EmitAttribute);

var namespaceProvider = context.AnalyzerConfigOptionsProvider.Select((x, _) =>
{
x.GlobalOptions.TryGetValue("build_property.RootNamespace", out var defaultNamespace);
return defaultNamespace;
})
.WithTrackingName("MasterMemory.AnalyzerConfig");

var generatorOptions = context.CompilationProvider.Select((compilation, _) =>
{
foreach (var attr in compilation.Assembly.GetAttributes())
{
if (attr.AttributeClass?.Name == "MasterMemoryGeneratorOptionsAttribute")
{
return MasterMemoryGeneratorOptions.FromAttribute(attr);
}
}

return default;
})
.WithTrackingName("MasterMemory.CompilationProvider");

var memoryTables = context.SyntaxProvider.ForAttributeWithMetadataName("MasterMemory.MemoryTableAttribute",
(node, token) => true,
(ctx, token) => ctx)
.Collect();
(ctx, token) =>
{
// class or record
var classDecl = ctx.TargetNode as TypeDeclarationSyntax;
var context = CodeGenerator.CreateGenerationContext(classDecl!);
return context;
})
.WithTrackingName("MasterMemory.SyntaxProvider.0_ForAttributeWithMetadataName")
.Collect()
.Select((xs, _) =>
{
var array = xs.ToArray();
Array.Sort(array, (a, b) => string.Compare(a.ClassName, b.ClassName, StringComparison.Ordinal));
return new EquatableArray<GenerationContext>(array);
})
.WithTrackingName("MasterMemory.SyntaxProvider.1_CollectAndSelect");

var allCombined = memoryTables
.Combine(namespaceProvider)
.Combine(generatorOptions)
.WithTrackingName("MasterMemory.SyntaxProvider.2_AllCombined");

context.RegisterSourceOutput(memoryTables, EmitMemoryTable);
context.RegisterSourceOutput(allCombined, EmitMemoryTable);
}

void EmitMemoryTable(SourceProductionContext context, ImmutableArray<GeneratorAttributeSyntaxContext> memoryTables)
void EmitMemoryTable(SourceProductionContext context, ((EquatableArray<GenerationContext>, string?), MasterMemoryGeneratorOptions) value)
{
var usingNamespace = "FooBarBaz"; // TODO:from option?
var prefixClassName = ""; // TODO
var throwIfKeyNotFound = false;

var list = memoryTables.Select(x =>
{
// TODO: RecordDeclaration
var classDecl = x.TargetNode as ClassDeclarationSyntax;
return CodeGenerator.CreateGenerationContext(classDecl!);
})
.ToList();
var ((memoryTables, defaultNamespace), generatorOptions) = value;

list.Sort((a, b) => string.Compare(a.ClassName, b.ClassName, StringComparison.Ordinal));
var usingNamespace = generatorOptions.Namespace ?? defaultNamespace ?? "MasterMemory";
var prefixClassName = generatorOptions.PrefixClassName ?? "";
var throwIfKeyNotFound = !generatorOptions.IsReturnNullIfKeyNotFound; // becareful, reverse!

var usingStrings = string.Join(Environment.NewLine, list.SelectMany(x => x.UsingStrings).Distinct().OrderBy(x => x, StringComparer.Ordinal));
var usingStrings = string.Join(Environment.NewLine, memoryTables.SelectMany(x => x.UsingStrings).Distinct().OrderBy(x => x, StringComparer.Ordinal));

var builderTemplate = new DatabaseBuilderTemplate();
var databaseTemplate = new MemoryDatabaseTemplate();
Expand All @@ -68,14 +77,14 @@ void EmitMemoryTable(SourceProductionContext context, ImmutableArray<GeneratorAt
builderTemplate.Namespace = databaseTemplate.Namespace = immutableBuilderTemplate.Namespace = resolverTemplate.Namespace = usingNamespace;
builderTemplate.PrefixClassName = databaseTemplate.PrefixClassName = immutableBuilderTemplate.PrefixClassName = resolverTemplate.PrefixClassName = prefixClassName;
builderTemplate.Using = databaseTemplate.Using = immutableBuilderTemplate.Using = resolverTemplate.Using = (usingStrings + Environment.NewLine + ("using " + usingNamespace + ".Tables;"));
builderTemplate.GenerationContexts = databaseTemplate.GenerationContexts = immutableBuilderTemplate.GenerationContexts = resolverTemplate.GenerationContexts = list.ToArray();
builderTemplate.GenerationContexts = databaseTemplate.GenerationContexts = immutableBuilderTemplate.GenerationContexts = resolverTemplate.GenerationContexts = memoryTables.ToArray();

Log(AddSource(context, builderTemplate.ClassName, builderTemplate.TransformText()));
Log(AddSource(context, immutableBuilderTemplate.ClassName, immutableBuilderTemplate.TransformText()));
Log(AddSource(context, databaseTemplate.ClassName, databaseTemplate.TransformText()));
Log(AddSource(context, resolverTemplate.ClassName, resolverTemplate.TransformText()));

foreach (var generationContext in list)
foreach (var generationContext in memoryTables)
{
var template = new TableTemplate()
{
Expand Down
36 changes: 36 additions & 0 deletions src/MasterMemory.SourceGenerator/MasterMemoryGeneratorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.CodeAnalysis;

namespace MasterMemory.SourceGenerator;

readonly record struct MasterMemoryGeneratorOptions(string? Namespace, string PrefixClassName, bool IsReturnNullIfKeyNotFound)
{
public static MasterMemoryGeneratorOptions FromAttribute(AttributeData attributeData)
{
var args = attributeData.NamedArguments;

var ns = args.FirstOrDefault(x => x.Key == nameof(Namespace)).Value.Value as string ?? null;
var prefix = args.FirstOrDefault(x => x.Key == nameof(PrefixClassName)).Value.Value as string ?? "";
var isReturnNull = args.FirstOrDefault(x => x.Key == nameof(IsReturnNullIfKeyNotFound)).Value.Value as bool? ?? null;

return new MasterMemoryGeneratorOptions(ns, prefix, isReturnNull ?? false);
}

public static void EmitAttribute(IncrementalGeneratorPostInitializationContext context)
{
context.AddSource("MasterMemory.MasterMemoryGeneratorOptions.cs", """
#nullable enable
using System;
namespace MasterMemory
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
internal sealed class MasterMemoryGeneratorOptionsAttribute : Attribute
{
public string? Namespace { get; set; } = null;
public string PrefixClassName { get; set; } = "";
public bool IsReturnNullIfKeyNotFound { get; set; } = false;
}
}
""");
}
}
58 changes: 58 additions & 0 deletions src/MasterMemory.SourceGenerator/Utility/EquatableArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Collections;
using System.Runtime.CompilerServices;

namespace MasterMemory;

public readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IEnumerable<T>
where T : IEquatable<T>
{
readonly T[]? array;

public EquatableArray() // for collection literal []
{
array = [];
}

public EquatableArray(T[] array)
{
this.array = array;
}

public static implicit operator EquatableArray<T>(T[] array)
{
return new EquatableArray<T>(array);
}

public ref readonly T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref array![index];
}

public int Length => array!.Length;

public ReadOnlySpan<T> AsSpan()
{
return array.AsSpan();
}

public ReadOnlySpan<T>.Enumerator GetEnumerator()
{
return AsSpan().GetEnumerator();
}

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return array.AsEnumerable().GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return array.AsEnumerable().GetEnumerator();
}

public bool Equals(EquatableArray<T> other)
{
return AsSpan().SequenceEqual(other.AsSpan());
}
}
22 changes: 22 additions & 0 deletions src/MasterMemory.SourceGenerator/Utility/IgnoreEquality.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace MasterMemory;

public readonly struct IgnoreEquality<T>(T value) : IEquatable<IgnoreEquality<T>>
{
public readonly T Value => value;

public static implicit operator IgnoreEquality<T>(T value)
{
return new IgnoreEquality<T>(value);
}

public static implicit operator T(IgnoreEquality<T> value)
{
return value.Value;
}

public bool Equals(IgnoreEquality<T> other)
{
// always true to ignore equality check.
return true;
}
}
Loading

0 comments on commit 9d5b0aa

Please sign in to comment.