From 3107d0c57d1a370b8ad4f12318220218962a31ab Mon Sep 17 00:00:00 2001 From: mrexodia Date: Mon, 5 Jun 2017 05:47:28 +0200 Subject: [PATCH 1/5] work with function declarations in headers --- .gitignore | 3 + CppTripleSlash.csproj | 1 + Function.cs | 113 +++++++++++++++++++++++++ TripleSlashCompletionCommandHandler.cs | 74 +++++++++------- 4 files changed, 161 insertions(+), 30 deletions(-) create mode 100644 .gitignore create mode 100644 Function.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07b26ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vs/ +bin/ +obj/ diff --git a/CppTripleSlash.csproj b/CppTripleSlash.csproj index 87f2d7e..68a5fe1 100644 --- a/CppTripleSlash.csproj +++ b/CppTripleSlash.csproj @@ -102,6 +102,7 @@ + diff --git a/Function.cs b/Function.cs new file mode 100644 index 0000000..45ed03a --- /dev/null +++ b/Function.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace CppTripleSlash +{ + class Function + { + class ParseException : Exception + { + public ParseException(string message) : base(message) { } + } + + public string ReturnType; + public List Arguments = new List(); + + private static string GetArgumentName(string arg) + { + if (arg == "...") //variadic + { + return arg; + } + if (arg.Contains("=")) //default value + { + int eqIndex = arg.IndexOf('='); + return GetArgumentName(arg.Substring(0, eqIndex - 1)); + } + if (arg.Contains("(")) //function parameter + { + Match match = Regex.Match(arg, "\\( *\\* *([a-zA-Z0-9_]+) *\\)"); + if (!match.Success) + { + throw new ParseException("FunctionParameterRegexFail"); + } + return match.Groups[1].Value; + } + //normal argument + int lastSpace = arg.LastIndexOf(' '); + if (lastSpace == -1) + { + throw new ParseException("NoSpaceInNormalArgument"); + } + return arg.Substring(lastSpace + 1).Trim(); + } + + private static IEnumerable SplitArgs(string args) + { + List result = new List(); + int depth = 0; + StringBuilder sb = new StringBuilder(); + foreach (char ch in args) + { + switch (ch) + { + case '(': + depth++; + sb.Append(ch); + break; + case ')': + depth--; + sb.Append(ch); + break; + default: + if (depth == 0 && ch == ',') + { + result.Add(sb.ToString()); + sb.Clear(); + } + else + { + sb.Append(ch); + } + break; + } + } + result.Add(sb.ToString()); + return result.Where(s => !string.IsNullOrEmpty(s)).Select(s => s.Trim()); + } + + public void Parse(string decl) + { + if (string.IsNullOrEmpty(decl)) + { + throw new ParseException("NullOrEmpty"); + } + decl = decl.Trim(); + if (!decl.EndsWith(";")) + { + throw new ParseException("NotEndsWithSemicolon"); + } + int firstParen = decl.IndexOf('('); + if (firstParen == -1) + { + throw new ParseException("NoFirstParen"); + } + int lastParen = decl.LastIndexOf(')'); + if (lastParen == -1) + { + throw new ParseException("NoLastParen"); + } + string nameReturnType = decl.Substring(0, firstParen).Trim(); + int lastSpace = nameReturnType.LastIndexOf(' '); + string args = decl.Substring(firstParen + 1, lastParen - firstParen - 1).Trim(); + foreach (string arg in SplitArgs(args)) + { + Arguments.Add(GetArgumentName(arg).Trim('*')); + } + ReturnType = nameReturnType.Substring(0, lastSpace).Replace("static", "").Trim(); + } + } +} diff --git a/TripleSlashCompletionCommandHandler.cs b/TripleSlashCompletionCommandHandler.cs index 99881e0..645220b 100644 --- a/TripleSlashCompletionCommandHandler.cs +++ b/TripleSlashCompletionCommandHandler.cs @@ -68,18 +68,18 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv // check for the triple slash if (typedChar == '/' && m_dte != null) { - string currentLine = m_textView.TextSnapshot.GetLineFromPosition( - m_textView.Caret.Position.BufferPosition.Position).GetText(); + string currentLine = GetCurrentLine(); if ((currentLine + "/").Trim() == "///") { // Calculate how many spaces - string spaces = currentLine.Replace(currentLine.TrimStart(), ""); + string prefix = currentLine.Replace(currentLine.TrimStart(), "") + "/// "; TextSelection ts = m_dte.ActiveDocument.Selection as TextSelection; int oldLine = ts.ActivePoint.Line; int oldOffset = ts.ActivePoint.LineCharOffset; ts.LineDown(); ts.EndOfLine(); + // Try to retrieve the CodeElement CodeElement codeElement = null; FileCodeModel fcm = m_dte.ActiveDocument.ProjectItem.FileCodeModel; if (fcm != null) @@ -87,40 +87,50 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv codeElement = fcm.CodeElementFromPoint(ts.ActivePoint, vsCMElement.vsCMElementFunction); } - if (codeElement != null && codeElement is CodeFunction) + // Process the function + Function function = new Function(); + if (codeElement is CodeFunction codeFunction) { - CodeFunction function = codeElement as CodeFunction; - StringBuilder sb = new StringBuilder("/ \r\n" + spaces + "/// \r\n" + spaces + "/// "); foreach (CodeElement child in codeElement.Children) { - CodeParameter parameter = child as CodeParameter; - if (parameter != null) + if (child is CodeParameter parameter) { - sb.AppendFormat("\r\n" + spaces + "/// ", parameter.Name); + function.Arguments.Add(parameter.Name); } } - - if (function.Type.AsString != "void") + function.ReturnType = codeFunction.Type.AsString; + } + else + { + try + { + function.Parse(GetCurrentLine()); + } + catch { - sb.AppendFormat("\r\n" + spaces + "/// "); } - - ts.MoveToLineAndOffset(oldLine, oldOffset); - ts.Insert(sb.ToString()); - ts.MoveToLineAndOffset(oldLine, oldOffset); - ts.LineDown(); - ts.EndOfLine(); - return VSConstants.S_OK; } - else + + // Add the XML comments to the file + StringBuilder sb = new StringBuilder($"/ \r\n{prefix}\r\n{prefix}"); + if(function.ReturnType.Length > 0) { - ts.MoveToLineAndOffset(oldLine, oldOffset); - ts.Insert("/ \r\n" + spaces + "/// \r\n" + spaces + "/// "); - ts.MoveToLineAndOffset(oldLine, oldOffset); - ts.LineDown(); - ts.EndOfLine(); - return VSConstants.S_OK; + foreach (string argument in function.Arguments) + { + sb.Append($"\r\n{prefix}"); + } + if (function.ReturnType != "void") + { + sb.AppendFormat($"\r\n{prefix}"); + } } + + ts.MoveToLineAndOffset(oldLine, oldOffset); + ts.Insert(sb.ToString()); + ts.MoveToLineAndOffset(oldLine, oldOffset); + ts.LineDown(); + ts.EndOfLine(); + return VSConstants.S_OK; } } @@ -215,8 +225,7 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv { if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN) { - string currentLine = m_textView.TextSnapshot.GetLineFromPosition( - m_textView.Caret.Position.BufferPosition.Position).GetText(); + string currentLine = GetCurrentLine(); if (currentLine.TrimStart().StartsWith("///")) { TextSelection ts = m_dte.ActiveDocument.Selection as TextSelection; @@ -231,8 +240,7 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv int retVal = m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); if (typedChar == '<') { - string currentLine = m_textView.TextSnapshot.GetLineFromPosition( - m_textView.Caret.Position.BufferPosition.Position).GetText(); + string currentLine = GetCurrentLine(); if (currentLine.TrimStart().StartsWith("///")) { if (m_session == null || m_session.IsDismissed) // If there is no active session, bring up completion @@ -311,5 +319,11 @@ private void OnSessionDismissed(object sender, EventArgs e) m_session = null; } } + + private string GetCurrentLine() + { + return m_textView.TextSnapshot.GetLineFromPosition( + m_textView.Caret.Position.BufferPosition.Position).GetText(); + } } } \ No newline at end of file From 785524c8b57bf8f727e6001c558dc0f8ab2db4da Mon Sep 17 00:00:00 2001 From: mrexodia Date: Mon, 5 Jun 2017 23:58:57 +0200 Subject: [PATCH 2/5] support for empty arguments --- Function.cs | 83 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/Function.cs b/Function.cs index 45ed03a..6a4b89a 100644 --- a/Function.cs +++ b/Function.cs @@ -6,6 +6,29 @@ namespace CppTripleSlash { + public static class StringExtensions + { + public static string SuperTrim(this string str) + { + var sb = new StringBuilder(str.Length); + var space = false; + for (var i = 0; i < str.Length; i++) + { + if (!space && str[i] == ' ') + { + space = true; + sb.Append(' '); + } + else + { + space = false; + sb.Append(str[i]); + } + } + return sb.ToString().Trim(); + } + } + class Function { class ParseException : Exception @@ -37,12 +60,42 @@ private static string GetArgumentName(string arg) return match.Groups[1].Value; } //normal argument + arg = arg + .Replace("const", "") + .Replace("volatile", "") + .Replace("&", "") + .Replace("*", "") + .SuperTrim() + //http://en.cppreference.com/w/cpp/language/types + .Replace("unsigned long long int", "int") + .Replace("signed long long int", "int") + .Replace("unsigned long long", "int") + .Replace("unsigned short int", "int") + .Replace("unsigned long int", "int") + .Replace("signed long long", "int") + .Replace("signed short int", "int") + .Replace("signed long int", "int") + .Replace("unsigned short", "int") + .Replace("unsigned long", "int") + .Replace("long long int", "int") + .Replace("unsigned int", "int") + .Replace("signed short", "int") + .Replace("signed long", "int") + .Replace("long double", "int") + .Replace("signed int", "int") + .Replace("short int", "int") + .Replace("long long", "int") + .Replace("long int", "int") + .Replace("unsigned", "int") + .Replace("signed", "int") + .Replace("short", "int") + .Replace("long", "int"); int lastSpace = arg.LastIndexOf(' '); if (lastSpace == -1) { - throw new ParseException("NoSpaceInNormalArgument"); + return ""; } - return arg.Substring(lastSpace + 1).Trim(); + return arg.Substring(lastSpace + 1).SuperTrim(); } private static IEnumerable SplitArgs(string args) @@ -76,16 +129,18 @@ private static IEnumerable SplitArgs(string args) } } result.Add(sb.ToString()); - return result.Where(s => !string.IsNullOrEmpty(s)).Select(s => s.Trim()); + return result.Where(s => !string.IsNullOrEmpty(s)).Select(s => s.SuperTrim()); } public void Parse(string decl) { + ReturnType = null; + Arguments.Clear(); if (string.IsNullOrEmpty(decl)) { throw new ParseException("NullOrEmpty"); } - decl = decl.Trim(); + decl = decl.SuperTrim(); if (!decl.EndsWith(";")) { throw new ParseException("NotEndsWithSemicolon"); @@ -100,14 +155,26 @@ public void Parse(string decl) { throw new ParseException("NoLastParen"); } - string nameReturnType = decl.Substring(0, firstParen).Trim(); + string nameReturnType = decl.Substring(0, firstParen).SuperTrim(); int lastSpace = nameReturnType.LastIndexOf(' '); - string args = decl.Substring(firstParen + 1, lastParen - firstParen - 1).Trim(); + string args = decl.Substring(firstParen + 1, lastParen - firstParen - 1).SuperTrim(); foreach (string arg in SplitArgs(args)) { - Arguments.Add(GetArgumentName(arg).Trim('*')); + Arguments.Add(GetArgumentName(arg).SuperTrim()); + } + ReturnType = nameReturnType.Substring(0, lastSpace).Replace("static", "").SuperTrim(); + } + + public override string ToString() + { + var sb = new StringBuilder(ReturnType + "("); + for (var i = 0; i < Arguments.Count; i++) + { + if (i > 0) + sb.Append(','); + sb.Append($"\"{Arguments[i]}\""); } - ReturnType = nameReturnType.Substring(0, lastSpace).Replace("static", "").Trim(); + return sb.ToString() + ")"; } } } From 3605c590732684cc882a09a15856392c29cebf2b Mon Sep 17 00:00:00 2001 From: mrexodia Date: Tue, 6 Jun 2017 02:06:02 +0200 Subject: [PATCH 3/5] fixed a null dereference exception when argument parsing failed --- TripleSlashCompletionCommandHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TripleSlashCompletionCommandHandler.cs b/TripleSlashCompletionCommandHandler.cs index 645220b..3b5a81b 100644 --- a/TripleSlashCompletionCommandHandler.cs +++ b/TripleSlashCompletionCommandHandler.cs @@ -113,7 +113,7 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv // Add the XML comments to the file StringBuilder sb = new StringBuilder($"/ \r\n{prefix}\r\n{prefix}"); - if(function.ReturnType.Length > 0) + if(!string.IsNullOrEmpty(function.ReturnType)) { foreach (string argument in function.Arguments) { From c572bfbdab371f12d742136e6e8ce621ae26fe03 Mon Sep 17 00:00:00 2001 From: mrexodia Date: Mon, 7 Aug 2017 05:21:44 +0200 Subject: [PATCH 4/5] small fixes to the argument parser --- Function.cs | 65 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/Function.cs b/Function.cs index 6a4b89a..fd0343b 100644 --- a/Function.cs +++ b/Function.cs @@ -27,6 +27,11 @@ public static string SuperTrim(this string str) } return sb.ToString().Trim(); } + + public static string ReplaceWord(this string str, string pattern, string replacement) + { + return Regex.Replace(str, "\\b" + pattern + "\\b", replacement); + } } class Function @@ -45,12 +50,12 @@ private static string GetArgumentName(string arg) { return arg; } - if (arg.Contains("=")) //default value + if (arg.Contains("=")) //default value => "int n = 42" { int eqIndex = arg.IndexOf('='); - return GetArgumentName(arg.Substring(0, eqIndex - 1)); + return GetArgumentName(arg.Substring(0, eqIndex)); } - if (arg.Contains("(")) //function parameter + if (arg.Contains("(")) //function parameter => "int (*name)(...)" { Match match = Regex.Match(arg, "\\( *\\* *([a-zA-Z0-9_]+) *\\)"); if (!match.Success) @@ -59,37 +64,41 @@ private static string GetArgumentName(string arg) } return match.Groups[1].Value; } + //char name[size] + var blockOpen = arg.IndexOf('['); + if (blockOpen != -1) + arg = arg.Substring(0, blockOpen); //normal argument arg = arg - .Replace("const", "") - .Replace("volatile", "") .Replace("&", "") .Replace("*", "") + .ReplaceWord("const", "") + .ReplaceWord("volatile", "") .SuperTrim() //http://en.cppreference.com/w/cpp/language/types - .Replace("unsigned long long int", "int") - .Replace("signed long long int", "int") - .Replace("unsigned long long", "int") - .Replace("unsigned short int", "int") - .Replace("unsigned long int", "int") - .Replace("signed long long", "int") - .Replace("signed short int", "int") - .Replace("signed long int", "int") - .Replace("unsigned short", "int") - .Replace("unsigned long", "int") - .Replace("long long int", "int") - .Replace("unsigned int", "int") - .Replace("signed short", "int") - .Replace("signed long", "int") - .Replace("long double", "int") - .Replace("signed int", "int") - .Replace("short int", "int") - .Replace("long long", "int") - .Replace("long int", "int") - .Replace("unsigned", "int") - .Replace("signed", "int") - .Replace("short", "int") - .Replace("long", "int"); + .ReplaceWord("unsigned long long int", "int") + .ReplaceWord("signed long long int", "int") + .ReplaceWord("unsigned long long", "int") + .ReplaceWord("unsigned short int", "int") + .ReplaceWord("unsigned long int", "int") + .ReplaceWord("signed long long", "int") + .ReplaceWord("signed short int", "int") + .ReplaceWord("signed long int", "int") + .ReplaceWord("unsigned short", "int") + .ReplaceWord("unsigned long", "int") + .ReplaceWord("long long int", "int") + .ReplaceWord("unsigned int", "int") + .ReplaceWord("signed short", "int") + .ReplaceWord("signed long", "int") + .ReplaceWord("long double", "int") + .ReplaceWord("signed int", "int") + .ReplaceWord("short int", "int") + .ReplaceWord("long long", "int") + .ReplaceWord("long int", "int") + .ReplaceWord("unsigned", "int") + .ReplaceWord("signed", "int") + .ReplaceWord("short", "int") + .ReplaceWord("long", "int"); int lastSpace = arg.LastIndexOf(' '); if (lastSpace == -1) { From b3ca70be28f1afa3ba1efd0bd290a416f154af4f Mon Sep 17 00:00:00 2001 From: mrexodia Date: Sun, 20 Aug 2017 22:30:50 +0200 Subject: [PATCH 5/5] further improvements to the function parser (now works with templates) --- Function.cs | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/Function.cs b/Function.cs index fd0343b..b672cb4 100644 --- a/Function.cs +++ b/Function.cs @@ -55,13 +55,9 @@ private static string GetArgumentName(string arg) int eqIndex = arg.IndexOf('='); return GetArgumentName(arg.Substring(0, eqIndex)); } - if (arg.Contains("(")) //function parameter => "int (*name)(...)" + Match match = Regex.Match(arg, @"\( *\* *([a-zA-Z0-9_]+) *\)"); + if (match.Success) //function parameter => "int (*name)(...)" { - Match match = Regex.Match(arg, "\\( *\\* *([a-zA-Z0-9_]+) *\\)"); - if (!match.Success) - { - throw new ParseException("FunctionParameterRegexFail"); - } return match.Groups[1].Value; } //char name[size] @@ -70,8 +66,8 @@ private static string GetArgumentName(string arg) arg = arg.Substring(0, blockOpen); //normal argument arg = arg - .Replace("&", "") - .Replace("*", "") + .Replace('&', ' ') + .Replace('*', ' ') .ReplaceWord("const", "") .ReplaceWord("volatile", "") .SuperTrim() @@ -110,27 +106,44 @@ private static string GetArgumentName(string arg) private static IEnumerable SplitArgs(string args) { List result = new List(); - int depth = 0; + int parenDepth = 0; + int templateDepth = 0; StringBuilder sb = new StringBuilder(); foreach (char ch in args) { switch (ch) { + case '<': + templateDepth++; + break; + case '>': + templateDepth--; + if (templateDepth == 0) + { + sb.Append(' '); + } + break; case '(': - depth++; - sb.Append(ch); + parenDepth++; + if (templateDepth == 0) + { + sb.Append(ch); + } break; case ')': - depth--; - sb.Append(ch); + parenDepth--; + if (templateDepth == 0) + { + sb.Append(ch); + } break; default: - if (depth == 0 && ch == ',') + if (parenDepth == 0 && templateDepth == 0 && ch == ',') { result.Add(sb.ToString()); sb.Clear(); } - else + else if (templateDepth == 0) { sb.Append(ch); }