diff --git a/samples/simple/ExamplePythonDependency/ExamplePythonDependency.csproj b/samples/simple/ExamplePythonDependency/ExamplePythonDependency.csproj index 47854904..eaddc601 100644 --- a/samples/simple/ExamplePythonDependency/ExamplePythonDependency.csproj +++ b/samples/simple/ExamplePythonDependency/ExamplePythonDependency.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/CSnakes/CSnakes.csproj b/src/CSnakes.SourceGeneration/CSnakes.SourceGeneration.csproj similarity index 97% rename from src/CSnakes/CSnakes.csproj rename to src/CSnakes.SourceGeneration/CSnakes.SourceGeneration.csproj index 2a250515..3a42def7 100644 --- a/src/CSnakes/CSnakes.csproj +++ b/src/CSnakes.SourceGeneration/CSnakes.SourceGeneration.csproj @@ -1,33 +1,33 @@ - - - netstandard2.0 - true - $(NoWarn);RS1035 - true - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - $(GetTargetPathDependsOn);GetDependencyTargetPaths - - - - - - - - - - + + + netstandard2.0 + true + $(NoWarn);RS1035 + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + $(GetTargetPathDependsOn);GetDependencyTargetPaths + + + + + + + + + + diff --git a/src/CSnakes/CallerArgumentExpressionAttribute.cs b/src/CSnakes.SourceGeneration/CallerArgumentExpressionAttribute.cs similarity index 97% rename from src/CSnakes/CallerArgumentExpressionAttribute.cs rename to src/CSnakes.SourceGeneration/CallerArgumentExpressionAttribute.cs index 0df37a44..8e87a417 100644 --- a/src/CSnakes/CallerArgumentExpressionAttribute.cs +++ b/src/CSnakes.SourceGeneration/CallerArgumentExpressionAttribute.cs @@ -1,44 +1,44 @@ -// credit: https://github.com/SimonCropp/Polyfill/blob/main/src/Polyfill/CallerArgumentExpressionAttribute.cs -// Bringing in the file rather than using the library to avoid problems of additional libraries -// within a source generator. - -// -#pragma warning disable - -#if NETFRAMEWORK || NETSTANDARD || NETCOREAPP2X - -namespace System.Runtime.CompilerServices; - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using Link = System.ComponentModel.DescriptionAttribute; - -/// -/// Indicates that a parameter captures the expression passed for another parameter as a string. -/// -[ExcludeFromCodeCoverage] -[DebuggerNonUserCode] -[AttributeUsage(AttributeTargets.Parameter)] -[Link("https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callerargumentexpressionattribute")] -#if PolyPublic -public -#endif -sealed class CallerArgumentExpressionAttribute : - Attribute -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// The name of the parameter whose expression should be captured as a string. - /// - public CallerArgumentExpressionAttribute(string parameterName) => - ParameterName = parameterName; - - /// - /// Gets the name of the parameter whose expression should be captured as a string. - /// - public string ParameterName { get; } -} - +// credit: https://github.com/SimonCropp/Polyfill/blob/main/src/Polyfill/CallerArgumentExpressionAttribute.cs +// Bringing in the file rather than using the library to avoid problems of additional libraries +// within a source generator. + +// +#pragma warning disable + +#if NETFRAMEWORK || NETSTANDARD || NETCOREAPP2X + +namespace System.Runtime.CompilerServices; + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Link = System.ComponentModel.DescriptionAttribute; + +/// +/// Indicates that a parameter captures the expression passed for another parameter as a string. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage(AttributeTargets.Parameter)] +[Link("https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callerargumentexpressionattribute")] +#if PolyPublic +public +#endif +sealed class CallerArgumentExpressionAttribute : + Attribute +{ + /// + /// Initializes a new instance of the class. + /// + /// + /// The name of the parameter whose expression should be captured as a string. + /// + public CallerArgumentExpressionAttribute(string parameterName) => + ParameterName = parameterName; + + /// + /// Gets the name of the parameter whose expression should be captured as a string. + /// + public string ParameterName { get; } +} + #endif \ No newline at end of file diff --git a/src/CSnakes/CaseHelper.cs b/src/CSnakes.SourceGeneration/CaseHelper.cs similarity index 97% rename from src/CSnakes/CaseHelper.cs rename to src/CSnakes.SourceGeneration/CaseHelper.cs index ef40d602..dadda49e 100644 --- a/src/CSnakes/CaseHelper.cs +++ b/src/CSnakes.SourceGeneration/CaseHelper.cs @@ -1,16 +1,16 @@ -namespace CSnakes -{ - public static class CaseHelper - { - public static string ToPascalCase(this string snakeCase) - { - return string.Join("", snakeCase.Split('_').Select(s => s.Length > 1 ? char.ToUpperInvariant(s[0]) + s.Substring(1) : "_")); - } - - public static string ToLowerPascalCase(this string snakeCase) - { - // Make sure the first letter is lowercase - return char.ToLowerInvariant(snakeCase[0]) + ToPascalCase(snakeCase).Substring(1); - } - } -} +namespace CSnakes +{ + public static class CaseHelper + { + public static string ToPascalCase(this string snakeCase) + { + return string.Join("", snakeCase.Split('_').Select(s => s.Length > 1 ? char.ToUpperInvariant(s[0]) + s.Substring(1) : "_")); + } + + public static string ToLowerPascalCase(this string snakeCase) + { + // Make sure the first letter is lowercase + return char.ToLowerInvariant(snakeCase[0]) + ToPascalCase(snakeCase).Substring(1); + } + } +} diff --git a/src/CSnakes/GeneratorError.cs b/src/CSnakes.SourceGeneration/GeneratorError.cs similarity index 95% rename from src/CSnakes/GeneratorError.cs rename to src/CSnakes.SourceGeneration/GeneratorError.cs index 8f840118..c0488320 100644 --- a/src/CSnakes/GeneratorError.cs +++ b/src/CSnakes.SourceGeneration/GeneratorError.cs @@ -1,27 +1,27 @@ -namespace CSnakes; - -public class GeneratorError -{ - - public string Message { get; } - - public int StartLine { get; } - - public int StartColumn { get; } - - public int EndLine { get; } - - public int EndColumn { get; } - - public string Code { get; } - - public GeneratorError(int startLine, int endLine, int startColumn, int endColumn, string message) - { - Message = message; - StartLine = startLine; - StartColumn = startColumn; - EndLine = endLine == -1 ? startLine : endLine; - EndColumn = endColumn; - Code = "hello"; - } -} +namespace CSnakes; + +public class GeneratorError +{ + + public string Message { get; } + + public int StartLine { get; } + + public int StartColumn { get; } + + public int EndLine { get; } + + public int EndColumn { get; } + + public string Code { get; } + + public GeneratorError(int startLine, int endLine, int startColumn, int endColumn, string message) + { + Message = message; + StartLine = startLine; + StartColumn = startColumn; + EndLine = endLine == -1 ? startLine : endLine; + EndColumn = endColumn; + Code = "hello"; + } +} diff --git a/src/CSnakes/Keywords.cs b/src/CSnakes.SourceGeneration/Keywords.cs similarity index 94% rename from src/CSnakes/Keywords.cs rename to src/CSnakes.SourceGeneration/Keywords.cs index bf7eb966..34da11e9 100644 --- a/src/CSnakes/Keywords.cs +++ b/src/CSnakes.SourceGeneration/Keywords.cs @@ -1,97 +1,97 @@ -namespace CSnakes; -internal static class Keywords -{ - static readonly string[] cSharpKeywords = [ - "abstract", - "as", - "base", - "bool", - "break", - "byte", - "case", - "catch", - "char", - "checked", - "class", - "const", - "continue", - "decimal", - "default", - "delegate", - "do", - "double", - "else", - "enum", - "event", - "explicit", - "extern", - "false", - "finally", - "fixed", - "float", - "for", - "foreach", - "goto", - "if", - "implicit", - "in", - "int", - "interface", - "internal", - "is", - "lock", - "long", - "namespace", - "new", - "null", - "object", - "operator", - "out", - "override", - "params", - "private", - "protected", - "public", - "readonly", - "ref", - "return", - "sbyte", - "sealed", - "short", - "sizeof", - "stackalloc", - "static", - "string", - "struct", - "switch", - "this", - "throw", - "true", - "try", - "typeof", - "uint", - "ulong", - "unchecked", - "unsafe", - "ushort", - "using", - "virtual", - "void", - "volatile", - "while" - ]; - - public static bool IsKeyword(string name) - { - return cSharpKeywords.Contains(name); - } - - public static string ValidIdentifier(string identifier) - { - if (!IsKeyword(identifier)) - { - return identifier; - } - return $"@{identifier}"; - } -} +namespace CSnakes; +internal static class Keywords +{ + static readonly string[] cSharpKeywords = [ + "abstract", + "as", + "base", + "bool", + "break", + "byte", + "case", + "catch", + "char", + "checked", + "class", + "const", + "continue", + "decimal", + "default", + "delegate", + "do", + "double", + "else", + "enum", + "event", + "explicit", + "extern", + "false", + "finally", + "fixed", + "float", + "for", + "foreach", + "goto", + "if", + "implicit", + "in", + "int", + "interface", + "internal", + "is", + "lock", + "long", + "namespace", + "new", + "null", + "object", + "operator", + "out", + "override", + "params", + "private", + "protected", + "public", + "readonly", + "ref", + "return", + "sbyte", + "sealed", + "short", + "sizeof", + "stackalloc", + "static", + "string", + "struct", + "switch", + "this", + "throw", + "true", + "try", + "typeof", + "uint", + "ulong", + "unchecked", + "unsafe", + "ushort", + "using", + "virtual", + "void", + "volatile", + "while" + ]; + + public static bool IsKeyword(string name) + { + return cSharpKeywords.Contains(name); + } + + public static string ValidIdentifier(string identifier) + { + if (!IsKeyword(identifier)) + { + return identifier; + } + return $"@{identifier}"; + } +} diff --git a/src/CSnakes/NotNullAttributePolyfill.cs b/src/CSnakes.SourceGeneration/NotNullAttributePolyfill.cs similarity index 96% rename from src/CSnakes/NotNullAttributePolyfill.cs rename to src/CSnakes.SourceGeneration/NotNullAttributePolyfill.cs index f16162cf..18a93f74 100644 --- a/src/CSnakes/NotNullAttributePolyfill.cs +++ b/src/CSnakes.SourceGeneration/NotNullAttributePolyfill.cs @@ -1,30 +1,30 @@ -// credit: https://github.com/SimonCropp/Polyfill/blob/main/src/Polyfill/Nullable/NotNullAttribute.cs -// Bringing in the file rather than using the library to avoid problems of additional libraries -// within a source generator. - -// -#pragma warning disable - -#if NETSTANDARD2_0 || NETFRAMEWORK || NETCOREAPP2X - -namespace System.Diagnostics.CodeAnalysis; - -using Targets = AttributeTargets; - -/// -/// Specifies that an output is not even if the -/// corresponding type allows it. -/// -[ExcludeFromCodeCoverage] -[DebuggerNonUserCode] -[AttributeUsage( - validOn: Targets.Field | - Targets.Parameter | - Targets.Property | - Targets.ReturnValue)] -#if PolyPublic -public -#endif -sealed class NotNullAttribute : - Attribute; +// credit: https://github.com/SimonCropp/Polyfill/blob/main/src/Polyfill/Nullable/NotNullAttribute.cs +// Bringing in the file rather than using the library to avoid problems of additional libraries +// within a source generator. + +// +#pragma warning disable + +#if NETSTANDARD2_0 || NETFRAMEWORK || NETCOREAPP2X + +namespace System.Diagnostics.CodeAnalysis; + +using Targets = AttributeTargets; + +/// +/// Specifies that an output is not even if the +/// corresponding type allows it. +/// +[ExcludeFromCodeCoverage] +[DebuggerNonUserCode] +[AttributeUsage( + validOn: Targets.Field | + Targets.Parameter | + Targets.Property | + Targets.ReturnValue)] +#if PolyPublic +public +#endif +sealed class NotNullAttribute : + Attribute; #endif \ No newline at end of file diff --git a/src/CSnakes/NullableExtensions.cs b/src/CSnakes.SourceGeneration/NullableExtensions.cs similarity index 97% rename from src/CSnakes/NullableExtensions.cs rename to src/CSnakes.SourceGeneration/NullableExtensions.cs index 8acf77bf..8f11c562 100644 --- a/src/CSnakes/NullableExtensions.cs +++ b/src/CSnakes.SourceGeneration/NullableExtensions.cs @@ -1,26 +1,26 @@ -// credit: https://github.com/dotnet/razor/blob/main/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/NullableExtensions.cs - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime; - -namespace CSnakes; -internal static class NullableExtensions -{ - [DebuggerStepThrough] - [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] - public static T AssumeNotNull( - [NotNull] this T? obj, - [CallerArgumentExpression(nameof(obj))] string? objExpression = null) - where T : class - => obj ?? throw new InvalidOperationException($"Expected '{objExpression}' to be non-null."); - - [DebuggerStepThrough] - [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] - public static T AssumeNotNull( - [NotNull] this T? obj, - [CallerArgumentExpression(nameof(obj))] string? objExpression = null) - where T : struct - => obj ?? throw new InvalidOperationException($"Expected '{objExpression}' to be non-null."); +// credit: https://github.com/dotnet/razor/blob/main/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/NullableExtensions.cs + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime; + +namespace CSnakes; +internal static class NullableExtensions +{ + [DebuggerStepThrough] + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + public static T AssumeNotNull( + [NotNull] this T? obj, + [CallerArgumentExpression(nameof(obj))] string? objExpression = null) + where T : class + => obj ?? throw new InvalidOperationException($"Expected '{objExpression}' to be non-null."); + + [DebuggerStepThrough] + [TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")] + public static T AssumeNotNull( + [NotNull] this T? obj, + [CallerArgumentExpression(nameof(obj))] string? objExpression = null) + where T : struct + => obj ?? throw new InvalidOperationException($"Expected '{objExpression}' to be non-null."); } \ No newline at end of file diff --git a/src/CSnakes/Packaging.targets b/src/CSnakes.SourceGeneration/Packaging.targets similarity index 90% rename from src/CSnakes/Packaging.targets rename to src/CSnakes.SourceGeneration/Packaging.targets index ca5504af..37ce9ac9 100644 --- a/src/CSnakes/Packaging.targets +++ b/src/CSnakes.SourceGeneration/Packaging.targets @@ -1,7 +1,7 @@ - CSnakes - CSnakes + CSnakes.SourceGeneration + CSnakes.SourceGeneration $(NoWarn);NU5128 diff --git a/src/CSnakes/Parser/PythonParser.Args.cs b/src/CSnakes.SourceGeneration/Parser/PythonParser.Args.cs similarity index 98% rename from src/CSnakes/Parser/PythonParser.Args.cs rename to src/CSnakes.SourceGeneration/Parser/PythonParser.Args.cs index 83aa7deb..7bf5579a 100644 --- a/src/CSnakes/Parser/PythonParser.Args.cs +++ b/src/CSnakes.SourceGeneration/Parser/PythonParser.Args.cs @@ -1,30 +1,30 @@ -using CSnakes.Parser.Types; -using Superpower; -using Superpower.Parsers; - -namespace CSnakes.Parser; -public static partial class PythonParser -{ - public static TokenListParser PythonStarArgTokenizer { get; } = - (from star in Token.EqualTo(PythonToken.Asterisk) - from name in Token.EqualTo(PythonToken.Identifier).Optional() - select new PythonParameterType(name.HasValue ? name.Value.ToStringValue() : "args", PythonFunctionParameterType.Star)) - .Named("Star Arg"); - - public static TokenListParser PythonDoubleStarArgTokenizer { get; } = - (from star in Token.EqualTo(PythonToken.DoubleAsterisk) - from name in Token.EqualTo(PythonToken.Identifier) - select new PythonParameterType(name.ToStringValue(), PythonFunctionParameterType.DoubleStar)) - .Named("Double Star Arg"); - - public static TokenListParser PythonNormalArgTokenizer { get; } = - (from name in Token.EqualTo(PythonToken.Identifier) - select new PythonParameterType(name.ToStringValue(), PythonFunctionParameterType.Normal)) - .Named("Normal Arg"); - - public static TokenListParser PythonArgTokenizer { get; } = - PythonStarArgTokenizer.AsNullable() - .Or(PythonDoubleStarArgTokenizer.AsNullable()) - .Or(PythonNormalArgTokenizer.AsNullable()) - .Named("Arg"); -} +using CSnakes.Parser.Types; +using Superpower; +using Superpower.Parsers; + +namespace CSnakes.Parser; +public static partial class PythonParser +{ + public static TokenListParser PythonStarArgTokenizer { get; } = + (from star in Token.EqualTo(PythonToken.Asterisk) + from name in Token.EqualTo(PythonToken.Identifier).Optional() + select new PythonParameterType(name.HasValue ? name.Value.ToStringValue() : "args", PythonFunctionParameterType.Star)) + .Named("Star Arg"); + + public static TokenListParser PythonDoubleStarArgTokenizer { get; } = + (from star in Token.EqualTo(PythonToken.DoubleAsterisk) + from name in Token.EqualTo(PythonToken.Identifier) + select new PythonParameterType(name.ToStringValue(), PythonFunctionParameterType.DoubleStar)) + .Named("Double Star Arg"); + + public static TokenListParser PythonNormalArgTokenizer { get; } = + (from name in Token.EqualTo(PythonToken.Identifier) + select new PythonParameterType(name.ToStringValue(), PythonFunctionParameterType.Normal)) + .Named("Normal Arg"); + + public static TokenListParser PythonArgTokenizer { get; } = + PythonStarArgTokenizer.AsNullable() + .Or(PythonDoubleStarArgTokenizer.AsNullable()) + .Or(PythonNormalArgTokenizer.AsNullable()) + .Named("Arg"); +} diff --git a/src/CSnakes/Parser/PythonParser.Constants.cs b/src/CSnakes.SourceGeneration/Parser/PythonParser.Constants.cs similarity index 97% rename from src/CSnakes/Parser/PythonParser.Constants.cs rename to src/CSnakes.SourceGeneration/Parser/PythonParser.Constants.cs index de005cf4..f5e3744b 100644 --- a/src/CSnakes/Parser/PythonParser.Constants.cs +++ b/src/CSnakes.SourceGeneration/Parser/PythonParser.Constants.cs @@ -1,128 +1,128 @@ -using CSnakes.Parser.Types; -using Superpower; -using Superpower.Model; -using Superpower.Parsers; -using System.Globalization; - -namespace CSnakes.Parser; -public static partial class PythonParser -{ - public static TextParser UnderScoreOrDigit { get; } = - Character.Matching(char.IsDigit, "digit").Or(Character.EqualTo('_')); - - public static TextParser IntegerConstantToken { get; } = - from sign in Character.EqualTo('-').OptionalOrDefault() - from firstdigit in Character.Digit - from digits in UnderScoreOrDigit.Many().OptionalOrDefault([]) - select Unit.Value; - - public static TextParser DecimalConstantToken { get; } = - from sign in Character.EqualTo('-').OptionalOrDefault() - from first in Character.Digit - from rest in UnderScoreOrDigit.Or(Character.In('.', 'e', 'E', '+', '-')).IgnoreMany() - select Unit.Value; - - public static TextParser HexidecimalConstantToken { get; } = - from prefix in Span.EqualTo("0x") - from digits in Character.EqualTo('_').Or(Character.HexDigit).AtLeastOnce() - select Unit.Value; - - public static TextParser BinaryConstantToken { get; } = - from prefix in Span.EqualTo("0b") - from digits in Character.In('0', '1', '_').AtLeastOnce() - select Unit.Value; - - public static TextParser DoubleQuotedStringConstantToken { get; } = - from open in Character.EqualTo('"') - from chars in Character.ExceptIn('"').Many() - from close in Character.EqualTo('"') - select Unit.Value; - - public static TextParser SingleQuotedStringConstantToken { get; } = - from open in Character.EqualTo('\'') - from chars in Character.ExceptIn('\'').Many() - from close in Character.EqualTo('\'') - select Unit.Value; - - public static TokenListParser DoubleQuotedStringConstantTokenizer { get; } = - Token.EqualTo(PythonToken.DoubleQuotedString) - .Apply(ConstantParsers.DoubleQuotedString) - .Select(s => new PythonConstant(s)) - .Named("Double Quoted String Constant"); - - public static TokenListParser SingleQuotedStringConstantTokenizer { get; } = - Token.EqualTo(PythonToken.SingleQuotedString) - .Apply(ConstantParsers.SingleQuotedString) - .Select(s => new PythonConstant(s)) - .Named("Single Quoted String Constant"); - - public static TokenListParser DecimalConstantTokenizer { get; } = - Token.EqualTo(PythonToken.Decimal) - .Select(token => new PythonConstant(double.Parse(token.ToStringValue().Replace("_", ""), NumberStyles.Float, CultureInfo.InvariantCulture))) - .Named("Decimal Constant"); - - public static TokenListParser IntegerConstantTokenizer { get; } = - Token.EqualTo(PythonToken.Integer) - .Select(d => new PythonConstant(long.Parse(d.ToStringValue().Replace("_", ""), NumberStyles.Integer))) - .Named("Integer Constant"); - - public static TokenListParser HexidecimalIntegerConstantTokenizer { get; } = - Token.EqualTo(PythonToken.HexidecimalInteger) - .Select(d => new PythonConstant { Type = PythonConstant.ConstantType.HexidecimalInteger, IntegerValue = long.Parse(d.ToStringValue().Substring(2).Replace("_", ""), NumberStyles.HexNumber) }) - .Named("Hexidecimal Integer Constant"); - - public static TokenListParser BinaryIntegerConstantTokenizer { get; } = - Token.EqualTo(PythonToken.BinaryInteger) - // TODO: Consider Binary Format specifier introduced in .NET 8 https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#binary-format-specifier-b - .Select(d => new PythonConstant { Type = PythonConstant.ConstantType.BinaryInteger, IntegerValue = (long)Convert.ToUInt64(d.ToStringValue().Substring(2).Replace("_", ""), 2) }) - .Named("Binary Integer Constant"); - - public static TokenListParser BoolConstantTokenizer { get; } = - Token.EqualTo(PythonToken.True).Or(Token.EqualTo(PythonToken.False)) - .Select(d => new PythonConstant(d.Kind == PythonToken.True)) - .Named("Bool Constant"); - - public static TokenListParser NoneConstantTokenizer { get; } = - Token.EqualTo(PythonToken.None) - .Select(d => PythonConstant.FromNone()) - .Named("None Constant"); - - // Any constant value - public static TokenListParser ConstantValueTokenizer { get; } = - DecimalConstantTokenizer.AsNullable() - .Or(IntegerConstantTokenizer.AsNullable()) - .Or(HexidecimalIntegerConstantTokenizer.AsNullable()) - .Or(BinaryIntegerConstantTokenizer.AsNullable()) - .Or(BoolConstantTokenizer.AsNullable()) - .Or(NoneConstantTokenizer.AsNullable()) - .Or(DoubleQuotedStringConstantTokenizer.AsNullable()) - .Or(SingleQuotedStringConstantTokenizer.AsNullable()) - .Named("Constant"); - - static class ConstantParsers - { - public static TextParser DoubleQuotedString { get; } = - from open in Character.EqualTo('"') - from chars in Character.ExceptIn('"', '\\') - .Or(Character.EqualTo('\\') - .IgnoreThen( - Character.EqualTo('\\') - .Or(Character.EqualTo('"')) - .Named("escape sequence"))) - .Many() - from close in Character.EqualTo('"') - select new string(chars); - - public static TextParser SingleQuotedString { get; } = - from open in Character.EqualTo('\'') - from chars in Character.ExceptIn('\'', '\\') - .Or(Character.EqualTo('\\') - .IgnoreThen( - Character.EqualTo('\\') - .Or(Character.EqualTo('\'')) - .Named("escape sequence"))) - .Many() - from close in Character.EqualTo('\'') - select new string(chars); - } -} +using CSnakes.Parser.Types; +using Superpower; +using Superpower.Model; +using Superpower.Parsers; +using System.Globalization; + +namespace CSnakes.Parser; +public static partial class PythonParser +{ + public static TextParser UnderScoreOrDigit { get; } = + Character.Matching(char.IsDigit, "digit").Or(Character.EqualTo('_')); + + public static TextParser IntegerConstantToken { get; } = + from sign in Character.EqualTo('-').OptionalOrDefault() + from firstdigit in Character.Digit + from digits in UnderScoreOrDigit.Many().OptionalOrDefault([]) + select Unit.Value; + + public static TextParser DecimalConstantToken { get; } = + from sign in Character.EqualTo('-').OptionalOrDefault() + from first in Character.Digit + from rest in UnderScoreOrDigit.Or(Character.In('.', 'e', 'E', '+', '-')).IgnoreMany() + select Unit.Value; + + public static TextParser HexidecimalConstantToken { get; } = + from prefix in Span.EqualTo("0x") + from digits in Character.EqualTo('_').Or(Character.HexDigit).AtLeastOnce() + select Unit.Value; + + public static TextParser BinaryConstantToken { get; } = + from prefix in Span.EqualTo("0b") + from digits in Character.In('0', '1', '_').AtLeastOnce() + select Unit.Value; + + public static TextParser DoubleQuotedStringConstantToken { get; } = + from open in Character.EqualTo('"') + from chars in Character.ExceptIn('"').Many() + from close in Character.EqualTo('"') + select Unit.Value; + + public static TextParser SingleQuotedStringConstantToken { get; } = + from open in Character.EqualTo('\'') + from chars in Character.ExceptIn('\'').Many() + from close in Character.EqualTo('\'') + select Unit.Value; + + public static TokenListParser DoubleQuotedStringConstantTokenizer { get; } = + Token.EqualTo(PythonToken.DoubleQuotedString) + .Apply(ConstantParsers.DoubleQuotedString) + .Select(s => new PythonConstant(s)) + .Named("Double Quoted String Constant"); + + public static TokenListParser SingleQuotedStringConstantTokenizer { get; } = + Token.EqualTo(PythonToken.SingleQuotedString) + .Apply(ConstantParsers.SingleQuotedString) + .Select(s => new PythonConstant(s)) + .Named("Single Quoted String Constant"); + + public static TokenListParser DecimalConstantTokenizer { get; } = + Token.EqualTo(PythonToken.Decimal) + .Select(token => new PythonConstant(double.Parse(token.ToStringValue().Replace("_", ""), NumberStyles.Float, CultureInfo.InvariantCulture))) + .Named("Decimal Constant"); + + public static TokenListParser IntegerConstantTokenizer { get; } = + Token.EqualTo(PythonToken.Integer) + .Select(d => new PythonConstant(long.Parse(d.ToStringValue().Replace("_", ""), NumberStyles.Integer))) + .Named("Integer Constant"); + + public static TokenListParser HexidecimalIntegerConstantTokenizer { get; } = + Token.EqualTo(PythonToken.HexidecimalInteger) + .Select(d => new PythonConstant { Type = PythonConstant.ConstantType.HexidecimalInteger, IntegerValue = long.Parse(d.ToStringValue().Substring(2).Replace("_", ""), NumberStyles.HexNumber) }) + .Named("Hexidecimal Integer Constant"); + + public static TokenListParser BinaryIntegerConstantTokenizer { get; } = + Token.EqualTo(PythonToken.BinaryInteger) + // TODO: Consider Binary Format specifier introduced in .NET 8 https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#binary-format-specifier-b + .Select(d => new PythonConstant { Type = PythonConstant.ConstantType.BinaryInteger, IntegerValue = (long)Convert.ToUInt64(d.ToStringValue().Substring(2).Replace("_", ""), 2) }) + .Named("Binary Integer Constant"); + + public static TokenListParser BoolConstantTokenizer { get; } = + Token.EqualTo(PythonToken.True).Or(Token.EqualTo(PythonToken.False)) + .Select(d => new PythonConstant(d.Kind == PythonToken.True)) + .Named("Bool Constant"); + + public static TokenListParser NoneConstantTokenizer { get; } = + Token.EqualTo(PythonToken.None) + .Select(d => PythonConstant.FromNone()) + .Named("None Constant"); + + // Any constant value + public static TokenListParser ConstantValueTokenizer { get; } = + DecimalConstantTokenizer.AsNullable() + .Or(IntegerConstantTokenizer.AsNullable()) + .Or(HexidecimalIntegerConstantTokenizer.AsNullable()) + .Or(BinaryIntegerConstantTokenizer.AsNullable()) + .Or(BoolConstantTokenizer.AsNullable()) + .Or(NoneConstantTokenizer.AsNullable()) + .Or(DoubleQuotedStringConstantTokenizer.AsNullable()) + .Or(SingleQuotedStringConstantTokenizer.AsNullable()) + .Named("Constant"); + + static class ConstantParsers + { + public static TextParser DoubleQuotedString { get; } = + from open in Character.EqualTo('"') + from chars in Character.ExceptIn('"', '\\') + .Or(Character.EqualTo('\\') + .IgnoreThen( + Character.EqualTo('\\') + .Or(Character.EqualTo('"')) + .Named("escape sequence"))) + .Many() + from close in Character.EqualTo('"') + select new string(chars); + + public static TextParser SingleQuotedString { get; } = + from open in Character.EqualTo('\'') + from chars in Character.ExceptIn('\'', '\\') + .Or(Character.EqualTo('\\') + .IgnoreThen( + Character.EqualTo('\\') + .Or(Character.EqualTo('\'')) + .Named("escape sequence"))) + .Many() + from close in Character.EqualTo('\'') + select new string(chars); + } +} diff --git a/src/CSnakes/Parser/PythonParser.Function.cs b/src/CSnakes.SourceGeneration/Parser/PythonParser.Function.cs similarity index 97% rename from src/CSnakes/Parser/PythonParser.Function.cs rename to src/CSnakes.SourceGeneration/Parser/PythonParser.Function.cs index 8e674c3f..486ee04c 100644 --- a/src/CSnakes/Parser/PythonParser.Function.cs +++ b/src/CSnakes.SourceGeneration/Parser/PythonParser.Function.cs @@ -1,116 +1,116 @@ -using Microsoft.CodeAnalysis.Text; -using CSnakes.Parser.Types; -using Superpower; -using Superpower.Model; -using Superpower.Parsers; - -using ParsedTokens = Superpower.Model.TokenList; - -namespace CSnakes.Parser; -public static partial class PythonParser -{ - public static TokenListParser PythonFunctionDefinitionTokenizer { get; } = - (from def in Token.EqualTo(PythonToken.Def) - from name in Token.EqualTo(PythonToken.Identifier) - from parameters in PythonParameterListTokenizer.AssumeNotNull() - from arrow in Token.EqualTo(PythonToken.Arrow).Optional().Then(returnType => PythonTypeDefinitionTokenizer.AssumeNotNull().OptionalOrDefault()) - from colon in Token.EqualTo(PythonToken.Colon) - select new PythonFunctionDefinition(name.ToStringValue(), arrow, parameters)) - .Named("Function Definition"); - - /// - /// Checks if the line starts with def. - /// - /// Line to check - /// - static bool IsFunctionSignature(string line) => - line.StartsWith("def ") || line.StartsWith("async def"); - - public static bool TryParseFunctionDefinitions(SourceText source, out PythonFunctionDefinition[] pythonSignatures, out GeneratorError[] errors) - { - // Go line by line - TextLineCollection lines = source.Lines; - List currentErrors = []; - List<(IEnumerable lines, ParsedTokens tokens)> functionLines = []; - List<(TextLine line, ParsedTokens tokens)> currentBuffer = []; - bool unfinishedFunctionSpec = false; - foreach (TextLine line in lines) - { - string lineOfCode = line.ToString(); - if (!IsFunctionSignature(lineOfCode) && !unfinishedFunctionSpec) - { - continue; - } - - // Parse the function signature - Result result = PythonTokenizer.Instance.TryTokenize(lineOfCode); - if (!result.HasValue) - { - currentErrors.Add(new( - line.LineNumber, - line.LineNumber, - result.ErrorPosition.Column, - result.ErrorPosition.Column + result.Location.Length, - result.FormatErrorMessageFragment()) - ); - - // Reset buffer - currentBuffer = []; - unfinishedFunctionSpec = false; - continue; - } - - ParsedTokens repositionedTokens = new(result.Value.Select(token => - { - Superpower.Model.TextSpan span = new(token.Span.Source!, new(token.Span.Position.Absolute, line.LineNumber, token.Span.Position.Column), token.Span.Length); - Token t = new(token.Kind, span); - return t; - }).ToArray()); - currentBuffer.Add((line, repositionedTokens)); - - // If this is a function definition on one line.. - if (repositionedTokens.Last().Kind == PythonToken.Colon) - { - IEnumerable bufferLines = currentBuffer.Select(x => x.line); - ParsedTokens combinedTokens = new(currentBuffer.SelectMany(x => x.tokens).ToArray()); - - functionLines.Add((bufferLines, combinedTokens)); - currentBuffer = []; - unfinishedFunctionSpec = false; - continue; - } - else - { - unfinishedFunctionSpec = true; - } - } - - List functionDefinitions = []; - - foreach ((IEnumerable currentLines, ParsedTokens tokens) in functionLines) - { - TokenListParserResult functionDefinition = - PythonFunctionDefinitionTokenizer.TryParse(tokens); - if (functionDefinition.HasValue) - { - functionDefinitions.Add(functionDefinition.Value); - } - else - { - // Error parsing the function definition - int lineNumber = currentLines.First().LineNumber; - currentErrors.Add(new( - lineNumber, - lineNumber + functionDefinition.ErrorPosition.Line - 1, - functionDefinition.ErrorPosition.Column, - functionDefinition.ErrorPosition.Column + 1, - functionDefinition.FormatErrorMessageFragment()) - ); - } - } - - pythonSignatures = [.. functionDefinitions]; - errors = [.. currentErrors]; - return errors.Length == 0; - } -} +using Microsoft.CodeAnalysis.Text; +using CSnakes.Parser.Types; +using Superpower; +using Superpower.Model; +using Superpower.Parsers; + +using ParsedTokens = Superpower.Model.TokenList; + +namespace CSnakes.Parser; +public static partial class PythonParser +{ + public static TokenListParser PythonFunctionDefinitionTokenizer { get; } = + (from def in Token.EqualTo(PythonToken.Def) + from name in Token.EqualTo(PythonToken.Identifier) + from parameters in PythonParameterListTokenizer.AssumeNotNull() + from arrow in Token.EqualTo(PythonToken.Arrow).Optional().Then(returnType => PythonTypeDefinitionTokenizer.AssumeNotNull().OptionalOrDefault()) + from colon in Token.EqualTo(PythonToken.Colon) + select new PythonFunctionDefinition(name.ToStringValue(), arrow, parameters)) + .Named("Function Definition"); + + /// + /// Checks if the line starts with def. + /// + /// Line to check + /// + static bool IsFunctionSignature(string line) => + line.StartsWith("def ") || line.StartsWith("async def"); + + public static bool TryParseFunctionDefinitions(SourceText source, out PythonFunctionDefinition[] pythonSignatures, out GeneratorError[] errors) + { + // Go line by line + TextLineCollection lines = source.Lines; + List currentErrors = []; + List<(IEnumerable lines, ParsedTokens tokens)> functionLines = []; + List<(TextLine line, ParsedTokens tokens)> currentBuffer = []; + bool unfinishedFunctionSpec = false; + foreach (TextLine line in lines) + { + string lineOfCode = line.ToString(); + if (!IsFunctionSignature(lineOfCode) && !unfinishedFunctionSpec) + { + continue; + } + + // Parse the function signature + Result result = PythonTokenizer.Instance.TryTokenize(lineOfCode); + if (!result.HasValue) + { + currentErrors.Add(new( + line.LineNumber, + line.LineNumber, + result.ErrorPosition.Column, + result.ErrorPosition.Column + result.Location.Length, + result.FormatErrorMessageFragment()) + ); + + // Reset buffer + currentBuffer = []; + unfinishedFunctionSpec = false; + continue; + } + + ParsedTokens repositionedTokens = new(result.Value.Select(token => + { + Superpower.Model.TextSpan span = new(token.Span.Source!, new(token.Span.Position.Absolute, line.LineNumber, token.Span.Position.Column), token.Span.Length); + Token t = new(token.Kind, span); + return t; + }).ToArray()); + currentBuffer.Add((line, repositionedTokens)); + + // If this is a function definition on one line.. + if (repositionedTokens.Last().Kind == PythonToken.Colon) + { + IEnumerable bufferLines = currentBuffer.Select(x => x.line); + ParsedTokens combinedTokens = new(currentBuffer.SelectMany(x => x.tokens).ToArray()); + + functionLines.Add((bufferLines, combinedTokens)); + currentBuffer = []; + unfinishedFunctionSpec = false; + continue; + } + else + { + unfinishedFunctionSpec = true; + } + } + + List functionDefinitions = []; + + foreach ((IEnumerable currentLines, ParsedTokens tokens) in functionLines) + { + TokenListParserResult functionDefinition = + PythonFunctionDefinitionTokenizer.TryParse(tokens); + if (functionDefinition.HasValue) + { + functionDefinitions.Add(functionDefinition.Value); + } + else + { + // Error parsing the function definition + int lineNumber = currentLines.First().LineNumber; + currentErrors.Add(new( + lineNumber, + lineNumber + functionDefinition.ErrorPosition.Line - 1, + functionDefinition.ErrorPosition.Column, + functionDefinition.ErrorPosition.Column + 1, + functionDefinition.FormatErrorMessageFragment()) + ); + } + } + + pythonSignatures = [.. functionDefinitions]; + errors = [.. currentErrors]; + return errors.Length == 0; + } +} diff --git a/src/CSnakes/Parser/PythonParser.Parameters.cs b/src/CSnakes.SourceGeneration/Parser/PythonParser.Parameters.cs similarity index 97% rename from src/CSnakes/Parser/PythonParser.Parameters.cs rename to src/CSnakes.SourceGeneration/Parser/PythonParser.Parameters.cs index e71c4e5d..dce0cc28 100644 --- a/src/CSnakes/Parser/PythonParser.Parameters.cs +++ b/src/CSnakes.SourceGeneration/Parser/PythonParser.Parameters.cs @@ -1,35 +1,35 @@ -using CSnakes.Parser.Types; -using Superpower; -using Superpower.Parsers; - -namespace CSnakes.Parser; -public static partial class PythonParser -{ - public static TokenListParser PositionalOnlyParameterParser { get; } = - Token.EqualTo(PythonToken.Slash) - .Select(d => new PythonFunctionParameter("/", null, null, PythonFunctionParameterType.Slash)) - .Named("Positional Only Signal"); - - public static TokenListParser PythonParameterTokenizer { get; } = - (from arg in PythonArgTokenizer - from type in Token.EqualTo(PythonToken.Colon).Optional().Then( - _ => PythonTypeDefinitionTokenizer.AssumeNotNull().OptionalOrDefault() - ) - from defaultValue in Token.EqualTo(PythonToken.Equal).Optional().Then( - _ => ConstantValueTokenizer.AssumeNotNull().OptionalOrDefault() - ) - select new PythonFunctionParameter(arg.Name, type, defaultValue, arg.ParameterType)) - .Named("Parameter"); - - public static TokenListParser ParameterOrSlash { get; } = - PositionalOnlyParameterParser.AsNullable() - .Or(PythonParameterTokenizer.AsNullable()) - .Named("Parameter"); - - public static TokenListParser PythonParameterListTokenizer { get; } = - (from openParen in Token.EqualTo(PythonToken.OpenParenthesis) - from parameters in ParameterOrSlash.ManyDelimitedBy(Token.EqualTo(PythonToken.Comma)) - from closeParen in Token.EqualTo(PythonToken.CloseParenthesis) - select parameters) - .Named("Parameter List"); -} +using CSnakes.Parser.Types; +using Superpower; +using Superpower.Parsers; + +namespace CSnakes.Parser; +public static partial class PythonParser +{ + public static TokenListParser PositionalOnlyParameterParser { get; } = + Token.EqualTo(PythonToken.Slash) + .Select(d => new PythonFunctionParameter("/", null, null, PythonFunctionParameterType.Slash)) + .Named("Positional Only Signal"); + + public static TokenListParser PythonParameterTokenizer { get; } = + (from arg in PythonArgTokenizer + from type in Token.EqualTo(PythonToken.Colon).Optional().Then( + _ => PythonTypeDefinitionTokenizer.AssumeNotNull().OptionalOrDefault() + ) + from defaultValue in Token.EqualTo(PythonToken.Equal).Optional().Then( + _ => ConstantValueTokenizer.AssumeNotNull().OptionalOrDefault() + ) + select new PythonFunctionParameter(arg.Name, type, defaultValue, arg.ParameterType)) + .Named("Parameter"); + + public static TokenListParser ParameterOrSlash { get; } = + PositionalOnlyParameterParser.AsNullable() + .Or(PythonParameterTokenizer.AsNullable()) + .Named("Parameter"); + + public static TokenListParser PythonParameterListTokenizer { get; } = + (from openParen in Token.EqualTo(PythonToken.OpenParenthesis) + from parameters in ParameterOrSlash.ManyDelimitedBy(Token.EqualTo(PythonToken.Comma)) + from closeParen in Token.EqualTo(PythonToken.CloseParenthesis) + select parameters) + .Named("Parameter List"); +} diff --git a/src/CSnakes/Parser/PythonParser.TypeDef.cs b/src/CSnakes.SourceGeneration/Parser/PythonParser.TypeDef.cs similarity index 97% rename from src/CSnakes/Parser/PythonParser.TypeDef.cs rename to src/CSnakes.SourceGeneration/Parser/PythonParser.TypeDef.cs index 48301164..286d71e8 100644 --- a/src/CSnakes/Parser/PythonParser.TypeDef.cs +++ b/src/CSnakes.SourceGeneration/Parser/PythonParser.TypeDef.cs @@ -1,34 +1,34 @@ -using CSnakes.Parser.Types; -using Superpower; -using Superpower.Model; -using Superpower.Parsers; - -namespace CSnakes.Parser; -public static partial class PythonParser -{ - public static TextParser QualifiedName { get; } = - Span.MatchedBy( - Character.Letter.Or(Character.EqualTo('_')) - .IgnoreThen(Character.LetterOrDigit.Or(Character.EqualTo('_')).Many()) - .AtLeastOnceDelimitedBy(Character.EqualTo('.')) - ); - - public static TokenListParser PythonTypeDefinitionTokenizer { get; } = - (from name in Token.EqualTo(PythonToken.Identifier).Or(Token.EqualTo(PythonToken.None)).OptionalOrDefault() -#pragma warning disable CS8620 - from openBracket in Token.EqualTo(PythonToken.OpenBracket) - .Then(_ => - PythonTypeDefinitionTokenizer - .AssumeNotNull() - .ManyDelimitedBy( - Token.EqualTo(PythonToken.Comma), - Token.EqualTo(PythonToken.CloseBracket) - ) - ) -#pragma warning restore CS8620 - .OptionalOrDefault() - select name.HasValue ? new PythonTypeSpec(name.ToStringValue(), openBracket) - : openBracket is null ? null : new PythonTypeSpec("argumentCollection__", openBracket) - ) - .Named("Type Definition"); -} +using CSnakes.Parser.Types; +using Superpower; +using Superpower.Model; +using Superpower.Parsers; + +namespace CSnakes.Parser; +public static partial class PythonParser +{ + public static TextParser QualifiedName { get; } = + Span.MatchedBy( + Character.Letter.Or(Character.EqualTo('_')) + .IgnoreThen(Character.LetterOrDigit.Or(Character.EqualTo('_')).Many()) + .AtLeastOnceDelimitedBy(Character.EqualTo('.')) + ); + + public static TokenListParser PythonTypeDefinitionTokenizer { get; } = + (from name in Token.EqualTo(PythonToken.Identifier).Or(Token.EqualTo(PythonToken.None)).OptionalOrDefault() +#pragma warning disable CS8620 + from openBracket in Token.EqualTo(PythonToken.OpenBracket) + .Then(_ => + PythonTypeDefinitionTokenizer + .AssumeNotNull() + .ManyDelimitedBy( + Token.EqualTo(PythonToken.Comma), + Token.EqualTo(PythonToken.CloseBracket) + ) + ) +#pragma warning restore CS8620 + .OptionalOrDefault() + select name.HasValue ? new PythonTypeSpec(name.ToStringValue(), openBracket) + : openBracket is null ? null : new PythonTypeSpec("argumentCollection__", openBracket) + ) + .Named("Type Definition"); +} diff --git a/src/CSnakes/Parser/PythonTokenizer.cs b/src/CSnakes.SourceGeneration/Parser/PythonTokenizer.cs similarity index 98% rename from src/CSnakes/Parser/PythonTokenizer.cs rename to src/CSnakes.SourceGeneration/Parser/PythonTokenizer.cs index a4d580f2..24637488 100644 --- a/src/CSnakes/Parser/PythonTokenizer.cs +++ b/src/CSnakes.SourceGeneration/Parser/PythonTokenizer.cs @@ -1,37 +1,37 @@ -using Superpower; -using Superpower.Parsers; -using Superpower.Tokenizers; - -namespace CSnakes.Parser; -public static class PythonTokenizer -{ - public static Tokenizer Instance { get; } = - new TokenizerBuilder() - .Ignore(Span.WhiteSpace) - .Ignore(Comment.ShellStyle) - .Match(Character.EqualTo('('), PythonToken.OpenParenthesis) - .Match(Character.EqualTo(')'), PythonToken.CloseParenthesis) - .Match(Character.EqualTo('['), PythonToken.OpenBracket) - .Match(Character.EqualTo(']'), PythonToken.CloseBracket) - .Match(Character.EqualTo(':'), PythonToken.Colon) - .Match(Character.EqualTo(','), PythonToken.Comma) - .Match(Character.EqualTo('*').IgnoreThen(Character.EqualTo('*')), PythonToken.DoubleAsterisk) - .Match(Character.EqualTo('*'), PythonToken.Asterisk) - .Match(Character.EqualTo('-').IgnoreThen(Character.EqualTo('>')), PythonToken.Arrow) - .Match(Character.EqualTo('/'), PythonToken.Slash) - .Match(Character.EqualTo('='), PythonToken.Equal) - .Match(Span.EqualTo("def"), PythonToken.Def, requireDelimiters: true) - .Match(Span.EqualTo("async"), PythonToken.Async, requireDelimiters: true) - .Match(Span.EqualTo("..."), PythonToken.Ellipsis) - .Match(Span.EqualTo("None"), PythonToken.None, requireDelimiters: true) - .Match(Span.EqualTo("True"), PythonToken.True, requireDelimiters: true) - .Match(Span.EqualTo("False"), PythonToken.False, requireDelimiters: true) - .Match(PythonParser.QualifiedName, PythonToken.Identifier, requireDelimiters: true) - .Match(PythonParser.IntegerConstantToken, PythonToken.Integer, requireDelimiters: true) - .Match(PythonParser.DecimalConstantToken, PythonToken.Decimal, requireDelimiters: true) - .Match(PythonParser.HexidecimalConstantToken, PythonToken.HexidecimalInteger, requireDelimiters: true) - .Match(PythonParser.BinaryConstantToken, PythonToken.BinaryInteger, requireDelimiters: true) - .Match(PythonParser.DoubleQuotedStringConstantToken, PythonToken.DoubleQuotedString) - .Match(PythonParser.SingleQuotedStringConstantToken, PythonToken.SingleQuotedString) - .Build(); -} +using Superpower; +using Superpower.Parsers; +using Superpower.Tokenizers; + +namespace CSnakes.Parser; +public static class PythonTokenizer +{ + public static Tokenizer Instance { get; } = + new TokenizerBuilder() + .Ignore(Span.WhiteSpace) + .Ignore(Comment.ShellStyle) + .Match(Character.EqualTo('('), PythonToken.OpenParenthesis) + .Match(Character.EqualTo(')'), PythonToken.CloseParenthesis) + .Match(Character.EqualTo('['), PythonToken.OpenBracket) + .Match(Character.EqualTo(']'), PythonToken.CloseBracket) + .Match(Character.EqualTo(':'), PythonToken.Colon) + .Match(Character.EqualTo(','), PythonToken.Comma) + .Match(Character.EqualTo('*').IgnoreThen(Character.EqualTo('*')), PythonToken.DoubleAsterisk) + .Match(Character.EqualTo('*'), PythonToken.Asterisk) + .Match(Character.EqualTo('-').IgnoreThen(Character.EqualTo('>')), PythonToken.Arrow) + .Match(Character.EqualTo('/'), PythonToken.Slash) + .Match(Character.EqualTo('='), PythonToken.Equal) + .Match(Span.EqualTo("def"), PythonToken.Def, requireDelimiters: true) + .Match(Span.EqualTo("async"), PythonToken.Async, requireDelimiters: true) + .Match(Span.EqualTo("..."), PythonToken.Ellipsis) + .Match(Span.EqualTo("None"), PythonToken.None, requireDelimiters: true) + .Match(Span.EqualTo("True"), PythonToken.True, requireDelimiters: true) + .Match(Span.EqualTo("False"), PythonToken.False, requireDelimiters: true) + .Match(PythonParser.QualifiedName, PythonToken.Identifier, requireDelimiters: true) + .Match(PythonParser.IntegerConstantToken, PythonToken.Integer, requireDelimiters: true) + .Match(PythonParser.DecimalConstantToken, PythonToken.Decimal, requireDelimiters: true) + .Match(PythonParser.HexidecimalConstantToken, PythonToken.HexidecimalInteger, requireDelimiters: true) + .Match(PythonParser.BinaryConstantToken, PythonToken.BinaryInteger, requireDelimiters: true) + .Match(PythonParser.DoubleQuotedStringConstantToken, PythonToken.DoubleQuotedString) + .Match(PythonParser.SingleQuotedStringConstantToken, PythonToken.SingleQuotedString) + .Build(); +} diff --git a/src/CSnakes/Parser/PythonTokens.cs b/src/CSnakes.SourceGeneration/Parser/PythonTokens.cs similarity index 93% rename from src/CSnakes/Parser/PythonTokens.cs rename to src/CSnakes.SourceGeneration/Parser/PythonTokens.cs index 7816c2bc..5a5d1ab0 100644 --- a/src/CSnakes/Parser/PythonTokens.cs +++ b/src/CSnakes.SourceGeneration/Parser/PythonTokens.cs @@ -1,61 +1,61 @@ -using Superpower.Display; - -namespace CSnakes.Parser; - -public enum PythonToken -{ - [Token(Example = "(")] - OpenParenthesis, - - [Token(Example = ")")] - CloseParenthesis, - - [Token(Example = "[")] - OpenBracket, - - [Token(Example = "]")] - CloseBracket, - - [Token(Example = ":")] - Colon, - - [Token(Example = ",")] - Comma, - - [Token(Example = "*")] - Asterisk, - - [Token(Example = "**")] - DoubleAsterisk, - - Identifier, - QualifiedIdentifier, - - [Token(Example = "->")] - Arrow, - - [Token(Example = "/")] - Slash, - - [Token(Example = "=")] - Equal, - - [Token(Example = "def")] - Def, - - [Token(Example = "async")] - Async, - - Integer, - Decimal, - HexidecimalInteger, - BinaryInteger, - DoubleQuotedString, - SingleQuotedString, - True, - False, - None, - - [Token(Example = "...")] - Ellipsis +using Superpower.Display; + +namespace CSnakes.Parser; + +public enum PythonToken +{ + [Token(Example = "(")] + OpenParenthesis, + + [Token(Example = ")")] + CloseParenthesis, + + [Token(Example = "[")] + OpenBracket, + + [Token(Example = "]")] + CloseBracket, + + [Token(Example = ":")] + Colon, + + [Token(Example = ",")] + Comma, + + [Token(Example = "*")] + Asterisk, + + [Token(Example = "**")] + DoubleAsterisk, + + Identifier, + QualifiedIdentifier, + + [Token(Example = "->")] + Arrow, + + [Token(Example = "/")] + Slash, + + [Token(Example = "=")] + Equal, + + [Token(Example = "def")] + Def, + + [Token(Example = "async")] + Async, + + Integer, + Decimal, + HexidecimalInteger, + BinaryInteger, + DoubleQuotedString, + SingleQuotedString, + True, + False, + None, + + [Token(Example = "...")] + Ellipsis } \ No newline at end of file diff --git a/src/CSnakes/Parser/Types/PythonConstant.cs b/src/CSnakes.SourceGeneration/Parser/Types/PythonConstant.cs similarity index 96% rename from src/CSnakes/Parser/Types/PythonConstant.cs rename to src/CSnakes.SourceGeneration/Parser/Types/PythonConstant.cs index 4516e6e7..05fb9123 100644 --- a/src/CSnakes/Parser/Types/PythonConstant.cs +++ b/src/CSnakes.SourceGeneration/Parser/Types/PythonConstant.cs @@ -1,77 +1,77 @@ -namespace CSnakes.Parser.Types; - -public class PythonConstant -{ - public enum ConstantType - { - Integer, - HexidecimalInteger, - BinaryInteger, - Float, - String, - Bool, - None, - } - - public PythonConstant() { } - - public PythonConstant(long value) - { - Type = ConstantType.Integer; - IntegerValue = value; - } - - public PythonConstant(double value) - { - Type = ConstantType.Float; - FloatValue = value; - } - - public PythonConstant(string value) - { - Type = ConstantType.String; - StringValue = value; - } - - public PythonConstant(bool value) - { - Type = ConstantType.Bool; - BoolValue = value; - } - - public static PythonConstant FromNone() => new PythonConstant { Type = ConstantType.None }; - public ConstantType Type { get; set; } - - public long IntegerValue { get; set; } - - public string? StringValue { get; set; } - - public double FloatValue { get; set; } - - public bool BoolValue { get; set; } - - public bool IsInteger { get => Type == ConstantType.Integer || Type == ConstantType.BinaryInteger || Type == ConstantType.HexidecimalInteger; } - - public override string ToString() - { - switch (Type) - { - case ConstantType.Integer: - return IntegerValue.ToString(); - case ConstantType.HexidecimalInteger: - return $"0x{IntegerValue:X}"; - case ConstantType.BinaryInteger: - return $"0b{IntegerValue:X}"; - case ConstantType.Float: - return FloatValue.ToString(); - case ConstantType.Bool: - return BoolValue.ToString(); - case ConstantType.String: - return StringValue ?? throw new ArgumentNullException(nameof(StringValue)); - case ConstantType.None: - return "None"; - default: - return "unknown"; - } - } -} +namespace CSnakes.Parser.Types; + +public class PythonConstant +{ + public enum ConstantType + { + Integer, + HexidecimalInteger, + BinaryInteger, + Float, + String, + Bool, + None, + } + + public PythonConstant() { } + + public PythonConstant(long value) + { + Type = ConstantType.Integer; + IntegerValue = value; + } + + public PythonConstant(double value) + { + Type = ConstantType.Float; + FloatValue = value; + } + + public PythonConstant(string value) + { + Type = ConstantType.String; + StringValue = value; + } + + public PythonConstant(bool value) + { + Type = ConstantType.Bool; + BoolValue = value; + } + + public static PythonConstant FromNone() => new PythonConstant { Type = ConstantType.None }; + public ConstantType Type { get; set; } + + public long IntegerValue { get; set; } + + public string? StringValue { get; set; } + + public double FloatValue { get; set; } + + public bool BoolValue { get; set; } + + public bool IsInteger { get => Type == ConstantType.Integer || Type == ConstantType.BinaryInteger || Type == ConstantType.HexidecimalInteger; } + + public override string ToString() + { + switch (Type) + { + case ConstantType.Integer: + return IntegerValue.ToString(); + case ConstantType.HexidecimalInteger: + return $"0x{IntegerValue:X}"; + case ConstantType.BinaryInteger: + return $"0b{IntegerValue:X}"; + case ConstantType.Float: + return FloatValue.ToString(); + case ConstantType.Bool: + return BoolValue.ToString(); + case ConstantType.String: + return StringValue ?? throw new ArgumentNullException(nameof(StringValue)); + case ConstantType.None: + return "None"; + default: + return "unknown"; + } + } +} diff --git a/src/CSnakes/Parser/Types/PythonFunctionDefinition.cs b/src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionDefinition.cs similarity index 97% rename from src/CSnakes/Parser/Types/PythonFunctionDefinition.cs rename to src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionDefinition.cs index 7c4b0641..75923cf0 100644 --- a/src/CSnakes/Parser/Types/PythonFunctionDefinition.cs +++ b/src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionDefinition.cs @@ -1,36 +1,36 @@ -namespace CSnakes.Parser.Types; -public class PythonFunctionDefinition(string name, PythonTypeSpec? returnType, PythonFunctionParameter[] pythonFunctionParameter) -{ - public string Name { get; private set; } = name; - - private readonly PythonFunctionParameter[] _parameters = FixupArguments(pythonFunctionParameter); - - private static PythonFunctionParameter[] FixupArguments(PythonFunctionParameter[]? parameters) - { - if (parameters is null || parameters.Length == 0) - return []; - - // Go through all parameters and mark those after the *arg as keyword only - bool keywordOnly = false; - for (int i = 1; i < parameters.Length; i++) - { - if (parameters[i].ParameterType == PythonFunctionParameterType.Star) - { - keywordOnly = true; - continue; - } - - parameters[i].IsKeywordOnly = keywordOnly; - } - - return parameters; - } - - public PythonTypeSpec ReturnType => returnType ?? PythonTypeSpec.Any; - - public PythonFunctionParameter[] Parameters => _parameters; - - public bool HasReturnTypeAnnotation() => returnType is not null; - - public bool IsAsync { get; set; } -} +namespace CSnakes.Parser.Types; +public class PythonFunctionDefinition(string name, PythonTypeSpec? returnType, PythonFunctionParameter[] pythonFunctionParameter) +{ + public string Name { get; private set; } = name; + + private readonly PythonFunctionParameter[] _parameters = FixupArguments(pythonFunctionParameter); + + private static PythonFunctionParameter[] FixupArguments(PythonFunctionParameter[]? parameters) + { + if (parameters is null || parameters.Length == 0) + return []; + + // Go through all parameters and mark those after the *arg as keyword only + bool keywordOnly = false; + for (int i = 1; i < parameters.Length; i++) + { + if (parameters[i].ParameterType == PythonFunctionParameterType.Star) + { + keywordOnly = true; + continue; + } + + parameters[i].IsKeywordOnly = keywordOnly; + } + + return parameters; + } + + public PythonTypeSpec ReturnType => returnType ?? PythonTypeSpec.Any; + + public PythonFunctionParameter[] Parameters => _parameters; + + public bool HasReturnTypeAnnotation() => returnType is not null; + + public bool IsAsync { get; set; } +} diff --git a/src/CSnakes/Parser/Types/PythonFunctionParameter.cs b/src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionParameter.cs similarity index 97% rename from src/CSnakes/Parser/Types/PythonFunctionParameter.cs rename to src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionParameter.cs index 929d6c40..af9b570f 100644 --- a/src/CSnakes/Parser/Types/PythonFunctionParameter.cs +++ b/src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionParameter.cs @@ -1,17 +1,17 @@ -namespace CSnakes.Parser.Types; -public class PythonFunctionParameter(string name, PythonTypeSpec? type, PythonConstant? defaultValue, PythonFunctionParameterType parameterType) -{ - public string Name { get; } = name; - - public PythonTypeSpec Type { get; } = type ?? PythonTypeSpec.Any; - - public bool IsPositionalOnly { get; set; } - - public bool IsKeywordOnly { get; set; } - - public PythonConstant? DefaultValue { get; set; } = defaultValue; - - public PythonFunctionParameterType ParameterType { get; } = parameterType; - - public bool HasTypeAnnotation() => type is not null; -} +namespace CSnakes.Parser.Types; +public class PythonFunctionParameter(string name, PythonTypeSpec? type, PythonConstant? defaultValue, PythonFunctionParameterType parameterType) +{ + public string Name { get; } = name; + + public PythonTypeSpec Type { get; } = type ?? PythonTypeSpec.Any; + + public bool IsPositionalOnly { get; set; } + + public bool IsKeywordOnly { get; set; } + + public PythonConstant? DefaultValue { get; set; } = defaultValue; + + public PythonFunctionParameterType ParameterType { get; } = parameterType; + + public bool HasTypeAnnotation() => type is not null; +} diff --git a/src/CSnakes/Parser/Types/PythonFunctionParameterType.cs b/src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionParameterType.cs similarity index 93% rename from src/CSnakes/Parser/Types/PythonFunctionParameterType.cs rename to src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionParameterType.cs index c905f5b6..029ec950 100644 --- a/src/CSnakes/Parser/Types/PythonFunctionParameterType.cs +++ b/src/CSnakes.SourceGeneration/Parser/Types/PythonFunctionParameterType.cs @@ -1,9 +1,9 @@ -namespace CSnakes.Parser.Types; - -public enum PythonFunctionParameterType -{ - Star, - DoubleStar, - Slash, - Normal -} +namespace CSnakes.Parser.Types; + +public enum PythonFunctionParameterType +{ + Star, + DoubleStar, + Slash, + Normal +} diff --git a/src/CSnakes/Parser/Types/PythonParameterType.cs b/src/CSnakes.SourceGeneration/Parser/Types/PythonParameterType.cs similarity index 97% rename from src/CSnakes/Parser/Types/PythonParameterType.cs rename to src/CSnakes.SourceGeneration/Parser/Types/PythonParameterType.cs index aac4169b..36cb4423 100644 --- a/src/CSnakes/Parser/Types/PythonParameterType.cs +++ b/src/CSnakes.SourceGeneration/Parser/Types/PythonParameterType.cs @@ -1,7 +1,7 @@ -namespace CSnakes.Parser.Types; - -public class PythonParameterType(string name, PythonFunctionParameterType parameterType) -{ - public string Name { get; } = name; - public PythonFunctionParameterType ParameterType { get; set; } = parameterType; -} +namespace CSnakes.Parser.Types; + +public class PythonParameterType(string name, PythonFunctionParameterType parameterType) +{ + public string Name { get; } = name; + public PythonFunctionParameterType ParameterType { get; set; } = parameterType; +} diff --git a/src/CSnakes/Parser/Types/PythonTypeSpec.cs b/src/CSnakes.SourceGeneration/Parser/Types/PythonTypeSpec.cs similarity index 96% rename from src/CSnakes/Parser/Types/PythonTypeSpec.cs rename to src/CSnakes.SourceGeneration/Parser/Types/PythonTypeSpec.cs index c7151d67..ffeb3267 100644 --- a/src/CSnakes/Parser/Types/PythonTypeSpec.cs +++ b/src/CSnakes.SourceGeneration/Parser/Types/PythonTypeSpec.cs @@ -1,16 +1,16 @@ -namespace CSnakes.Parser.Types; -public class PythonTypeSpec(string name, PythonTypeSpec[] arguments) -{ - public string Name { get; } = name; - - public PythonTypeSpec[] Arguments { get; } = arguments; - - public override string ToString() => - HasArguments() ? - $"{Name}[{string.Join(", ", Arguments.Select(a => a.ToString()))}]" : - Name; - - public bool HasArguments() => Arguments is not null && Arguments.Length > 0; - - public static PythonTypeSpec Any => new("Any", []); -} +namespace CSnakes.Parser.Types; +public class PythonTypeSpec(string name, PythonTypeSpec[] arguments) +{ + public string Name { get; } = name; + + public PythonTypeSpec[] Arguments { get; } = arguments; + + public override string ToString() => + HasArguments() ? + $"{Name}[{string.Join(", ", Arguments.Select(a => a.ToString()))}]" : + Name; + + public bool HasArguments() => Arguments is not null && Arguments.Length > 0; + + public static PythonTypeSpec Any => new("Any", []); +} diff --git a/src/CSnakes/PythonStaticGenerator.cs b/src/CSnakes.SourceGeneration/PythonStaticGenerator.cs similarity index 97% rename from src/CSnakes/PythonStaticGenerator.cs rename to src/CSnakes.SourceGeneration/PythonStaticGenerator.cs index 3a8b5d52..61731ee8 100644 --- a/src/CSnakes/PythonStaticGenerator.cs +++ b/src/CSnakes.SourceGeneration/PythonStaticGenerator.cs @@ -1,127 +1,127 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using CSnakes.Parser; -using CSnakes.Parser.Types; -using CSnakes.Reflection; - -namespace CSnakes; - -[Generator(LanguageNames.CSharp)] -public class PythonStaticGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - // System.Diagnostics.Debugger.Launch(); - var pythonFilesPipeline = context.AdditionalTextsProvider - .Where(static text => Path.GetExtension(text.Path) == ".py") - .Collect(); - - context.RegisterSourceOutput(pythonFilesPipeline, static (sourceContext, inputFiles) => - { - foreach (var file in inputFiles) - { - // Add environment path - var @namespace = "CSnakes.Runtime"; - - var fileName = Path.GetFileNameWithoutExtension(file.Path); - - // Convert snakecase to pascal case - var pascalFileName = string.Join("", fileName.Split('_').Select(s => char.ToUpperInvariant(s[0]) + s.Substring(1))); - // Read the file - var code = file.GetText(sourceContext.CancellationToken); - - if (code is null) continue; - - // Parse the Python file - var result = PythonParser.TryParseFunctionDefinitions(code, out PythonFunctionDefinition[] functions, out GeneratorError[]? errors); - - foreach (var error in errors) - { - // Update text span - Location errorLocation = Location.Create(file.Path, TextSpan.FromBounds(0, 1), new LinePositionSpan(new LinePosition(error.StartLine, error.StartColumn), new LinePosition(error.EndLine, error.EndColumn))); - sourceContext.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("PSG004", "PythonStaticGenerator", error.Message, "PythonStaticGenerator", DiagnosticSeverity.Error, true), errorLocation)); - } - - if (result) - { - IEnumerable methods = ModuleReflection.MethodsFromFunctionDefinitions(functions, fileName); - string source = FormatClassFromMethods(@namespace, pascalFileName, methods, fileName, functions); - sourceContext.AddSource($"{pascalFileName}.py.cs", source); - sourceContext.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("PSG002", "PythonStaticGenerator", $"Generated {pascalFileName}.py.cs", "PythonStaticGenerator", DiagnosticSeverity.Info, true), Location.None)); - } - } - }); - } - - public static string FormatClassFromMethods(string @namespace, string pascalFileName, IEnumerable methods, string fileName, PythonFunctionDefinition[] functions) - { - var paramGenericArgs = methods - .Select(m => m.ParameterGenericArgs) - .Where(l => l is not null && l.Any()); - - return $$""" - // - #nullable enable - using CSnakes.Runtime; - using CSnakes.Runtime.Python; - - using System; - using System.Collections.Generic; - using System.Diagnostics; - - using Microsoft.Extensions.Logging; - - namespace {{@namespace}}; - public static class {{pascalFileName}}Extensions - { - private static I{{pascalFileName}}? instance; - - public static I{{pascalFileName}} {{pascalFileName}}(this IPythonEnvironment env) - { - if (instance is null) - { - instance = new {{pascalFileName}}Internal(env.Logger); - } - Debug.Assert(!env.IsDisposed()); - return instance; - } - - private class {{pascalFileName}}Internal : I{{pascalFileName}} - { - private readonly PyObject module; - - private readonly ILogger logger; - private readonly IDictionary functions; - - internal {{pascalFileName}}Internal(ILogger logger) - { - this.logger = logger; - using (GIL.Acquire()) - { - logger.LogDebug("Importing module {ModuleName}", "{{fileName}}"); - module = Import.ImportModule("{{fileName}}"); - functions = new Dictionary(); - {{ string.Join(Environment.NewLine, functions.Select(f => $"functions[\"{f.Name}\"] = module.GetAttr(\"{f.Name}\");")) }} - } - } - - public void Dispose() - { - logger.LogDebug("Disposing module {ModuleName}", "{{fileName}}"); - foreach (var function in functions) - { - function.Value.Dispose(); - } - module.Dispose(); - } - - {{methods.Select(m => m.Syntax).Compile()}} - } - } - public interface I{{pascalFileName}} - { - {{string.Join(Environment.NewLine, methods.Select(m => m.Syntax).Select(m => $"{m.ReturnType.NormalizeWhitespace()} {m.Identifier.Text}{m.ParameterList.NormalizeWhitespace()};"))}} - } - """; - } +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using CSnakes.Parser; +using CSnakes.Parser.Types; +using CSnakes.Reflection; + +namespace CSnakes; + +[Generator(LanguageNames.CSharp)] +public class PythonStaticGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // System.Diagnostics.Debugger.Launch(); + var pythonFilesPipeline = context.AdditionalTextsProvider + .Where(static text => Path.GetExtension(text.Path) == ".py") + .Collect(); + + context.RegisterSourceOutput(pythonFilesPipeline, static (sourceContext, inputFiles) => + { + foreach (var file in inputFiles) + { + // Add environment path + var @namespace = "CSnakes.Runtime"; + + var fileName = Path.GetFileNameWithoutExtension(file.Path); + + // Convert snakecase to pascal case + var pascalFileName = string.Join("", fileName.Split('_').Select(s => char.ToUpperInvariant(s[0]) + s.Substring(1))); + // Read the file + var code = file.GetText(sourceContext.CancellationToken); + + if (code is null) continue; + + // Parse the Python file + var result = PythonParser.TryParseFunctionDefinitions(code, out PythonFunctionDefinition[] functions, out GeneratorError[]? errors); + + foreach (var error in errors) + { + // Update text span + Location errorLocation = Location.Create(file.Path, TextSpan.FromBounds(0, 1), new LinePositionSpan(new LinePosition(error.StartLine, error.StartColumn), new LinePosition(error.EndLine, error.EndColumn))); + sourceContext.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("PSG004", "PythonStaticGenerator", error.Message, "PythonStaticGenerator", DiagnosticSeverity.Error, true), errorLocation)); + } + + if (result) + { + IEnumerable methods = ModuleReflection.MethodsFromFunctionDefinitions(functions, fileName); + string source = FormatClassFromMethods(@namespace, pascalFileName, methods, fileName, functions); + sourceContext.AddSource($"{pascalFileName}.py.cs", source); + sourceContext.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor("PSG002", "PythonStaticGenerator", $"Generated {pascalFileName}.py.cs", "PythonStaticGenerator", DiagnosticSeverity.Info, true), Location.None)); + } + } + }); + } + + public static string FormatClassFromMethods(string @namespace, string pascalFileName, IEnumerable methods, string fileName, PythonFunctionDefinition[] functions) + { + var paramGenericArgs = methods + .Select(m => m.ParameterGenericArgs) + .Where(l => l is not null && l.Any()); + + return $$""" + // + #nullable enable + using CSnakes.Runtime; + using CSnakes.Runtime.Python; + + using System; + using System.Collections.Generic; + using System.Diagnostics; + + using Microsoft.Extensions.Logging; + + namespace {{@namespace}}; + public static class {{pascalFileName}}Extensions + { + private static I{{pascalFileName}}? instance; + + public static I{{pascalFileName}} {{pascalFileName}}(this IPythonEnvironment env) + { + if (instance is null) + { + instance = new {{pascalFileName}}Internal(env.Logger); + } + Debug.Assert(!env.IsDisposed()); + return instance; + } + + private class {{pascalFileName}}Internal : I{{pascalFileName}} + { + private readonly PyObject module; + + private readonly ILogger logger; + private readonly IDictionary functions; + + internal {{pascalFileName}}Internal(ILogger logger) + { + this.logger = logger; + using (GIL.Acquire()) + { + logger.LogDebug("Importing module {ModuleName}", "{{fileName}}"); + module = Import.ImportModule("{{fileName}}"); + functions = new Dictionary(); + {{ string.Join(Environment.NewLine, functions.Select(f => $"functions[\"{f.Name}\"] = module.GetAttr(\"{f.Name}\");")) }} + } + } + + public void Dispose() + { + logger.LogDebug("Disposing module {ModuleName}", "{{fileName}}"); + foreach (var function in functions) + { + function.Value.Dispose(); + } + module.Dispose(); + } + + {{methods.Select(m => m.Syntax).Compile()}} + } + } + public interface I{{pascalFileName}} + { + {{string.Join(Environment.NewLine, methods.Select(m => m.Syntax).Select(m => $"{m.ReturnType.NormalizeWhitespace()} {m.Identifier.Text}{m.ParameterList.NormalizeWhitespace()};"))}} + } + """; + } } \ No newline at end of file diff --git a/src/CSnakes/Reflection/ArgumentReflection.cs b/src/CSnakes.SourceGeneration/Reflection/ArgumentReflection.cs similarity index 97% rename from src/CSnakes/Reflection/ArgumentReflection.cs rename to src/CSnakes.SourceGeneration/Reflection/ArgumentReflection.cs index 19de7598..507cb472 100644 --- a/src/CSnakes/Reflection/ArgumentReflection.cs +++ b/src/CSnakes.SourceGeneration/Reflection/ArgumentReflection.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using CSnakes.Parser.Types; diff --git a/src/CSnakes/Reflection/MethodReflection.cs b/src/CSnakes.SourceGeneration/Reflection/MethodReflection.cs similarity index 97% rename from src/CSnakes/Reflection/MethodReflection.cs rename to src/CSnakes.SourceGeneration/Reflection/MethodReflection.cs index 597df350..10ae8092 100644 --- a/src/CSnakes/Reflection/MethodReflection.cs +++ b/src/CSnakes.SourceGeneration/Reflection/MethodReflection.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using CSnakes.Parser.Types; diff --git a/src/CSnakes/Reflection/ModuleReflection.cs b/src/CSnakes.SourceGeneration/Reflection/ModuleReflection.cs similarity index 96% rename from src/CSnakes/Reflection/ModuleReflection.cs rename to src/CSnakes.SourceGeneration/Reflection/ModuleReflection.cs index 7edb41bf..bf6e0af5 100644 --- a/src/CSnakes/Reflection/ModuleReflection.cs +++ b/src/CSnakes.SourceGeneration/Reflection/ModuleReflection.cs @@ -1,24 +1,24 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using CSnakes.Parser.Types; - -namespace CSnakes.Reflection; - -public static class ModuleReflection -{ - public static IEnumerable MethodsFromFunctionDefinitions(PythonFunctionDefinition[] functions, string moduleName) - { - return functions.Select(function => MethodReflection.FromMethod(function, moduleName)); - } - - public static string Compile(this IEnumerable methods) - { - using StringWriter sw = new(); - foreach (var method in methods) - { - method.NormalizeWhitespace().WriteTo(sw); - } - return sw.ToString(); - } -} +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using CSnakes.Parser.Types; + +namespace CSnakes.Reflection; + +public static class ModuleReflection +{ + public static IEnumerable MethodsFromFunctionDefinitions(PythonFunctionDefinition[] functions, string moduleName) + { + return functions.Select(function => MethodReflection.FromMethod(function, moduleName)); + } + + public static string Compile(this IEnumerable methods) + { + using StringWriter sw = new(); + foreach (var method in methods) + { + method.NormalizeWhitespace().WriteTo(sw); + } + return sw.ToString(); + } +} diff --git a/src/CSnakes/Reflection/TypeReflection.cs b/src/CSnakes.SourceGeneration/Reflection/TypeReflection.cs similarity index 97% rename from src/CSnakes/Reflection/TypeReflection.cs rename to src/CSnakes.SourceGeneration/Reflection/TypeReflection.cs index d2da044d..ab009225 100644 --- a/src/CSnakes/Reflection/TypeReflection.cs +++ b/src/CSnakes.SourceGeneration/Reflection/TypeReflection.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using CSnakes.Parser.Types; diff --git a/src/CSnakes.Tests/CSnakes.Tests.csproj b/src/CSnakes.Tests/CSnakes.Tests.csproj index 40202b34..aa9845ca 100644 --- a/src/CSnakes.Tests/CSnakes.Tests.csproj +++ b/src/CSnakes.Tests/CSnakes.Tests.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/CSnakes.sln b/src/CSnakes.sln index 6a415c85..c19a5c5d 100644 --- a/src/CSnakes.sln +++ b/src/CSnakes.sln @@ -10,7 +10,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSnakes", "CSnakes\CSnakes.csproj", "{3912E0F4-493B-4B26-A44D-B095F6CF7538}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSnakes.SourceGeneration", "CSnakes.SourceGeneration\CSnakes.SourceGeneration.csproj", "{3912E0F4-493B-4B26-A44D-B095F6CF7538}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSnakes.Runtime", "CSnakes.Runtime\CSnakes.Runtime.csproj", "{9BAB43D1-017F-4470-B898-42022328D17F}" EndProject @@ -22,6 +22,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSnakes.Runtime.Tests", "CS EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Profile", "Profile\Profile.csproj", "{93264FC1-2880-4959-9576-50D260039BC2}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4374E3D5-3A2C-47F8-B33E-F3C43A20B530}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E52DC71C-FB58-4E57-9CCA-8A78EAA49123}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -56,6 +60,14 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3912E0F4-493B-4B26-A44D-B095F6CF7538} = {4374E3D5-3A2C-47F8-B33E-F3C43A20B530} + {9BAB43D1-017F-4470-B898-42022328D17F} = {4374E3D5-3A2C-47F8-B33E-F3C43A20B530} + {E01D5C27-CB38-4A0D-B297-33E1E10C1A82} = {E52DC71C-FB58-4E57-9CCA-8A78EAA49123} + {F179777C-913A-46FE-BE2F-15A59EAB7E88} = {E52DC71C-FB58-4E57-9CCA-8A78EAA49123} + {CB00C1F5-8C01-4FE7-82AD-7F9B4398E3F8} = {E52DC71C-FB58-4E57-9CCA-8A78EAA49123} + {93264FC1-2880-4959-9576-50D260039BC2} = {E52DC71C-FB58-4E57-9CCA-8A78EAA49123} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4ACC77F9-1BB8-42DE-B647-01C458922F49} EndGlobalSection diff --git a/src/Integration.Tests/Integration.Tests.csproj b/src/Integration.Tests/Integration.Tests.csproj index 342922ac..cd9280cb 100644 --- a/src/Integration.Tests/Integration.Tests.csproj +++ b/src/Integration.Tests/Integration.Tests.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Profile/Profile.csproj b/src/Profile/Profile.csproj index 33edd747..d224de7d 100644 --- a/src/Profile/Profile.csproj +++ b/src/Profile/Profile.csproj @@ -22,7 +22,7 @@ - +