diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index f142278..3beb3d0 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -936,7 +936,17 @@ def _params_helper1(self, stack): elif a.startswith("__attribute__((__const__))"): stack = stack[6:] - stack = stack[stack.index("(") + 1 :] + last_paren_index = len(stack) - stack[-1::-1].index(")") - 1 + open_paren_count = 1 + method_paren_start_idx = last_paren_index-1 + while open_paren_count > 0: + if stack[method_paren_start_idx] == ")": + open_paren_count+=1 + elif stack[method_paren_start_idx] == "(": + open_paren_count-=1 + method_paren_start_idx -= 1 + + stack = stack[method_paren_start_idx + 2 :] if not stack: return [] if ( @@ -998,33 +1008,6 @@ def __init__(self, nameStack, curClass, methinfo, curTemplate, doxygen, location if doxygen: self["doxygen"] = doxygen - # Remove leading keywords - for i, word in enumerate(nameStack): - if word not in Resolver.C_KEYWORDS: - nameStack = nameStack[i:] - break - - if "operator" in nameStack: - rtnType = " ".join(nameStack[: nameStack.index("operator")]) - self["name"] = "".join( - nameStack[nameStack.index("operator") : nameStack.index("(")] - ) - else: - rtnType = " ".join(nameStack[: nameStack.index("(") - 1]) - self["name"] = " ".join( - nameStack[nameStack.index("(") - 1 : nameStack.index("(")] - ) - - if len(rtnType) == 0 or self["name"] == curClass: - rtnType = "void" - - self["rtnType"] = ( - rtnType.replace(" :: ", "::") - .replace(" < ", "<") - .replace(" > ", "> ") - .replace(">>", "> >") - .replace(" ,", ",") - ) # deal with "noexcept" specifier/operator self["noexcept"] = None @@ -1064,6 +1047,8 @@ def __init__(self, nameStack, curClass, methinfo, curTemplate, doxygen, location break self.update(methinfo) + if len(self["rtnType"]) == 0 or self["name"] == curClass: + self["rtnType"] = "void" set_location_info(self, location) paramsStack = self._params_helper1(nameStack) @@ -1304,6 +1289,7 @@ def __init__(self, nameStack, doxygen, location, **kwargs): def _filter_name(self, name): name = name.replace(" :", ":").replace(": ", ":") name = name.replace(" < ", "<") + name = name.replace(" ( ", "(").replace(" ) ", ")") name = name.replace(" > ", "> ").replace(">>", "> >") name = name.replace(") >", ")>") name = name.replace(" {", "{").replace(" }", "}") @@ -2168,7 +2154,7 @@ def finalize(self): } def parse_method_type(self, stack): - trace_print("meth type info", stack) + debug_print("meth type info %s", stack) info = { "debug": " ".join(stack) .replace(" :: ", "::") @@ -2183,13 +2169,57 @@ def parse_method_type(self, stack): info.update(self._method_type_defaults) - header = stack[: stack.index("(")] + last_paren_index = len(stack) - stack[-1::-1].index(")") - 1 + open_paren_count = 1 + method_paren_start_idx = last_paren_index-1 + while open_paren_count > 0: + if stack[method_paren_start_idx] == ")": + open_paren_count+=1 + elif stack[method_paren_start_idx] == "(": + open_paren_count-=1 + method_paren_start_idx -= 1 + + header = stack[: method_paren_start_idx+1] header = " ".join(header) + # Replace fields that would mess up our re-split below header = header.replace(" :: ", "::") header = header.replace(" < ", "<") header = header.replace(" > ", "> ") + header = header.replace("> >", ">>") header = header.replace("default ", "default") header = header.strip() + # Remove leading keywords, splitting on spaces to avoid removing keywords embedded in other words + + # Re-split to find method declarations like A::B::meth() that were formed by joining separate tokens + header = header.split() + name = header.pop() + for word in Resolver.C_KEYWORDS.union(set(ignoreSymbols)): + if word in header: + info[word] = True + header.remove(word) + header = " ".join(header) + # Now replace fields for aesthetics + header = header.replace(" (", "(") + header = header.replace("( ", "(") + header = header.replace(" )", ")") + header = header.replace(") ", ")") + header = header.replace(" ,", ",") + if "operator" in stack: + info["rtnType"] = " ".join(stack[: stack.index("operator")]) + op = "".join( + stack[stack.index("operator")+1 : method_paren_start_idx+1] + ) + if not op: + if " ".join(["operator", "(", ")", "("]) in " ".join(stack): + op = "()" + else: + debug_print("Error parsing operator") + return None + name = "operator"+op + info["operator"] = op + else: + info["rtnType"] = header + info["returns"] = info["rtnType"] if stack[-1] == "{": info["defined"] = True @@ -2210,32 +2240,9 @@ def parse_method_type(self, stack): elif stack[-2] == "delete": info["deleted"] = True - r = header.split() - name = None - if "operator" in stack: # rare case op overload defined outside of class - op = stack[stack.index("operator") + 1 : stack.index("(")] - op = "".join(op) - if not op: - if " ".join(["operator", "(", ")", "("]) in " ".join(stack): - op = "()" - else: - trace_print("Error parsing operator") - return None - - info["operator"] = op - name = "operator" + op - a = stack[: stack.index("operator")] - - elif r: - name = r[-1] - a = r[:-1] # strip name - if name is None: - return None # if name.startswith('~'): name = name[1:] - while a and a[0] == "}": # strip - can have multiple } } - a = a[1:] if "::" in name: # klass,name = name.split('::') # methods can be defined outside of class @@ -2254,7 +2261,7 @@ def parse_method_type(self, stack): info["defined"] = True info["default"] = True name = name[1:] - elif not a or (name == self.curClass and len(self.curClass)): + elif (name == self.curClass and len(self.curClass)): info["constructor"] = True if "default;" in stack: info["defined"] = True @@ -2262,27 +2269,6 @@ def parse_method_type(self, stack): info["name"] = name - for tag in self.C_KEYWORDS: - if tag in a: - info[tag] = True - a.remove(tag) # inplace - if "template" in a: - a.remove("template") - b = " ".join(a) - if ">" in b: - info["template"] = b[: b.index(">") + 1] - info["returns"] = b[ - b.index(">") + 1 : - ] # find return type, could be incorrect... TODO - if "' - if typname not in self._template_typenames: - self._template_typenames.append(typname) - else: - info["returns"] = " ".join(a) - else: - info["returns"] = " ".join(a) info["returns"] = info["returns"].replace(" <", "<").strip() ## be careful with templates, do not count pointers inside template diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index c454a32..cfc35c7 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -1535,7 +1535,7 @@ def test_name_0(self): def test_type_0(self): self.assertEqual( self.cppHeader.classes["DriverFuncs"]["properties"]["public"][0]["type"], - "void * ( * ) ( )", + "void *(* )()", ) def test_function_pointer_field_0(self): @@ -1555,7 +1555,7 @@ def test_name_1(self): def test_type_1(self): self.assertEqual( self.cppHeader.classes["DriverFuncs"]["properties"]["public"][1]["type"], - "void ( * ) ( void * buf, int buflen )", + "void(* )(void * buf, int buflen )", ) def test_function_pointer_field_1(self): @@ -1631,7 +1631,7 @@ def setUp(self): def test_rtn_type(self): self.assertEqual( self.cppHeader.classes["AlmondClass"]["methods"]["public"][0]["rtnType"], - "std::map > >", + "std::map>>", ) def test_param_1_name(self): @@ -2538,7 +2538,7 @@ def test_set_callback(self): ) self.assertEqual( self.cppHeader.functions[8]["parameters"][1]["type"], - "long ( * ) ( struct test_st *, int, const char *, int long, long, long )", + "long(* )(struct test_st *, int, const char *, int long, long, long )", ) @@ -2851,8 +2851,8 @@ def test_using(self): "desc": None, "name": "", "namespace": "std::", - "raw_type": "std::function", - "type": "function", + "raw_type": "std::function", + "type": "function", "typealias": "VoidFunction", "using_type": "typealias", } @@ -2909,11 +2909,11 @@ def test_fn(self): "raw_type": "std::string", }, { - "type": "function", + "type": "function", "name": "fn", "desc": None, "namespace": "std::", - "raw_type": "std::function", + "raw_type": "std::function", }, { "type": "thing", @@ -2946,11 +2946,11 @@ def test_class(self): "raw_type": "std::string", }, { - "type": "function", + "type": "function", "name": "fn", "desc": None, "namespace": "std::", - "raw_type": "std::function", + "raw_type": "std::function", }, { "type": "thing", @@ -4063,5 +4063,73 @@ def test_fn(self): self.assertEqual(c.typedefs["mmmmp"], "typedef int ( * ) ( int , int )") +class DecltypeMethodReturnClass_meth1(unittest.TestCase): + def setUp(self): + self.cppHeader = CppHeaderParser.CppHeader( + """ +#include +#include +using namespace std; + +class DecltypeMethodReturnClass +{ +public: + const int returnInt(); + + decltype(returnInt()) meth1(decltype(returnInt()) arg1); + + const std::optional meth2(const decltype(returnInt()) v1); + + template + decltype(T::Q) meth3(int v1); + +}; +""", + "string", + ) + + def test_name(self): + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][1]["name"], + "meth1", + ) + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][2]["name"], + "meth2", + ) + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][3]["name"], + "meth3", + ) + + def test_rtntype(self): + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][1]["rtnType"], + "decltype(returnInt())", + ) + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][2]["rtnType"], + "const std::optional", + ) + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][3]["rtnType"], + "decltype(T::Q)", + ) + + def test_parameters(self): + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][1]["parameters"][0]['type'], + "decltype(returnInt() )", + ) + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][2]["parameters"][0]['type'], + "const decltype(returnInt() )", + ) + self.assertEqual( + self.cppHeader.classes["DecltypeMethodReturnClass"]["methods"]["public"][3]["parameters"][0]['type'], + "int", + ) + + if __name__ == "__main__": unittest.main()