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) { 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 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(); var creationSyntax = root.DescendantNodes().OfType().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().FirstOrDefault(); return creationSyntax; } return null; } 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; } private List GetScriptVariables(String code) { List vars = new List(); SyntaxTree tree = CSharpSyntaxTree.ParseText(code); var root = (CompilationUnitSyntax)tree.GetRoot(); List variables = new List(); 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 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); } } } }