using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Scripting; using RobotNet.Script.Shares; using System.Collections.Immutable; using System.Reflection; using System.Text; namespace RobotNet.ScriptManager.Helpers; public static class ScriptConfiguration { public static readonly string ScriptStorePath; public static readonly string DllsPath; public static readonly ScriptOptions ScriptOptions; public static readonly ImmutableArray MetadataReferences; public static readonly ImmutableArray UsingNamespaces; public static readonly string UsingNamespacesScript; public static readonly string DeveloptGlobalsScript; public static readonly string RuntimeGlobalsScript; static ScriptConfiguration() { ScriptStorePath = "scripts"; DllsPath = "dlls"; UsingNamespaces = ["System", "System.Collections.Generic", "System.Linq.Expressions", "System.Threading", "System.Threading.Tasks", "System.Runtime.CompilerServices", "RobotNet.Script"]; UsingNamespacesScript = string.Join(Environment.NewLine, UsingNamespaces.Select(ns => $"using {ns};")); List metadataRefs = []; var currentDirectory = Directory.GetCurrentDirectory(); if (Directory.Exists(DllsPath)) { foreach (var dll in Directory.GetFiles(DllsPath, "*.dll")) { metadataRefs.Add(MetadataReference.CreateFromFile(Path.Combine(currentDirectory, dll), properties: MetadataReferenceProperties.Assembly)); } } MetadataReferences = [.. metadataRefs]; var options = ScriptOptions.Default; options.MetadataReferences.Clear(); ScriptOptions = options.AddReferences(MetadataReferences).AddImports(UsingNamespaces).WithEmitDebugInformation(false); DeveloptGlobalsScript = BuildDeveloptGlobalsScript(); RuntimeGlobalsScript = BuildRuntimeGlobalsScript(); } public static string GetScriptCode() => ReadAllTextFromFolder(ScriptStorePath); private static string ReadAllTextFromFolder(string path) { var dirInfo = new DirectoryInfo(path); var code = string.Join(Environment.NewLine, [.. dirInfo.GetFiles("*.cs").Select(f => File.ReadAllText(f.FullName))]); var after = string.Join(Environment.NewLine, dirInfo.GetDirectories().Select(dir => ReadAllTextFromFolder(dir.FullName)).ToArray()); return string.Join(Environment.NewLine, code, after); } private static string BuildRuntimeGlobalsScript() { var type = typeof(IRobotNetGlobals); var sb = new StringBuilder(); foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { sb.AppendLine($@"{CSharpSyntaxHelper.ToTypeString(field.FieldType)} {field.Name} {{ get => ({CSharpSyntaxHelper.ToTypeString(field.FieldType)})robotnet[""{field.Name}""]; set => robotnet[""{field.Name}""] = value; }}"); } foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (prop.GetIndexParameters().Length == 0) { var hasGetter = prop.GetGetMethod() != null; var hasSetter = prop.GetSetMethod() != null; if (hasGetter || hasSetter) { var propBuilder = new StringBuilder(); propBuilder.AppendLine($"{CSharpSyntaxHelper.ToTypeString(prop.PropertyType)} {prop.Name}"); propBuilder.AppendLine("{"); if (hasGetter) propBuilder.AppendLine($@" get => ((Func<{CSharpSyntaxHelper.ToTypeString(prop.PropertyType)}>)robotnet[""get_{prop.Name}""])();"); if (hasSetter) propBuilder.AppendLine($@" set => ((Action<{CSharpSyntaxHelper.ToTypeString(prop.PropertyType)}>)robotnet[""set_{prop.Name}""])(value);"); propBuilder.AppendLine("}"); sb.AppendLine(propBuilder.ToString()); } } else { // Handle indexers (properties with parameters) var indexParams = prop.GetIndexParameters(); var paramDecl = string.Join(", ", indexParams.Select(p => $"{CSharpSyntaxHelper.ToTypeString(p.ParameterType)} {p.Name}")); var paramNames = string.Join(", ", indexParams.Select(p => p.Name)); var getterDelegateType = $"Func<{string.Join(", ", indexParams.Select(p => CSharpSyntaxHelper.ToTypeString(p.ParameterType)).Concat([CSharpSyntaxHelper.ToTypeString(prop.PropertyType)]))}>"; var setterDelegateType = $"Action<{string.Join(", ", indexParams.Select(p => CSharpSyntaxHelper.ToTypeString(p.ParameterType)).Concat([CSharpSyntaxHelper.ToTypeString(prop.PropertyType)]))}>"; var propBuilder = new StringBuilder(); propBuilder.AppendLine($"{CSharpSyntaxHelper.ToTypeString(prop.PropertyType)} this[{paramDecl}]"); propBuilder.AppendLine("{"); if (prop.GetGetMethod() != null) propBuilder.AppendLine($@" get => (({getterDelegateType})robotnet[""get_{prop.Name}_indexer""])({paramNames});"); if (prop.GetSetMethod() != null) propBuilder.AppendLine($@" set => (({setterDelegateType})robotnet[""set_{prop.Name}_indexer""])({(string.IsNullOrEmpty(paramNames) ? "value" : paramNames + ", value")});"); propBuilder.AppendLine("}"); sb.AppendLine(propBuilder.ToString()); } } foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { if (!method.IsSpecialName) { var parameters = method.GetParameters(); var parametersStr = string.Join(", ", parameters.Select(ToParameterString)); var args = string.Join(", ", parameters.Select(p => p.Name)); var returnType = CSharpSyntaxHelper.ToTypeString(method.ReturnType); var paramTypes = string.Join(",", parameters.Select(p => p.ParameterType.FullName)); var methodKey = $"{method.Name}({paramTypes})"; var methodType = method.ReturnType == typeof(void) ? (parameters.Length == 0 ? "Action" : $"Action<{string.Join(", ", parameters.Select(p => CSharpSyntaxHelper.ToTypeString(p.ParameterType)))}>") : $"Func<{string.Join(", ", parameters.Select(p => CSharpSyntaxHelper.ToTypeString(p.ParameterType)).Concat([CSharpSyntaxHelper.ToTypeString(method.ReturnType)]))}>"; sb.AppendLine($@"{returnType} {method.Name}({parametersStr}) => (({methodType})robotnet[""{methodKey}""]){(string.IsNullOrEmpty(args) ? "()" : $"({args})")};"); } } var code = sb.ToString(); return sb.ToString(); } private static string BuildDeveloptGlobalsScript() { var sb = new StringBuilder(); var glovalType = typeof(IRobotNetGlobals); var properties = glovalType.GetProperties(); foreach (var property in properties) { var propStr = ""; if (property.CanRead) { propStr = $"{CSharpSyntaxHelper.ToTypeString(property.PropertyType)} {property.Name} {{ get; {(property.CanWrite ? "set; " : "")}}}"; } else if (property.CanWrite) { propStr = $"{CSharpSyntaxHelper.ToTypeString(property.PropertyType)} {property.Name} {{ set => throw new System.NotImplementedException(); }}"; } if(string.IsNullOrEmpty(propStr)) continue; sb.AppendLine(propStr); } var fields = glovalType.GetFields(); foreach (var field in fields) { sb.AppendLine($"{CSharpSyntaxHelper.ToTypeString(field.FieldType)} {field.Name};"); } var methods = glovalType.GetMethods(); foreach (var method in methods) { if (method.Name.StartsWith("get_") || method.Name.StartsWith("set_")) continue; var notImplementedMethod = $"{CSharpSyntaxHelper.ToTypeString(method.ReturnType)} {method.Name}({string.Join(',', method.GetParameters().Select(ToParameterString))}) => throw new System.NotImplementedException();"; sb.AppendLine(notImplementedMethod); } return sb.ToString(); } private static string ToParameterString(ParameterInfo parameter) { var modifier = ""; if (parameter.IsDefined(typeof(ParamArrayAttribute), false)) modifier = "params "; else if (parameter.IsIn && parameter.ParameterType.IsByRef && !parameter.IsOut) modifier = "in "; else if (parameter.IsOut) modifier = "out "; else if (parameter.ParameterType.IsByRef) modifier = "ref "; var typeString = CSharpSyntaxHelper.ToTypeString( parameter.ParameterType.IsByRef ? parameter.ParameterType.GetElementType()! : parameter.ParameterType ); var defaultValue = ""; if (parameter.HasDefaultValue) { if (parameter.DefaultValue != null) { if (parameter.ParameterType.IsEnum) { defaultValue = $" = {CSharpSyntaxHelper.ToTypeString(parameter.ParameterType)}.{parameter.DefaultValue}"; } else if (parameter.DefaultValue is string) { defaultValue = $" = \"{parameter.DefaultValue}\""; } else if (parameter.DefaultValue is bool b) { defaultValue = $" = {b.ToString().ToLower()}"; } else { defaultValue = $" = {parameter.DefaultValue}"; } } else { defaultValue = " = null"; } } return $"{modifier}{typeString} {parameter.Name}{defaultValue}"; } }