aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting/ScriptSession.cs
Commit message (Collapse)AuthorAgeFilesLines
* MERGERoy Ben Shabat2019-04-091-0/+58
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<ScriptSymbol> GetContextSymbols(String code, int caretOffset)
        {
            var currentNode = GetCaretOffsetNode(code, caretOffset);

            if (currentNode == null) return new List<ScriptSymbol>();

            if (currentNode.Ancestors().OfType<BaseTypeDeclarationSyntax>().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<ScriptSymbol> vars = new List<ScriptSymbol>();

            foreach (var symbol in symbols.DistinctBy(x => x.Name))
            {
                if (symbol.ContainingSymbol.GetType().Name == "LambdaSymbol")
                {
                    var invocationNode = currentNode.Ancestors().OfType<InvocationExpressionSyntax>().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)
                    {
                        var type = ReplaceFakeScript(prop.GetValue(symbol).ToString());

                        ScriptSymbol varSymbol = new ScriptSymbol();
                        varSymbol.Name = symbol.Name;
                        varSymbol.Type = type;
                        varSymbol.Class = ReplaceFakeScript(symbol.ContainingType?.Name);
                        varSymbol.Kind = symbol.Kind;
                        varSymbol.Accessibility = symbol.DeclaredAccessibility;
                        varSymbol.ContainingNamespace = ReplaceFakeScript(symbol.ContainingNamespace?.Name);
                        varSymbol.Summary = GetSymbolDocumentation(symbol);
                        vars.Add(varSymbol);

                        if (type == "?")
                        {
                            try
                            {
                                var variables = GetScriptVariables(code);
                                var variable = variables.FirstOrDefault(x => x.Name == symbol.Name);

                                if (variable != null)
                                {
                                    varSymbol.Type = variable.Type;
                                }
                            }
                            catch { }
                        }
                    }
                }
            }

            return vars.Where(x => x.Type != "?").ToList();
        }

        public List<String> GetUsings(String code)
        {
            Regex r = new Regex("(using [^;^\n]+;)");
            var matches = r.Matches(code);
            return matches.OfType<Match>().Select(x => x.Value.Replace("using ", "").Replace(";", "").Replace("\n", "").Replace("\t", "").Replace("\r", "")).ToList();
        }

        public List<String> GetUsingsFull(String code)
        {
            Regex r = new Regex("(using [^;^\n]+;)");
            var matches = r.Matches(code);
            return matches.OfType<Match>().Select(x => x.Value).ToList();
        }

        public List<ScriptType> GetDeclaredTypes(String code)
        {
            List<ScriptType> types = new List<ScriptType>();

            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<BaseTypeDeclarationSyntax>())
            {
                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<PropertyDeclarationSyntax>())
                    {
                        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<FieldDeclarationSyntax>())
                    {
                        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<MethodDeclarationSyntax>())
                    {
                        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<ParameterSyntax>())
                            {
                                if (p.Type != null && p.Identifier != null)
                                {
                                    m.Parameters.Add(new KeyValuePair<string, string>(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();
            var creationSyntax = root.DescendantNodes().OfType<ObjectCreationExpressionSyntax>().FirstOrDefault();
            return creationSyntax;
        }

        public MethodDeclarationSyntax GetCurrentConstructionExpressionAlt(String code)
        {
            if (code.Contains("=") && code.Contains("new"))
            {
                SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
                CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
                var creationSyntax = root.DescendantNodes().OfType<MethodDeclarationSyntax>().FirstOrDefault();
                return creationSyntax;
            }

            return null;
        }

        public T GetExpressionFirst<T>(String line) where T : CSharpSyntaxNode
        {
            SyntaxTree tree = CSharpSyntaxTree.ParseText(line);
            CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
            return root.DescendantNodes().OfType<T>().FirstOrDefault();
        }

        public List<T> GetExpressions<T>(String line) where T : CSharpSyntaxNode
        {
            SyntaxTree tree = CSharpSyntaxTree.ParseText(line);
            CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
            return root.DescendantNodes().OfType<T>().ToList();
        }

        public List<T> GetDirectExpressions<T>(SyntaxNode node) where T : CSharpSyntaxNode
        {
            return node.DescendantNodes().OfType<T>().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<SyntaxNode> 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<KeyValuePair<String, String>> GetMethodSymbolParameters(ISymbol symbol)
        {
            List<KeyValuePair<String, String>> parameters = new List<KeyValuePair<string, string>>();

            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<string, string>(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;
        }

        private List<ScriptVariable> GetScriptVariables(String code)
        {
            List<ScriptVariable> vars = new List<ScriptVariable>();

            SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
            var root = (CompilationUnitSyntax)tree.GetRoot();

            List<VariableDeclaratorSyntax> variables = new List<VariableDeclaratorSyntax>();
            FillVariables(variables, root.Members[0]);
            variables = variables.Distinct().ToList();

            foreach (var item in variables)
            {
                ScriptVariable v = new ScriptVariable();
                v.Name = item.Identifier.ToString();

                if (item.Initializer.Value.GetType() == typeof(ObjectCreationExpressionSyntax))
                {
                    v.Type = (item.Initializer.Value as ObjectCreationExpressionSyntax).Type.ToString();
                }
                else if (item.Initializer.Value.GetType() == typeof(InvocationExpressionSyntax))
                {
                    v.Type = (((item.Initializer.Value as InvocationExpressionSyntax).Expression as MemberAccessExpressionSyntax).Name as GenericNameSyntax).TypeArgumentList.Arguments[0].ToString();
                }

                vars.Add(v);
            }

            return vars;
        }

        private void FillVariables(List<VariableDeclaratorSyntax> variables, SyntaxNode node)
        {
            foreach (var item in node.DescendantNodes(x => true))
            {
                if (item.GetType() == typeof(VariableDeclaratorSyntax))
                {
                    variables.Add(item as VariableDeclaratorSyntax);
                }
                else
                {
                    FillVariables(variables, item);
                }
            }
        }

        private class ScriptVariable
        {
            public String Name { get; set; }
            public String Type { get; set; }

            public override string ToString()
            {
                return String.Format("{0} : {1}", Type, Name);
            }
        }
    }
}