diff --git a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/DefaultTokenTypesJsonStringProvider.cs b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/DefaultTokenTypesJsonStringProvider.cs new file mode 100644 index 0000000..97a05e6 --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/DefaultTokenTypesJsonStringProvider.cs @@ -0,0 +1,9 @@ +using HydraScript.Domain.FrontEnd.Lexer; + +namespace HydraScript.Infrastructure.LexerRegexGenerator; + +internal class DefaultTokenTypesJsonStringProvider : + ITokenTypesJsonStringProvider +{ + public string TokenTypesJsonString => TokenTypesJson.String; +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/HydraScript.Infrastructure.LexerRegexGenerator.csproj b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/HydraScript.Infrastructure.LexerRegexGenerator.csproj index 890cbc7..bd90347 100644 --- a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/HydraScript.Infrastructure.LexerRegexGenerator.csproj +++ b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/HydraScript.Infrastructure.LexerRegexGenerator.csproj @@ -14,4 +14,8 @@ + + + + diff --git a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/ITokenTypesJsonStringProvider.cs b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/ITokenTypesJsonStringProvider.cs new file mode 100644 index 0000000..666f0d9 --- /dev/null +++ b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/ITokenTypesJsonStringProvider.cs @@ -0,0 +1,6 @@ +namespace HydraScript.Infrastructure.LexerRegexGenerator; + +public interface ITokenTypesJsonStringProvider +{ + public string TokenTypesJsonString { get; } +} \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.cs b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.cs index afab806..781b3cf 100644 --- a/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.cs +++ b/src/Infrastructure/HydraScript.Infrastructure.LexerRegexGenerator/PatternGenerator.cs @@ -1,9 +1,6 @@ -using System.Collections.Immutable; using System.Text; using System.Text.Json; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace HydraScript.Infrastructure.LexerRegexGenerator; @@ -11,86 +8,32 @@ namespace HydraScript.Infrastructure.LexerRegexGenerator; [Generator] public partial class PatternGenerator : IIncrementalGenerator { - private const string AttributeSourceCode = @"// - -namespace HydraScript.Infrastructure; - -[System.AttributeUsage(System.AttributeTargets.Class)] -public class PatternContainerAttribute(string json) : System.Attribute - where T : HydraScript.Domain.FrontEnd.Lexer.IGeneratedRegexContainer -{ - public string Json { get; } = json; -} -"; + public ITokenTypesJsonStringProvider Provider { get; init; } = new DefaultTokenTypesJsonStringProvider(); public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterPostInitializationOutput(ctx => ctx.AddSource( - "PatternContainerAttribute.g.cs", - SourceText.From(AttributeSourceCode, Encoding.UTF8))); - - var provider = context.SyntaxProvider - .ForAttributeWithMetadataName( - "HydraScript.Infrastructure.PatternContainerAttribute`1", - static (s, _) => IsSyntaxTargetForGeneration(s), - static (ctx, _) => GetTypeDeclarationForSourceGen(ctx)) - .Where(static x => x is not null) - .Select(static (x, _) => x!); - - context.RegisterImplementationSourceOutput(provider.Collect(), GenerateCode); - } - - private static bool IsSyntaxTargetForGeneration(SyntaxNode node) => - node is ClassDeclarationSyntax candidate && - candidate.Modifiers.Any(SyntaxKind.PartialKeyword) && - candidate.Modifiers.Any(SyntaxKind.InternalKeyword); - - private static RegexContainerInfo? GetTypeDeclarationForSourceGen( - GeneratorAttributeSyntaxContext context) - { - var attribute = context.Attributes.FirstOrDefault(); - if (attribute is null) - return null; - var visitable = (ClassDeclarationSyntax)context.TargetNode; - var json = attribute.ConstructorArguments.First().Value!.ToString()!; - return new RegexContainerInfo( - ClassName: visitable.Identifier.Text, - json); - } + var tokenTypes = JsonSerializer.Deserialize( + Provider.TokenTypesJsonString, + PatternGeneratorContext.Default.IEnumerableTokenType)! + .OrderBy(x => x.Priority) + .Concat([new TokenType("ERROR", @"\S+", int.MaxValue)]); + var pattern = string.Join('|', tokenTypes.Select(t => t.GetNamedRegex())); - private static void GenerateCode( - SourceProductionContext context, - ImmutableArray containerInfos) - { - foreach (var info in containerInfos) - { - var tokenTypes = JsonSerializer.Deserialize( - info.Json, - PatternGeneratorContext.Default.IEnumerableTokenType)! - .OrderBy(x => x.Priority) - .Concat([new TokenType("ERROR", @"\S+", int.MaxValue)]); - var pattern = string.Join('|', tokenTypes.Select(t => t.GetNamedRegex())); - - var code = $@"// + var code = $@"// using System.Diagnostics.CodeAnalysis; namespace HydraScript.Infrastructure; -internal partial class {info.ClassName} +internal partial class PatternContainer {{ [StringSyntax(StringSyntaxAttribute.Regex)] - public const string Pattern = - """""" - {pattern} - """"""; + public const string Value = """"""{pattern}""""""; }} "; - context.AddSource($"{info.ClassName}.g.cs", SourceText.From(code, Encoding.UTF8)); - } - } - private record RegexContainerInfo( - string ClassName, - string Json); + context.RegisterPostInitializationOutput(ctx => ctx.AddSource( + "PatternContainer.g.cs", + SourceText.From(code, Encoding.UTF8))); + } } \ No newline at end of file diff --git a/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs b/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs index 67df795..62b08b3 100644 --- a/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs +++ b/src/Infrastructure/HydraScript.Infrastructure/GeneratedRegexContainer.cs @@ -3,9 +3,8 @@ namespace HydraScript.Infrastructure; -[PatternContainer(TokenTypesJson.String)] internal partial class GeneratedRegexContainer : IGeneratedRegexContainer { - [GeneratedRegex("""(?[/]{2}.*)|(?[0-9]+[.][0-9]+)|(?[0-9]+)|(?null)|(?true|false)|(?\"(\\.|[^"\\])*\")|(?let|const|function|if|else|while|break|continue|return|as|type)|(?[+]{1,2}|[-]|[*]|[/]|[%]|([!]|[=])[=]|([<]|[>])[=]?|[!]|[|]{2}|[&]{2}|[~]|[:]{2})|(?[a-zA-Z][a-zA-Z0-9]*)|(?[?])|(?[:])|(?[;])|(?[=])|(?[,])|(?[{])|(?[}])|(?[(])|(?[)])|(?[.])|(?[[])|(?[]])|(?\S+)""")] + [GeneratedRegex(PatternContainer.Value)] public static partial Regex GetRegex(); } \ No newline at end of file diff --git a/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj b/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj index 1f0b0ca..8b0b58e 100644 --- a/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj +++ b/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests.csproj @@ -8,6 +8,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/PatternGeneratorTests.cs b/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/PatternGeneratorTests.cs index 10840d0..7fd07bf 100644 --- a/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/PatternGeneratorTests.cs +++ b/tests/HydraScript.Infrastructure.LexerRegexGenerator.Tests/PatternGeneratorTests.cs @@ -1,31 +1,54 @@ using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using NSubstitute; using Xunit; namespace HydraScript.Infrastructure.LexerRegexGenerator.Tests; public class PatternGeneratorTests { + [StringSyntax(StringSyntaxAttribute.Json)] + private const string JsonStringReplacement = + """ + [ + { + "tag": "Test2", + "pattern": "test2", + "priority": 2 + }, + { + "tag": "Test1", + "pattern": "test1", + "priority": 1 + } + ] + """; + [Fact] public void Initialize_PatternContainerMarked_CorrectlyGenerated() { - var inputCompilation = CreateCompilation( - """ - using System.Text.RegularExpressions; - using HydraScript.Domain.FrontEnd.Lexer; + var provider = Substitute.For(); + provider.TokenTypesJsonString.Returns(JsonStringReplacement); + var generator = new PatternGenerator + { + Provider = provider + }; + GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - namespace HydraScript.Infrastructure; + driver = driver.RunGeneratorsAndUpdateCompilation(CreateCompilation(string.Empty), out var outputCompilation, + out var diagnostics); + Debug.Assert(diagnostics.IsEmpty); + Debug.Assert(outputCompilation.SyntaxTrees.Count() == 2); - [PatternContainer("[{ \"tag\": \"Number\", \"pattern\": \"[0-9]+\", \"priority\": 2 }, { \"tag\": \"Word\", \"pattern\": \"[a-zA-Z]+\", \"priority\": 1 }]")] - internal partial class TestPatternContainer : IGeneratedRegexContainer - { - public static Regex GetRegex() => throw new NotImplementedException(); - } - """); + var runResult = driver.GetRunResult(); - const string expectedSource = + var generatedFileSyntax = runResult.GeneratedTrees + .Single(t => t.FilePath.EndsWith("PatternContainer.g.cs")); + + const string expectedSource = """" // @@ -33,30 +56,14 @@ internal partial class TestPatternContainer : IGeneratedRegexContainer namespace HydraScript.Infrastructure; -internal partial class TestPatternContainer +internal partial class PatternContainer { [StringSyntax(StringSyntaxAttribute.Regex)] - public const string Pattern = - """ - (?[a-zA-Z]+)|(?[0-9]+)|(?\S+) - """; + public const string Value = """(?test1)|(?test2)|(?\S+)"""; } """"; - var generator = new PatternGenerator(); - GeneratorDriver driver = CSharpGeneratorDriver.Create(generator); - - driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, - out var diagnostics); - Debug.Assert(diagnostics.IsEmpty); - Debug.Assert(outputCompilation.SyntaxTrees.Count() == 3); - - var runResult = driver.GetRunResult(); - - var generatedFileSyntax = runResult.GeneratedTrees - .Single(t => t.FilePath.EndsWith("TestPatternContainer.g.cs")); - Assert.Equal( expectedSource, generatedFileSyntax.GetText().ToString(), diff --git a/tests/HydraScript.Tests/Unit/Infrastructure/GeneratedRegexContainerTests.cs b/tests/HydraScript.Tests/Unit/Infrastructure/GeneratedRegexContainerTests.cs deleted file mode 100644 index 895b5b8..0000000 --- a/tests/HydraScript.Tests/Unit/Infrastructure/GeneratedRegexContainerTests.cs +++ /dev/null @@ -1,13 +0,0 @@ -using HydraScript.Infrastructure; -using Xunit; - -namespace HydraScript.Tests.Unit.Infrastructure; - -public class GeneratedRegexContainerTests -{ - [Fact] - public void GetRegex_Generated_ManualIsUpToDate() => - GeneratedRegexContainer.Pattern.Trim().Should().Be( - GeneratedRegexContainer.GetRegex().ToString(), - "because В атрибут GeneratedRegex не подставлена актуальная сгенерированная регулярка"); -} \ No newline at end of file