diff options
| author | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2019-04-09 01:47:48 +0300 |
|---|---|---|
| committer | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2019-04-09 01:47:48 +0300 |
| commit | 080f1697e97e13461ec6df4d31c8924d01257a1b (patch) | |
| tree | b1fe0285de7bc9bc52e9e2195e66fe022bf8f5b3 /Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs | |
| parent | 1608e69a417bc5e40a607c3958c4a60f19f66f1a (diff) | |
| download | Tango-080f1697e97e13461ec6df4d31c8924d01257a1b.tar.gz Tango-080f1697e97e13461ec6df4d31c8924d01257a1b.zip | |
MERGE
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs')
| -rw-r--r-- | Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs new file mode 100644 index 000000000..b3f5348f8 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs @@ -0,0 +1,268 @@ +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<String> Lines { get; set; } + } + + public Task<String> Compile(Script script) + { + return Task.Factory.StartNew<String>(() => + { + String code = script.Code; + List<IncludeResult> includeResults = new List<IncludeResult>(); + + 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<CompilationError> errors = new List<CompilationError>(); + + 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<ScriptSession> Run(Script script) + { + return Task.Factory.StartNew<ScriptSession>(() => + { + 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<String> GetIncludes(String code, String workingFolder) + { + if (Directory.Exists(workingFolder)) + { + Environment.CurrentDirectory = workingFolder; + } + + List<String> lines = code.ToLines(); + List<String> includeFiles = new List<string>(); + + 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<IncludeResult> includeResults) + { + if (Directory.Exists(workingFolder)) + { + Environment.CurrentDirectory = workingFolder; + } + + List<String> 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<String> usings = new List<string>(); + + List<String> 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 + } +} |
