From 080f1697e97e13461ec6df4d31c8924d01257a1b Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Tue, 9 Apr 2019 01:47:48 +0300 Subject: MERGE --- .../Scripting/Tango.Scripting/CompilationError.cs | 28 ++ .../Tango.Scripting/CompilationException.cs | 28 ++ .../Scripting/Tango.Scripting/ExtensionMethods.cs | 18 + .../Scripting/Tango.Scripting/GlobalObject.cs | 21 ++ .../Scripting/Tango.Scripting/IScriptSession.cs | 13 + .../Scripting/Tango.Scripting/IScriptingEngine.cs | 14 + .../Tango.Scripting/Parsing/ScriptParser.cs | 382 +++++++++++++++++++++ .../Tango.Scripting/Parsing/ScriptSymbol.cs | 31 ++ .../Tango.Scripting/Parsing/ScriptType.cs | 23 ++ .../Tango.Scripting/Properties/AssemblyInfo.cs | 7 + .../Scripting/Tango.Scripting/ReferenceAssembly.cs | 45 +++ .../Scripting/Tango.Scripting/Script.cs | 41 +++ .../Scripting/Tango.Scripting/ScriptSession.cs | 58 ++++ .../Tango.Scripting/ScriptSessionState.cs | 16 + .../ScriptSessionStateChangedEventArgs.cs | 17 + .../Scripting/Tango.Scripting/ScriptingEngine.cs | 268 +++++++++++++++ .../Tango.Scripting/Tango.Scripting.csproj | 154 +++++++++ .../Scripting/Tango.Scripting/app.config | 83 +++++ .../Scripting/Tango.Scripting/packages.config | 51 +++ 19 files changed, 1298 insertions(+) create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/CompilationError.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/CompilationException.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ExtensionMethods.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/GlobalObject.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/IScriptSession.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/IScriptingEngine.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptParser.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptSymbol.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptType.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Properties/AssemblyInfo.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ReferenceAssembly.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Script.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionState.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionStateChangedEventArgs.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/ScriptingEngine.cs create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/Tango.Scripting.csproj create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/app.config create mode 100644 Software/Visual_Studio/Scripting/Tango.Scripting/packages.config (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting') diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationError.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationError.cs new file mode 100644 index 000000000..03c96a413 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationError.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class CompilationError + { + public String File { get; set; } + + public String Name + { + get { return Path.GetFileName(File); } + } + + public String Message { get; set; } + public int Line { get; set; } + public int Character { get; set; } + + public override string ToString() + { + return String.Format("{0} {1} ({2},{3})", Message, Name, Line, Character); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationException.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationException.cs new file mode 100644 index 000000000..1b27dfe5b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/CompilationException.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class CompilationException : Exception + { + public List Errors { get; set; } + + public CompilationException() + { + Errors = new List(); + } + + public CompilationException(params CompilationError[] errors) + { + Errors = errors.ToList(); + } + + public override string ToString() + { + return String.Join(Environment.NewLine, Errors.Select(x => x.ToString())); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ExtensionMethods.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ExtensionMethods.cs new file mode 100644 index 000000000..ea29986b7 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ExtensionMethods.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + internal static class ExtensionMethods + { + public static List ToLines(this String str) + { + //return str.Split(new[] { '\r', '\n' }).ToList(); + if (str == null) return new List(); + return str.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None).ToList(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/GlobalObject.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/GlobalObject.cs new file mode 100644 index 000000000..4cac65ef6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/GlobalObject.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class GlobalObject : IDisposable + { + public GlobalObject() + { + + } + + public virtual void Dispose() + { + + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptSession.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptSession.cs new file mode 100644 index 000000000..3062d6bc0 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptSession.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public interface IScriptSession : IDisposable + { + + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptingEngine.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptingEngine.cs new file mode 100644 index 000000000..84d4d8d04 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/IScriptingEngine.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public interface IScriptingEngine : IDisposable + { + Task Compile(Script script); + Task Run(Script script); + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptParser.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptParser.cs new file mode 100644 index 000000000..15760c950 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptParser.cs @@ -0,0 +1,382 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Tango.Scripting.Parsing +{ + public class ScriptParser + { + private const string fakeScript = "CurrentScript"; + private string fakeScriptName = "CurrentScript."; + + private String ReplaceFakeScript(String str) + { + if (str != null) + { + return str.Replace(fakeScriptName, "").Replace(fakeScript, ""); + } + + return str; + } + + private CompilationUnitSyntax GetRoot(String code) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + var root = (CompilationUnitSyntax)tree.GetRoot(); + return root; + } + + private SemanticModel GetSemanticModel(String code) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + var compilation = CSharpCompilation.Create("CSharpScript").AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + return model; + } + + public List GetContextSymbols(String code, int caretOffset) + { + var currentNode = GetCaretOffsetNode(code, caretOffset); + + if (currentNode == null) return new List(); + + if (currentNode.Ancestors().OfType().Count() == 0) + { + var usings = GetUsingsFull(code); + + int removedLength = usings.Select(x => x.Length).Sum(); + + foreach (var use in usings) + { + code = code.Replace(use, ""); + } + + String scriptStart = $"public class {fakeScript}\n{{\n"; + code = $"{scriptStart}{code}\n}}"; + caretOffset = caretOffset - removedLength + scriptStart.Length; + } + + var model = GetSemanticModel(code); + var symbols = model.LookupSymbols(caretOffset).ToList().Where(x => x.Kind == SymbolKind.Property || x.Kind == SymbolKind.Field || x.Kind == SymbolKind.Parameter || x.Kind == SymbolKind.Local || x.Kind == SymbolKind.Method).ToList(); + + List vars = new List(); + + foreach (var symbol in symbols.DistinctBy(x => x.Name)) + { + if (symbol.ContainingSymbol.GetType().Name == "LambdaSymbol") + { + var invocationNode = currentNode.Ancestors().OfType().FirstOrDefault(); + + if (invocationNode != null) + { + var expressionNode = invocationNode.Expression as MemberAccessExpressionSyntax; + + if (expressionNode != null && expressionNode.Name != null) + { + var name = expressionNode.Name as GenericNameSyntax; + + if (name != null) + { + var type = name.TypeArgumentList.Arguments.FirstOrDefault()?.ToString(); + + vars.Add(new ScriptSymbol() + { + Name = symbol.Name, + Type = ReplaceFakeScript(type), + Class = ReplaceFakeScript(symbol.ContainingType?.Name), + Kind = symbol.Kind, + Accessibility = symbol.DeclaredAccessibility, + ContainingNamespace = ReplaceFakeScript(symbol.ContainingNamespace?.Name), + Summary = GetSymbolDocumentation(symbol), + }); + } + } + } + } + if (symbol.Kind == SymbolKind.Method) + { + var prop = symbol.GetType().GetProperty("ReturnType"); + + if (prop != null) + { + ScriptSymbol m = new ScriptSymbol() + { + Name = symbol.Name, + Type = ReplaceFakeScript(prop.GetValue(symbol).ToString()), + Class = ReplaceFakeScript(symbol.ContainingType?.Name), + Kind = symbol.Kind, + Accessibility = symbol.DeclaredAccessibility, + ContainingNamespace = ReplaceFakeScript(symbol.ContainingNamespace?.Name), + Summary = GetSymbolDocumentation(symbol), + }; + + m.Parameters = GetMethodSymbolParameters(symbol); + + vars.Add(m); + } + } + else + { + var prop = symbol.GetType().GetProperty("Type"); + + if (prop != null) + { + vars.Add(new ScriptSymbol() + { + Name = symbol.Name, + Type = ReplaceFakeScript(prop.GetValue(symbol).ToString()), + Class = ReplaceFakeScript(symbol.ContainingType?.Name), + Kind = symbol.Kind, + Accessibility = symbol.DeclaredAccessibility, + ContainingNamespace = ReplaceFakeScript(symbol.ContainingNamespace?.Name), + Summary = GetSymbolDocumentation(symbol), + }); + } + } + } + + return vars.Where(x => x.Type != "?").ToList(); + } + + public List GetUsings(String code) + { + Regex r = new Regex("(using [^;^\n]+;)"); + var matches = r.Matches(code); + return matches.OfType().Select(x => x.Value.Replace("using ", "").Replace(";", "").Replace("\n", "").Replace("\t", "").Replace("\r", "")).ToList(); + } + + public List GetUsingsFull(String code) + { + Regex r = new Regex("(using [^;^\n]+;)"); + var matches = r.Matches(code); + return matches.OfType().Select(x => x.Value).ToList(); + } + + public List GetDeclaredTypes(String code) + { + List types = new List(); + + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + var compilation = CSharpCompilation.Create("CSharpScript").AddSyntaxTrees(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + + foreach (var d in root.DescendantNodes().Where(x => x is ClassDeclarationSyntax || x is EnumDeclarationSyntax || x is InterfaceDeclarationSyntax).OfType()) + { + var type = model.GetDeclaredSymbol(d); + if (!String.IsNullOrWhiteSpace(type.Name)) + { + ScriptType scriptType = new ScriptType(); + scriptType.Name = type.Name; + scriptType.Kind = type.TypeKind; + scriptType.ContainingNamespace = type.ContainingNamespace?.Name; + scriptType.Summary = GetNodeDocumentation(d); + + + foreach (var symbol in d.DescendantNodes().OfType()) + { + var symbolModel = model.GetDeclaredSymbol(symbol); + + scriptType.Symbols.Add(new ScriptSymbol() + { + Class = scriptType.Name, + Accessibility = symbolModel.DeclaredAccessibility, + Kind = SymbolKind.Property, + Name = symbolModel.Name, + Type = symbolModel.Type.ToString(), + ContainingNamespace = symbolModel.ContainingNamespace?.Name, + Summary = GetNodeDocumentation(symbol), + }); + } + + foreach (var symbol in d.DescendantNodes().OfType()) + { + var symbolModel = model.GetDeclaredSymbol(symbol.Declaration.Variables.FirstOrDefault()) as IFieldSymbol; + + if (symbolModel != null) + { + scriptType.Symbols.Add(new ScriptSymbol() + { + Class = scriptType.Name, + Accessibility = symbolModel.DeclaredAccessibility, + Kind = SymbolKind.Field, + Name = symbolModel.Name, + Type = symbolModel.Type.ToString(), + ContainingNamespace = symbolModel.ContainingNamespace?.Name, + Summary = GetNodeDocumentation(symbol), + }); + } + } + + foreach (var symbol in d.DescendantNodes().OfType()) + { + var symbolModel = model.GetDeclaredSymbol(symbol); + + if (symbolModel != null) + { + ScriptSymbol m = new ScriptSymbol() + { + Class = scriptType.Name, + Accessibility = symbolModel.DeclaredAccessibility, + Kind = SymbolKind.Method, + Name = symbolModel.Name, + Type = symbolModel.ReturnType.ToString(), + ContainingNamespace = symbolModel.ContainingNamespace?.Name, + Summary = GetNodeDocumentation(symbol), + }; + + foreach (var p in symbol.DescendantNodes().OfType()) + { + if (p.Type != null && p.Identifier != null) + { + m.Parameters.Add(new KeyValuePair(p.Type.ToString(), p.Identifier.ToString())); + } + } + + scriptType.Symbols.Add(m); + } + } + + types.Add(scriptType); + } + } + + return types; + } + + public ObjectCreationExpressionSyntax GetCurrentConstructionExpression(String code) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + return root.DescendantNodes().OfType().FirstOrDefault(); + } + + public T GetExpressionFirst(String line) where T : CSharpSyntaxNode + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(line); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + return root.DescendantNodes().OfType().FirstOrDefault(); + } + + public List GetExpressions(String line) where T : CSharpSyntaxNode + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(line); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + return root.DescendantNodes().OfType().ToList(); + } + + public List GetDirectExpressions(SyntaxNode node) where T : CSharpSyntaxNode + { + return node.DescendantNodes().OfType().ToList(); + } + + private SyntaxNode GetCaretOffsetNode(String code, int offset) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CompilationUnitSyntax root = tree.GetCompilationUnitRoot(); + return root.DescendantNodes().Where(x => offset >= x.FullSpan.Start && offset <= x.FullSpan.End).OrderBy(x => x.FullSpan.Length).FirstOrDefault(); + } + + private List GetNodeAncestors(SyntaxNode node) + { + return node.Ancestors().ToList(); + } + + private String GetNodeDocumentation(SyntaxNode node) + { + try + { + var trivia = node.GetLeadingTrivia().FirstOrDefault(t => t.Kind() == SyntaxKind.SingleLineCommentTrivia); + + if (trivia != null && !String.IsNullOrWhiteSpace(trivia.ToString())) + { + return trivia.ToString().Replace("//", ""); + } + } + catch { } + + return "No documentation."; + } + + private String GetSymbolDocumentation(ISymbol symbol) + { + if (symbol != null) + { + var prop = symbol.GetType().GetProperty("SyntaxNode"); + + if (prop != null) + { + var node = prop.GetValue(symbol) as SyntaxNode; + + if (node != null) + { + return GetNodeDocumentation(node.Parent); + } + } + } + + return "No documentation."; + } + + private SyntaxNode GetSymbolSyntaxNode(ISymbol symbol) + { + if (symbol != null) + { + var prop = symbol.GetType().GetProperty("SyntaxNode"); + + if (prop != null) + { + var node = prop.GetValue(symbol) as SyntaxNode; + + return node; + } + } + + return null; + } + + private List> GetMethodSymbolParameters(ISymbol symbol) + { + List> parameters = new List>(); + + try + { + var prop = symbol.GetType().GetProperty("Parameters"); + + if (prop != null) + { + var array = prop.GetValue(symbol) as IEnumerable; + + foreach (var item in array) + { + var type = item.GetType().GetProperty("Type").GetValue(item).ToString(); + var value = item.GetType().GetProperty("Name").GetValue(item).ToString(); + + parameters.Add(new KeyValuePair(type, value)); + } + } + } + catch { } + + return parameters; + } + + public String IndentCSharpCode(String code) + { + var tree = CSharpSyntaxTree.ParseText(code); + var root = tree.GetRoot().NormalizeWhitespace(); + var ret = root.ToFullString(); + return ret; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptSymbol.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptSymbol.cs new file mode 100644 index 000000000..d6fdaeebf --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptSymbol.cs @@ -0,0 +1,31 @@ +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Parsing +{ + public class ScriptSymbol + { + public String Name { get; set; } + public String Type { get; set; } + public SymbolKind Kind { get; set; } + public String Class { get; set; } + public Accessibility Accessibility { get; set; } + public String ContainingNamespace { get; set; } + public List> Parameters { get; set; } + public String Summary { get; set; } + + public ScriptSymbol() + { + Parameters = new List>(); + } + + public override string ToString() + { + return $"{Kind.ToString()} : {Type} : {Name}"; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptType.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptType.cs new file mode 100644 index 000000000..3ca34a85e --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Parsing/ScriptType.cs @@ -0,0 +1,23 @@ +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting.Parsing +{ + public class ScriptType + { + public String Name { get; set; } + public TypeKind Kind { get; set; } + public List Symbols { get; set; } + public string ContainingNamespace { get; set; } + public String Summary { get; set; } + + public ScriptType() + { + Symbols = new List(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1fd19cced --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Tango - Scripting Library")] +[assembly: ComVisible(false)] +[assembly: AssemblyVersion("2.0.36.1608")] diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ReferenceAssembly.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ReferenceAssembly.cs new file mode 100644 index 000000000..c4e1925a2 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ReferenceAssembly.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class ReferenceAssembly + { + public Assembly Assembly { get; set; } + + public String File + { + get { return Assembly.Location; } + } + + public String Name + { + get { return Path.GetFileNameWithoutExtension(File); } + } + + public ReferenceAssembly(Assembly assembly) + { + Assembly = assembly; + } + + public ReferenceAssembly(String file) + { + Assembly = Assembly.LoadFrom(file); + } + + public ReferenceAssembly(Type type) + { + Assembly = type.Assembly; + } + + public override string ToString() + { + return Assembly.ToString(); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Script.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/Script.cs new file mode 100644 index 000000000..a4cd38c3d --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Script.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class Script + { + public String Name { get; set; } + + public String File { get; set; } + + public GlobalObject GlobalObject { get; set; } + + public List ReferenceAssemblies { get; private set; } + + public List Imports { get; private set; } + + public String Code { get; set; } + + public String WorkingFolder { get; set; } + + public String EntryPoint { get; set; } + + public ApartmentState ApartmentState { get; set; } + + public Script() + { + ReferenceAssemblies = new List(); + Imports = new List(); + GlobalObject = new GlobalObject(); + File = "Script.cs"; + Name = "Script.cs"; + WorkingFolder = Environment.CurrentDirectory; + ApartmentState = ApartmentState.MTA; + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs new file mode 100644 index 000000000..647ec7d87 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class ScriptSession + { + private Action _abortAction; + + public event EventHandler StateChanged; + + public Script Script { get; private set; } + + public String EffectiveCode { get; set; } + + public ScriptSessionState State { get; set; } + + public ScriptSession(Script script, String effectiveCode, Action abortAction) + { + _abortAction = abortAction; + Script = script; + EffectiveCode = effectiveCode; + } + + public void Abort() + { + _abortAction(); + State = ScriptSessionState.Aborted; + RaiseStateChanged(); + } + + internal void Failed(Exception ex) + { + State = ScriptSessionState.Failed; + RaiseStateChanged(null, ex); + } + + internal void Completed(object returnValue) + { + State = ScriptSessionState.Completed; + RaiseStateChanged(returnValue, null); + } + + private void RaiseStateChanged(object returnValue = null, Exception ex = null) + { + StateChanged?.Invoke(this, + new ScriptSessionStateChangedEventArgs() + { + ReturnValue = returnValue, + State = State, + Exception = ex + }); + } + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionState.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionState.cs new file mode 100644 index 000000000..5b65623f9 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionState.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public enum ScriptSessionState + { + Running, + Completed, + Aborted, + Failed, + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionStateChangedEventArgs.cs b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionStateChangedEventArgs.cs new file mode 100644 index 000000000..7ea3b924b --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSessionStateChangedEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Scripting +{ + public class ScriptSessionStateChangedEventArgs : EventArgs + { + public Object ReturnValue { get; set; } + + public ScriptSessionState State { get; set; } + + public Exception Exception { get; set; } + } +} 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 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 + } +} diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/Tango.Scripting.csproj b/Software/Visual_Studio/Scripting/Tango.Scripting/Tango.Scripting.csproj new file mode 100644 index 000000000..044ac88d6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/Tango.Scripting.csproj @@ -0,0 +1,154 @@ + + + + + Debug + AnyCPU + {1E938FD2-C669-4738-98C9-77F96CE4D451} + Library + Properties + Tango.Scripting + Tango.Scripting + v4.6.1 + 512 + true + + + true + full + false + ..\..\Build\Scripting\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\Build\Scripting\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Microsoft.CodeAnalysis.Common.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.dll + + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.dll + + + ..\..\packages\Microsoft.CodeAnalysis.CSharp.Scripting.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.CSharp.Scripting.dll + + + ..\..\packages\Microsoft.CodeAnalysis.Scripting.Common.2.4.0\lib\netstandard1.3\Microsoft.CodeAnalysis.Scripting.dll + + + + ..\..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll + + + ..\..\packages\System.Collections.Immutable.1.3.1\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll + + + + ..\..\packages\System.Console.4.3.0\lib\net46\System.Console.dll + + + + ..\..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll + + + ..\..\packages\System.Diagnostics.StackTrace.4.3.0\lib\net46\System.Diagnostics.StackTrace.dll + + + ..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll + + + ..\..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll + + + ..\..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll + + + + ..\..\packages\System.Reflection.Metadata.1.4.2\lib\portable-net45+win8\System.Reflection.Metadata.dll + + + ..\..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll + + + ..\..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll + + + ..\..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll + + + ..\..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll + + + ..\..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll + + + ..\..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll + + + ..\..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll + + + + + + + + + + ..\..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll + + + ..\..\packages\System.Xml.XmlDocument.4.3.0\lib\net46\System.Xml.XmlDocument.dll + + + ..\..\packages\System.Xml.XPath.4.3.0\lib\net46\System.Xml.XPath.dll + + + ..\..\packages\System.Xml.XPath.XDocument.4.3.0\lib\net46\System.Xml.XPath.XDocument.dll + + + + + GlobalVersionInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + {a34ee0f0-649d-41c8-8489-b6f1cc6924ee} + Tango.Core + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/app.config b/Software/Visual_Studio/Scripting/Tango.Scripting/app.config new file mode 100644 index 000000000..38ef71542 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/app.config @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting/packages.config b/Software/Visual_Studio/Scripting/Tango.Scripting/packages.config new file mode 100644 index 000000000..02b4f06f6 --- /dev/null +++ b/Software/Visual_Studio/Scripting/Tango.Scripting/packages.config @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.3.1