RobotNet/RobotNet.ScriptManager/Helpers/ScriptConfiguration.cs
2025-10-15 15:15:53 +07:00

227 lines
10 KiB
C#

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<MetadataReference> MetadataReferences;
public static readonly ImmutableArray<string> 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<MetadataReference> 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}";
}
}