summaryrefslogtreecommitdiffstats
path: root/src/CodeParser/CParser
diff options
context:
space:
mode:
authorRowan Goemans <RB.Goemans@student.han.nl>2017-10-30 01:46:33 +0100
committerRowan Goemans <RB.Goemans@student.han.nl>2017-10-30 01:46:33 +0100
commit5ae892f8267c76473330e5da3d2eafdb4f6447ef (patch)
treedcad101896db2e2e0867168bd233c97711920a0c /src/CodeParser/CParser
parentd2bda50f26bffd6a56574188704d9df50fecabf8 (diff)
downloaddoxdocgen-5ae892f8267c76473330e5da3d2eafdb4f6447ef.tar.gz
-- Completely rewritten parser to support all known(to me) C++ constructs, this includes:
- Function pointers as parameters and as returns. - Template support. - Trailing return type. - Variadic templates. - Variadic parametes, C style and template style. - Correct parsing of template types with more then 1 template parameter.
Diffstat (limited to 'src/CodeParser/CParser')
-rw-r--r--src/CodeParser/CParser/CParser.ts387
-rw-r--r--src/CodeParser/CParser/CppParser.ts16
-rw-r--r--src/CodeParser/CParser/ParseTree.ts220
-rw-r--r--src/CodeParser/CParser/Token.ts24
4 files changed, 647 insertions, 0 deletions
diff --git a/src/CodeParser/CParser/CParser.ts b/src/CodeParser/CParser/CParser.ts
new file mode 100644
index 0000000..d0d0dcb
--- /dev/null
+++ b/src/CodeParser/CParser/CParser.ts
@@ -0,0 +1,387 @@
+import { Position, TextDocumentContentChangeEvent, TextEditor, TextLine, workspace } from "vscode";
+import { Config, ConfigType } from "../../Config";
+import Generator from "../../DocGen/CGen";
+import { IDocGen } from "../../DocGen/DocGen";
+import ICodeParser from "../CodeParser";
+import { ParseTree } from "./ParseTree";
+import { Token, TokenType } from "./Token";
+
+/**
+ *
+ * Parses C code for methods and signatures
+ *
+ * @export
+ * @class CParser
+ * @implements {ICodeParser}
+ */
+export default class CParser implements ICodeParser {
+ protected activeEditor: TextEditor;
+ protected activeSelection: Position;
+
+ private keywords: string[];
+ private lexerVocabulary;
+
+ constructor() {
+ this.keywords = [
+ "static",
+ "inline",
+ "friend",
+ "virtual",
+ "extern",
+ "explicit",
+ "const",
+ "struct",
+ "class",
+ "override",
+ ];
+
+ 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 => (x.match("^\\[\\[[^\\[]*?\\]\\]") || [])[0],
+ CloseParenthesis: (x: string): string => (x.match("^\\)") || [])[0],
+ Comma: (x: string): string => (x.match("^,") || [])[0],
+ 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 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) {
+ 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];
+ if (reMatch === undefined) {
+ return undefined;
+ }
+
+ // Check if symbol includes a template for instance Matrix<T, M, N> and include it if so.
+ if (x.slice(reMatch.length, x.length).trim().startsWith("<") === false) {
+ return reMatch;
+ }
+
+ const offsets: number[] = this.GetSubExprStartEnd(x, reMatch.length, "<", ">");
+ return offsets[1] === 0 ? undefined : x.slice(0, offsets[1]);
+ },
+ };
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public Parse(activeEdit: TextEditor, event: TextDocumentContentChangeEvent): IDocGen {
+ this.activeEditor = activeEdit;
+ this.activeSelection = this.activeEditor.selection.active;
+
+ const activeLine: TextLine = this.activeEditor.document.lineAt(this.activeEditor.selection.active.line);
+
+ let line: string = this.getLogicalLine();
+
+ // Not a method
+ if (line.length === 0) {
+ return null;
+ }
+
+ // template parsing is simpler by using heuristics rather then tokenizing first.
+ const template: string = this.GetTemplate(line);
+ const templateArgs: string[] = this.GetArgsFromTemplate(template);
+ let args: string[] = [];
+ let retVals: string[] = [];
+
+ line = line.slice(template.length, line.length + 1).trim();
+
+ try {
+ // Tokenize rest of expression;
+ const tokens: Token[] = this.Tokenize(line);
+ // Create hierarchical tree based on the parenthesis.
+ const tree: ParseTree = ParseTree.CreateTree(tokens).Compact();
+
+ const parsedArgs: ParseTree[] = tree.GetArgTrees();
+ const parsedReturns: ParseTree = tree.GetReturnTree();
+
+ args = parsedArgs
+ .map((a) => this.GetArgNameFromArgTree(a));
+
+ retVals = this.GetArgTypeFromReturnTree(parsedReturns);
+
+ } catch (err) {
+ args = [];
+ retVals = [];
+ }
+
+ const cppGenerator: IDocGen = new Generator(
+ this.activeEditor,
+ this.activeSelection,
+ args,
+ templateArgs,
+ retVals,
+ );
+ return cppGenerator;
+ }
+
+ /***************************************************************************
+ Implementation
+ ***************************************************************************/
+ protected 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 = "";
+ }
+
+ while (nextLineTxt.length === 0) { // Get first method line
+ nextLine = new Position(nextLine.line + 1, nextLine.character);
+ nextLineTxt = this.activeEditor.document.lineAt(nextLine.line).text.trim();
+ }
+
+ logicalLine += nextLineTxt;
+
+ // Get method end line
+ while (nextLineTxt.indexOf(";") === -1 && nextLineTxt.indexOf("{") === -1) { // Check for method end
+ nextLine = new Position(nextLine.line + 1, nextLine.character);
+ nextLineTxt = this.activeEditor.document.lineAt(nextLine.line).text.trim();
+
+ logicalLine += " " + nextLineTxt;
+ }
+
+ logicalLine = logicalLine.replace(/[{|;]$/, "").trim();
+
+ return logicalLine;
+ }
+
+ private Tokenize(expression: string): Token[] {
+ const tokens: Token[] = [];
+ expression = expression.trim();
+
+ while (expression.length !== 0) {
+ const matches: Token[] = Object.keys(this.lexerVocabulary)
+ .map((k): Token => new Token(TokenType[k], this.lexerVocabulary[k](expression)))
+ .filter((t) => t.Value !== undefined);
+
+ if (matches.length === 0) {
+ throw new Error("Next token couldn\'t be determined: " + expression);
+ } else if (matches.length > 1) {
+ throw new Error("Multiple matches for next token: " + expression);
+ }
+
+ const match = matches[0];
+ tokens.push(match);
+ expression = expression.slice(match.Value.length, expression.length).trim();
+ }
+
+ return tokens;
+ }
+
+ private GetArgNameFromArgTree(tree: ParseTree): string {
+ const hasEllipsis: boolean = tree.nodes
+ .filter((n) => n instanceof Token && n.Type === TokenType.Ellipsis)
+ .length > 0;
+
+ const indexTrailingReturn: number = tree.nodes
+ .findIndex((t) => t instanceof Token ? t.Type === TokenType.Arrow : false);
+
+ const isFuncPtr: boolean = tree.nodes
+ .slice(0, indexTrailingReturn === -1 ? tree.nodes.length : indexTrailingReturn)
+ .filter((n) => n instanceof ParseTree)
+ .length === 2;
+
+ // If it is a function pointer the name is in the first tree.
+ if (isFuncPtr === true) {
+ const nestedTokens: Token[] = tree.nodes
+ .filter((n) => n instanceof ParseTree)
+ .map((n) => n as ParseTree)[0]
+ .nodes
+ .filter((n) => n instanceof Token)
+ .map((n) => n as Token)
+ .filter((t) => t.Type === TokenType.Symbol)
+ .filter((t) => this.keywords.find((k) => k === t.Value) === undefined);
+
+ return nestedTokens[0].Value;
+ }
+
+ const tokens: Token[] = tree.nodes
+ .filter((n) => n instanceof Token)
+ .map((n) => n as Token)
+ .filter((t) => t.Type === TokenType.Symbol)
+ .filter((t) => this.keywords.find((k) => k === t.Value) === undefined);
+
+ if (tokens.length === 0 && hasEllipsis === true) {
+ return "...";
+ }
+
+ if (tokens.length < 2) {
+ return "";
+ }
+
+ return tokens[1].Value;
+ }
+
+ private GetArgTypeFromReturnTree(tree: ParseTree): string[] {
+ // First strip out the param name or function name since it's not part of the type.
+ const indexTrailingReturn: number = tree.nodes
+ .findIndex((t) => t instanceof Token ? t.Type === TokenType.Arrow : false);
+
+ const isFuncPtr: boolean = tree.nodes
+ .slice(0, indexTrailingReturn === -1 ? tree.nodes.length : indexTrailingReturn)
+ .filter((n) => n instanceof ParseTree)
+ .length === 2;
+
+ let treeToModify: ParseTree = tree;
+ let symbolsFound = 0;
+ // If it is a function pointer the name is in the first tree.
+ if (isFuncPtr === true) {
+ treeToModify = tree.nodes
+ .filter((n) => n instanceof ParseTree)
+ .map((n) => n as ParseTree)[0];
+
+ // Function pointer so delete the first symbol in the tree
+ // Fake that one symbol was found.
+ symbolsFound = 1;
+ } else {
+ // Check for special case if return type is boolean or void.
+ for (const token of treeToModify.nodes) {
+ if (token instanceof ParseTree) {
+ break;
+ }
+ if (token.Type !== TokenType.Symbol) {
+ continue;
+ }
+
+ if (token.Value === "bool") {
+ return ["true", "false"];
+ } else if (token.Value === "void") {
+ return [];
+ }
+ }
+ }
+
+ for (let i = 0; i < treeToModify.nodes.length; i++) {
+ const node = treeToModify.nodes[i];
+ if (node instanceof ParseTree) {
+ break;
+ }
+
+ if (node instanceof Token
+ && node.Type === TokenType.Symbol
+ && this.keywords.find((k) => k === node.Value) === undefined
+ ) {
+ symbolsFound++;
+ }
+
+ if (symbolsFound === 2) {
+ treeToModify.nodes.splice(i, 1);
+ break;
+ }
+ }
+
+ return [tree.ToString()];
+ }
+
+ 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(">")).trim() + ",";
+
+ 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).trim());
+ } 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/CodeParser/CParser/CppParser.ts b/src/CodeParser/CParser/CppParser.ts
new file mode 100644
index 0000000..b3279c6
--- /dev/null
+++ b/src/CodeParser/CParser/CppParser.ts
@@ -0,0 +1,16 @@
+import { Position, TextDocumentContentChangeEvent, TextEditor, TextLine } from "vscode";
+import Generator from "../../DocGen/CGen";
+import { IDocGen } from "../../DocGen/DocGen";
+import CParser from "./CParser";
+
+/**
+ *
+ * Parses C++ code for methods and signatures
+ *
+ * @export
+ * @class CppParser
+ * @implements {ICodeParser}
+ */
+export default class CppParser extends CParser {
+ // For now C++ is the same as C
+}
diff --git a/src/CodeParser/CParser/ParseTree.ts b/src/CodeParser/CParser/ParseTree.ts
new file mode 100644
index 0000000..9090212
--- /dev/null
+++ b/src/CodeParser/CParser/ParseTree.ts
@@ -0,0 +1,220 @@
+import { Token, TokenType } from "./Token";
+
+export class ParseTree {
+
+ /**
+ * Create a tree from tokens. This consumes the tokens.
+ * @param tokens The tokens to create a tree for.
+ * @param inNested If currently allready nesting.
+ */
+ public static CreateTree(tokens: Token[], inNested: boolean = false): ParseTree {
+ const tree: ParseTree = new ParseTree();
+
+ while (tokens.length > 0) {
+ const token: Token = tokens.shift();
+ switch (token.Type) {
+ case TokenType.OpenParenthesis:
+ tree.nodes.push(this.CreateTree(tokens, true));
+ break;
+ case TokenType.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<Token | ParseTree> = [];
+
+ /**
+ * Compact empty branches. Example ((foo))(((bar))) will become (foo)(bar)
+ * @param tree The ParseTree to compact. Defaults to the current tree.
+ */
+ public Compact(tree: ParseTree = this): ParseTree {
+ const newTree: ParseTree = new ParseTree();
+ newTree.nodes = tree.nodes.map((n) => n);
+ const isNotCompact = (n) => n instanceof ParseTree && n.nodes.length === 1 && n.nodes[0] instanceof ParseTree;
+
+ // Compact current level of nodes to the maximum amount.
+ while (newTree.nodes.some((n) => isNotCompact(n))) {
+ newTree.nodes = newTree.nodes
+ .map((n) => n instanceof ParseTree && isNotCompact(n) ? n.nodes[0] : n);
+ }
+
+ // Compact all nested parsetrees and filter out empty branches.
+ newTree.nodes = newTree.nodes
+ .map((n) => n instanceof ParseTree ? this.Compact(n) : n);
+
+ return newTree;
+ }
+
+ /**
+ * Create string from the parsetree which is a representation of the original code.
+ */
+ public ToString(tree: ParseTree = this): string {
+ let code: string = "";
+
+ for (const node of tree.nodes) {
+ if (node instanceof ParseTree) {
+ code += "(" + this.ToString(node) + ")";
+ continue;
+ }
+
+ switch (node.Type) {
+ case TokenType.Symbol:
+ code += code === "" ? node.Value : " " + node.Value;
+ break;
+ case TokenType.Pointer:
+ code += node.Value;
+ break;
+ case TokenType.Reference:
+ code += node.Value;
+ break;
+ case TokenType.ArraySubscript:
+ code += node.Value;
+ break;
+ case TokenType.CurlyBlock:
+ code += node.Value;
+ break;
+ case TokenType.Assignment:
+ code += " " + node.Value + " ";
+ break;
+ case TokenType.Comma:
+ code += node.Value;
+ break;
+ case TokenType.Arrow:
+ code += " " + node.Value + " ";
+ break;
+ case TokenType.Ellipsis:
+ code += node.Value;
+ break;
+ case TokenType.Attribute:
+ code += code === "" ? node.Value : " " + node.Value;
+ break;
+ }
+ }
+
+ return code;
+ }
+
+ /**
+ * Get the tree for the return.
+ */
+ public GetReturnTree(): ParseTree {
+ const returnTree: ParseTree = new ParseTree();
+
+ const indexTrailingReturn: number = this.nodes
+ .findIndex((t) => t instanceof Token ? t.Type === TokenType.Arrow : false);
+
+ const isFuncPtr: boolean = this.nodes
+ .slice(0, indexTrailingReturn === -1 ? this.nodes.length : indexTrailingReturn)
+ .filter((n) => n instanceof ParseTree)
+ .length === 2;
+
+ if (isFuncPtr === true) {
+ const trees: ParseTree[] = this.nodes
+ .filter((n) => n instanceof ParseTree)
+ .map((n) => n as ParseTree);
+
+ // Add left most part to the return tokens.
+ for (const node of this.nodes) {
+ if (node instanceof ParseTree) {
+ break;
+ }
+ returnTree.nodes.push(node);
+ }
+
+ // add left and right parse tree to return tokens
+ const leftTree: ParseTree = new ParseTree();
+ for (const node of trees[0].nodes) {
+ if (node instanceof ParseTree) {
+ break;
+ }
+ leftTree.nodes.push(node);
+ }
+
+ returnTree.nodes.push(leftTree);
+ returnTree.nodes.push(trees[1]);
+ } else if (indexTrailingReturn !== -1) {
+ returnTree.nodes = this.nodes
+ .slice(indexTrailingReturn + 1, this.nodes.length);
+
+ // Don't include the auto so start from index 1.
+ for (let i: number = 1; i < this.nodes.length; i++) {
+ if (this.nodes[i] instanceof ParseTree) {
+ break;
+ }
+ returnTree.nodes.push(this.nodes[i]);
+ }
+ } else {
+ for (const node of this.nodes) {
+ if (node instanceof ParseTree) {
+ break;
+ }
+ returnTree.nodes.push(node);
+ }
+ }
+
+ return returnTree;
+ }
+
+ /**
+ * Get the arguments of the function and create a new ParseTree for each one.
+ */
+ public GetArgTrees(): ParseTree[] {
+ const args: ParseTree[] = [];
+
+ const indexTrailingReturn: number = this.nodes
+ .findIndex((t) => t instanceof Token ? t.Type === TokenType.Arrow : false);
+
+ const isFuncPtr: boolean = this.nodes
+ .slice(0, indexTrailingReturn === -1 ? this.nodes.length : indexTrailingReturn)
+ .filter((n) => n instanceof ParseTree)
+ .length === 2;
+
+ let argsTree: ParseTree = this.nodes
+ .filter((n) => n instanceof ParseTree)
+ .map((t) => t as ParseTree)[0];
+
+ // If it is a func ptr get the nested tree.
+ if (isFuncPtr === true) {
+ argsTree = argsTree.nodes
+ .filter((n) => n instanceof ParseTree)
+ .map((t) => t as ParseTree)[0];
+ }
+
+ if (argsTree === undefined) {
+ throw new Error("Couldn't find arguments tree");
+ }
+
+ // split args at command and create a tree for each one.
+ let lastComma: number = 0;
+ for (let i = 0; i < argsTree.nodes.length; i++) {
+ const node = argsTree.nodes[i];
+ if (node instanceof Token && node.Type === TokenType.Comma) {
+ const tree: ParseTree = new ParseTree();
+ tree.nodes = argsTree.nodes.slice(lastComma, i);
+ args.push(tree);
+ lastComma = i + 1;
+ }
+ }
+
+ const lastArgTree: ParseTree = new ParseTree();
+ lastArgTree.nodes = argsTree.nodes.slice(lastComma, argsTree.nodes.length);
+ if (lastArgTree.nodes.length > 0) {
+ args.push(lastArgTree);
+ }
+
+ return args;
+ }
+}
diff --git a/src/CodeParser/CParser/Token.ts b/src/CodeParser/CParser/Token.ts
new file mode 100644
index 0000000..379fd46
--- /dev/null
+++ b/src/CodeParser/CParser/Token.ts
@@ -0,0 +1,24 @@
+export enum TokenType {
+ Symbol,
+ Pointer,
+ Reference,
+ ArraySubscript,
+ OpenParenthesis,
+ CloseParenthesis,
+ CurlyBlock,
+ Assignment,
+ Comma,
+ Arrow,
+ Ellipsis,
+ Attribute,
+}
+
+export class Token {
+ public Type: TokenType;
+ public Value: string;
+
+ constructor(type: TokenType, value: string) {
+ this.Type = type;
+ this.Value = value;
+ }
+}