using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace Tango.Scripting { /// /// Represents a C# scripting engine capable of executing C# scripts using the Roslyn engine. /// public class ScriptEngine { private CancellationTokenSource _cancaller; private OnExecuteParameters _onExecuteParameters; public List ReferencedAssemblies { get; private set; } private class IncludesResult { public String File { get; set; } public List Lines { get; set; } } /// /// Initializes a new instance of the class. /// /// The parameters. public ScriptEngine(OnExecuteParameters parameters) { _onExecuteParameters = parameters; ReferencedAssemblies = new List(); } /// /// Runs the specified code. /// /// The code. /// public async Task Run(String code, String workingFolder) { try { List includeResults = new List(); code = ReplaceIncludes(code, workingFolder, includeResults); } catch (Exception ex) { throw ex; } var options = CreateOptions(); String methodParameters = CreateMethodParameters(); _cancaller = new CancellationTokenSource(); await CSharpScript.RunAsync( code + Environment.NewLine + Environment.NewLine + "await Task.Factory.StartNew(() => { OnExecute(" + methodParameters + "); });", options: options, globals: _onExecuteParameters, cancellationToken: _cancaller.Token); } public Task> Compile(String code, String workingFolder) { return Task.Factory.StartNew>(() => { List includeResults = new List(); try { includeResults.Add(new IncludesResult() { File = "Current Tab", Lines = code.ToLines() }); code = ReplaceIncludes(code, workingFolder, includeResults); } catch (Exception ex) { return new List() { new CompilerError() { Error = ex.Message } }; } var options = CreateOptions(); String methodParameters = CreateMethodParameters(); _cancaller = new CancellationTokenSource(); var script = CSharpScript.Create( code, options: options, globalsType: _onExecuteParameters.GetType()); var results = script.Compile(); List errors = new List(); foreach (var result in results.Where(x => x.Severity == DiagnosticSeverity.Error)) { CompilerError error = new CompilerError(); error.Error = result.GetMessage(); error.Character = result.Location.GetMappedLineSpan().StartLinePosition.Character + 1; int lineIndex = 0; IncludesResult include = null; foreach (var inc in includeResults) { for (int i = 0; i < inc.Lines.Count; i++) { if (inc.Lines[i] == code.ToLines()[result.Location.GetMappedLineSpan().StartLinePosition.Line]) { include = inc; lineIndex = i; break; } } } if (include != null) { error.File = Path.GetFileName(include.File); error.Line = lineIndex + 1; } errors.Add(error); } return errors; }); } private String CreateMethodParameters() { return String.Join(", ", _onExecuteParameters.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance).Select(x => x.Name)); } private List GetIncludes(String code, String workingFolder) { if (Directory.Exists(workingFolder)) { Environment.CurrentDirectory = workingFolder; } List lines = code.ToLines(); List includeFiles = new List(); for (int i = 0; i < lines.Count; i++) { var line = lines[i]; if (line.Trim().StartsWith("include")) { String path = line.Replace("include", "").Trim().Replace("\"", ""); if (!File.Exists(path)) { throw new FileNotFoundException("Could not locate include file '" + path + "'."); } includeFiles.Add(path); } } return includeFiles; } private String ReplaceIncludes(String code, String workingFolder, List includeResults) { if (Directory.Exists(workingFolder)) { Environment.CurrentDirectory = workingFolder; } List lines = code.ToLines().ToList(); for (int i = 0; i < lines.Count; i++) { var line = lines[i]; if (line.Trim().StartsWith("include")) { String path = line.Replace("include", "").Trim().Replace("\"", ""); if (!File.Exists(path)) { throw new FileNotFoundException("Could not locate include file '" + path + "'."); } String includeContent = File.ReadAllText(path); includeResults.Add(new IncludesResult() { File = path, Lines = includeContent.ToLines() }); String content = ReplaceIncludes(includeContent, Path.GetDirectoryName(path), includeResults); if (content.Contains("OnExecute(")) { throw new InvalidProgramException(String.Format("Include file '{0}' contains and OnExecute method. Please remove it before trying to compile.", path)); } lines[i] = content; } } code = ClearnUsings(String.Join(Environment.NewLine, lines)); return code; } private String ClearnUsings(String code) { List usings = new List(); List lines = code.ToLines(); foreach (var line in lines) { if (line.Trim().StartsWith("using")) { usings.Add(line); } } lines.RemoveAll(x => x.Trim().StartsWith("using")); return String.Join(Environment.NewLine, usings.Distinct()) + Environment.NewLine + String.Join(Environment.NewLine, lines); } private ScriptOptions CreateOptions() { //My References. var options = ScriptOptions.Default; //My Assemblies. options = options.AddReferences(typeof(Form).Assembly.Location); options = options.AddReferences(typeof(Enumerable).Assembly.Location); options = options.AddReferences(typeof(ScriptEngine).Assembly.Location); foreach (var asm in ReferencedAssemblies) { options = options.AddReferences(asm.Assembly.Location); } //Imports. options = options.AddImports( "System", "System.Collections.Generic", "System.Linq", "System.Text", "System.Diagnostics", "System.Windows.Forms" ); return options; } /// /// Stops this instance. /// public void Stop() { _cancaller.Cancel(); } } }