diff --git a/YesNt-Interpreter.sln b/YesNt-Interpreter.sln index 0181ad5..b2c2492 100644 --- a/YesNt-Interpreter.sln +++ b/YesNt-Interpreter.sln @@ -14,38 +14,66 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YesNt.Interpreter.Tests", "YesNt.Interpreter.Tests\YesNt.Interpreter.Tests.csproj", "{2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YesNt.Interpreter.Generator", "YesNt.Interpreter.Generator\YesNt.Interpreter.Generator.csproj", "{85AAB233-9C4D-4B42-8117-00010958DD1D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x64.ActiveCfg = Debug|x64 {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x64.Build.0 = Debug|x64 + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x86.ActiveCfg = Debug|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Debug|x86.Build.0 = Debug|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|Any CPU.ActiveCfg = Release|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|Any CPU.Build.0 = Release|Any CPU {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x64.ActiveCfg = Release|x64 {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x64.Build.0 = Release|x64 + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x86.ActiveCfg = Release|Any CPU + {33BDA036-A37E-475E-AACF-8ED96E31BA1A}.Release|x86.Build.0 = Release|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|Any CPU.Build.0 = Debug|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x64.ActiveCfg = Debug|x64 {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x64.Build.0 = Debug|x64 + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x86.ActiveCfg = Debug|Any CPU + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Debug|x86.Build.0 = Debug|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|Any CPU.ActiveCfg = Release|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|Any CPU.Build.0 = Release|Any CPU {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x64.ActiveCfg = Release|x64 {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x64.Build.0 = Release|x64 + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x86.ActiveCfg = Release|Any CPU + {E40573DB-912A-4871-BB65-3EE6E7D72E79}.Release|x86.Build.0 = Release|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|Any CPU.Build.0 = Debug|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x64.ActiveCfg = Debug|x64 {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x64.Build.0 = Debug|x64 + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Debug|x86.Build.0 = Debug|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|Any CPU.ActiveCfg = Release|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|Any CPU.Build.0 = Release|Any CPU {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x64.ActiveCfg = Release|x64 {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x64.Build.0 = Release|x64 + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x86.ActiveCfg = Release|Any CPU + {2F95DCA7-3E43-4F2E-8FB2-067F0EC962B1}.Release|x86.Build.0 = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x64.ActiveCfg = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x64.Build.0 = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x86.ActiveCfg = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Debug|x86.Build.0 = Debug|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|Any CPU.Build.0 = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|x64.ActiveCfg = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|x64.Build.0 = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|x86.ActiveCfg = Release|Any CPU + {85AAB233-9C4D-4B42-8117-00010958DD1D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs b/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs new file mode 100644 index 0000000..a228de1 --- /dev/null +++ b/YesNt.Interpreter.Generator/StatementRegistryGenerator.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Microsoft.CodeAnalysis; + +namespace YesNt.Interpreter.Generator; + +[Generator] +public sealed class StatementRegistryGenerator : ISourceGenerator +{ + private const string StatementAttributeName = "YesNt.Interpreter.Attributes.StatementAttribute"; + private const string StaticStatementAttributeName = "YesNt.Interpreter.Attributes.StaticStatementAttribute"; + + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + Compilation compilation = context.Compilation; + + List statementMethods = []; + List staticStatementMethods = []; + + CollectMethods(compilation.Assembly.GlobalNamespace, statementMethods, staticStatementMethods); + + string source = GenerateRegistrySource(statementMethods, staticStatementMethods); + context.AddSource("GeneratedStatementRegistry.g.cs", source); + } + + private static void CollectMethods( + INamespaceSymbol namespaceSymbol, + List statementMethods, + List staticStatementMethods) + { + foreach (INamespaceSymbol childNamespace in namespaceSymbol.GetNamespaceMembers()) + { + CollectMethods(childNamespace, statementMethods, staticStatementMethods); + } + + foreach (INamedTypeSymbol type in namespaceSymbol.GetTypeMembers()) + { + CollectMethods(type, statementMethods, staticStatementMethods); + } + } + + private static void CollectMethods( + INamedTypeSymbol typeSymbol, + List statementMethods, + List staticStatementMethods) + { + foreach (ISymbol member in typeSymbol.GetMembers()) + { + if (member is IMethodSymbol method && method.MethodKind == MethodKind.Ordinary) + { + foreach (AttributeData attribute in method.GetAttributes()) + { + string? attributeName = attribute.AttributeClass?.ToDisplayString(); + if (attributeName == StatementAttributeName) + { + statementMethods.Add(new MethodRegistration(typeSymbol, method, attribute)); + } + else if (attributeName == StaticStatementAttributeName) + { + staticStatementMethods.Add(new MethodRegistration(typeSymbol, method, attribute)); + } + } + } + } + + foreach (INamedTypeSymbol nestedType in typeSymbol.GetTypeMembers()) + { + CollectMethods(nestedType, statementMethods, staticStatementMethods); + } + } + + private static string GenerateRegistrySource( + List statementMethods, + List staticStatementMethods) + { + StringBuilder sb = new StringBuilder(); + + _ = sb.AppendLine("// "); + _ = sb.AppendLine("#nullable enable"); + _ = sb.AppendLine("using System;"); + _ = sb.AppendLine("using System.Collections.Generic;"); + _ = sb.AppendLine("using System.Linq;"); + _ = sb.AppendLine(); + _ = sb.AppendLine("namespace YesNt.Interpreter.Runtime;"); + _ = sb.AppendLine(); + _ = sb.AppendLine("internal static class GeneratedStatementRegistry"); + _ = sb.AppendLine("{"); + _ = sb.AppendLine(" internal static void Register("); + _ = sb.AppendLine(" RuntimeInformation runtimeInfo,"); + _ = sb.AppendLine(" out Dictionary> statements,"); + _ = sb.AppendLine(" out List> staticStatements)"); + _ = sb.AppendLine(" {"); + + List allTypes = statementMethods + .Concat(staticStatementMethods) + .Select(x => x.ContainingType) + .GroupBy(x => x, SymbolEqualityComparer.Default) + .Select(g => g.First()) + .OrderBy(x => x.ToDisplayString()) + .ToList(); + + Dictionary instanceNames = new Dictionary(SymbolEqualityComparer.Default); + int index = 0; + foreach (INamedTypeSymbol type in allTypes) + { + string instanceName = $"instance{index++}"; + instanceNames[type] = instanceName; + _ = sb.AppendLine($" var {instanceName} = new global::{type.ToDisplayString()}();"); + _ = sb.AppendLine($" {instanceName}.RuntimeInfo = runtimeInfo;"); + } + + _ = sb.AppendLine(" var statementEntries = new List>>();"); + + foreach (MethodRegistration method in statementMethods + .OrderBy(x => x.ContainingType.ToDisplayString()) + .ThenBy(x => x.Method.Name)) + { + string instanceName = instanceNames[method.ContainingType]; + string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Attributes.StatementAttribute", method.Attribute); + _ = sb.AppendLine($" statementEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));"); + } + + _ = sb.AppendLine(" var staticEntries = new List>();"); + + foreach (MethodRegistration method in staticStatementMethods + .OrderBy(x => x.ContainingType.ToDisplayString()) + .ThenBy(x => x.Method.Name)) + { + string instanceName = instanceNames[method.ContainingType]; + string attributeCreation = BuildAttributeCreation("global::YesNt.Interpreter.Attributes.StaticStatementAttribute", method.Attribute); + _ = sb.AppendLine($" staticEntries.Add(new({attributeCreation}, {instanceName}.{method.Method.Name}));"); + } + + _ = sb.AppendLine(" statements = statementEntries"); + _ = sb.AppendLine(" .OrderBy(s => s.Key.Priority)"); + _ = sb.AppendLine(" .ThenByDescending(s => s.Key.Name.Length)"); + _ = sb.AppendLine(" .ToDictionary(x => x.Key, x => x.Value);"); + _ = sb.AppendLine(); + _ = sb.AppendLine(" staticStatements = staticEntries"); + _ = sb.AppendLine(" .OrderBy(s => s.Key.Priority)"); + _ = sb.AppendLine(" .ToList();"); + _ = sb.AppendLine(" }"); + _ = sb.AppendLine("}"); + + return sb.ToString(); + } + + private static string BuildAttributeCreation(string attributeTypeName, AttributeData attribute) + { + string ctorArgs = string.Join(", ", + attribute.ConstructorArguments.Select(ToLiteral)); + + string creation = $"new {attributeTypeName}({ctorArgs})"; + + if (attribute.NamedArguments.Length == 0) + { + return creation; + } + + string namedArgs = string.Join(", ", + attribute.NamedArguments.Select(arg => $"{arg.Key} = {ToLiteral(arg.Value)}")); + + return $"{creation} {{ {namedArgs} }}"; + } + + private static string ToLiteral(TypedConstant constant) + { + if (constant.IsNull) + { + return "null!"; + } + + if (constant.Type is null) + { + return "null!"; + } + + if (constant.Kind == TypedConstantKind.Enum) + { + string enumType = $"global::{constant.Type.ToDisplayString()}"; + object value = constant.Value!; + return $"({enumType}){Convert.ToInt64(value)}"; + } + + return constant.Type.SpecialType switch + { + SpecialType.System_String => "\"" + EscapeString((string)constant.Value!) + "\"", + SpecialType.System_Char => "'" + EscapeChar((char)constant.Value!) + "'", + SpecialType.System_Boolean => (bool)constant.Value! ? "true" : "false", + SpecialType.System_Int32 => ((int)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture), + SpecialType.System_Int64 => ((long)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture) + "L", + SpecialType.System_Single => ((float)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture) + "f", + SpecialType.System_Double => ((double)constant.Value!).ToString(System.Globalization.CultureInfo.InvariantCulture), + _ => constant.Value!.ToString() ?? "null!" + }; + } + + private static string EscapeString(string value) + { + return value + .Replace("\\", "\\\\") + .Replace("\"", "\\\"") + .Replace("\r", "\\r") + .Replace("\n", "\\n") + .Replace("\t", "\\t"); + } + + private static string EscapeChar(char value) + { + return value switch + { + '\\' => "\\\\", + '\'' => "\\'", + '\r' => "\\r", + '\n' => "\\n", + '\t' => "\\t", + _ => value.ToString() + }; + } + + private sealed class MethodRegistration + { + public MethodRegistration(INamedTypeSymbol containingType, IMethodSymbol method, AttributeData attribute) + { + ContainingType = containingType; + Method = method; + Attribute = attribute; + } + + public INamedTypeSymbol ContainingType { get; } + + public IMethodSymbol Method { get; } + + public AttributeData Attribute { get; } + } +} diff --git a/YesNt.Interpreter.Generator/YesNt.Interpreter.Generator.csproj b/YesNt.Interpreter.Generator/YesNt.Interpreter.Generator.csproj new file mode 100644 index 0000000..4be3731 --- /dev/null +++ b/YesNt.Interpreter.Generator/YesNt.Interpreter.Generator.csproj @@ -0,0 +1,13 @@ + + + netstandard2.0 + latest + enable + true + true + + + + + + diff --git a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs index 0559a7c..da6aa9a 100644 --- a/YesNt.Interpreter/Runtime/YesNtInterpreter.cs +++ b/YesNt.Interpreter/Runtime/YesNtInterpreter.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Reflection; using YesNt.Interpreter.Attributes; using YesNt.Interpreter.Enums; @@ -49,47 +48,9 @@ public class YesNtInterpreter public void Initialize() { - Assembly assembly = Assembly.GetExecutingAssembly(); - Type[] types = assembly.GetTypes(); - - IEnumerable allStatementRuntimeInfo = types.Where(t => t.IsSubclassOf(typeof(StatementRuntimeInformation))); - statements.Clear(); - - foreach (Type type in allStatementRuntimeInfo) - { - object statementInfo = Activator.CreateInstance(type); - - MethodInfo[] allMethodInfo = statementInfo.GetType().GetMethods(); - - StatementRuntimeInformation statementRuntimeInfo = statementInfo as StatementRuntimeInformation; - statementRuntimeInfo.RuntimeInfo = runtimeInfo; - - foreach (MethodInfo methodInfo in allMethodInfo) - { - StatementAttribute statementAttribute = methodInfo.GetCustomAttribute(); - if (statementAttribute is not null) - { - Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; - statements.Add(statementAttribute, method); - } - - StaticStatementAttribute staticStatementAttribute = methodInfo.GetCustomAttribute(); - if (staticStatementAttribute is not null) - { - Action method = methodInfo.CreateDelegate(typeof(Action), statementInfo) as Action; - staticStatements.Add(new(staticStatementAttribute, method)); - } - } - } - - statements = statements - .OrderBy(s => s.Key.Priority) - .ThenByDescending(s => s.Key.Name.Length) - .ToDictionary(x => x.Key, x => x.Value); - staticStatements = staticStatements - .OrderBy(s => s.Key.Priority) - .ToList(); + staticStatements.Clear(); + GeneratedStatementRegistry.Register(runtimeInfo, out statements, out staticStatements); runtimeInfo.OnDebugOutput += (s) => OnDebugOutput?.Invoke(s); runtimeInfo.OnLineExecuted += (DebugEventArgs e) => OnLineExecuted?.Invoke(e); @@ -283,4 +244,4 @@ public class YesNtInterpreter return true; } -} \ No newline at end of file +} diff --git a/YesNt.Interpreter/YesNt.Interpreter.csproj b/YesNt.Interpreter/YesNt.Interpreter.csproj index 1093ec0..f6e5bb2 100644 --- a/YesNt.Interpreter/YesNt.Interpreter.csproj +++ b/YesNt.Interpreter/YesNt.Interpreter.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -9,4 +9,10 @@ AnyCPU;x64 + + + +