From 31f6517aa5d8d3ccdd0b39880337c000943d08c1 Mon Sep 17 00:00:00 2001 From: Rowan Goemans Date: Sun, 31 Dec 2017 04:07:55 +0100 Subject: -- Completed unit tests for operators. Conversion operators aren't working yet. -- Renamed all C things to Cpp since that is really what they are. -- Made empty files for the remaining unit tests. --- src/CodeParserController.ts | 4 +- src/Lang/C/CArgument.ts | 6 - src/Lang/C/CDocGen.ts | 178 -------- src/Lang/C/CParseTree.ts | 121 ------ src/Lang/C/CParser.ts | 608 --------------------------- src/Lang/C/CToken.ts | 26 -- src/Lang/Cpp/CppArgument.ts | 6 + src/Lang/Cpp/CppDocGen.ts | 178 ++++++++ src/Lang/Cpp/CppParseTree.ts | 124 ++++++ src/Lang/Cpp/CppParser.ts | 609 ++++++++++++++++++++++++++++ src/Lang/Cpp/CppToken.ts | 26 ++ src/test/CTests/Attributes.test.ts | 62 --- src/test/CTests/Con-AndDestructor.test.ts | 63 --- src/test/CTests/FunctionPointer.test.ts | 42 -- src/test/CTests/Operators.test.ts | 279 ------------- src/test/CTests/ReturnTypes.test.ts | 102 ----- src/test/CTests/Templates.test.ts | 51 --- src/test/CTests/TestSetup.ts | 52 --- src/test/CTests/TrailingReturns.test.ts | 39 -- src/test/CppTests/Attributes.test.ts | 62 +++ src/test/CppTests/Con-AndDestructor.test.ts | 63 +++ src/test/CppTests/Config.tests.ts | 20 + src/test/CppTests/FunctionPointer.test.ts | 42 ++ src/test/CppTests/Operators.test.ts | 300 ++++++++++++++ src/test/CppTests/Parameters.test.ts | 20 + src/test/CppTests/ReturnTypes.test.ts | 102 +++++ src/test/CppTests/Templates.test.ts | 51 +++ src/test/CppTests/TestSetup.ts | 52 +++ src/test/CppTests/TrailingReturns.test.ts | 39 ++ 29 files changed, 1696 insertions(+), 1631 deletions(-) delete mode 100644 src/Lang/C/CArgument.ts delete mode 100644 src/Lang/C/CDocGen.ts delete mode 100644 src/Lang/C/CParseTree.ts delete mode 100644 src/Lang/C/CParser.ts delete mode 100644 src/Lang/C/CToken.ts create mode 100644 src/Lang/Cpp/CppArgument.ts create mode 100644 src/Lang/Cpp/CppDocGen.ts create mode 100644 src/Lang/Cpp/CppParseTree.ts create mode 100644 src/Lang/Cpp/CppParser.ts create mode 100644 src/Lang/Cpp/CppToken.ts delete mode 100644 src/test/CTests/Attributes.test.ts delete mode 100644 src/test/CTests/Con-AndDestructor.test.ts delete mode 100644 src/test/CTests/FunctionPointer.test.ts delete mode 100644 src/test/CTests/Operators.test.ts delete mode 100644 src/test/CTests/ReturnTypes.test.ts delete mode 100644 src/test/CTests/Templates.test.ts delete mode 100644 src/test/CTests/TestSetup.ts delete mode 100644 src/test/CTests/TrailingReturns.test.ts create mode 100644 src/test/CppTests/Attributes.test.ts create mode 100644 src/test/CppTests/Con-AndDestructor.test.ts create mode 100644 src/test/CppTests/Config.tests.ts create mode 100644 src/test/CppTests/FunctionPointer.test.ts create mode 100644 src/test/CppTests/Operators.test.ts create mode 100644 src/test/CppTests/Parameters.test.ts create mode 100644 src/test/CppTests/ReturnTypes.test.ts create mode 100644 src/test/CppTests/Templates.test.ts create mode 100644 src/test/CppTests/TestSetup.ts create mode 100644 src/test/CppTests/TrailingReturns.test.ts diff --git a/src/CodeParserController.ts b/src/CodeParserController.ts index 32d1d75..1b42f3d 100644 --- a/src/CodeParserController.ts +++ b/src/CodeParserController.ts @@ -10,7 +10,7 @@ import { } from "vscode"; import CodeParser from "./Common/ICodeParser"; import { Config } from "./Config"; -import CParser from "./Lang/C/CParser"; +import CppParser from "./Lang/Cpp/CppParser"; /** * * Checks if the event matches the specified guidelines and if a parser exists for this language @@ -86,7 +86,7 @@ export default class CodeParserController { switch (lang) { case "c": case "cpp": - parser = new CParser(this.cfg); + parser = new CppParser(this.cfg); break; default: // tslint:disable-next-line:no-console diff --git a/src/Lang/C/CArgument.ts b/src/Lang/C/CArgument.ts deleted file mode 100644 index 46f180c..0000000 --- a/src/Lang/C/CArgument.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { CParseTree } from "./CParseTree"; - -export class CArgument { - public name: string = null; - public type: CParseTree = new CParseTree(); -} diff --git a/src/Lang/C/CDocGen.ts b/src/Lang/C/CDocGen.ts deleted file mode 100644 index 5488020..0000000 --- a/src/Lang/C/CDocGen.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { Position, Range, Selection, TextEditor, TextLine, WorkspaceEdit } from "vscode"; -import { IDocGen } from "../../Common/IDocGen"; -import { Config } from "../../Config"; -import { CArgument } from "./CArgument"; -import { CParseTree } from "./CParseTree"; -import { CToken, CTokenType } from "./CToken"; - -export default class CDocGen implements IDocGen { - protected activeEditor: TextEditor; - - protected readonly cfg: Config; - - protected func: CArgument; - protected templateParams: string[]; - protected params: CArgument[]; - - /** - * @param {TextEditor} actEdit Active editor window - * @param {Position} cursorPosition Where the cursor of the user currently is - * @param {string[]} templateParams The template parameters of the declaration. - * @param {CArgument} func The type and name of the function to generate doxygen. - * Doesn't contain anything if it is not a function. - * @param {CArgument[]} params The parameters of the function. Doesn't contain anything if it is not a function. - */ - public constructor( - actEdit: TextEditor, - cursorPosition: Position, - cfg: Config, - templateParams: string[], - func: CArgument, - params: CArgument[], - ) { - this.activeEditor = actEdit; - this.cfg = cfg; - this.func = func; - this.templateParams = templateParams; - this.params = params; - } - - /** - * @inheritdoc - */ - public GenerateDoc(rangeToReplace: Range) { - const comment: string = this.generateComment(); - - this.activeEditor.edit((editBuilder) => { - editBuilder.replace(rangeToReplace, comment); // Insert the comment - }); - - // Set cursor to first DoxyGen command. - this.moveCursurToFirstDoxyCommand(comment, rangeToReplace.start.line, rangeToReplace.start.character); - } - - /*************************************************************************** - Implementation - ***************************************************************************/ - protected getIndentation(): string { - const line: TextLine = this.activeEditor.document.lineAt(this.activeEditor.selection.start.line); - return line.text.slice(0, line.firstNonWhitespaceCharacterIndex - 1); - } - - protected getTemplatedString(replace: string, template: string, param: string): string { - return template.replace(replace, param); - } - - protected generateBrief(lines: string[]) { - lines.push(this.cfg.commentPrefix + this.cfg.briefTemplate); - } - - protected generateFromTemplate(lines: string[], replace: string, template: string, templateWith: string[]) { - let line: string = ""; - - templateWith.forEach((element: string) => { - // Ignore null values - if (element !== null) { - line = this.cfg.commentPrefix; - line += this.getTemplatedString(replace, template, element); - lines.push(line); - } - }); - } - - protected generateReturnParams(): string[] { - const params: string[] = []; - - // Check if return type is a pointer - const ptrReturnIndex = this.func.type.nodes - .findIndex((n) => n instanceof CToken && n.type === CTokenType.Pointer); - - // Special case for void functions. - const voidReturnIndex = this.func.type.nodes - .findIndex((n) => n instanceof CToken && n.type === CTokenType.Symbol && n.value === "void"); - - // Special case for bool return type. - const boolReturnIndex: number = this.func.type.nodes - .findIndex((n) => n instanceof CToken && n.type === CTokenType.Symbol && n.value === "bool"); - - if (boolReturnIndex !== -1 && this.cfg.boolReturnsTrueFalse === true) { - params.push("true"); - params.push("false"); - } else if (voidReturnIndex !== -1 && ptrReturnIndex !== -1) { - params.push(this.cfg.includeTypeAtReturn === true ? this.func.type.Yield() : ""); - } else if (voidReturnIndex === -1 && this.func.type.nodes.length > 0) { - params.push(this.cfg.includeTypeAtReturn === true ? this.func.type.Yield() : ""); - } - - return params; - } - - protected generateComment(): string { - const lines: string[] = []; - - if (this.cfg.firstLine.trim().length !== 0) { - lines.push(this.cfg.firstLine); - } - - if (this.cfg.briefTemplate.trim().length !== 0) { - this.generateBrief(lines); - if (this.cfg.newLineAfterBrief === true) { - lines.push(this.cfg.commentPrefix); - } - } - - if (this.cfg.tparamTemplate.trim().length !== 0 && this.templateParams.length > 0) { - this.generateFromTemplate( - lines, - this.cfg.paramTemplateReplace, - this.cfg.tparamTemplate, - this.templateParams, - ); - if (this.cfg.newLineAfterTParams === true) { - lines.push(this.cfg.commentPrefix); - } - } - - if (this.cfg.paramTemplate.trim().length !== 0 && this.params.length > 0) { - const paramNames: string[] = this.params.map((p) => p.name); - this.generateFromTemplate(lines, this.cfg.paramTemplateReplace, this.cfg.paramTemplate, paramNames); - if (this.cfg.newLineAfterParams === true) { - lines.push(this.cfg.commentPrefix); - } - } - - if (this.cfg.returnTemplate.trim().length !== 0 && this.func.type !== null) { - const returnParams = this.generateReturnParams(); - this.generateFromTemplate(lines, this.cfg.typeTemplateReplace, this.cfg.returnTemplate, returnParams); - } - - if (this.cfg.lastLine.trim().length !== 0) { - lines.push(this.cfg.lastLine); - } - - const comment: string = lines.join("\n" + this.getIndentation()); - return comment; - } - - protected moveCursurToFirstDoxyCommand(comment: string, baseLine: number, baseCharacter) { - // Find first offset of a new line in the comment. Since that's when the line where the first param starts. - let line: number = baseLine; - let character: number = comment.indexOf("\n"); - - // If a first line is included find the 2nd line with a newline. - if (this.cfg.firstLine.trim().length !== 0) { - line++; - const oldCharacter: number = character; - character = comment.indexOf("\n", oldCharacter + 1) - oldCharacter; - } - - // If newline is not found means no first param was found so Set to base line before the newline. - if (character < 0) { - line = baseLine; - character = baseCharacter; - } - - const moveTo: Position = new Position(line, character); - this.activeEditor.selection = new Selection(moveTo, moveTo); - } -} diff --git a/src/Lang/C/CParseTree.ts b/src/Lang/C/CParseTree.ts deleted file mode 100644 index f3ec870..0000000 --- a/src/Lang/C/CParseTree.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { CToken, CTokenType } from "./CToken"; - -export class CParseTree { - - /** - * Create a tree from CTokens. This consumes the CTokens. - * @param CTokens The CTokens to create a tree for. - * @param inNested If currently allready nesting. - */ - public static CreateTree(CTokens: CToken[], inNested: boolean = false): CParseTree { - const tree: CParseTree = new CParseTree(); - - while (CTokens.length > 0) { - const token: CToken = CTokens.shift(); - switch (token.type) { - case CTokenType.OpenParenthesis: - tree.nodes.push(this.CreateTree(CTokens, true)); - break; - case CTokenType.CloseParenthesis: - if (inNested === false) { - throw new Error("Unmatched closing parenthesis."); - } - return tree; - default: - tree.nodes.push(token); - break; - } - } - - if (inNested === true) { - throw new Error("No match found for an opening parenthesis."); - } - - return tree; - } - - public nodes: Array = []; - - /** - * Compact empty branches. Example ((foo))(((bar))) will become (foo)(bar) - * @param tree The CParseTree to compact. Defaults to the current tree. - */ - public Compact(tree: CParseTree = this): CParseTree { - const newTree: CParseTree = new CParseTree(); - newTree.nodes = tree.nodes.map((n) => n); - const isNotCompact = (n) => n instanceof CParseTree && n.nodes.length === 1 && n.nodes[0] instanceof CParseTree; - - // Compact current level of nodes to the maximum amount. - while (newTree.nodes.some((n) => isNotCompact(n))) { - newTree.nodes = newTree.nodes - .map((n) => n instanceof CParseTree && isNotCompact(n) ? n.nodes[0] : n); - } - - // Compact all nested CParseTrees. - newTree.nodes = newTree.nodes - .map((n) => n instanceof CParseTree ? this.Compact(n) : n); - - return newTree; - } - - /** - * Copy CParseTree. - * @param tree The CParseTree to compact. Defaults to the current tree. - */ - public Copy(tree: CParseTree = this): CParseTree { - const newTree: CParseTree = new CParseTree(); - newTree.nodes = tree.nodes - .map((n) => n instanceof CToken ? n : this.Copy(n)); - return newTree; - } - - /** - * Create string from the CParseTree which is a representation of the original code. - * @param tree The CParseTree to compact. Defaults to the current tree. - */ - public Yield(tree: CParseTree = this): string { - let code: string = ""; - - for (const node of tree.nodes) { - if (node instanceof CParseTree) { - code += "(" + this.Yield(node) + ")"; - continue; - } - - switch (node.type) { - case CTokenType.Symbol: - code += code === "" ? node.value : " " + node.value; - break; - case CTokenType.Pointer: - code += node.value; - break; - case CTokenType.Reference: - code += node.value; - break; - case CTokenType.ArraySubscript: - code += node.value; - break; - case CTokenType.CurlyBlock: - code += node.value; - break; - case CTokenType.Assignment: - code += " " + node.value; - break; - case CTokenType.Comma: - code += node.value; - break; - case CTokenType.Arrow: - code += " " + node.value; - break; - case CTokenType.Ellipsis: - code += node.value; - break; - case CTokenType.Attribute: - code += code === "" ? node.value : " " + node.value; - break; - } - } - - return code; - } -} diff --git a/src/Lang/C/CParser.ts b/src/Lang/C/CParser.ts deleted file mode 100644 index 859dc6b..0000000 --- a/src/Lang/C/CParser.ts +++ /dev/null @@ -1,608 +0,0 @@ -import { Position, TextDocumentContentChangeEvent, TextEditor, TextLine, workspace } from "vscode"; -import ICodeParser from "../../Common/ICodeParser"; -import { IDocGen } from "../../Common/IDocGen"; -import { Config } from "../../Config"; -import { CArgument } from "./CArgument"; -import CDocGen from "./CDocGen"; -import { CParseTree } from "./CParseTree"; -import { CToken, CTokenType } from "./CToken"; - -/** - * - * Parses C code for methods and signatures - * - * @export - * @class CParser - * @implements {ICodeParser} - */ -export default class CParser implements ICodeParser { - protected activeEditor: TextEditor; - protected activeSelection: Position; - protected readonly cfg: Config; - - private typeKeywords: string[]; - private stripKeywords: string[]; - private keywords: string[]; - private attributes: string[]; - private lexerVocabulary; - - constructor(cfg: Config) { - this.cfg = cfg; - - this.typeKeywords = [ - "constexpr", - "const", - "struct", - ]; - - this.stripKeywords = [ - "final", - "static", - "inline", - "friend", - "virtual", - "extern", - "explicit", - "class", - "override", - "typename", - ]; - - this.attributes = [ - "noexcept", - "throw", - "alignas", - ]; - - // Non type keywords will be stripped from the final return type. - this.keywords = this.typeKeywords.concat(this.stripKeywords); - - this.lexerVocabulary = { - ArraySubscript: (x: string): string => (x.match("^\\[[^\\[]*?\\]") || [])[0], - Arrow: (x: string): string => (x.match("^->") || [])[0], - Assignment: (x: string): string => (x.match("^=") || [])[0], - Attribute: (x: string): string => { - const attribute: string = (x.match("^\\[\\[[^\\[]*?\\]\\]") || [])[0]; - if (attribute !== undefined) { - return attribute; - } - - const foundIndex: number = this.attributes - .findIndex((n: string) => x.startsWith(n) === true); - - if (foundIndex === -1) { - return undefined; - } - - if (x.slice(this.attributes[foundIndex].length).trim().startsWith("(") === false) { - return x.slice(0, this.attributes[foundIndex].length); - } - - const startEndOffset: number[] = this.GetSubExprStartEnd(x, 0, "(", ")"); - return startEndOffset[1] === 0 ? undefined : x.slice(0, startEndOffset[1]); - }, - CloseParenthesis: (x: string): string => (x.match("^\\)") || [])[0], - Comma: (x: string): string => (x.match("^,") || [])[0], - CommentBlock: (x: string): string => { - if (x.startsWith("/*") === false) { - return undefined; - } - - let closeOffset: number = x.indexOf("*/"); - closeOffset = closeOffset === -1 ? x.length : closeOffset + 2; - return x.slice(0, closeOffset); - }, - CommentLine: (x: string): string => { - if (x.startsWith("//") === false) { - return undefined; - } - - let closeOffset: number = x.indexOf("\n"); - closeOffset = closeOffset === -1 ? x.length : closeOffset + 1; - return x.slice(0, closeOffset); - }, - CurlyBlock: (x: string): string => { - if (x.startsWith("{") === false) { - return undefined; - } - const startEndOffset: number[] = this.GetSubExprStartEnd(x, 0, "{", "}"); - return startEndOffset[1] === 0 ? undefined : x.slice(0, startEndOffset[1]); - }, - Ellipsis: (x: string): string => (x.match("^\\.\\.\\.") || [])[0], - OpenParenthesis: (x: string): string => (x.match("^\\(") || [])[0], - Pointer: (x: string): string => (x.match("^\\*") || [])[0], - Reference: (x: string): string => (x.match("^&") || [])[0], - Symbol: (x: string): string => { - // Handle access specifiers since they aren't really symbols. - if (x.startsWith("public:") || x.startsWith("protected:") || x.startsWith("private:")) { - return undefined; - } - - // Handle specifiers - const specifierFound: number = this.attributes - .findIndex((n: string) => x.startsWith(n) === true); - - if (specifierFound !== -1) { - return undefined; - } - - // Handle decltype special cases. - if (x.startsWith("decltype") === true) { - const startEndOffset: number[] = this.GetSubExprStartEnd(x, 0, "(", ")"); - return startEndOffset[1] === 0 ? undefined : x.slice(0, startEndOffset[1]); - } - - // Special case group up the fundamental types with the modifiers. - // tslint:disable-next-line:max-line-length - let reMatch: string = (x.match("^(unsigned|signed|short|long|int|char|double)(\\s+(unsigned|signed|short|long|int|char|double))+") || [])[0]; - if (reMatch !== undefined) { - return reMatch.trim(); - } - - // Regex to handle a part of all symbols and includes all symbol special cases. - // This is run in a loop because template parts of a symbol can't be parsed using regex. - // tslint:disable-next-line:max-line-length - const symbolRegex: string = "^([a-z|A-Z|:|_|~|\\d]*operator\\s*(\"\"_[a-z|A-Z]+|>>=|<<=|->\\*|\\+=|-=|\\*=|\\/=|%=|\\^=|&=|\\|=|<<|>>|==|!=|<=|->|>=|&&|\\|\\||\\+\\+|--|\\+|-|\\*|\\/|%|\\^|&|\||~|!|=|<|>|,|\\[\\s*\\]|\\(\\s*\\)|(new|delete)\\s*(\\[\\s*\\]){0,1}){0,1}|[a-z|A-Z|:|_|~|\\d]+)"; - - reMatch = (x.match(symbolRegex) || [])[0]; - if (reMatch === undefined) { - return undefined; - } - - let symbol: string = reMatch; - while (true) { - if (x.slice(symbol.length).trim().startsWith("<") === true) { - const offsets: number[] = this.GetSubExprStartEnd(x, symbol.length, "<", ">"); - if (offsets[1] === 0) { - return undefined; - } - symbol = x.slice(0, offsets[1]); - } - - reMatch = (x.slice(symbol.length).match(symbolRegex) || [])[0]; - if (reMatch === undefined) { - break; - } - - symbol += reMatch; - } - - return symbol.replace(/\s+$/, ""); - }, - }; - } - - /** - * @inheritdoc - */ - public Parse(activeEdit: TextEditor): IDocGen { - this.activeEditor = activeEdit; - this.activeSelection = this.activeEditor.selection.active; - - let line: string = ""; - try { - line = this.getLogicalLine(); - } catch (err) { - // console.dir(err); - } - - // template parsing is simpler by using heuristics rather then CTokenizing first. - const templateArgs: string[] = []; - while (line.startsWith("template")) { - const template: string = this.GetTemplate(line); - - templateArgs.push.apply(templateArgs, this.GetArgsFromTemplate(template)); - - line = line.slice(template.length, line.length + 1).trim(); - } - - let args: [CArgument, CArgument[]] = [new CArgument(), []]; - try { - args = this.GetReturnAndArgs(line); - } catch (err) { - // console.dir(err); - } - - return new CDocGen( - this.activeEditor, - this.activeSelection, - this.cfg, - templateArgs, - args[0], - args[1], - ); - } - - /*************************************************************************** - Implementation - ***************************************************************************/ - private getLogicalLine(): string { - let logicalLine: string = ""; - - let nextLine: Position = new Position(this.activeSelection.line + 1, this.activeSelection.character); - - let nextLineTxt: string = this.activeEditor.document.lineAt(nextLine.line).text.trim(); - - // VSCode may enter a * on itself, we don"t want that in our method - if (nextLineTxt === "*") { - nextLineTxt = ""; - } - - let currentNest: number = 0; - logicalLine = nextLineTxt; - - // Get method end line - let linesToGet: number = 20; - while (linesToGet-- > 0) { // Check for end of expression. - nextLine = new Position(nextLine.line + 1, nextLine.character); - nextLineTxt = this.activeEditor.document.lineAt(nextLine.line).text.trim(); - - // Check if method has finished if curly brace is opened while - // nesting is occuring. - for (let i: number = 0; i < nextLineTxt.length; i++) { - if (nextLineTxt[i] === "(") { - currentNest++; - } else if (nextLineTxt[i] === ")") { - currentNest--; - } else if (nextLineTxt[i] === "{" && currentNest === 0) { - logicalLine += "\n" + nextLineTxt.slice(0, i); - return logicalLine.replace(/^\s+|\s+$/g, ""); - } else if ((nextLineTxt[i] === ";" - || (nextLineTxt[i] === ":" && nextLineTxt[i - 1] !== ":" && nextLineTxt[i + 1] !== ":")) - && currentNest === 0) { - - logicalLine += "\n" + nextLineTxt.slice(0, i); - return logicalLine.replace(/^\s+|\s+$/g, ""); - } - } - - logicalLine += "\n" + nextLineTxt; - } - - throw new Error("More then 20 lines were gotten from editor and no end of expression was found."); - } - - private Tokenize(expression: string): CToken[] { - const CTokens: CToken[] = []; - expression = expression.replace(/^\s+|\s+$/g, ""); - - while (expression.length !== 0) { - const matches: CToken[] = Object.keys(this.lexerVocabulary) - .map((k): CToken => new CToken(CTokenType[k], this.lexerVocabulary[k](expression))) - .filter((t) => t.value !== undefined); - - if (matches.length === 0) { - throw new Error("Next CToken couldn\'t be determined: " + expression); - } else if (matches.length > 1) { - throw new Error("Multiple matches for next CToken: " + expression); - } - - CTokens.push(matches[0]); - expression = expression.slice(matches[0].value.length, expression.length).replace(/^\s+|\s+$/g, ""); - } - - return CTokens; - } - - private GetReturnAndArgs(line: string): [CArgument, CArgument[]] { - // CTokenize rest of expression and remove comment CTokens; - const CTokens: CToken[] = this.Tokenize(line) - .filter((t) => t.type !== CTokenType.CommentBlock) - .filter((t) => t.type !== CTokenType.CommentLine); - - // Create hierarchical tree based on the parenthesis. - const tree: CParseTree = CParseTree.CreateTree(CTokens).Compact(); - - // return argument. - const func = this.GetArgument(tree); - // check if it is a constructor or descructor since these have no name.. - // and reverse the assignment of type and name. - if (func.name === null) { - if (func.type.nodes.length !== 1) { - throw new Error("Too many symbols found for constructor/descructor."); - } else if (func.type.nodes[0] instanceof CParseTree) { - throw new Error("One node found with just a CParseTree. Malformed input."); - } - - func.name = (func.type.nodes[0] as CToken).value; - func.type.nodes = []; - } - - // Get arguments list as a CParseTree and create arguments from them. - const params = this.GetArgumentList(tree) - .map((a) => this.GetArgument(a)); - - return [func, params]; - } - - private RemoveUnusedTokens(tree: CParseTree): CParseTree { - tree = tree.Copy(); - - // First slice of everything after assignment since that will not be used. - const assignmentIndex = tree.nodes.findIndex((n) => n instanceof CToken && n.type === CTokenType.Assignment); - if (assignmentIndex !== -1) { - tree.nodes = tree.nodes.slice(0, assignmentIndex); - } - - // Specifiers aren't needed so remove them. - tree.nodes = tree.nodes - .filter((n) => n instanceof CParseTree || (n instanceof CToken && n.type !== CTokenType.Attribute)); - - return tree; - } - - private GetArgumentList(tree: CParseTree): CParseTree[] { - const args: CParseTree[] = []; - - tree = this.RemoveUnusedTokens(tree); - - let cursor: CParseTree = tree; - while (this.IsFuncPtr(cursor.nodes) === true) { - cursor = cursor.nodes.find((n) => n instanceof CParseTree) as CParseTree; - } - - const argTree: CParseTree = cursor.nodes.find((n) => n instanceof CParseTree) as CParseTree; - if (argTree === undefined) { - throw new Error("Function arguments not found."); - } - - // Split the argument tree on commas - let arg: CParseTree = new CParseTree(); - for (const node of argTree.nodes) { - if (node instanceof CToken && node.type === CTokenType.Comma) { - args.push(arg); - arg = new CParseTree(); - } else { - arg.nodes.push(node); - } - } - - if (arg.nodes.length > 0) { - args.push(arg); - } - - return args; - } - - private IsFuncPtr(nodes: Array) { - return nodes.filter((n) => n instanceof CParseTree).length === 2; - } - - private StripNonTypeNodes(tree: CParseTree) { - tree.nodes = tree.nodes - // All strippable keywords. - .filter((n) => { - return !(n instanceof CToken - && n.type === CTokenType.Symbol - && this.stripKeywords.find((k) => k === n.value) !== undefined); - }); - } - - private GetArgumentFromTrailingReturn(tree: CParseTree, startTrailingReturn: number): CArgument { - const argument: CArgument = new CArgument(); - - // Find index of auto prior to the first CParseTree. - // If auto is not found something is going wrong since trailing return - // requires auto. - let autoIndex: number = -1; - for (let i: number = 0; i < tree.nodes.length; i++) { - const node = tree.nodes[i]; - if (node instanceof CParseTree) { - break; - } - if (node.type === CTokenType.Symbol && node.value === "auto") { - autoIndex = i; - break; - } - } - - if (autoIndex === -1) { - throw new Error("Function declaration has trailing return but type is not auto."); - } - - // Get symbol between auto and CParseTree which is the argument name. It also may not be a keyword. - for (let i: number = autoIndex + 1; i < tree.nodes.length; i++) { - const node = tree.nodes[i]; - if (node instanceof CParseTree) { - break; - } - if (node.type === CTokenType.Symbol && this.keywords.find((k) => k === node.value) === undefined) { - argument.name = node.value; - break; - } - } - - argument.type.nodes = tree.nodes.slice(startTrailingReturn + 1, tree.nodes.length); - this.StripNonTypeNodes(argument.type); - - return argument; - } - - private GetArgumentFromFuncPtr(tree: CParseTree): CArgument { - const argument: CArgument = new CArgument(); - - argument.type = tree; - - let cursor: CParseTree = tree; - - while (this.IsFuncPtr(cursor.nodes) === true) { - cursor = cursor.nodes.find((n) => n instanceof CParseTree) as CParseTree; - } - - // Remove CParseTree. This can be if it is a function declaration. - const argumentsIndex = cursor.nodes.findIndex((n) => n instanceof CParseTree); - if (argumentsIndex !== -1) { - cursor.nodes.splice(argumentsIndex, 1); - } - - // Find first symbol that is the argument name. - // Remove it from the tree and set the name to the argument name - for (let i: number = 0; i < cursor.nodes.length; i++) { - const node = cursor.nodes[i]; - if (node instanceof CParseTree) { - continue; - } - - if (node.type === CTokenType.Symbol && this.keywords.find((k) => k === node.value) === undefined) { - argument.name = node.value; - cursor.nodes.splice(i, 1); - } - } - - this.StripNonTypeNodes(argument.type); - return argument; - } - - private GetDefaultArgument(tree: CParseTree): CArgument { - const argument: CArgument = new CArgument(); - - for (const node of tree.nodes) { - if (node instanceof CParseTree) { - break; - } - const symbolCount = argument.type.nodes - .filter((n) => n instanceof CToken) - .map((n) => n as CToken) - .filter((n) => n.type === CTokenType.Symbol) - .filter((n) => this.keywords.find((k) => k === n.value) === undefined) - .length; - - if (node.type === CTokenType.Symbol - && this.keywords.find((k) => k === node.value) === undefined - ) { - if (symbolCount === 1 && argument.name === null) { - argument.name = node.value; - continue; - } else if (symbolCount > 1) { - throw new Error("Too many non keyword symbols."); - } - } - - argument.type.nodes.push(node); - } - - this.StripNonTypeNodes(argument.type); - return argument; - } - - private GetArgument(tree: CParseTree): CArgument { - // Copy tree structure leave original untouched. - const copy = this.RemoveUnusedTokens(tree); - - // Special case with only ellipsis. C style variadic arguments - if (copy.nodes.length === 1) { - const node = copy.nodes[0]; - if (node instanceof CToken && node.type === CTokenType.Ellipsis) { - const argument: CArgument = new CArgument(); - argument.name = node.value; - return argument; - } - } - - // Check if it is has a trailing return. - const startTrailingReturn: number = copy.nodes - .findIndex((t) => t instanceof CToken ? t.type === CTokenType.Arrow : false); - - // Special case trailing return. - if (startTrailingReturn !== -1) { - return this.GetArgumentFromTrailingReturn(copy, startTrailingReturn); - } - - // Handle function pointers - if (this.IsFuncPtr(copy.nodes) === true) { - return this.GetArgumentFromFuncPtr(copy); - } - - return this.GetDefaultArgument(copy); - } - - private GetSubExprStartEnd(expression: string, startSearch: number, openExpr: string, closeExpr: string): number[] { - let openExprOffset: number = -1; - let nestedCount: number = 0; - for (let i: number = startSearch; i < expression.length; i++) { - if (expression[i] === openExpr && openExprOffset === -1) { - openExprOffset = i; - } - - if (expression[i] === openExpr) { - nestedCount++; - } else if (expression[i] === closeExpr && nestedCount > 0) { - nestedCount--; - } - - if (expression[i] === closeExpr && nestedCount === 0 && openExprOffset !== -1) { - return [openExprOffset, i + 1]; - } - } - - return [0, 0]; - } - - private GetTemplate(expression: string): string { - if (expression.startsWith("template") === false) { - return ""; - } - - let startTemplateOffset: number = -1; - for (let i: number = "template".length; i < expression.length; i++) { - if (expression[i] === "<") { - startTemplateOffset = i; - break; - } else if (expression[i] !== " ") { - return ""; - } - } - - if (startTemplateOffset === -1) { - return ""; - } - - const [start, end] = this.GetSubExprStartEnd(expression, startTemplateOffset, "<", ">"); - return expression.slice(0, end); - } - - private GetArgsFromTemplate(template: string): string[] { - const args: string[] = []; - if (template === "") { - return args; - } - - // Remove <> and add a comma to the end to remove edge case. - template = template.slice(template.indexOf("<") + 1, template.lastIndexOf(">")).replace(/^\s+|\s+$/g, "") + ","; - - const nestedCounts: { [key: string]: number; } = { - "(": 0, - "<": 0, - "{": 0, - }; - - let lastSeparator: number = 0; - for (let i: number = 0; i < template.length; i++) { - const notInSubExpr: boolean = nestedCounts["<"] === 0 - && nestedCounts["("] === 0 - && nestedCounts["{"] === 0; - - if (notInSubExpr === true && template[i] === ",") { - args.push(template.slice(lastSeparator + 1, i).replace(/^\s+|\s+$/g, "")); - } else if (notInSubExpr === true && (template[i] === " " || template[i] === ".")) { - lastSeparator = i; - } - - if (template[i] === "(") { - nestedCounts["("]++; - } else if (template[i] === ")" && nestedCounts["("] > 0) { - nestedCounts["("]--; - } else if (template[i] === "<") { - nestedCounts["<"]++; - } else if (template[i] === ">" && nestedCounts["<"] > 0) { - nestedCounts["<"]--; - } else if (template[i] === "{") { - nestedCounts["{"]++; - } else if (template[i] === "}" && nestedCounts["{"] > 0) { - nestedCounts["{"]--; - } - } - - return args; - } -} diff --git a/src/Lang/C/CToken.ts b/src/Lang/C/CToken.ts deleted file mode 100644 index 75214aa..0000000 --- a/src/Lang/C/CToken.ts +++ /dev/null @@ -1,26 +0,0 @@ -export enum CTokenType { - Symbol, - Pointer, - Reference, - ArraySubscript, - OpenParenthesis, - CloseParenthesis, - CurlyBlock, - Assignment, - Comma, - Arrow, - CommentBlock, - CommentLine, - Ellipsis, - Attribute, -} - -export class CToken { - public type: CTokenType; - public value: string; - - constructor(type: CTokenType, value: string) { - this.type = type; - this.value = value; - } -} diff --git a/src/Lang/Cpp/CppArgument.ts b/src/Lang/Cpp/CppArgument.ts new file mode 100644 index 0000000..9b781c8 --- /dev/null +++ b/src/Lang/Cpp/CppArgument.ts @@ -0,0 +1,6 @@ +import { CppParseTree } from "./CppParseTree"; + +export class CppArgument { + public name: string = null; + public type: CppParseTree = new CppParseTree(); +} diff --git a/src/Lang/Cpp/CppDocGen.ts b/src/Lang/Cpp/CppDocGen.ts new file mode 100644 index 0000000..e715583 --- /dev/null +++ b/src/Lang/Cpp/CppDocGen.ts @@ -0,0 +1,178 @@ +import { Position, Range, Selection, TextEditor, TextLine, WorkspaceEdit } from "vscode"; +import { IDocGen } from "../../Common/IDocGen"; +import { Config } from "../../Config"; +import { CppArgument } from "./CppArgument"; +import { CppParseTree } from "./CppParseTree"; +import { CppToken, CppTokenType } from "./CppToken"; + +export default class CppDocGen implements IDocGen { + protected activeEditor: TextEditor; + + protected readonly cfg: Config; + + protected func: CppArgument; + protected templateParams: string[]; + protected params: CppArgument[]; + + /** + * @param {TextEditor} actEdit Active editor window + * @param {Position} cursorPosition Where the cursor of the user currently is + * @param {string[]} templateParams The template parameters of the declaration. + * @param {CppArgument} func The type and name of the function to generate doxygen. + * Doesn't contain anything if it is not a function. + * @param {CppArgument[]} params The parameters of the function. Doesn't contain anything if it is not a function. + */ + public constructor( + actEdit: TextEditor, + cursorPosition: Position, + cfg: Config, + templateParams: string[], + func: CppArgument, + params: CppArgument[], + ) { + this.activeEditor = actEdit; + this.cfg = cfg; + this.func = func; + this.templateParams = templateParams; + this.params = params; + } + + /** + * @inheritdoc + */ + public GenerateDoc(rangeToReplace: Range) { + const comment: string = this.generateComment(); + + this.activeEditor.edit((editBuilder) => { + editBuilder.replace(rangeToReplace, comment); // Insert the comment + }); + + // Set cursor to first DoxyGen command. + this.moveCursurToFirstDoxyCommand(comment, rangeToReplace.start.line, rangeToReplace.start.character); + } + + /*************************************************************************** + Implementation + ***************************************************************************/ + protected getIndentation(): string { + const line: TextLine = this.activeEditor.document.lineAt(this.activeEditor.selection.start.line); + return line.text.slice(0, line.firstNonWhitespaceCharacterIndex - 1); + } + + protected getTemplatedString(replace: string, template: string, param: string): string { + return template.replace(replace, param); + } + + protected generateBrief(lines: string[]) { + lines.push(this.cfg.commentPrefix + this.cfg.briefTemplate); + } + + protected generateFromTemplate(lines: string[], replace: string, template: string, templateWith: string[]) { + let line: string = ""; + + templateWith.forEach((element: string) => { + // Ignore null values + if (element !== null) { + line = this.cfg.commentPrefix; + line += this.getTemplatedString(replace, template, element); + lines.push(line); + } + }); + } + + protected generateReturnParams(): string[] { + const params: string[] = []; + + // Check if return type is a pointer + const ptrReturnIndex = this.func.type.nodes + .findIndex((n) => n instanceof CppToken && n.type === CppTokenType.Pointer); + + // Special case for void functions. + const voidReturnIndex = this.func.type.nodes + .findIndex((n) => n instanceof CppToken && n.type === CppTokenType.Symbol && n.value === "void"); + + // Special case for bool return type. + const boolReturnIndex: number = this.func.type.nodes + .findIndex((n) => n instanceof CppToken && n.type === CppTokenType.Symbol && n.value === "bool"); + + if (boolReturnIndex !== -1 && this.cfg.boolReturnsTrueFalse === true) { + params.push("true"); + params.push("false"); + } else if (voidReturnIndex !== -1 && ptrReturnIndex !== -1) { + params.push(this.cfg.includeTypeAtReturn === true ? this.func.type.Yield() : ""); + } else if (voidReturnIndex === -1 && this.func.type.nodes.length > 0) { + params.push(this.cfg.includeTypeAtReturn === true ? this.func.type.Yield() : ""); + } + + return params; + } + + protected generateComment(): string { + const lines: string[] = []; + + if (this.cfg.firstLine.trim().length !== 0) { + lines.push(this.cfg.firstLine); + } + + if (this.cfg.briefTemplate.trim().length !== 0) { + this.generateBrief(lines); + if (this.cfg.newLineAfterBrief === true) { + lines.push(this.cfg.commentPrefix); + } + } + + if (this.cfg.tparamTemplate.trim().length !== 0 && this.templateParams.length > 0) { + this.generateFromTemplate( + lines, + this.cfg.paramTemplateReplace, + this.cfg.tparamTemplate, + this.templateParams, + ); + if (this.cfg.newLineAfterTParams === true) { + lines.push(this.cfg.commentPrefix); + } + } + + if (this.cfg.paramTemplate.trim().length !== 0 && this.params.length > 0) { + const paramNames: string[] = this.params.map((p) => p.name); + this.generateFromTemplate(lines, this.cfg.paramTemplateReplace, this.cfg.paramTemplate, paramNames); + if (this.cfg.newLineAfterParams === true) { + lines.push(this.cfg.commentPrefix); + } + } + + if (this.cfg.returnTemplate.trim().length !== 0 && this.func.type !== null) { + const returnParams = this.generateReturnParams(); + this.generateFromTemplate(lines, this.cfg.typeTemplateReplace, this.cfg.returnTemplate, returnParams); + } + + if (this.cfg.lastLine.trim().length !== 0) { + lines.push(this.cfg.lastLine); + } + + const comment: string = lines.join("\n" + this.getIndentation()); + return comment; + } + + protected moveCursurToFirstDoxyCommand(comment: string, baseLine: number, baseCharacter) { + // Find first offset of a new line in the comment. Since that's when the line where the first param starts. + let line: number = baseLine; + let character: number = comment.indexOf("\n"); + + // If a first line is included find the 2nd line with a newline. + if (this.cfg.firstLine.trim().length !== 0) { + line++; + const oldCharacter: number = character; + character = comment.indexOf("\n", oldCharacter + 1) - oldCharacter; + } + + // If newline is not found means no first param was found so Set to base line before the newline. + if (character < 0) { + line = baseLine; + character = baseCharacter; + } + + const moveTo: Position = new Position(line, character); + this.activeEditor.selection = new Selection(moveTo, moveTo); + } +} diff --git a/src/Lang/Cpp/CppParseTree.ts b/src/Lang/Cpp/CppParseTree.ts new file mode 100644 index 0000000..41fb8b2 --- /dev/null +++ b/src/Lang/Cpp/CppParseTree.ts @@ -0,0 +1,124 @@ +import { CppToken, CppTokenType } from "./CppToken"; + +export class CppParseTree { + + /** + * Create a tree from CppTokens. This consumes the CppTokens. + * @param CppTokens The CppTokens to create a tree for. + * @param inNested If currently allready nesting. + */ + public static CreateTree(CppTokens: CppToken[], inNested: boolean = false): CppParseTree { + const tree: CppParseTree = new CppParseTree(); + + while (CppTokens.length > 0) { + const token: CppToken = CppTokens.shift(); + switch (token.type) { + case CppTokenType.OpenParenthesis: + tree.nodes.push(this.CreateTree(CppTokens, true)); + break; + case CppTokenType.CloseParenthesis: + if (inNested === false) { + throw new Error("Unmatched closing parenthesis."); + } + return tree; + default: + tree.nodes.push(token); + break; + } + } + + if (inNested === true) { + throw new Error("No match found for an opening parenthesis."); + } + + return tree; + } + + public nodes: Array = []; + + /** + * Compact empty branches. Example ((foo))(((bar))) will become (foo)(bar) + * @param tree The CppParseTree to compact. Defaults to the current tree. + */ + public Compact(tree: CppParseTree = this): CppParseTree { + const newTree: CppParseTree = new CppParseTree(); + newTree.nodes = tree.nodes.map((n) => n); + const isNotCompact = (n) => { + return n instanceof CppParseTree + && n.nodes.length === 1 && n.nodes[0] instanceof CppParseTree; + }; + + // Compact current level of nodes to the maximum amount. + while (newTree.nodes.some((n) => isNotCompact(n))) { + newTree.nodes = newTree.nodes + .map((n) => n instanceof CppParseTree && isNotCompact(n) ? n.nodes[0] : n); + } + + // Compact all nested CppParseTrees. + newTree.nodes = newTree.nodes + .map((n) => n instanceof CppParseTree ? this.Compact(n) : n); + + return newTree; + } + + /** + * Copy CppParseTree. + * @param tree The CppParseTree to compact. Defaults to the current tree. + */ + public Copy(tree: CppParseTree = this): CppParseTree { + const newTree: CppParseTree = new CppParseTree(); + newTree.nodes = tree.nodes + .map((n) => n instanceof CppToken ? n : this.Copy(n)); + return newTree; + } + + /** + * Create string from the CppParseTree which is a representation of the original code. + * @param tree The CppParseTree to compact. Defaults to the current tree. + */ + public Yield(tree: CppParseTree = this): string { + let code: string = ""; + + for (const node of tree.nodes) { + if (node instanceof CppParseTree) { + code += "(" + this.Yield(node) + ")"; + continue; + } + + switch (node.type) { + case CppTokenType.Symbol: + code += code === "" ? node.value : " " + node.value; + break; + case CppTokenType.Pointer: + code += node.value; + break; + case CppTokenType.Reference: + code += node.value; + break; + case CppTokenType.ArraySubscript: + code += node.value; + break; + case CppTokenType.CurlyBlock: + code += node.value; + break; + case CppTokenType.Assignment: + code += " " + node.value; + break; + case CppTokenType.Comma: + code += node.value; + break; + case CppTokenType.Arrow: + code += " " + node.value; + break; + case CppTokenType.Ellipsis: + code += node.value; + break; + case CppTokenType.Attribute: + code += code === "" ? node.value : " " + node.value; + break; + } + } + + return code; + } +} diff --git a/src/Lang/Cpp/CppParser.ts b/src/Lang/Cpp/CppParser.ts new file mode 100644 index 0000000..27abc8a --- /dev/null +++ b/src/Lang/Cpp/CppParser.ts @@ -0,0 +1,609 @@ +import { Position, TextDocumentContentChangeEvent, TextEditor, TextLine, workspace } from "vscode"; +import ICodeParser from "../../Common/ICodeParser"; +import { IDocGen } from "../../Common/IDocGen"; +import { Config } from "../../Config"; +import { CppArgument } from "./CppArgument"; +import CppDocGen from "./CppDocGen"; +import { CppParseTree } from "./CppParseTree"; +import { CppToken, CppTokenType } from "./CppToken"; + +/** + * + * Parses C code for methods and signatures + * + * @export + * @class CParser + * @implements {ICodeParser} + */ +export default class CppParser implements ICodeParser { + protected activeEditor: TextEditor; + protected activeSelection: Position; + protected readonly cfg: Config; + + private typeKeywords: string[]; + private stripKeywords: string[]; + private keywords: string[]; + private attributes: string[]; + private lexerVocabulary; + + constructor(cfg: Config) { + this.cfg = cfg; + + this.typeKeywords = [ + "constexpr", + "const", + "struct", + ]; + + this.stripKeywords = [ + "final", + "static", + "inline", + "friend", + "virtual", + "extern", + "explicit", + "class", + "override", + "typename", + ]; + + this.attributes = [ + "noexcept", + "throw", + "alignas", + ]; + + // Non type keywords will be stripped from the final return type. + this.keywords = this.typeKeywords.concat(this.stripKeywords); + + this.lexerVocabulary = { + ArraySubscript: (x: string): string => (x.match("^\\[[^\\[]*?\\]") || [])[0], + Arrow: (x: string): string => (x.match("^->") || [])[0], + Assignment: (x: string): string => (x.match("^=") || [])[0], + Attribute: (x: string): string => { + const attribute: string = (x.match("^\\[\\[[^\\[]*?\\]\\]") || [])[0]; + if (attribute !== undefined) { + return attribute; + } + + const foundIndex: number = this.attributes + .findIndex((n: string) => x.startsWith(n) === true); + + if (foundIndex === -1) { + return undefined; + } + + if (x.slice(this.attributes[foundIndex].length).trim().startsWith("(") === false) { + return x.slice(0, this.attributes[foundIndex].length); + } + + const startEndOffset: number[] = this.GetSubExprStartEnd(x, 0, "(", ")"); + return startEndOffset[1] === 0 ? undefined : x.slice(0, startEndOffset[1]); + }, + CloseParenthesis: (x: string): string => (x.match("^\\)") || [])[0], + Comma: (x: string): string => (x.match("^,") || [])[0], + CommentBlock: (x: string): string => { + if (x.startsWith("/*") === false) { + return undefined; + } + + let closeOffset: number = x.indexOf("*/"); + closeOffset = closeOffset === -1 ? x.length : closeOffset + 2; + return x.slice(0, closeOffset); + }, + CommentLine: (x: string): string => { + if (x.startsWith("//") === false) { + return undefined; + } + + let closeOffset: number = x.indexOf("\n"); + closeOffset = closeOffset === -1 ? x.length : closeOffset + 1; + return x.slice(0, closeOffset); + }, + CurlyBlock: (x: string): string => { + if (x.startsWith("{") === false) { + return undefined; + } + const startEndOffset: number[] = this.GetSubExprStartEnd(x, 0, "{", "}"); + return startEndOffset[1] === 0 ? undefined : x.slice(0, startEndOffset[1]); + }, + Ellipsis: (x: string): string => (x.match("^\\.\\.\\.") || [])[0], + OpenParenthesis: (x: string): string => (x.match("^\\(") || [])[0], + Pointer: (x: string): string => (x.match("^\\*") || [])[0], + Reference: (x: string): string => (x.match("^&") || [])[0], + Symbol: (x: string): string => { + // Handle access specifiers since they aren't really symbols. + if (x.startsWith("public:") || x.startsWith("protected:") || x.startsWith("private:")) { + return undefined; + } + + // Handle specifiers + const specifierFound: number = this.attributes + .findIndex((n: string) => x.startsWith(n) === true); + + if (specifierFound !== -1) { + return undefined; + } + + // Handle decltype special cases. + if (x.startsWith("decltype") === true) { + const startEndOffset: number[] = this.GetSubExprStartEnd(x, 0, "(", ")"); + return startEndOffset[1] === 0 ? undefined : x.slice(0, startEndOffset[1]); + } + + // Special case group up the fundamental types with the modifiers. + // tslint:disable-next-line:max-line-length + let reMatch: string = (x.match("^(unsigned|signed|short|long|int|char|double)(\\s+(unsigned|signed|short|long|int|char|double))+(?!a-z|A-Z|:|_)") || [])[0]; + if (reMatch !== undefined) { + return reMatch.trim(); + } + + // Regex to handle a part of all symbols and includes all symbol special cases. + // This is run in a loop because template parts of a symbol can't be parsed using regex. + // tslint:disable-next-line:max-line-length + const symbolRegex: string = "^([a-z|A-Z|:|_|~|\\d]*operator\\s*(\"\"_[a-z|A-Z|_|\\d]+|>>=|<<=|->\\*|\\+=|-=|\\*=|\\/=|%=|\\^=|&=|\\|=|<<|>>|==|!=|<=|->|>=|&&|\\|\\||\\+\\+|--|\\+|-|\\*|\\/|%|\\^|&|\||~|!|=|<|>|,|\\[\\s*\\]|\\(\\s*\\)|(new|delete)\\s*(\\[\\s*\\]){0,1}){0,1}|[a-z|A-Z|:|_|~|\\d]+)"; + + reMatch = (x.match(symbolRegex) || [])[0]; + if (reMatch === undefined) { + return undefined; + } + + let symbol: string = reMatch; + while (true) { + if (x.slice(symbol.length).trim().startsWith("<") === true) { + const offsets: number[] = this.GetSubExprStartEnd(x, symbol.length, "<", ">"); + if (offsets[1] === 0) { + return undefined; + } + symbol = x.slice(0, offsets[1]); + } + + reMatch = (x.slice(symbol.length).match(symbolRegex) || [])[0]; + if (reMatch === undefined) { + break; + } + + symbol += reMatch; + } + + return symbol.replace(/\s+$/, ""); + }, + }; + } + + /** + * @inheritdoc + */ + public Parse(activeEdit: TextEditor): IDocGen { + this.activeEditor = activeEdit; + this.activeSelection = this.activeEditor.selection.active; + + let line: string = ""; + try { + line = this.getLogicalLine(); + } catch (err) { + // console.dir(err); + } + + // template parsing is simpler by using heuristics rather then CppTokenizing first. + const templateArgs: string[] = []; + while (line.startsWith("template")) { + const template: string = this.GetTemplate(line); + + templateArgs.push.apply(templateArgs, this.GetArgsFromTemplate(template)); + + line = line.slice(template.length, line.length + 1).trim(); + } + + let args: [CppArgument, CppArgument[]] = [new CppArgument(), []]; + try { + args = this.GetReturnAndArgs(line); + } catch (err) { + // console.dir(err); + } + + return new CppDocGen( + this.activeEditor, + this.activeSelection, + this.cfg, + templateArgs, + args[0], + args[1], + ); + } + + /*************************************************************************** + Implementation + ***************************************************************************/ + private getLogicalLine(): string { + let logicalLine: string = ""; + + let nextLine: Position = new Position(this.activeSelection.line + 1, this.activeSelection.character); + + let nextLineTxt: string = this.activeEditor.document.lineAt(nextLine.line).text.trim(); + + // VSCode may enter a * on itself, we don"t want that in our method + if (nextLineTxt === "*") { + nextLineTxt = ""; + } + + let currentNest: number = 0; + logicalLine = nextLineTxt; + + // Get method end line + let linesToGet: number = 20; + while (linesToGet-- > 0) { // Check for end of expression. + nextLine = new Position(nextLine.line + 1, nextLine.character); + nextLineTxt = this.activeEditor.document.lineAt(nextLine.line).text.trim(); + + // Check if method has finished if curly brace is opened while + // nesting is occuring. + for (let i: number = 0; i < nextLineTxt.length; i++) { + if (nextLineTxt[i] === "(") { + currentNest++; + } else if (nextLineTxt[i] === ")") { + currentNest--; + } else if (nextLineTxt[i] === "{" && currentNest === 0) { + logicalLine += "\n" + nextLineTxt.slice(0, i); + return logicalLine.replace(/^\s+|\s+$/g, ""); + } else if ((nextLineTxt[i] === ";" + || (nextLineTxt[i] === ":" && nextLineTxt[i - 1] !== ":" && nextLineTxt[i + 1] !== ":")) + && currentNest === 0) { + + logicalLine += "\n" + nextLineTxt.slice(0, i); + return logicalLine.replace(/^\s+|\s+$/g, ""); + } + } + + logicalLine += "\n" + nextLineTxt; + } + + throw new Error("More then 20 lines were gotten from editor and no end of expression was found."); + } + + private Tokenize(expression: string): CppToken[] { + const CppTokens: CppToken[] = []; + expression = expression.replace(/^\s+|\s+$/g, ""); + + while (expression.length !== 0) { + const matches: CppToken[] = Object.keys(this.lexerVocabulary) + .map((k): CppToken => new CppToken(CppTokenType[k], this.lexerVocabulary[k](expression))) + .filter((t) => t.value !== undefined); + + if (matches.length === 0) { + throw new Error("Next CppToken couldn\'t be determined: " + expression); + } else if (matches.length > 1) { + throw new Error("Multiple matches for next CppToken: " + expression); + } + + CppTokens.push(matches[0]); + expression = expression.slice(matches[0].value.length, expression.length).replace(/^\s+|\s+$/g, ""); + } + + return CppTokens; + } + + private GetReturnAndArgs(line: string): [CppArgument, CppArgument[]] { + // CppTokenize rest of expression and remove comment CppTokens; + const CppTokens: CppToken[] = this.Tokenize(line) + .filter((t) => t.type !== CppTokenType.CommentBlock) + .filter((t) => t.type !== CppTokenType.CommentLine); + + // Create hierarchical tree based on the parenthesis. +