From 5b0912d99732536ae18d3c70730cc6df2ed3af24 Mon Sep 17 00:00:00 2001 From: Rowan Goemans Date: Thu, 28 Dec 2017 21:00:30 +0100 Subject: -- Implemented support for all operators except the conversion operator. -- Implemented support for noexcept and throw. -- Implemented support for constexpr. -- Implemented support for multiple template specifications. -- Rewritten some unit tests. -- Fixed bug where unnamed parameters weren't recognized properly -- Fixed bug where namespace with template params weren't being parsed correctly. --- src/CodeParser/CParser/CParser.ts | 141 +++++++++++++++++++++++++++++------- src/CodeParser/CParser/Token.ts | 1 + src/test/MockDocument.ts | 9 ++- src/test/TestSetup.ts | 46 ++++++++++++ src/test/extension.test.ts | 149 -------------------------------------- src/test/functionPointer.test.ts | 42 +++++++++++ src/test/simpleReturn.test.ts | 79 ++++++++++++++++++++ 7 files changed, 286 insertions(+), 181 deletions(-) create mode 100644 src/test/TestSetup.ts delete mode 100644 src/test/extension.test.ts create mode 100644 src/test/functionPointer.test.ts create mode 100644 src/test/simpleReturn.test.ts (limited to 'src') diff --git a/src/CodeParser/CParser/CParser.ts b/src/CodeParser/CParser/CParser.ts index 9ec7e14..3203b40 100644 --- a/src/CodeParser/CParser/CParser.ts +++ b/src/CodeParser/CParser/CParser.ts @@ -22,10 +22,12 @@ export default class CParser implements ICodeParser { private typeKeywords: string[]; private stripKeywords: string[]; private keywords: string[]; + private noexcepts: string[]; private lexerVocabulary; constructor() { this.typeKeywords = [ + "constexpr", "const", "struct", ]; @@ -39,6 +41,12 @@ export default class CParser implements ICodeParser { "explicit", "class", "override", + "typename", + ]; + + this.noexcepts = [ + "noexcept", + "throw", ]; // Non type keywords will be stripped from the final return type. @@ -77,6 +85,22 @@ export default class CParser implements ICodeParser { return startEndOffset[1] === 0 ? undefined : x.slice(0, startEndOffset[1]); }, Ellipsis: (x: string): string => (x.match("^\\.\\.\\.") || [])[0], + Noexcept: (x: string): string => { + const foundIndex: number = this.noexcepts + .findIndex((n: string) => x.startsWith(n) === true); + + if (foundIndex === -1) { + return undefined; + } + + if (x.slice(this.noexcepts[foundIndex].length).trim().startsWith("(") === false) { + return x.slice(0, this.noexcepts[foundIndex].length); + } + + const startEndOffset: number[] = this.GetSubExprStartEnd(x, 0, "(", ")"); + return startEndOffset[1] === 0 ? undefined : x.slice(0, startEndOffset[1]); + + }, OpenParenthesis: (x: string): string => (x.match("^\\(") || [])[0], Pointer: (x: string): string => (x.match("^\\*") || [])[0], Reference: (x: string): string => (x.match("^&") || [])[0], @@ -85,27 +109,57 @@ export default class CParser implements ICodeParser { if (x.startsWith("public:") || x.startsWith("protected:") || x.startsWith("private:")) { return undefined; } - // Handle operator and decltype special cases. - if (x.startsWith("operator") === true) { - const startBrace: number = x.indexOf("("); - return startBrace === -1 ? undefined : x.slice(0, startBrace); - } else if (x.startsWith("decltype") === true) { + + // Handle noexcept and throw since they aren't normal symbols. + const noExceptFound: number = this.noexcepts + .findIndex((n: string) => x.startsWith(n) === true); + + if (noExceptFound !== -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]); } - const reMatch: string = (x.match("^[a-z|A-Z|:|_|~|\\d]+") || [])[0]; + // 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; } - // Check if symbol includes a template for instance Matrix and include it if so. - if (x.slice(reMatch.length, x.length).trim().startsWith("<") === false) { - return reMatch; + 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.trim(); } - const offsets: number[] = this.GetSubExprStartEnd(x, reMatch.length, "<", ">"); - return offsets[1] === 0 ? undefined : x.slice(0, offsets[1]); + return symbol.trim(); }, }; } @@ -125,10 +179,14 @@ export default class CParser implements ICodeParser { } // template parsing is simpler by using heuristics rather then tokenizing first. - const template: string = this.GetTemplate(line); - const templateArgs: string[] = this.GetArgsFromTemplate(template); + 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(); + line = line.slice(template.length, line.length + 1).trim(); + } let retAndArgs: string[][] = [[], []]; try { @@ -185,7 +243,10 @@ export default class CParser implements ICodeParser { } else if (nextLineTxt[i] === "{" && currentNest === 0) { logicalLine += "\n" + nextLineTxt.slice(0, i); return logicalLine.replace(/^\s+|\s+$/g, ""); - } else if (nextLineTxt[i] === ";" && currentNest === 0) { + } 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, ""); } @@ -233,6 +294,18 @@ export default class CParser implements ICodeParser { // return argument. const returnArg = 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 (returnArg.Name === undefined) { + if (returnArg.Type.nodes.length !== 1) { + throw new Error("Too many symbols found for constructor/descructor."); + } else if (returnArg.Type.nodes[0] instanceof ParseTree) { + throw new Error("One node found with just a parsetree. Malformed input."); + } + + returnArg.Name = (returnArg.Type.nodes[0] as Token).Value; + returnArg.Type.nodes = []; + } // Check if return type is a pointer const ptrReturnIndex = returnArg.Type.nodes @@ -275,11 +348,11 @@ export default class CParser implements ICodeParser { tree.nodes = tree.nodes.slice(0, assignmentIndex); } - // Check if there is an initializer list and slice of everything after it. - const initializerList = tree.nodes - .findIndex((n) => n instanceof Token && n.Type === TokenType.Symbol && n.Value === ":"); - if (initializerList !== -1) { - tree.nodes = tree.nodes.slice(0, initializerList); + // Noexcept isn't needed so slice everything after it. + const noexcept = tree.nodes + .findIndex((n) => n instanceof Token && n.Type === TokenType.Noexcept); + if (noexcept !== -1) { + tree.nodes = tree.nodes.slice(0, noexcept); } return tree; @@ -412,17 +485,29 @@ export default class CParser implements ICodeParser { private GetDefaultArgument(tree: ParseTree): Argument { const argument: Argument = new Argument(); - for (let i = tree.nodes.length - 1; i >= 0; i--) { - const node = tree.nodes[i]; - if (node instanceof Token && node.Type === TokenType.Symbol) { - argument.Name = node.Value; - tree.nodes.splice(i, 1); + for (const node of tree.nodes) { + if (node instanceof ParseTree) { break; } - } + const symbolCount = argument.Type.nodes + .filter((n) => n instanceof Token) + .map((n) => n as Token) + .filter((n) => n.Type === TokenType.Symbol) + .filter((n) => this.keywords.find((k) => k === n.Value) === undefined) + .length; + + if (node.Type === TokenType.Symbol + && this.keywords.find((k) => k === node.Value) === undefined + ) { + if (symbolCount === 1 && argument.Name === undefined) { + argument.Name = node.Value; + continue; + } else if (symbolCount > 1) { + throw new Error("Too many non keyword symbols."); + } + } - if (tree.nodes.length > 0) { - argument.Type.nodes = tree.nodes.filter((x) => x instanceof Token); + argument.Type.nodes.push(node); } this.StripNonTypeNodes(argument.Type); diff --git a/src/CodeParser/CParser/Token.ts b/src/CodeParser/CParser/Token.ts index b947ec0..c9d3065 100644 --- a/src/CodeParser/CParser/Token.ts +++ b/src/CodeParser/CParser/Token.ts @@ -13,6 +13,7 @@ export enum TokenType { CommentLine, Ellipsis, Attribute, + Noexcept, } export class Token { diff --git a/src/test/MockDocument.ts b/src/test/MockDocument.ts index 447adc9..cb6bbfe 100644 --- a/src/test/MockDocument.ts +++ b/src/test/MockDocument.ts @@ -12,18 +12,19 @@ export default class MockDocument implements vscode.TextDocument { public eol: vscode.EndOfLine; public lineCount: number; private line: vscode.TextLine; - private firstCall: boolean; + private callCount: number; public constructor(line: vscode.TextLine) { this.line = line; - this.firstCall = true; + this.callCount = 0; } public save(): Thenable { throw new Error("Method not implemented."); } public lineAt(line: number | vscode.Position): vscode.TextLine; public lineAt(position: any): any { - if (this.firstCall) { - this.firstCall = false; + if (++this.callCount === 1) { + return new MockLine(""); + } else if (this.callCount === 2) { return this.line; } else { return new MockLine(";"); diff --git a/src/test/TestSetup.ts b/src/test/TestSetup.ts new file mode 100644 index 0000000..2f4f64a --- /dev/null +++ b/src/test/TestSetup.ts @@ -0,0 +1,46 @@ +import * as vscode from "vscode"; +import CodeParser from "../CodeParser/CodeParser"; +import CParser from "../CodeParser/CParser/CParser"; +import { IDocGen } from "../DocGen/DocGen"; +import * as myExtension from "../extension"; +import MockDocument from "./MockDocument"; +import MockEditor from "./MockEditor"; +import MockLine from "./MockLine"; +import MockPosition from "./MockPosition"; +import MockSelection from "./MockSelection"; + +export default class TestSetup { + private editor: MockEditor; + + constructor(method: string) { + this.SetMethod(method); + } + + public SetMethod(method: string): TestSetup { + let position: MockPosition; + position = new MockPosition(0, 0); + + let selection: MockSelection; + selection = new MockSelection(position); + + let line: MockLine; + line = new MockLine(method); + + let doc: MockDocument; + doc = new MockDocument(line); + + this.editor = new MockEditor(selection, doc); + + return this; + } + + public GetResult(): string { + let parser: CodeParser; + parser = new CParser(); + + const gen: IDocGen = parser.Parse(this.editor); + gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); + + return this.editor.editBuilder.text; + } +} diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts deleted file mode 100644 index da2819d..0000000 --- a/src/test/extension.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -// -// Note: This example test is leveraging the Mocha test framework. -// Please refer to their documentation on https://mochajs.org/ for help. -// - -// The module 'assert' provides assertion methods from node -import * as assert from "assert"; - -// You can import and use all API from the 'vscode' module -// as well as import your extension to test it -import * as vscode from "vscode"; -import CodeParser from "../CodeParser/CodeParser"; -import CParser from "../CodeParser/CParser/CParser"; -import { IDocGen } from "../DocGen/DocGen"; -import * as myExtension from "../extension"; -import MockDocument from "./MockDocument"; -import MockEditor from "./MockEditor"; -import MockLine from "./MockLine"; -import MockPosition from "./MockPosition"; -import MockSelection from "./MockSelection"; - -// Defines a Mocha test suite to group tests of similar kind together -suite("Comment generation", () => { - - let editor: MockEditor; - - function setup(method: string): IDocGen { - let parser: CodeParser; - parser = new CParser(); - - let position: MockPosition; - position = new MockPosition(0, 0); - - let selection: MockSelection; - selection = new MockSelection(position); - - let line: MockLine; - line = new MockLine(method); - - let doc: MockDocument; - doc = new MockDocument(line); - - editor = new MockEditor(selection, doc); - - const gen: IDocGen = parser.Parse(editor); - - return gen; - } - - // Tests - test("void main(int argc, char **argv)", () => { - const gen: IDocGen = setup("void main(int argc, char **argv)"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @param argc \n * @param argv \n */", editor.editBuilder.text); - }); - - test("bool fuzzy_equal(const matrix& mat, T fuzz) const", () => { - const gen: IDocGen = setup("bool fuzzy_equal(const matrix& mat, T fuzz) const"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @param mat \n * @param fuzz \n * @return true \n * @return false \n */", - editor.editBuilder.text); - }); - - test("int main()", () => { - const gen: IDocGen = setup("int main()"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @return int \n */", - editor.editBuilder.text); - }); - - test("void foo(std::string str, std::vector lst)", () => { - const gen: IDocGen = setup("void foo(std::string str, std::vector lst)"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @param str \n * @param lst \n */", - editor.editBuilder.text); - }); - - test("std::vector foo()", () => { - const gen: IDocGen = setup("std::vector foo()"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @return std::vector \n */", - editor.editBuilder.text); - }); - - test("myClass(int i)", () => { - const gen: IDocGen = setup("myClass(int i)"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @param i \n */", - editor.editBuilder.text); - }); - - test("myClass(int i) : i(i)", () => { - const gen: IDocGen = setup("myClass(int i) : i(i)"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @param i \n */", - editor.editBuilder.text); - }); - - test("virtual ~myClass()", () => { - const gen: IDocGen = setup("virtual ~myClass()"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n */", - editor.editBuilder.text); - }); - - test("static inline unsigned int rewind_tospace(const unsigned char* chunk, unsigned int len)", () => { - // tslint:disable-next-line:max-line-length - const gen: IDocGen = setup("static inline unsigned int rewind_tospace(const unsigned char* chunk, unsigned int len)"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @param chunk \n * @param len \n * @return unsigned int \n */", - editor.editBuilder.text); - }); - - test("void foo_bar(void (*my_function_pointer) (unsigned int arg1, char arg2))", () => { - const gen: IDocGen = setup("void foo_bar(void (*my_function_pointer) (unsigned int arg1, char arg2))"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @param my_function_pointer \n */", - editor.editBuilder.text); - }); - - test("int (*idputs(int (*puts)(const char *)))(const char *)", () => { - const gen: IDocGen = setup("int (*idputs(int (*puts)(const char *)))(const char *)"); - - gen.GenerateDoc(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))); - - assert.equal("/**\n * @brief \n * \n * @param puts \n * @return int(*)(const char*) \n */", - editor.editBuilder.text); - }); -}); diff --git a/src/test/functionPointer.test.ts b/src/test/functionPointer.test.ts new file mode 100644 index 0000000..3b6b857 --- /dev/null +++ b/src/test/functionPointer.test.ts @@ -0,0 +1,42 @@ +// +// Note: This example test is leveraging the Mocha test framework. +// Please refer to their documentation on https://mochajs.org/ for help. +// + +// The module 'assert' provides assertion methods from node +import * as assert from "assert"; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +import * as vscode from "vscode"; +import TestSetup from "./TestSetup"; + +// Defines a Mocha test suite to group tests of similar kind together +suite("Function pointer tests", () => { + const testSetup: TestSetup = new TestSetup("void foo();"); + + // Tests + test("function pointer return", () => { + const result = testSetup.SetMethod("int (*idputs(int a, int b))(char *);").GetResult(); + assert.equal("/**\n * @brief \n * \n * @param a \n * @param b \n * @return int(*)(char*) \n */", result); + }); + + test("nested function pointer return", () => { + const result = testSetup.SetMethod("int (*(*(*foo(int a, int b))(int))(double))(float);").GetResult(); + assert.equal("/**\n * @brief \n * \n * @param a \n * @param b \n" + + " * @return int(*(*(*)(int))(double))(float) \n */", result); + }); + + test("function pointer parameter", () => { + const result = testSetup.SetMethod("int foo(int (*puts)(const char *));").GetResult(); + assert.equal("/**\n * @brief \n * \n * @param puts \n * @return int \n */", result); + }); + + test("struct function pointer return with struct FP parameter", () => { + const result = testSetup.SetMethod("struct foo (*idputs(int (*puts)(const char *), const" + + " struct test(*str)(int *, const struct test(*str2))))(const char *);").GetResult(); + + assert.equal("/**\n * @brief \n * \n * @param puts \n * @param str " + + "\n * @return struct foo(*)(const char*) \n */", result); + }); +}); diff --git a/src/test/simpleReturn.test.ts b/src/test/simpleReturn.test.ts new file mode 100644 index 0000000..8f3e5d1 --- /dev/null +++ b/src/test/simpleReturn.test.ts @@ -0,0 +1,79 @@ +// +// Note: This example test is leveraging the Mocha test framework. +// Please refer to their documentation on https://mochajs.org/ for help. +// + +// The module 'assert' provides assertion methods from node +import * as assert from "assert"; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +import * as vscode from "vscode"; +import TestSetup from "./TestSetup"; + +// Defines a Mocha test suite to group tests of similar kind together +suite("Arguments tests", () => { + + const testSetup: TestSetup = new TestSetup("void foo();"); + + // Tests + test("void return", () => { + const result = testSetup.SetMethod("void foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n */", result); + }); + + test("simple type return", () => { + const result = testSetup.SetMethod("int foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return int \n */", result); + }); + + test("bool return type", () => { + const result = testSetup.SetMethod("bool foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return true \n * @return false \n */", result); + }); + + test("pointer return type", () => { + const result = testSetup.SetMethod("int* foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return int* \n */", result); + }); + + test("bool pointer return type", () => { + const result = testSetup.SetMethod("bool* foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return true \n * @return false \n * @return null \n */", result); + }); + + test("Fundamental return type with modifiers", () => { + let result = testSetup.SetMethod("unsigned int foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return unsigned int \n */", result); + + result = testSetup.SetMethod("unsigned short int foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return unsigned short int \n */", result); + + result = testSetup.SetMethod("signed short foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return signed short \n */", result); + + result = testSetup.SetMethod("long foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return long \n */", result); + + result = testSetup.SetMethod("unsigned long long int foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return unsigned long long int \n */", result); + + result = testSetup.SetMethod("signed foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return signed \n */", result); + + result = testSetup.SetMethod("unsigned foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return unsigned \n */", result); + + result = testSetup.SetMethod("unsigned char foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return unsigned char \n */", result); + + result = testSetup.SetMethod("long double foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return long double \n */", result); + }); + + test("struct return type", () => { + const result = testSetup.SetMethod("struct my_struct foo();").GetResult(); + assert.equal("/**\n * @brief \n * \n * @return struct my_struct \n */", result); + }); + +}); -- cgit v1.2.3