using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace Tango.Scripting { public class ScriptingEngine : IScriptingEngine { private class IncludeResult { public String File { get; set; } public List Lines { get; set; } } public Task Compile(Script script) { return Task.Factory.StartNew(() => { String code = script.Code; List includeResults = new List(); try { includeResults.Add(new IncludeResult() { File = script.File, Lines = script.Code.ToLines() }); code = ReplaceIncludes(script, code, script.WorkingFolder, includeResults); } catch (Exception ex) { throw new CompilationException(new CompilationError() { Message = ex.Message }); } var options = CreateOptions(script); var csharpScript = CSharpScript.Create(code, options: options, globalsType: script.GlobalObject.GetType()); var results = csharpScript.Compile(); List errors = new List(); foreach (var result in results.Where(x => x.Severity == DiagnosticSeverity.Error)) { CompilationError error = new CompilationError(); error.Message = result.GetMessage(); error.Character = result.Location.GetMappedLineSpan().StartLinePosition.Character + 1; int lineIndex = 0; IncludeResult 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 = include.File; error.Line = lineIndex + 1; } errors.Add(error); } if (errors.Count > 0) { throw new CompilationException(errors.ToArray()); } return code; }); } public Task Run(Script script) { return Task.Factory.StartNew(() => { var effectivCode = Compile(script).Result; var options = CreateOptions(script); var _cancaller = new CancellationTokenSource(); Thread scriptThread = null; ScriptSession session = null; session = new ScriptSession(script, effectivCode, () => { scriptThread.Abort(); }); scriptThread = new Thread(() => { try { var result = CSharpScript.RunAsync(effectivCode, options: options, globals: script.GlobalObject, cancellationToken: _cancaller.Token).Result; session.Completed(result.ReturnValue); } catch (ThreadAbortException) { } catch (Exception ex) { session.Failed(ex.InnerException); } }); scriptThread.SetApartmentState(script.ApartmentState); scriptThread.IsBackground = true; scriptThread.Start(); return session; }); } public void Dispose() { throw new NotImplementedException(); } #region Private Methods 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(Script script, 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 IncludeResult() { File = path, Lines = includeContent.ToLines() }); String content = ReplaceIncludes(script, includeContent, Path.GetDirectoryName(path), includeResults); if (!String.IsNullOrWhiteSpace(script.EntryPoint)) { if (content.Contains(script.EntryPoint + "(")) { throw new InvalidProgramException(String.Format("Include file '{0}' contains an OnExecute method. Please remove it before trying to compile.", path)); } } lines[i] = content; } } code = ClearUsings(String.Join(Environment.NewLine, lines)); return code; } private String ClearUsings(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(Script script) { //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(ScriptingEngine).Assembly.Location); foreach (var asm in script.ReferenceAssemblies) { options = options.AddReferences(asm.File); } //Imports. options = options.AddImports( "System", "System.Collections.Generic", "System.Linq", "System.Text", "System.Diagnostics", "System.Windows.Forms", "System.Threading" ); if (script.Imports.Count > 0) { options = options.AddImports(script.Imports); } return options; } #endregion } }