diff --git a/CSharpRepl.Tests/EvalTests.cs b/CSharpRepl.Tests/EvalTests.cs index 1afdd23..52623f8 100644 --- a/CSharpRepl.Tests/EvalTests.cs +++ b/CSharpRepl.Tests/EvalTests.cs @@ -65,7 +65,7 @@ public EvalTests(ITestOutputHelper outputHelper) [Theory] [InlineData("1+1", 2L, "int")] [InlineData("return 1+1;", 2L, "int")] - [InlineData("return Random.Next(1,2);", 1L, "int")] + [InlineData("return Random.Shared.Next(1,2);", 1L, "int")] [InlineData(@"var a = ""thing""; return a;", "thing", "string")] [InlineData("Math.Pow(1,2)", 1D, "double")] [InlineData(@"Enumerable.Range(0,1).Select(a=>""@"");", null, null)] @@ -79,7 +79,7 @@ public async Task Eval_WellFormattedCodeExecutes(string expr, object expected, s var (result, statusCode) = await Execute(expr); var res = result.ReturnValue as JsonElement?; object convertedValue; - if (res.Value.ValueKind == JsonValueKind.Array) + if (res?.ValueKind == JsonValueKind.Array) { convertedValue = res.Value.GetRawText(); } @@ -150,9 +150,9 @@ public async Task Eval_JsonNetSerializationFailureHandled(string expr, string me } [Theory] - [InlineData(@"Enumerable.Range(0,1).Select(a=>""@"")", "@", 1, "SelectRangeIterator")] - [InlineData(@"return Enumerable.Range(0,1).Select(a=>""@"");", "@", 1, "SelectRangeIterator")] - public async Task Eval_EnumerablesReturnArraysOf(string expr, object expected, int count, string type) + [InlineData(@"Enumerable.Range(0,1).Select(a=>""@"")", "@", 1)] + [InlineData(@"return Enumerable.Range(0,1).Select(a=>""@"");", "@", 1)] + public async Task Eval_EnumerablesReturnArraysOf(string expr, object expected, int count) { var (result, statusCode) = await Execute(expr); @@ -162,13 +162,11 @@ public async Task Eval_EnumerablesReturnArraysOf(string expr, object expected, i Assert.Equal(expr, result.Code); Assert.Equal(expected, res.Value[0].GetString()); Assert.Equal(count, res.Value.GetArrayLength()); - Assert.Equal(type, result.ReturnTypeName); } [Theory] [InlineData("return 1+1", "CompilationErrorException", "; expected")] [InlineData(@"throw new Exception(""test"");", "Exception", "test")] - [InlineData("return System.Environment.MachineName;", "CompilationErrorException", "Usage of this API is prohibited\nUsage of this API is prohibited")] [InlineData("return DoesNotCompile()", "CompilationErrorException", "; expected\nThe name 'DoesNotCompile' does not exist in the current context")] public async Task Eval_FaultyCodeThrowsExpectedException(string expr, string exception, string message) { @@ -193,15 +191,6 @@ public async Task Eval_ConsoleOutputIsCaptured(string expr, string consoleOut, o Assert.Equal(returnValue, result.ReturnValue); } - [Fact] - public async Task Eval_LoadDLLThatExposesTypeOfADependency() - { - var expr = "#nuget CK.ActivityMonitor\nvar m = new CK.Core.ActivityMonitor();"; - var (result, statusCode) = await Execute(expr); - - Assert.Equal(HttpStatusCode.OK, statusCode); - } - [Fact] public async Task Eval_FaultyDirectiveFailsGracefully() { @@ -233,15 +222,17 @@ public async void Eval_ValidateGenericMathSupport() Assert.Equal(HttpStatusCode.OK, statusCode); } - [Fact(Skip = "Test is failing presumably because of a bug in ByteSize")] + [Fact] public async Task Eval_SupportsNugetDirectiveWithActualUsage() { - var expr = @"#nuget ByteSize - var input = ""80527998976 B""; - if (ByteSize.TryParse(input, NumberStyles.Any, new CultureInfo(""en-us""), out var output)) + var expr = """ + #nuget ByteSize + var input = "80527998976 B"; + if (ByteSize.TryParse(input, NumberStyles.Any, new CultureInfo("en-us"), out var output)) { Console.WriteLine(output); - }"; + } + """; var (result, statusCode) = await Execute(expr); diff --git a/CSharpRepl/CSharpRepl.csproj b/CSharpRepl/CSharpRepl.csproj index 7a9fdd9..9bb5c82 100644 --- a/CSharpRepl/CSharpRepl.csproj +++ b/CSharpRepl/CSharpRepl.csproj @@ -1,7 +1,7 @@  - true + true preview net9.0 Exe @@ -15,8 +15,11 @@ - - + + + + compile + diff --git a/CSharpRepl/Eval/BlacklistedTypesAnalyzer.cs b/CSharpRepl/Eval/BlacklistedTypesAnalyzer.cs deleted file mode 100644 index f48b0ee..0000000 --- a/CSharpRepl/Eval/BlacklistedTypesAnalyzer.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace CSDiscordService.Eval -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class BlacklistedTypesAnalyzer : DiagnosticAnalyzer - { - public const string DiagnosticId = "MOD0001"; - private static readonly LocalizableString Title = "Prohibited API"; - private static readonly LocalizableString MessageFormat = "Usage of this API is prohibited"; - private const string Category = "Discord"; - - internal static DiagnosticDescriptor Rule = -#pragma warning disable RS2008 // Enable analyzer release tracking - new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, true); -#pragma warning restore RS2008 // Enable analyzer release tracking - - public override ImmutableArray SupportedDiagnostics => - ImmutableArray.Create(Rule); - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); - context.EnableConcurrentExecution(); - context.RegisterSemanticModelAction(AnalyzeSymbol); - } - - private static void AnalyzeSymbol(SemanticModelAnalysisContext context) - { - var model = context.SemanticModel; - - var tree = model.SyntaxTree; - var nodes = tree.GetRoot() - .DescendantNodes(n => true) - .Where(n => n is IdentifierNameSyntax || n is ExpressionSyntax); - - foreach (var node in nodes) - { - var symbol = node is IdentifierNameSyntax - ? model.GetSymbolInfo(node).Symbol - : model.GetTypeInfo(node).Type; - - if (symbol is INamedTypeSymbol namedSymbol && - namedSymbol.Name == typeof(Environment).Name) - { - context.ReportDiagnostic(Diagnostic.Create(Rule, node.GetLocation())); - } - } - } - } -} diff --git a/CSharpRepl/Eval/CSharpEval.cs b/CSharpRepl/Eval/CSharpEval.cs index 4e86269..4ba3109 100644 --- a/CSharpRepl/Eval/CSharpEval.cs +++ b/CSharpRepl/Eval/CSharpEval.cs @@ -1,7 +1,6 @@ using CSDiscordService.Eval.ResultModels; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Scripting; -using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Scripting; using System; using System.Collections.Immutable; @@ -17,10 +16,6 @@ namespace CSDiscordService.Eval { public class CSharpEval { - private static readonly ImmutableArray Analyzers = - ImmutableArray.Create(new BlacklistedTypesAnalyzer()); - - private static readonly Random random = new Random(); private readonly JsonSerializerOptions _serializerOptions; private readonly IPreProcessorService _preProcessor; private readonly ILogger _logger; @@ -31,12 +26,10 @@ public CSharpEval(JsonSerializerOptions serializerOptons, IPreProcessorService p _preProcessor = preProcessor; _logger = logger; } - public async Task RunEvalAsync(string code) { var sb = new StringBuilder(); using var textWr = new ConsoleLikeStringWriter(sb); - var env = new BasicEnvironment(); var sw = Stopwatch.StartNew(); @@ -47,16 +40,16 @@ public async Task RunEvalAsync(string code) } catch(Exception ex) { - var diagnostics = Diagnostic.Create(new DiagnosticDescriptor("REPL01", ex.Message, ex.Message, "Code", DiagnosticSeverity.Error, true), + var diagnostic = Diagnostic.Create(new DiagnosticDescriptor("REPL01", ex.Message, ex.Message, "Code", DiagnosticSeverity.Error, true), Location.Create("", TextSpan.FromBounds(0,0), new LinePositionSpan(LinePosition.Zero, LinePosition.Zero))); _logger.LogCritical(ex, "{message}", ex.Message); - return EvalResult.CreateErrorResult(code, sb.ToString(), sw.Elapsed, new[] { diagnostics }.ToImmutableArray()); + return EvalResult.CreateErrorResult(code, sb.ToString(), sw.Elapsed, [diagnostic]); } var eval = CSharpScript.Create(context.Code, context.Options, typeof(Globals)); - var compilation = eval.GetCompilation().WithAnalyzers(Analyzers); + var compilation = eval.GetCompilation(); - var compileResult = await compilation.GetAllDiagnosticsAsync(); + var compileResult = compilation.GetDiagnostics(); var compileErrors = compileResult.Where(a => a.Severity == DiagnosticSeverity.Error).ToImmutableArray(); sw.Stop(); @@ -67,16 +60,14 @@ public async Task RunEvalAsync(string code) } var globals = new Globals(); - Globals.Random = random; Globals.Console = textWr; - Globals.Environment = env; sw.Restart(); ScriptState result; try { - result = await eval.RunAsync(globals, ex => true); + result = await eval.RunAsync(globals, _ => true); } catch (CompilationErrorException ex) { diff --git a/CSharpRepl/Eval/DisassemblyService.cs b/CSharpRepl/Eval/DisassemblyService.cs index ea102c3..9e007ce 100644 --- a/CSharpRepl/Eval/DisassemblyService.cs +++ b/CSharpRepl/Eval/DisassemblyService.cs @@ -2,17 +2,11 @@ using ICSharpCode.Decompiler.Disassembler; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; -using Newtonsoft.Json; using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; -using System.Linq.Expressions; -using System.Net.Http; -using System.Reflection; using System.Text; -using System.Text.RegularExpressions; using System.Threading; using ICSharpCode.Decompiler.Metadata; @@ -20,21 +14,8 @@ namespace CSDiscordService.Eval { public class DisassemblyService { - private static readonly IReadOnlyCollection References = ImmutableArray.Create( - MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(ValueTuple<>).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(List<>).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(JsonConvert).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(string).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(HttpClient).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(Regex).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(BinaryExpression).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(typeof(Console).GetTypeInfo().Assembly.Location), - MetadataReference.CreateFromFile(Assembly.Load("System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a").Location) - ); - - private static readonly ImmutableArray Imports = ImmutableArray.Create( + private static readonly ImmutableArray Imports = + [ "System", "System.IO", "System.Linq", @@ -46,11 +27,9 @@ public class DisassemblyService "System.Threading", "System.Threading.Tasks", "System.Net.Http", - "Newtonsoft.Json", - "Newtonsoft.Json.Linq", "System.Reflection", "System.Reflection.Emit" - ); + ]; public string GetIl(string code) { @@ -85,7 +64,7 @@ public object Main() .WithAllowUnsafe(true) .WithPlatform(Platform.AnyCpu); - var compilation = CSharpCompilation.Create(Guid.NewGuid().ToString(), options: compOpts, references: References) + var compilation = CSharpCompilation.Create(Guid.NewGuid().ToString(), options: compOpts, references: Basic.Reference.Assemblies.Net90.References.All) .AddSyntaxTrees(scriptSyntaxTree); var sb = new StringBuilder(); diff --git a/CSharpRepl/Eval/Globals.cs b/CSharpRepl/Eval/Globals.cs index 670e310..8b1d732 100644 --- a/CSharpRepl/Eval/Globals.cs +++ b/CSharpRepl/Eval/Globals.cs @@ -6,19 +6,8 @@ namespace CSDiscordService.Eval { public class Globals { - public static Random Random { get; set; } public static ConsoleLikeStringWriter Console { get; internal set; } - public static BasicEnvironment Environment { get; internal set; } - public void ResetButton() - { - System.Environment.Exit(0); - } - - public void PowerButton() - { - System.Environment.Exit(1); - } public void Cmd(string name, string args = "") { var psi = new ProcessStartInfo(name) @@ -33,22 +22,5 @@ public void Cmd(string name, string args = "") Console.WriteLine(p.StandardOutput.ReadToEnd()); Console.WriteLine(p.StandardError.ReadToEnd()); } - } - - [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] - public class ʘ‿ʘAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] - public class ʘ_ʘAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] - public class ಠ_ಠAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] - public class ಠ‿ಠAttribute : Attribute { } - - [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] - public class 눈ᆺ눈Attribute : Attribute { } - } \ No newline at end of file diff --git a/CSharpRepl/Eval/NugetDirectiveProcessor.cs b/CSharpRepl/Eval/NugetDirectiveProcessor.cs index 117590b..27c424d 100644 --- a/CSharpRepl/Eval/NugetDirectiveProcessor.cs +++ b/CSharpRepl/Eval/NugetDirectiveProcessor.cs @@ -25,11 +25,13 @@ public class NugetDirectiveProcessor : IDirectiveProcessor, IDisposable public bool CanProcessDirective(string directive) { + ObjectDisposedException.ThrowIf(disposedValue, this); return directive != null && directive.StartsWith("#nuget"); } public async Task PreProcess(string directive, ScriptExecutionContext context, Action logger) { + ObjectDisposedException.ThrowIf(disposedValue, this); var actionLogger = new NugetLogger(logger); var nugetDirective = NugetPreProcessorDirective.Parse(directive); string frameworkName = typeof(NugetDirectiveProcessor).Assembly.GetCustomAttributes(true) @@ -115,12 +117,13 @@ await GetPackageDependencies( foreach (var path in libraries) { - var assembly = Assembly.LoadFrom(path); - if (context.TryAddReferenceAssembly(assembly)) + var asm = Assembly.LoadFile(path); + if (context.References.Add(asm)) { - foreach (var ns in assembly.GetTypes().Where(a => a.Namespace != null).Select(a => a.Namespace).Distinct()) + foreach (var type in asm.GetTypes()) { - context.AddImport(ns); + if (type.IsPublic && type.Namespace is not null) + context.Imports.Add(type.Namespace); } } } @@ -134,16 +137,20 @@ private async Task CreateEmptyNugetConfig(string packagesPath, string feedUrl) if(!File.Exists(fullPath)) { - await File.WriteAllTextAsync(fullPath, $@" - - - - - - - - -"); + await File.WriteAllTextAsync(fullPath, + $""" + + + + + + + + + + + """ + ); } } @@ -212,23 +219,11 @@ await PackageExtractor.ExtractPackageAsync( return selected.Where(a => Path.GetExtension(a) == ".dll") .Select(a => Path.Combine(pathResolver.GetInstalledPath(package), a)); } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - cache.Dispose(); - } - disposedValue = true; - } - } - + public void Dispose() { - Dispose(disposing: true); - GC.SuppressFinalize(this); + cache.Dispose(); + disposedValue = true; } private class NugetLogger : ILogger diff --git a/CSharpRepl/Eval/ResultModels/BasicEnvironment.cs b/CSharpRepl/Eval/ResultModels/BasicEnvironment.cs deleted file mode 100644 index fd090a8..0000000 --- a/CSharpRepl/Eval/ResultModels/BasicEnvironment.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text.RegularExpressions; - -namespace CSDiscordService.Eval.ResultModels -{ - public class BasicEnvironment - { - public string CommandLine { get; } = Environment.CommandLine; - public string CurrentDirectory { get; set; } = Environment.CurrentDirectory; - public int CurrentManagedThreadId { get; } = Environment.CurrentManagedThreadId; - public int ExitCode { get; set; } = Environment.ExitCode; - public bool HasShutdownStarted { get; } = Environment.HasShutdownStarted; - public bool Is64BitOperatingSystem { get; } = Environment.Is64BitOperatingSystem; - public bool Is64BitProcess { get; } = Environment.Is64BitProcess; - public string MachineName { get; } = Environment.MachineName; - public string NewLine { get; } = Environment.NewLine; - public OperatingSystem OSVersion { get; } = Environment.OSVersion; - public int ProcessorCount { get; } = Environment.ProcessorCount; - public string StackTrace { get; } = Environment.StackTrace; - public string SystemDirectory { get; } = Environment.SystemDirectory; - public int SystemPageSize { get; } = Environment.SystemPageSize; - public int TickCount { get; } = Environment.TickCount; - public string UserDomainName { get; } = Environment.UserDomainName; - public bool UserInteractive { get; } = Environment.UserInteractive; - public string UserName { get; } = Environment.UserName; - public Version Version { get; } = Environment.Version; - public long WorkingSet { get; } = Environment.WorkingSet; - - // The last two are marked static to reproduce a real environment, where user and machine variables persists between executions. - private readonly Dictionary _processEnv = new Dictionary(); - private static readonly Dictionary _userEnv = new Dictionary(); - private static readonly Dictionary _machineEnv = new Dictionary(); - - - public void Exit(int exitCode) => throw new ExitException(exitCode); - public void FailFast(string message, Exception exception = null) => throw new FailFastException(message, exception); - public string[] GetCommandLineArgs() => Environment.GetCommandLineArgs(); - public string GetFolderPath(SpecialFolder folder, SpecialFolderOption option = SpecialFolderOption.None) => Environment.GetFolderPath((Environment.SpecialFolder)folder, (Environment.SpecialFolderOption)option); - public string[] GetLogicalDrives => Environment.GetLogicalDrives(); - - public string ExpandEnvironmentVariables(string name) - { - var env = (Dictionary)GetEnvironmentVariables(); - var regex = new Regex("%([^%]+)%"); - var me = new MatchEvaluator(m => env.ContainsKey(m.Groups[1].Value) ? env[m.Groups[1].Value] : m.Value); - - return regex.Replace(name, me); - } - - public void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target = EnvironmentVariableTarget.Process) - { - if (target == EnvironmentVariableTarget.Process) - { - _processEnv[variable] = value; - } - else if (target == EnvironmentVariableTarget.User) - { - _userEnv[variable] = value; - } - else if (target == EnvironmentVariableTarget.Machine) - { - _machineEnv[variable] = value; - } - } - - public string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target = EnvironmentVariableTarget.Process) - { - var searchDictionary = GetEnvironmentVariables(target); - - return searchDictionary.Contains(variable) ? (string)searchDictionary[variable] : null; - } - - public IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target = EnvironmentVariableTarget.Process) - { - Dictionary searchDictionary = new Dictionary(_machineEnv); - if (target != EnvironmentVariableTarget.Machine) - { - foreach (var kvp in _userEnv) - { - searchDictionary[kvp.Key] = kvp.Value; - } - } - if (target == EnvironmentVariableTarget.Process) - { - foreach (var kvp in _processEnv) - { - searchDictionary[kvp.Key] = kvp.Value; - } - } - - return searchDictionary; - } - - - public enum SpecialFolder - { - Desktop = Environment.SpecialFolder.Desktop, - Programs = Environment.SpecialFolder.Programs, - MyDocuments = Environment.SpecialFolder.MyDocuments, - Favorites = Environment.SpecialFolder.Favorites, - Startup = Environment.SpecialFolder.Startup, - Recent = Environment.SpecialFolder.Recent, - SendTo = Environment.SpecialFolder.SendTo, - StartMenu = Environment.SpecialFolder.StartMenu, - MyMusic = Environment.SpecialFolder.MyMusic, - MyVideos = Environment.SpecialFolder.MyVideos, - DesktopDirectory = Environment.SpecialFolder.DesktopDirectory, - MyComputer = Environment.SpecialFolder.MyComputer, - NetworkShortcuts = Environment.SpecialFolder.NetworkShortcuts, - Fonts = Environment.SpecialFolder.Fonts, - Templates = Environment.SpecialFolder.Templates, - CommonStartMenu = Environment.SpecialFolder.CommonStartMenu, - CommonPrograms = Environment.SpecialFolder.CommonPrograms, - CommonStartup = Environment.SpecialFolder.CommonStartup, - CommonDesktopDirectory = Environment.SpecialFolder.CommonDesktopDirectory, - ApplicationData = Environment.SpecialFolder.ApplicationData, - PrinterShortcuts = Environment.SpecialFolder.PrinterShortcuts, - LocalApplicationData = Environment.SpecialFolder.LocalApplicationData, - InternetCache = Environment.SpecialFolder.InternetCache, - Cookies = Environment.SpecialFolder.Cookies, - History = Environment.SpecialFolder.History, - CommonApplicationData = Environment.SpecialFolder.CommonApplicationData, - Windows = Environment.SpecialFolder.Windows, - System = Environment.SpecialFolder.System, - ProgramFiles = Environment.SpecialFolder.ProgramFiles, - MyPictures = Environment.SpecialFolder.MyPictures, - UserProfile = Environment.SpecialFolder.UserProfile, - SystemX86 = Environment.SpecialFolder.SystemX86, - ProgramFilesX86 = Environment.SpecialFolder.ProgramFilesX86, - CommonProgramFiles = Environment.SpecialFolder.CommonProgramFiles, - CommonProgramFilesX86 = Environment.SpecialFolder.CommonProgramFilesX86, - CommonTemplates = Environment.SpecialFolder.CommonTemplates, - CommonDocuments = Environment.SpecialFolder.CommonDocuments, - CommonAdminTools = Environment.SpecialFolder.CommonAdminTools, - AdminTools = Environment.SpecialFolder.AdminTools, - CommonMusic = Environment.SpecialFolder.CommonMusic, - CommonPictures = Environment.SpecialFolder.CommonPictures, - CommonVideos = Environment.SpecialFolder.CommonVideos, - Resources = Environment.SpecialFolder.Resources, - LocalizedResources = Environment.SpecialFolder.LocalizedResources, - CommonOemLinks = Environment.SpecialFolder.CommonOemLinks, - CDBurning = Environment.SpecialFolder.CDBurning - } - - public enum SpecialFolderOption - { - Create = Environment.SpecialFolderOption.Create, - DoNotVerify = Environment.SpecialFolderOption.DoNotVerify, - None = Environment.SpecialFolderOption.None - } - } -} diff --git a/CSharpRepl/Eval/ResultModels/EvalResult.cs b/CSharpRepl/Eval/ResultModels/EvalResult.cs index 28daabc..bda5cc9 100644 --- a/CSharpRepl/Eval/ResultModels/EvalResult.cs +++ b/CSharpRepl/Eval/ResultModels/EvalResult.cs @@ -94,8 +94,10 @@ public override string ToString() new ModuleJsonConverter(), new AssemblyJsonConverterFactory(), new DirectoryInfoJsonConverter(), new AngouriMathEntityConverter(), new AngouriMathEntityVarsConverter(), - new IntPtrJsonConverter() - } + new NumberConverter(), + new ByteEnumerableJsonConverter(), + new MultidimArrayConverterFactory() + } }); } } diff --git a/CSharpRepl/Eval/ResultModels/ExitException.cs b/CSharpRepl/Eval/ResultModels/ExitException.cs deleted file mode 100644 index 3817f35..0000000 --- a/CSharpRepl/Eval/ResultModels/ExitException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace CSDiscordService.Eval.ResultModels -{ - public class ExitException : Exception - { - public ExitException(int exitCode) : base($"Script exited with code {exitCode}") { } - } -} diff --git a/CSharpRepl/Eval/ResultModels/FailFastException.cs b/CSharpRepl/Eval/ResultModels/FailFastException.cs deleted file mode 100644 index 93e6cf9..0000000 --- a/CSharpRepl/Eval/ResultModels/FailFastException.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace CSDiscordService.Eval.ResultModels -{ - class FailFastException : Exception - { - public FailFastException(string message, Exception exception) : base($"Script fast-failed with message \"{message}\"", exception) { } - } -} diff --git a/CSharpRepl/Eval/ScriptExecutionContext.cs b/CSharpRepl/Eval/ScriptExecutionContext.cs index 37a77b4..96f42c7 100644 --- a/CSharpRepl/Eval/ScriptExecutionContext.cs +++ b/CSharpRepl/Eval/ScriptExecutionContext.cs @@ -1,78 +1,69 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; -using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using System.Reflection; -using AngouriMath; -using Microsoft.CodeAnalysis; namespace CSDiscordService.Eval { public class ScriptExecutionContext { - private static readonly List DefaultImports = - new() - { - "Newtonsoft.Json", - "Newtonsoft.Json.Linq", - "System", - "System.Collections", - "System.Collections.Concurrent", - "System.Collections.Immutable", - "System.Collections.Generic", - "System.Diagnostics", - "System.Dynamic", - "System.Security.Cryptography", - "System.Globalization", - "System.IO", - "System.Linq", - "System.Linq.Expressions", - "System.Net", - "System.Net.Http", - "System.Numerics", - "System.Reflection", - "System.Reflection.Emit", - "System.Runtime.CompilerServices", - "System.Runtime.InteropServices", - "System.Runtime.Intrinsics", - "System.Runtime.Intrinsics.X86", - "System.Text", - "System.Text.RegularExpressions", - "System.Threading", - "System.Threading.Tasks", - "System.Text.Json", - "CSDiscordService.Eval", - "AngouriMath", - "AngouriMath.Extensions", - "HonkSharp.Fluency", - "HonkSharp.Functional" - }; + private static readonly IEnumerable DefaultImports = + [ + "System", + "System.Collections", + "System.Collections.Concurrent", + "System.Collections.Immutable", + "System.Collections.Generic", + "System.Diagnostics", + "System.Dynamic", + "System.Security.Cryptography", + "System.Globalization", + "System.IO", + "System.Linq", + "System.Linq.Expressions", + "System.Net", + "System.Net.Http", + "System.Numerics", + "System.Reflection", + "System.Reflection.Emit", + "System.Runtime.CompilerServices", + "System.Runtime.InteropServices", + "System.Runtime.Intrinsics", + "System.Runtime.Intrinsics.X86", + "System.Text", + "System.Text.RegularExpressions", + "System.Threading", + "System.Threading.Tasks", + "System.Text.Json", + "CSDiscordService.Eval", + "AngouriMath", + "AngouriMath.Extensions", + "HonkSharp.Fluency", + "HonkSharp.Functional" + ]; + + private static readonly IEnumerable DefaultReferences = + [ + typeof(Enumerable).Assembly, + typeof(System.Net.Http.HttpClient).Assembly, + typeof(List<>).Assembly, + typeof(System.ValueTuple).Assembly, + typeof(Globals).Assembly, + typeof(System.Memory<>).Assembly, + typeof(AngouriMath.Entity).Assembly + ]; - private static readonly List DefaultReferences = - new() - { - typeof(Enumerable).GetTypeInfo().Assembly, - typeof(HttpClient).GetTypeInfo().Assembly, - typeof(List<>).GetTypeInfo().Assembly, - typeof(string).GetTypeInfo().Assembly, - typeof(ValueTuple).GetTypeInfo().Assembly, - typeof(Globals).GetTypeInfo().Assembly, - typeof(Memory<>).GetTypeInfo().Assembly, - typeof(Entity).GetTypeInfo().Assembly - }; public ScriptOptions Options => ScriptOptions.Default - .WithLanguageVersion(LanguageVersion.Preview) - .WithOptimizationLevel(OptimizationLevel.Release) - .WithImports(Imports) - .WithReferences(References); + .WithLanguageVersion(LanguageVersion.Preview) + .WithImports(Imports) + .WithReferences(References); - public HashSet References { get; private set; } = new HashSet(DefaultReferences); + public HashSet References { get; } = DefaultReferences.ToHashSet(); - public HashSet Imports { get; private set; } = new HashSet(DefaultImports); + public HashSet Imports { get; } = DefaultImports.ToHashSet(); public string Code { get; set; } @@ -80,36 +71,5 @@ public ScriptExecutionContext(string code) { Code = code; } - - public void AddImport(string import) - { - if(string.IsNullOrEmpty(import)) - { - return; - } - - if (Imports.Contains(import)) - { - return; - } - - Imports.Add(import); - } - - public bool TryAddReferenceAssembly(Assembly assembly) - { - if(assembly is null) - { - return false; - } - - if(References.Contains(assembly)) - { - return false; - } - - References.Add(assembly); - return true; - } } } diff --git a/CSharpRepl/Infrastructure/JsonFormatters/TypeJsonConverter.cs b/CSharpRepl/Infrastructure/JsonFormatters/TypeJsonConverter.cs index f741697..9c5d289 100644 --- a/CSharpRepl/Infrastructure/JsonFormatters/TypeJsonConverter.cs +++ b/CSharpRepl/Infrastructure/JsonFormatters/TypeJsonConverter.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; +using System.Globalization; using System.IO; +using System.Linq; +using System.Numerics; using System.Reflection; -using System.Security; using System.Text.Json; using System.Text.Json.Serialization; @@ -32,17 +35,47 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp writer.WriteStringValue((value as Type).AssemblyQualifiedName); } } + + public class NumberConverter : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => + typeToConvert != typeof(float) + && typeToConvert != typeof(double) + && typeToConvert.GetInterfaces().Any(i => i.Name == "INumber`1" && i.GenericTypeArguments[0] == typeToConvert); + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => + (JsonConverter)Activator.CreateInstance(typeof(NumberJsonConverter<>).MakeGenericType(typeToConvert)); + } - public class IntPtrJsonConverter : JsonConverter + public class NumberJsonConverter : JsonConverter where T : INumber { - public override IntPtr Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new IntPtr(reader.GetInt64()); - } - - public override void Write(Utf8JsonWriter writer, IntPtr value, JsonSerializerOptions options) - { - writer.WriteNumberValue(value.ToInt64()); + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (T.IsInteger(value)) + { + var longValue = long.CreateSaturating(value); + var ulongValue = ulong.CreateSaturating(value); + if (longValue is not (long.MinValue or long.MaxValue)) + { + writer.WriteNumberValue(longValue); + } + else if (ulongValue is not ulong.MaxValue) + { + writer.WriteNumberValue(ulongValue); + } + else + { + writer.WriteStringValue(value.ToString(null, CultureInfo.InvariantCulture)); + } + } + else + { + writer.WriteStringValue(value.ToString(null, CultureInfo.InvariantCulture)); + } } } @@ -141,4 +174,75 @@ public override void Write(Utf8JsonWriter writer, DirectoryInfo value, JsonSeria writer.WriteStringValue(value.FullName); } } + + public class ByteEnumerableJsonConverter : JsonConverter> + { + public override bool CanConvert(Type typeToConvert) => typeToConvert.GetInterfaces().Contains(typeof(IEnumerable)); + + public override IEnumerable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, IEnumerable value, JsonSerializerOptions options) + { + ((JsonConverter>)options.GetConverter(typeof(IEnumerable))).Write(writer, value.Select(int (x) => x), options); + } + } + + public class MultidimArrayConverterFactory : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => typeToConvert is { IsArray: true, IsSZArray: false }; + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + return (JsonConverter)Activator.CreateInstance(typeof(MultidimArrayJsonConverter<>).MakeGenericType(typeToConvert.GetElementType()!)); + } + } + + public class MultidimArrayJsonConverter : JsonConverter + { + public override bool CanConvert(Type typeToConvert) => typeToConvert is { IsArray: true, IsSZArray: false }; + + public override Array Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, Array value, JsonSerializerOptions options) + { + var converter = (JsonConverter)options.GetConverter(typeof(T)); + var currentRankIndexes = Enumerable.Range(0, value.Rank).Select(value.GetLowerBound).ToArray(); + var currentRank = 0; + while (true) + { + if (currentRankIndexes[currentRank] == value.GetLowerBound(currentRank)) + { + writer.WriteStartArray(); + } + if (currentRankIndexes[currentRank] == value.GetUpperBound(currentRank) + 1) + { + writer.WriteEndArray(); + currentRankIndexes[currentRank] = value.GetLowerBound(currentRank); + if (currentRank-- == 0) + { + return; + } + else + { + currentRankIndexes[currentRank] += 1; + continue; + } + } + if (currentRank == currentRankIndexes.Length - 1) + { + converter.Write(writer, (T)value.GetValue(currentRankIndexes), options); + currentRankIndexes[currentRank] += 1; + } + else + { + currentRank += 1; + } + } + } + } } diff --git a/CSharpRepl/Program.cs b/CSharpRepl/Program.cs index 9c64745..a697c3d 100644 --- a/CSharpRepl/Program.cs +++ b/CSharpRepl/Program.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore; using Microsoft.Extensions.Logging; using System; diff --git a/CSharpRepl/Startup.cs b/CSharpRepl/Startup.cs index 055ef0a..c70805e 100644 --- a/CSharpRepl/Startup.cs +++ b/CSharpRepl/Startup.cs @@ -43,8 +43,10 @@ public void ConfigureServices(IServiceCollection services) new ModuleJsonConverter(), new AssemblyJsonConverterFactory(), new DirectoryInfoJsonConverter(), new AngouriMathEntityConverter(), new AngouriMathEntityVarsConverter(), - new IntPtrJsonConverter() - } + new NumberConverter(), + new ByteEnumerableJsonConverter(), + new MultidimArrayConverterFactory() + } }; services.AddControllers(o => @@ -69,7 +71,9 @@ public void ConfigureServices(IServiceCollection services) o.JsonSerializerOptions.Converters.Add(new DirectoryInfoJsonConverter()); o.JsonSerializerOptions.Converters.Add(new AngouriMathEntityConverter()); o.JsonSerializerOptions.Converters.Add(new AngouriMathEntityVarsConverter()); - o.JsonSerializerOptions.Converters.Add(new IntPtrJsonConverter()); + o.JsonSerializerOptions.Converters.Add(new NumberConverter()); + o.JsonSerializerOptions.Converters.Add(new ByteEnumerableJsonConverter()); + o.JsonSerializerOptions.Converters.Add(new MultidimArrayConverterFactory()); }); services.AddSingleton(jsonOptions); diff --git a/CSharpRepl/TaskExtensions.cs b/CSharpRepl/TaskExtensions.cs deleted file mode 100644 index 5038f63..0000000 --- a/CSharpRepl/TaskExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Runtime.ExceptionServices; -using System.Threading.Tasks; - -namespace CSDiscordService -{ - public static class TaskExtensions - { - public static T AsSync(this Task task, bool configureAwait) - { - return task.ConfigureAwait(configureAwait).GetAwaiter().GetResult(); - } - - public static void AsSync(this Task task, bool configureAwait) - { - task.ConfigureAwait(configureAwait).GetAwaiter().GetResult(); - } - } -} \ No newline at end of file diff --git a/CSharpRepl/TypeExtensions.cs b/CSharpRepl/TypeExtensions.cs index 881340e..a0d097a 100644 --- a/CSharpRepl/TypeExtensions.cs +++ b/CSharpRepl/TypeExtensions.cs @@ -1,9 +1,10 @@ using System; using System.Linq; +using System.Text.RegularExpressions; namespace CSDiscordService { - public static class TypeExtensions + public static partial class TypeExtensions { public static string ParseGenericArgs(this Type type) { @@ -18,41 +19,40 @@ public static string ParseGenericArgs(this Type type) var returnArgs = args.Select(a => a.ParseGenericArgs()); return returnTypeName.Replace($"`{args.Length}", $"<{string.Join(", ", returnArgs)}>"); } - private const string ArrayBrackets = "[]"; private static string GetPrimitiveTypeName(Type type) { var typeName = type.Name; if (type.IsArray) { - typeName = typeName.Replace(ArrayBrackets, string.Empty); + typeName = ParseGenericArgs(type.GetElementType()!); } - string returnValue; - switch (typeName) + var returnValue = typeName switch { - case "Boolean": returnValue = "bool"; break; - case "Byte": returnValue = "byte"; break; - case "Char": returnValue = "char"; break; - case "Decimal": returnValue = "decimal"; break; - case "Double": returnValue = "double"; break; - case "Int16": returnValue = "short"; break; - case "Int32": returnValue = "int"; break; - case "Int64": returnValue = "long"; break; - case "SByte": returnValue = "sbyte"; break; - case "Single": returnValue = "float"; break; - case "String": returnValue = "string"; break; - case "UInt16": returnValue = "ushort"; break; - case "UInt32": returnValue = "uint"; break; - case "UInt64": returnValue = "ulong"; break; - case "Object": returnValue = "object"; break; - default: - return type.Name; - } + "Boolean" => "bool", + "Byte" => "byte", + "Char" => "char", + "Decimal" => "decimal", + "Double" => "double", + "Int16" => "short", + "Int32" => "int", + "Int64" => "long", + "SByte" => "sbyte", + "Single" => "float", + "String" => "string", + "UInt16" => "ushort", + "UInt32" => "uint", + "UInt64" => "ulong", + "Object" => "object", + "IntPtr" => "nint", + "UIntPtr" => "nuint", + _ => typeName + }; if (type.IsArray) { - return string.Join(string.Empty, returnValue, ArrayBrackets); + returnValue = $"{returnValue}[{new string(',', type.GetArrayRank() - 1)}]"; } return returnValue; } diff --git a/nuget.config b/nuget.config index 95282c9..4447c52 100644 --- a/nuget.config +++ b/nuget.config @@ -2,6 +2,6 @@ - + \ No newline at end of file