139 lines
6.5 KiB
C#
139 lines
6.5 KiB
C#
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Microsoft.CodeAnalysis.Scripting;
|
|
using RobotNet10.ScriptEngine;
|
|
using RobotNet10.ScriptEngine.Models;
|
|
using RobotNet10.ScriptEngine.Shared;
|
|
using System.Collections.Immutable;
|
|
using System.Text;
|
|
|
|
namespace RobotNet10.ScriptEngine.Helpers;
|
|
|
|
public class ScriptBuilder
|
|
{
|
|
public readonly string DllPath = "dlls";
|
|
public ScriptOptions ScriptOptions { get; private set; } = ScriptOptions.Default;
|
|
public ImmutableArray<MetadataReference> MetadataReferences { get; private set; } = [];
|
|
public readonly string UsingNamespacesScript = "";
|
|
public string VerifyGlobalsScript = "";
|
|
public string RuntimeGlobalsScript = "";
|
|
|
|
public ScriptBuilder(IScriptEngineResource scriptResource, string dllPath)
|
|
{
|
|
if (!string.IsNullOrEmpty(dllPath))
|
|
{
|
|
DllPath = dllPath;
|
|
}
|
|
UsingNamespacesScript = string.Join(Environment.NewLine, scriptResource.UsingNamespaces.Select(ns => $"using {ns};"));
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine($"private Dictionary<string, object?> {nameof(ScriptGlobals.RobotNet)} = [];");
|
|
sb.AppendLine($"private Dictionary<string, object?> {nameof(ScriptGlobals.AppApis)} = [];");
|
|
sb.AppendLine($"private Dictionary<string, object?> {nameof(ScriptGlobals.GlobalVariables)} = [];");
|
|
sb.AppendLine($"private Dictionary<string, object?> {nameof(ScriptGlobals.MissionParameters)} = [];");
|
|
VerifyGlobalsScript = sb.ToString();
|
|
|
|
RuntimeGlobalsScript = ScriptHelper.BuildGlobalsScript(scriptResource.AppGlobalType);
|
|
|
|
List<MetadataReference> metadataRefs = [];
|
|
var currentDirectory = Directory.GetCurrentDirectory();
|
|
if (Directory.Exists(DllPath))
|
|
{
|
|
foreach (var dll in Directory.GetFiles(DllPath, "*.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(scriptResource.UsingNamespaces).WithEmitDebugInformation(false);
|
|
}
|
|
|
|
public void Build(string code, out IEnumerable<ScriptVariableModel> variables, out IEnumerable<ScriptTaskModel> tasks, out IEnumerable<ScriptMissionModel> missions)
|
|
{
|
|
try
|
|
{
|
|
var listVariables = new List<ScriptVariableModel>();
|
|
|
|
var wrappedCode = string.Join(Environment.NewLine, [UsingNamespacesScript, "public class DummyClass", "{", VerifyGlobalsScript, RuntimeGlobalsScript, code, "}"]);
|
|
|
|
var devCompilation = CSharpCompilation.Create("CodeAnalysis")
|
|
.WithReferences(MetadataReferences)
|
|
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
|
|
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(wrappedCode));
|
|
|
|
var diagnostics = devCompilation.GetDiagnostics();
|
|
if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error))
|
|
{
|
|
var message = new StringBuilder("Script compilation errors:\r\n");
|
|
foreach (var diag in diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))
|
|
{
|
|
var location = diag.Location;
|
|
var lineSpan = location.GetLineSpan();
|
|
message.AppendLine($"\t❌ Error: {diag.GetMessage()}");
|
|
message.AppendLine($"\t Location: Line {lineSpan.StartLinePosition.Line + 1}, Column {lineSpan.StartLinePosition.Character + 1}");
|
|
if (location.SourceTree != null && lineSpan.IsValid)
|
|
{
|
|
var line = location.SourceTree.GetText().Lines[lineSpan.StartLinePosition.Line];
|
|
message.AppendLine($"\t Code: {line.ToString().Trim()}");
|
|
}
|
|
}
|
|
throw new ScriptCompilationException(message.ToString().TrimEnd());
|
|
}
|
|
|
|
wrappedCode = string.Join(Environment.NewLine, [UsingNamespacesScript, "public class DummyClass", "{", code, "}"]);
|
|
var syntaxTree = CSharpSyntaxTree.ParseText(wrappedCode);
|
|
var root = syntaxTree.GetCompilationUnitRoot();
|
|
|
|
var runCompilation = CSharpCompilation.Create("CodeAnalysis")
|
|
.AddSyntaxTrees(syntaxTree)
|
|
.WithReferences(MetadataReferences)
|
|
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
|
|
|
var classNode = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault(c => c.Identifier.Text.Equals("DummyClass"));
|
|
if (classNode == null)
|
|
{
|
|
throw new ScriptCompilationException("No class named 'DummyClass' found in the script. This is an internal error.");
|
|
}
|
|
|
|
var semanticModel = runCompilation.GetSemanticModel(syntaxTree);
|
|
|
|
try
|
|
{
|
|
variables = ScriptHelper.ExportScriptVariables(classNode, semanticModel, ScriptOptions);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new ScriptCompilationException($"Failed to export script variables: {ex.Message}", ex);
|
|
}
|
|
|
|
try
|
|
{
|
|
ScriptHelper.ExportScriptTasksAndMissions(classNode, semanticModel, RuntimeGlobalsScript, ScriptOptions, out var scriptTasks, out var scriptMissions);
|
|
tasks = scriptTasks;
|
|
missions = scriptMissions;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new ScriptCompilationException($"Failed to export script tasks and missions: {ex.Message}", ex);
|
|
}
|
|
}
|
|
catch (ScriptCompilationException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new ScriptCompilationException($"Unexpected error during script compilation: {ex.Message}", ex);
|
|
}
|
|
finally
|
|
{
|
|
// Only force GC if we're dealing with large scripts or many compilations
|
|
// In normal cases, let GC handle it naturally
|
|
// GC.Collect();
|
|
}
|
|
}
|
|
}
|