From da346a5e566feb3af0dd9f254d0144bec317956d Mon Sep 17 00:00:00 2001 From: David Caron Date: Wed, 4 Aug 2021 15:13:55 -0400 Subject: [PATCH 01/24] add access property to 'using' CppVariable --- CppHeaderParser/CppHeaderParser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index 9796cea..f142278 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -1204,6 +1204,7 @@ class CppVariable(_CppVariable): * ``extern`` - True if its an extern, False if not * ``parent`` - If not None, either the class this is a property of, or the method this variable is a parameter in + * ``access`` - Anything in supportedAccessSpecifier """ Vars = [] @@ -1320,6 +1321,7 @@ def __str__(self): "desc", "line_number", "extern", + "access", ] cpy = dict((k, v) for (k, v) in list(self.items()) if k in keys_white_list) if "array_size" in self: @@ -3382,6 +3384,7 @@ def _evaluate_stack(self, token=None): else: atype["raw_type"] = ns + atype["type"] + atype["access"] = self.curAccessSpecifier if self.curClass: klass = self.classes[self.curClass] klass["using"][alias] = atype From 2dc5df5c8fd2234f47d462de6bd1e8e5034febca Mon Sep 17 00:00:00 2001 From: David Caron Date: Thu, 5 Aug 2021 10:00:23 -0400 Subject: [PATCH 02/24] add test for access property on 'using' --- test/test_CppHeaderParser.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index 514cc48..c454a32 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -4015,6 +4015,7 @@ def setUp(self): self.cppHeader = CppHeaderParser.CppHeader( """ template class P { +using A = typename f::TP::A; public: using State = typename f::TP::S; P(State st); @@ -4026,9 +4027,15 @@ def setUp(self): def test_fn(self): c = self.cppHeader.classes["P"] self.assertEqual("P", c["name"]) + self.assertEqual(len(c["using"]), 2) state = c["using"]["State"] self.assertEqual(state["raw_type"], "typename f::TP::S") self.assertEqual(state["type"], "typename TP::S") + self.assertEqual(state["access"], "public") + private = c["using"]["A"] + self.assertEqual(private["raw_type"], "typename f::TP::A") + self.assertEqual(private["type"], "typename TP::A") + self.assertEqual(private["access"], "private") m = c["methods"]["public"][0] self.assertEqual(m["name"], "P") From e97f7c91280c69226e06cda72729779054386d47 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sat, 8 Jan 2022 03:25:25 -0500 Subject: [PATCH 03/24] Update black format --- CppHeaderParser/CppHeaderParser.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index f142278..159cdb3 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -111,7 +111,6 @@ def debug_print(fmt, *args): args = (inspect.currentframe().f_back.f_lineno,) + args print(fmt % args) - else: debug_caller_lineno = None @@ -128,7 +127,6 @@ def trace_print(*args): sys.stdout.write(" %s" % a) sys.stdout.write("\n") - else: def trace_print(*args): From 0cb3e932961afd1cc1148d2a49c3231d344a0701 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sat, 8 Jan 2022 03:25:48 -0500 Subject: [PATCH 04/24] Detect constexpr functions --- CppHeaderParser/CppHeaderParser.py | 2 +- test/test_CppHeaderParser.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index 159cdb3..0d6b76c 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -1468,7 +1468,7 @@ class Resolver(object): C_MODIFIERS = "* & const constexpr static mutable".split() C_MODIFIERS = set(C_MODIFIERS) - C_KEYWORDS = "extern virtual static explicit inline friend".split() + C_KEYWORDS = "extern virtual static explicit inline friend constexpr".split() C_KEYWORDS = set(C_KEYWORDS) SubTypedefs = {} # TODO deprecate? diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index c454a32..374658c 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -2999,6 +2999,21 @@ def test_fn(self): self.assertEqual(p["defaultValue"], "0.02") +class ConstExprFn_TestCase(unittest.TestCase): + def setUp(self): + self.cppHeader = CppHeaderParser.CppHeader( + """ +constexpr int overloaded_constexpr(int a, int b, int c) { return a + b + c; } +""", + "string", + ) + + def test_fn(self): + m = self.cppHeader.functions[0] + self.assertEqual(m["constexpr"], True) + self.assertEqual(m["rtnType"], "int") + + class DefaultEnum_TestCase(unittest.TestCase): def setUp(self): self.cppHeader = CppHeaderParser.CppHeader( From 763456603410470eea7933e49fb9fb2d3164950c Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 27 Sep 2022 23:37:12 -0400 Subject: [PATCH 05/24] Support extern template declarations --- CppHeaderParser/CppHeaderParser.py | 38 ++++++++++++++++++++++++++++-- test/test_CppHeaderParser.py | 32 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index 0d6b76c..eb5c5ac 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -1425,6 +1425,10 @@ def __str__(self): return self["value"] +class CppExternTemplate(dict): + pass + + # Implementation is shared between CppPragma, CppDefine, CppInclude but they are # distinct so that they may diverge if required without interface-breaking # changes @@ -2747,6 +2751,8 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): #: List of enums in this header as :class:`.CppEnum` self.enums = [] + self.extern_templates = [] + #: List of variables in this header as :class:`.CppVariable` self.variables = [] self.global_enums = {} @@ -2879,7 +2885,7 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): break tok.value = TagStr(tok.value, location=tok.location) - # debug_print("TOK: %s", tok) + debug_print("TOK: %s", tok) if tok.type == "NAME": if tok.value in self.IGNORE_NAMES: continue @@ -3459,6 +3465,21 @@ def _evaluate_stack(self, token=None): self.curTemplate = None def _parse_template(self): + # check for 'extern template' + extern_template = False + if len(self.stmtTokens) == 1 and self.stmtTokens[0].value == "extern": + extern_template = True + tok = self._next_token_must_be("NAME") + if not tok.value == "class": + raise self._parse_error((tok,), "class") + + tok = self._next_token_must_be("NAME") + if tok.value == "__attribute__": + self._parse_gcc_attribute() + tok = self._next_token_must_be("NAME") + + extern_template_name = tok.value + tok = self._next_token_must_be("<") consumed = self._consume_balanced_tokens(tok) tmpl = " ".join(tok.value for tok in consumed) @@ -3471,7 +3492,20 @@ def _parse_template(self): .replace(" , ", ", ") .replace(" = ", "=") ) - self.curTemplate = "template" + tmpl + + if extern_template: + self.extern_templates.append( + CppExternTemplate( + name=extern_template_name, + params=tmpl, + namespace=self.cur_namespace(), + ) + ) + self.stack = [] + self.nameStack = [] + self.stmtTokens = [] + else: + self.curTemplate = "template" + tmpl def _parse_gcc_attribute(self): tok1 = self._next_token_must_be("(") diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index 374658c..f5c5d10 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -4078,5 +4078,37 @@ def test_fn(self): self.assertEqual(c.typedefs["mmmmp"], "typedef int ( * ) ( int , int )") +class ExternTemplateTest(unittest.TestCase): + def setUp(self): + self.cppHeader = CppHeaderParser.CppHeader( + """ +extern template class MyClass<1,2>; +extern template class __attribute__(("something")) MyClass<3,4>; + +namespace foo { +extern template class MyClass<5,6>; +}; + +""", + "string", + ) + + def test_fn(self): + c = self.cppHeader + et0, et1, et2 = c.extern_templates + + self.assertEqual(et0["name"], "MyClass") + self.assertEqual(et0["namespace"], "") + self.assertEqual(et0["params"], "<1, 2>") + + self.assertEqual(et1["name"], "MyClass") + self.assertEqual(et1["namespace"], "") + self.assertEqual(et1["params"], "<3, 4>") + + self.assertEqual(et2["name"], "MyClass") + self.assertEqual(et2["namespace"], "foo") + self.assertEqual(et2["params"], "<5, 6>") + + if __name__ == "__main__": unittest.main() From cc0d4a0610a73488ec301bda9332415c37a4e3e9 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 27 Sep 2022 23:57:37 -0400 Subject: [PATCH 06/24] Correctly detect templates for 'using' statements --- CppHeaderParser/CppHeaderParser.py | 18 ++++++++++++++---- test/test_CppHeaderParser.py | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index eb5c5ac..87dae7d 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -1207,7 +1207,7 @@ class CppVariable(_CppVariable): Vars = [] - def __init__(self, nameStack, doxygen, location, **kwargs): + def __init__(self, nameStack, doxygen, location, is_var=True, **kwargs): debug_print("var trace %s", nameStack) if len(nameStack) and nameStack[0] == "extern": self["extern"] = True @@ -1297,7 +1297,8 @@ def __init__(self, nameStack, doxygen, location, **kwargs): pass self.init() - CppVariable.Vars.append(self) # save and resolve later + if is_var: + CppVariable.Vars.append(self) # save and resolve later def _filter_name(self, name): name = name.replace(" :", ":").replace(": ", ":") @@ -3359,7 +3360,10 @@ def _evaluate_stack(self, token=None): alias = self.nameStack[1] ns, stack = _split_namespace(self.nameStack[3:]) atype = CppVariable( - stack, self._get_stmt_doxygen(), self._get_location(stack) + stack, + self._get_stmt_doxygen(), + self._get_location(stack), + is_var=False, ) # namespace refers to the embedded type @@ -3374,7 +3378,10 @@ def _evaluate_stack(self, token=None): # from a base class ns, stack = _split_namespace(self.nameStack[1:]) atype = CppVariable( - stack, self._get_stmt_doxygen(), self._get_location(stack) + stack, + self._get_stmt_doxygen(), + self._get_location(stack), + is_var=False, ) alias = atype["type"] atype["using_type"] = "declaration" @@ -3383,6 +3390,9 @@ def _evaluate_stack(self, token=None): else: atype["namespace"] = ns + atype["template"] = self.curTemplate + self.curTemplate = None + if atype["type"].startswith("typename "): atype["raw_type"] = "typename " + ns + atype["type"][9:] else: diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index f5c5d10..8337858 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -4110,5 +4110,30 @@ def test_fn(self): self.assertEqual(et2["params"], "<5, 6>") +class UsingTemplateTest(unittest.TestCase): + def setUp(self): + self.cppHeader = CppHeaderParser.CppHeader( + """ +class X { + template + using TT = U; +}; + +""", + "string", + ) + + def test_fn(self): + u = self.cppHeader.classes["X"]["using"] + self.assertEqual(len(u), 1) + tt = u["TT"] + + self.assertEqual(tt["access"], "private") + self.assertEqual(tt["raw_type"], "U") + self.assertEqual(tt["type"], "U") + self.assertEqual(tt["typealias"], "TT") + self.assertEqual(tt["template"], "template") + + if __name__ == "__main__": unittest.main() From f98128ce340fb4419800d01751e191ad26fa5177 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 27 Sep 2022 23:59:04 -0400 Subject: [PATCH 07/24] Update black formatting --- docs/conf.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 696192f..cc167b2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,8 +31,8 @@ master_doc = "index" # General information about the project. -project = u"robotpy-cppheaderparser" -copyright = u"2019 RobotPy Development Team" +project = "robotpy-cppheaderparser" +copyright = "2019 RobotPy Development Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -200,8 +200,8 @@ ( "index", "sphinx.tex", - u"robotpy-cppheaderparser Documentation", - u"Author", + "robotpy-cppheaderparser Documentation", + "Author", "manual", ) ] @@ -232,7 +232,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ("index", "sphinx", u"robotpy-cppheaderparser Documentation", [u"Author"], 1) + ("index", "sphinx", "robotpy-cppheaderparser Documentation", ["Author"], 1) ] # If true, show URL addresses after external links. @@ -248,8 +248,8 @@ ( "index", "sphinx", - u"robotpy-cppheaderparser Documentation", - u"Author", + "robotpy-cppheaderparser Documentation", + "Author", "sphinx", "One line description of project.", "Miscellaneous", @@ -272,10 +272,10 @@ # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. -epub_title = u"robotpy-cppheaderparser" -epub_author = u"RobotPy Development Team" -epub_publisher = u"RobotPy Development Team" -epub_copyright = u"2019 RobotPy Development Team" +epub_title = "robotpy-cppheaderparser" +epub_author = "RobotPy Development Team" +epub_publisher = "RobotPy Development Team" +epub_copyright = "2019 RobotPy Development Team" # The basename for the epub file. It defaults to the project name. # epub_basename = u'..' From 7dfe47347c63a99bfc3d9b99b7c29391b4037afe Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 20 Oct 2022 01:56:52 -0400 Subject: [PATCH 08/24] Doxygen comments were being discarded when an attribute was encountered --- CppHeaderParser/CppHeaderParser.py | 7 +++++-- test/test_CppHeaderParser.py | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index 87dae7d..a13ad2e 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -2895,9 +2895,11 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): self._parse_template() continue elif tok.value == "alignas": + self._doxygen_cache = self.lex.get_doxygen() self._parse_attribute_specifier_seq(tok) continue elif tok.value == "__attribute__": + self._doxygen_cache = self.lex.get_doxygen() self._parse_gcc_attribute() continue elif not self.stack and tok.value == "static_assert": @@ -2906,6 +2908,7 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): continue elif tok.type == "DBL_LBRACKET": + self._doxygen_cache = self.lex.get_doxygen() self._parse_attribute_specifier_seq(tok) continue @@ -3076,7 +3079,7 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): newNsLen = len(self.nameStack) if nslen != newNsLen and newNsLen == 1: - if not self.curTemplate: + if not self._doxygen_cache: self._doxygen_cache = self.lex.get_doxygen() except Exception as e: @@ -3471,7 +3474,7 @@ def _evaluate_stack(self, token=None): # its a little confusing to have some if/else above return and others not, and then clearning the nameStack down here self.nameStack = [] - self.lex.doxygenCommentCache = "" + self._doxygen_cache = None self.curTemplate = None def _parse_template(self): diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index 8337858..46eaaeb 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -3414,6 +3414,10 @@ def setUp(self): class C2 {}; +/// hasattr comment +[[nodiscard]] +int hasattr(); + """, "string", ) @@ -3443,6 +3447,11 @@ def test_cls2(self): self.assertEqual("C2", c["name"]) self.assertEqual("/// template comment", c["doxygen"]) + def test_hasattr(self): + fn = self.cppHeader.functions[1] + self.assertEqual("hasattr", fn["name"]) + self.assertEqual("/// hasattr comment", fn["doxygen"]) + class EnumParameter_TestCase(unittest.TestCase): def setUp(self): From d20f739fe8e9570b14ac7dbc9996d64478cd1c83 Mon Sep 17 00:00:00 2001 From: Stephen Hansen Date: Wed, 26 Oct 2022 21:42:31 -0400 Subject: [PATCH 09/24] Fix enum parsing bug with decimals as char values --- CppHeaderParser/CppHeaderParser.py | 3 ++- test/TestSampleClass.h | 1 + test/test_CppHeaderParser.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index a13ad2e..a07ad6e 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -1350,7 +1350,8 @@ def resolve_enum_values(self, values): # Remove single quotes from single quoted chars (unless part of some expression if len(a) == 3 and a[0] == "'" and a[2] == "'": a = v["value"] = a[1] - if a.lower().startswith("0x"): + a = i = ord(a) + elif a.lower().startswith("0x"): try: i = a = int(a, 16) except: diff --git a/test/TestSampleClass.h b/test/TestSampleClass.h index 439635c..aebae3c 100644 --- a/test/TestSampleClass.h +++ b/test/TestSampleClass.h @@ -73,6 +73,7 @@ namespace Alpha Z_B = 0x2B, Z_C = 'j', Z_D, + Z_E = '9', } Zebra; }; diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index 46eaaeb..ac93501 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -418,6 +418,7 @@ def test_values(self): {"name": "Z_B", "raw_value": "0x2B", "value": 43}, {"name": "Z_C", "raw_value": "j", "value": 106}, {"name": "Z_D", "value": 107}, + {"name": "Z_E", "raw_value": "9", "value": 57}, ], ) From fd528bb8a4f77e20c8813f6d6e284b73e7995f98 Mon Sep 17 00:00:00 2001 From: Stephen Hansen Date: Wed, 26 Oct 2022 21:53:00 -0400 Subject: [PATCH 10/24] adding an integer to the enum test --- test/TestSampleClass.h | 1 + test/test_CppHeaderParser.py | 1 + 2 files changed, 2 insertions(+) diff --git a/test/TestSampleClass.h b/test/TestSampleClass.h index aebae3c..45efef7 100644 --- a/test/TestSampleClass.h +++ b/test/TestSampleClass.h @@ -74,6 +74,7 @@ namespace Alpha Z_C = 'j', Z_D, Z_E = '9', + Z_F = 9, } Zebra; }; diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index ac93501..d91edca 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -419,6 +419,7 @@ def test_values(self): {"name": "Z_C", "raw_value": "j", "value": 106}, {"name": "Z_D", "value": 107}, {"name": "Z_E", "raw_value": "9", "value": 57}, + {"name": "Z_F", "value": 9}, ], ) From 953c40854e7f84b51fdea30d7fd54a30bf36a531 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 3 Nov 2022 22:22:17 -0400 Subject: [PATCH 11/24] Add support for parsing ref-qualifiers --- CppHeaderParser/CppHeaderParser.py | 20 ++++++++++++---- test/test_CppHeaderParser.py | 37 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index a07ad6e..b8f744b 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -2208,11 +2208,21 @@ def parse_method_type(self, stack): else: assert 0 - if len(stack) > 3 and stack[-1] == ";" and stack[-3] == "=": - if stack[-2] == "0": - info["pure_virtual"] = True - elif stack[-2] == "delete": - info["deleted"] = True + refqual = "" + if len(stack) > 3: + if stack[-1] == ";" and stack[-3] == "=": + if stack[-2] == "0": + info["pure_virtual"] = True + elif stack[-2] == "delete": + info["deleted"] = True + + for e in reversed(stack): + if e == ")": + break + elif e == "&": + refqual += "&" + + info["ref_qualifiers"] = refqual r = header.split() name = None diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index d91edca..e3472f5 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -4146,5 +4146,42 @@ def test_fn(self): self.assertEqual(tt["template"], "template") +class RefQualifierTest(unittest.TestCase): + def setUp(self): + self.cppHeader = CppHeaderParser.CppHeader( + """ +struct X { + void fn0(); + void fn1() &; + void fn2() &&; + void fn3() && = 0; +}; + +""", + "string", + ) + + def test_fn0(self): + fn = self.cppHeader.classes["X"]["methods"]["public"][0] + self.assertEqual(fn["name"], "fn0") + self.assertEqual(fn["ref_qualifiers"], "") + + def test_fn1(self): + fn = self.cppHeader.classes["X"]["methods"]["public"][1] + self.assertEqual(fn["name"], "fn1") + self.assertEqual(fn["ref_qualifiers"], "&") + + def test_fn1(self): + fn = self.cppHeader.classes["X"]["methods"]["public"][2] + self.assertEqual(fn["name"], "fn2") + self.assertEqual(fn["ref_qualifiers"], "&&") + + def test_fn3(self): + fn = self.cppHeader.classes["X"]["methods"]["public"][3] + self.assertEqual(fn["name"], "fn3") + self.assertEqual(fn["ref_qualifiers"], "&&") + self.assertEqual(fn["pure_virtual"], True) + + if __name__ == "__main__": unittest.main() From 4d307a886ed1efd9e26d768a4bb3491d4fde9443 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sun, 4 Dec 2022 10:16:52 -0500 Subject: [PATCH 12/24] Add option to remove preprocessor macro detection --- CppHeaderParser/CppHeaderParser.py | 42 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index b8f744b..5897f6b 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -2702,7 +2702,14 @@ def show(self): for className in list(self.classes.keys()): self.classes[className].show() - def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): + def __init__( + self, + headerFileName, + argType="file", + encoding=None, + preprocessed=False, + **kwargs + ): """Create the parsed C++ header file parse tree headerFileName - Name of the file to parse OR actual file contents (depends on argType) @@ -2802,21 +2809,24 @@ def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): "[ ]+", " ", supportedAccessSpecifier[i] ).strip() - # Change multi line #defines and expressions to single lines maintaining line nubmers - # Based from http://stackoverflow.com/questions/2424458/regular-expression-to-match-cs-multiline-preprocessor-statements - matches = re.findall(r"(?m)^(?:.*\\\r?\n)+.*$", headerFileStr) - is_define = re.compile(r"[ \t\v]*#[Dd][Ee][Ff][Ii][Nn][Ee]") - for m in matches: - # Keep the newlines so that linecount doesnt break - num_newlines = len([a for a in m if a == "\n"]) - if is_define.match(m): - new_m = m.replace("\n", "\\n") - else: - # Just expression taking up multiple lines, make it take 1 line for easier parsing - new_m = m.replace("\\\n", " ") - if num_newlines > 0: - new_m += "\n" * (num_newlines) - headerFileStr = headerFileStr.replace(m, new_m) + if not preprocessed: + # Change multi line #defines and expressions to single lines maintaining line nubmers + # Based from http://stackoverflow.com/questions/2424458/regular-expression-to-match-cs-multiline-preprocessor-statements + matches = re.findall(r"(?m)^(?:.*\\\r?\n)+.*$", headerFileStr) + is_define = re.compile(r"[ \t\v]*#[Dd][Ee][Ff][Ii][Nn][Ee]") + for m in matches: + # Keep the newlines so that linecount doesnt break + num_newlines = len([a for a in m if a == "\n"]) + if is_define.match(m): + new_m = m.replace( + "\n", "\\n" + ) + else: + # Just expression taking up multiple lines, make it take 1 line for easier parsing + new_m = m.replace("\\\n", " ") + if num_newlines > 0: + new_m += "\n" * (num_newlines) + headerFileStr = headerFileStr.replace(m, new_m) # Filter out Extern "C" statements. These are order dependent matches = re.findall( From 8d4abd7a949b7289170fd685c704b847f475ac82 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sun, 4 Dec 2022 21:56:07 -0500 Subject: [PATCH 13/24] Fix extern 'C' parsing - Linkage wasn't being recorded previously - Namespaces were being destroyed --- CppHeaderParser/CppHeaderParser.py | 48 +++++++++++++++++++----------- test/test_CppHeaderParser.py | 46 ++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index 5897f6b..05588e9 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -139,6 +139,9 @@ def trace_print(*args): #: Symbols to ignore, usually special macros ignoreSymbols = ["Q_OBJECT"] +_BRACE_REASON_OTHER = 0 +_BRACE_REASON_NS = 1 +_BRACE_REASON_EXTERN = 2 # Track what was added in what order and at what depth parseHistory = [] @@ -1506,6 +1509,11 @@ def cur_namespace(self, add_double_colon=False): i += 1 return rtn + def cur_linkage(self): + if len(self.linkage_stack): + return self.linkage_stack[-1] + return "" + def guess_ctypes_type(self, string): pointers = string.count("*") string = string.replace("*", "") @@ -2372,6 +2380,7 @@ def _evaluate_method_stack(self): self._get_location(self.nameStack), ) newMethod["parent"] = None + newMethod["linkage"] = self.cur_linkage() self.functions.append(newMethod) # Reset template once it has been used @@ -2524,6 +2533,7 @@ def _evaluate_property_stack(self, clearStack=True, addToVar=None): self._get_location(self.nameStack), ) newVar["namespace"] = self.current_namespace() + newVar["linkage"] = self.cur_linkage() if self.curClass: klass = self.classes[self.curClass] klass["properties"][self.curAccessSpecifier].append(newVar) @@ -2542,6 +2552,7 @@ def _evaluate_property_stack(self, clearStack=True, addToVar=None): self._get_location(self.nameStack), ) newVar["namespace"] = self.cur_namespace(False) + newVar["linkage"] = self.cur_linkage() if addToVar: newVar.update(addToVar) self.variables.append(newVar) @@ -2600,6 +2611,7 @@ def _evaluate_class_stack(self): ) self.curTemplate = None newClass["declaration_method"] = self.nameStack[0] + newClass["linkage"] = self.cur_linkage() self.classes_order.append(newClass) # good idea to save ordering self.stack = [] # fixes if class declared with ';' in closing brace self.stmtTokens = [] @@ -2782,6 +2794,7 @@ def __init__( self.curAccessSpecifier = "private" # private is default self.curTemplate = None self.accessSpecifierStack = [] + self.linkage_stack = [] debug_print( "curAccessSpecifier changed/defaulted to %s", self.curAccessSpecifier ) @@ -2828,16 +2841,6 @@ def __init__( new_m += "\n" * (num_newlines) headerFileStr = headerFileStr.replace(m, new_m) - # Filter out Extern "C" statements. These are order dependent - matches = re.findall( - re.compile(r'extern[\t ]+"[Cc]"[\t \n\r]*{', re.DOTALL), headerFileStr - ) - for m in matches: - # Keep the newlines so that linecount doesnt break - num_newlines = len([a for a in m if a == "\n"]) - headerFileStr = headerFileStr.replace(m, "\n" * num_newlines) - headerFileStr = re.sub(r'extern[ ]+"[Cc]"[ ]*', "", headerFileStr) - # Filter out any ignore symbols that end with "()" to account for #define magic functions for ignore in ignoreSymbols: if not ignore.endswith("()"): @@ -2876,6 +2879,8 @@ def __init__( ) self.braceDepth = 0 + self.braceReason = [] + self.lastBraceReason = _BRACE_REASON_OTHER lex = Lexer(self.headerFileName) lex.input(headerFileStr) @@ -2948,23 +2953,20 @@ def __init__( continue if parenDepth == 0 and tok.type == "{": + self.lastBraceReason = _BRACE_REASON_OTHER if len(self.nameStack) >= 2 and is_namespace( self.nameStack ): # namespace {} with no name used in boost, this sets default? - if ( - self.nameStack[1] - == "__IGNORED_NAMESPACE__CppHeaderParser__" - ): # Used in filtering extern "C" - self.nameStack[1] = "" self.nameSpaces.append("".join(self.nameStack[1:])) ns = self.cur_namespace() self.stack = [] self.stmtTokens = [] if ns not in self.namespaces: self.namespaces.append(ns) + self.lastBraceReason = _BRACE_REASON_NS # Detect special condition of macro magic before class declaration so we # can filter it out - if "class" in self.nameStack and self.nameStack[0] != "class": + elif "class" in self.nameStack and self.nameStack[0] != "class": classLocationNS = self.nameStack.index("class") classLocationS = self.stack.index("class") if ( @@ -2997,14 +2999,20 @@ def __init__( self.stmtTokens = [] if not self.braceHandled: self.braceDepth += 1 + self.braceReason.append(self.lastBraceReason) elif parenDepth == 0 and tok.type == "}": if self.braceDepth == 0: continue - if self.braceDepth == len(self.nameSpaces): - tmp = self.nameSpaces.pop() + reason = self.braceReason.pop() + if reason == _BRACE_REASON_NS: + self.nameSpaces.pop() self.stack = [] # clear stack when namespace ends? self.stmtTokens = [] + elif reason == _BRACE_REASON_EXTERN: + self.linkage_stack.pop() + self.stack = [] # clear stack when linkage ends? + self.stmtTokens = [] else: self._evaluate_stack() self.braceDepth -= 1 @@ -3368,8 +3376,10 @@ def _evaluate_stack(self, token=None): pass elif len(self.nameStack) == 2 and self.nameStack[0] == "extern": debug_print("trace extern") + self.linkage_stack.append(self.nameStack[1].strip('"')) self.stack = [] self.stmtTokens = [] + self.lastBraceReason = _BRACE_REASON_EXTERN elif ( len(self.nameStack) == 2 and self.nameStack[0] == "friend" ): # friend class declaration @@ -3677,12 +3687,14 @@ def _parse_enum(self): def _install_enum(self, newEnum, instancesData): if len(self.curClass): newEnum["namespace"] = self.cur_namespace(False) + newEnum["linkage"] = self.cur_linkage() klass = self.classes[self.curClass] klass["enums"][self.curAccessSpecifier].append(newEnum) if self.curAccessSpecifier == "public" and "name" in newEnum: klass._public_enums[newEnum["name"]] = newEnum else: newEnum["namespace"] = self.cur_namespace(True) + newEnum["linkage"] = self.cur_linkage() self.enums.append(newEnum) if "name" in newEnum and newEnum["name"]: self.global_enums[newEnum["name"]] = newEnum diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index e3472f5..c8aef1b 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -4183,5 +4183,51 @@ def test_fn3(self): self.assertEqual(fn["pure_virtual"], True) +class ExternCQuirk(unittest.TestCase): + # bug where extern "C" reset the namespace + def setUp(self): + self.cppHeader = CppHeaderParser.CppHeader( + """ +namespace cs { +extern "C" { + struct InCSAndExtern {}; + void FnInCSAndExtern(InCSAndExtern *n); +} + +class InCS {}; + +} + +void FnNotInCSOrExtern(); + +""", + "string", + ) + + def test_fn(self): + + # NotCS should be in namespace cs, extern C + c = self.cppHeader.classes["InCSAndExtern"] + self.assertEqual(c["namespace"], "cs") + self.assertEqual(c["linkage"], "C") + + # FnNotCS should be in namespace cs, extern C + fn = self.cppHeader.functions[0] + self.assertEqual(fn["name"], "FnInCSAndExtern") + self.assertEqual(fn["namespace"], "cs::") + self.assertEqual(fn["linkage"], "C") + + # InCS should be in namespace cs + c = self.cppHeader.classes["InCS"] + self.assertEqual(c["namespace"], "cs") + self.assertEqual(c["linkage"], "") + + # FnNotCS should not in namespace cs nor extern C + fn = self.cppHeader.functions[1] + self.assertEqual(fn["name"], "FnNotInCSOrExtern") + self.assertEqual(fn["namespace"], "") + self.assertEqual(fn["linkage"], "") + + if __name__ == "__main__": unittest.main() From da1a074314e753a970bc00e7eccc4a62a8c616d7 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Sun, 4 Dec 2022 22:18:21 -0500 Subject: [PATCH 14/24] Remove SubTypedefs thing - Doesn't seem useful --- CppHeaderParser/CppHeaderParser.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index 05588e9..4482287 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -1480,7 +1480,6 @@ class Resolver(object): C_KEYWORDS = "extern virtual static explicit inline friend constexpr".split() C_KEYWORDS = set(C_KEYWORDS) - SubTypedefs = {} # TODO deprecate? NAMESPACES = [] CLASSES = {} @@ -1878,21 +1877,6 @@ def finalize_vars(self): var["ctypes_type"] = "ctypes.c_void_p" var["unresolved"] = True - elif tag in self.SubTypedefs: # TODO remove SubTypedefs - if ( - "property_of_class" in var - or "property_of_struct" in var - ): - trace_print( - "class:", self.SubTypedefs[tag], "tag:", tag - ) - var["typedef"] = self.SubTypedefs[tag] # class name - var["ctypes_type"] = "ctypes.c_void_p" - else: - trace_print("WARN-this should almost never happen!") - trace_print(var) - var["unresolved"] = True - elif tag in self._template_typenames: var["typename"] = tag var["ctypes_type"] = "ctypes.c_void_p" @@ -2069,10 +2053,6 @@ def finalize(self): trace_print("meth returns class:", meth["returns"]) meth["returns_class"] = True - elif meth["returns"] in self.SubTypedefs: - meth["returns_class"] = True - meth["returns_nested"] = self.SubTypedefs[meth["returns"]] - elif meth["returns"] in cls._public_enums: enum = cls._public_enums[meth["returns"]] meth["returns_enum"] = enum.get("type") @@ -2481,7 +2461,6 @@ def _evaluate_property_stack(self, clearStack=True, addToVar=None): klass["typedefs"][self.curAccessSpecifier].append(name) if self.curAccessSpecifier == "public": klass._public_typedefs[name] = typedef["type"] - Resolver.SubTypedefs[name] = self.curClass else: assert 0 elif self.curClass: From cc3967e189261b4e7b70c3272c1ae68178b3d20c Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 5 Oct 2023 03:26:58 -0400 Subject: [PATCH 15/24] deprecate robotpy-cppheaderparser --- .github/ISSUE_TEMPLATE/bug-report.yml | 25 +++++++++++++++++++++++++ README.rst | 15 +++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..618c600 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,25 @@ +name: Bug Report +description: | + Please be aware that we are no longer making fixes ourselves + to robotpy-cppheaderparser, but we will accept pull requests with appropriately + tested fixes. We recommend all users migrate to our new parser which can be + found at https://github.com/robotpy/cxxheaderparser +title: "[BUG]: " +body: + - type: textarea + id: description + attributes: + label: Problem description + placeholder: >- + Provide a short description, state the expected behavior and what + actually happens. + validations: + required: true + + - type: textarea + id: code + attributes: + label: Reproducible example code + placeholder: >- + Minimal code to reproduce this issue + render: text \ No newline at end of file diff --git a/README.rst b/README.rst index 77e2eb6..627f849 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,17 @@ robotpy-cppheaderparser ======================= -|Build Status| +**robotpy-cppheaderparser is DEPRECATED, and we are no longer updating it**. +We will accept pull requests that have fixes and appropriate tests, but we +will no longer be making any fixes ourselves. + +We highly recommend all current and future users to migrate to cxxheaderparser, +which supports all the syntax that CppHeaderParser does and much much more. The +parser output is very different, but it is strictly typed and hopefully easier +to work with. You can find it at https://github.com/robotpy/cxxheaderparser, +or try the live interactive demo at https://robotpy.github.io/cxxheaderparser/ + +--------- CppHeaderParser is a pure python C++ header parser that parses C++ headers and creates a data structure that you can use to do many types @@ -62,6 +72,3 @@ Past contributors include: .. _CppHeaderParser: https://bitbucket.org/senex/cppheaderparser .. _pcpp: https://github.com/ned14/pcpp - -.. |Build Status| image:: https://travis-ci.org/robotpy/robotpy-cppheaderparser.svg?branch=master - :target: https://travis-ci.org/robotpy/robotpy-cppheaderparser \ No newline at end of file From 212ef369389c3aea3b21c7493d5e1553840a6306 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 5 Oct 2023 03:34:42 -0400 Subject: [PATCH 16/24] Update the updates --- .github/ISSUE_TEMPLATE/bug-report.yml | 6 +----- .github/workflows/dist.yml | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 618c600..a69c2a8 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,9 +1,5 @@ name: Bug Report -description: | - Please be aware that we are no longer making fixes ourselves - to robotpy-cppheaderparser, but we will accept pull requests with appropriately - tested fixes. We recommend all users migrate to our new parser which can be - found at https://github.com/robotpy/cxxheaderparser +description: We are no longer making fixes ourselves to robotpy-cppheaderparser, but we will accept pull requests with appropriately tested fixes. We recommend all users migrate to cxxheaderparser. title: "[BUG]: " body: - type: textarea diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index d6801dd..abaadec 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -32,8 +32,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-latest, macos-latest, ubuntu-18.04] - python_version: [2.7, 3.5, 3.6, 3.7, 3.8] + os: [windows-latest, macos-latest, ubuntu-20.04] + python_version: [3.5, 3.6, 3.7, 3.8] architecture: [x86, x64] exclude: - os: macos-latest From c20d7d50c50e0b6f916f2d3925f7f983e8507d02 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 5 Oct 2023 03:57:25 -0400 Subject: [PATCH 17/24] Update black --- CppHeaderParser/CppHeaderParser.py | 2 -- CppHeaderParser/lexer.py | 1 - test/test_CppHeaderParser.py | 2 -- 3 files changed, 5 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index 4482287..ad78c1e 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -112,7 +112,6 @@ def debug_print(fmt, *args): print(fmt % args) else: - debug_caller_lineno = None def debug_print(fmt, *args): @@ -3192,7 +3191,6 @@ def _next_token_must_be(self, *tokenTypes): } def _consume_balanced_tokens(self, *init_tokens): - _balanced_token_map = self._balanced_token_map consumed = list(init_tokens) diff --git a/CppHeaderParser/lexer.py b/CppHeaderParser/lexer.py index fa62a1a..b63501f 100644 --- a/CppHeaderParser/lexer.py +++ b/CppHeaderParser/lexer.py @@ -6,7 +6,6 @@ class Lexer(object): - tokens = [ "NUMBER", "FLOAT_NUMBER", diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index c8aef1b..a7810a7 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -3330,7 +3330,6 @@ def setUp(self): ) def test_existance(self): - self.assertIn("S", self.cppHeader.classes) self.assertIn("PS", self.cppHeader.typedefs) self.assertEqual("x", self.cppHeader.variables[0]["name"]) @@ -4205,7 +4204,6 @@ class InCS {}; ) def test_fn(self): - # NotCS should be in namespace cs, extern C c = self.cppHeader.classes["InCSAndExtern"] self.assertEqual(c["namespace"], "cs") From 36357a08e832c6a4aa58b33e7a6ece4ac1c517ac Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 5 Oct 2023 03:58:52 -0400 Subject: [PATCH 18/24] Update gh actions --- .github/workflows/dist.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index abaadec..0ec4934 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -11,7 +11,7 @@ jobs: - uses: psf/black@stable check-doc: - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 @@ -38,7 +38,7 @@ jobs: exclude: - os: macos-latest architecture: x86 - - os: ubuntu-18.04 + - os: ubuntu-20.04 architecture: x86 steps: From 2c7891a3bd52799b29e9f5b09828049b12d3fb10 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 16 May 2024 19:48:04 -0400 Subject: [PATCH 19/24] Update black --- CppHeaderParser/CppHeaderParser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index ad78c1e..a7c2ce5 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -1879,9 +1879,9 @@ def finalize_vars(self): elif tag in self._template_typenames: var["typename"] = tag var["ctypes_type"] = "ctypes.c_void_p" - var[ - "unresolved" - ] = True # TODO, how to deal with templates? + var["unresolved"] = ( + True # TODO, how to deal with templates? + ) elif tag.startswith( "_" From d1ec3bf78d996b097f569261b4b96e72a0fde30b Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Thu, 16 May 2024 19:49:20 -0400 Subject: [PATCH 20/24] Update github actions --- .github/workflows/dist.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 0ec4934..ce3ac9a 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -7,19 +7,19 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: psf/black@stable check-doc: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: 3.8 - name: Sphinx @@ -32,22 +32,22 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-latest, macos-latest, ubuntu-20.04] - python_version: [3.5, 3.6, 3.7, 3.8] - architecture: [x86, x64] + os: [windows-latest, macos-13, ubuntu-20.04] + python_version: [3.8] + architecture: [x64] exclude: - - os: macos-latest + - os: macos-13 architecture: x86 - os: ubuntu-20.04 architecture: x86 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python_version }} architecture: ${{ matrix.architecture }} @@ -72,12 +72,12 @@ jobs: if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: 3.8 - run: pip --disable-pip-version-check install wheel From 60238ebce89822ea3a57d62f9923734a2a2c7bd1 Mon Sep 17 00:00:00 2001 From: LE GARREC Vincent Date: Tue, 21 May 2024 07:38:13 +0200 Subject: [PATCH 21/24] Fix duplicated testcase name And fix CarrotClass_TestCase --- test/test_CppHeaderParser.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index a7810a7..5714e6c 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -1162,14 +1162,9 @@ def test_method_params(self): [], ) - def test_class_template(self): - self.assertEqual( - self.cppHeader.classes["CarrotClass"]["template"], "template" - ) - # Bug 3517289 -class CarrotClass_TestCase(unittest.TestCase): +class ExternClass_TestCase(unittest.TestCase): def setUp(self): self.cppHeader = CppHeaderParser.CppHeader("TestSampleClass.h") From 8a2caee9d82c00b819bdd2bb572911f3f7b9a8d9 Mon Sep 17 00:00:00 2001 From: LE GARREC Vincent Date: Wed, 22 May 2024 23:12:24 +0200 Subject: [PATCH 22/24] Add support to variable with [] inside type (#85) * Add support to variable with [] inside type std::unique_ptr variable; * Type of "array_size" should always be the same * Add test for std::unique_ptr * Add support for initialization with {} int double {0}; int double {(1.2+3.2)}; * Fix format --- CppHeaderParser/CppHeaderParser.py | 88 +++++++++++++++++++++--------- test/test_CppHeaderParser.py | 85 ++++++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 28 deletions(-) diff --git a/CppHeaderParser/CppHeaderParser.py b/CppHeaderParser/CppHeaderParser.py index a7c2ce5..c6061e6 100644 --- a/CppHeaderParser/CppHeaderParser.py +++ b/CppHeaderParser/CppHeaderParser.py @@ -141,6 +141,7 @@ def trace_print(*args): _BRACE_REASON_OTHER = 0 _BRACE_REASON_NS = 1 _BRACE_REASON_EXTERN = 2 +_BRACE_REASON_VARIABLE = 3 # Track what was added in what order and at what depth parseHistory = [] @@ -239,10 +240,13 @@ def is_property_namestack(nameStack): r = False if "(" not in nameStack and ")" not in nameStack: r = True - elif ( - "(" in nameStack - and "=" in nameStack - and nameStack.index("=") < nameStack.index("(") + elif "(" in nameStack and ( + ( # = initialization + "=" in nameStack and nameStack.index("=") < nameStack.index("(") + ) + or ( # {} initialization + "{" in nameStack and nameStack.index("{") < nameStack.index("(") + ) ): r = True # See if we are a function pointer @@ -1217,29 +1221,48 @@ def __init__(self, nameStack, doxygen, location, is_var=True, **kwargs): else: self["extern"] = False + if "=" in nameStack: + self["type"] = " ".join(nameStack[: nameStack.index("=") - 1]) + self["name"] = nameStack[nameStack.index("=") - 1] + default = " ".join(nameStack[nameStack.index("=") + 1 :]) + nameStack = nameStack[: nameStack.index("=")] + default = self._filter_name(default) + self["default"] = default + # backwards compat; deprecate camelCase in dicts + self["defaultValue"] = default + elif "{" in nameStack and "}" in nameStack: + posBracket = nameStack.index("{") + self["type"] = " ".join(nameStack[: posBracket - 1]) + self["name"] = nameStack[posBracket - 1] + default = " ".join(nameStack[posBracket + 1 : -1]) + nameStack = nameStack[:posBracket] + default = self._filter_name(default) + self["default"] = default + # backwards compat; deprecate camelCase in dicts + self["defaultValue"] = default + _stack_ = nameStack - if "[" in nameStack: # strip off array informatin - arrayStack = nameStack[nameStack.index("[") :] - if nameStack.count("[") > 1: + self["array"] = 0 + while "]" in nameStack[-1]: # strip off array information + arrayPos = len(nameStack) - 1 - nameStack[::-1].index("[") + arrayStack = nameStack[arrayPos:] + if self["array"] == 1: debug_print("Multi dimensional array") debug_print("arrayStack=%s", arrayStack) - nums = [x for x in arrayStack if x.isdigit()] - # Calculate size by multiplying all dimensions - p = 1 - for n in nums: - p *= int(n) + if len(arrayStack) == 3: + n = arrayStack[1] # Multi dimensional array - self["array_size"] = p + if not "multi_dimensional_array_size" in self: + self["multi_dimensional_array_size"] = self["array_size"] + self["multi_dimensional_array_size"] += "x" + n + self["array_size"] = str(int(self["array_size"]) * int(n)) self["multi_dimensional_array"] = 1 - self["multi_dimensional_array_size"] = "x".join(nums) else: debug_print("Array") if len(arrayStack) == 3: self["array_size"] = arrayStack[1] - nameStack = nameStack[: nameStack.index("[")] + nameStack = nameStack[:arrayPos] self["array"] = 1 - else: - self["array"] = 0 nameStack = self._name_stack_helper(nameStack) if doxygen: @@ -1268,15 +1291,6 @@ def __init__(self, nameStack, doxygen, location, is_var=True, **kwargs): ) self["function_pointer"] = 1 - elif "=" in nameStack: - self["type"] = " ".join(nameStack[: nameStack.index("=") - 1]) - self["name"] = nameStack[nameStack.index("=") - 1] - default = " ".join(nameStack[nameStack.index("=") + 1 :]) - default = self._filter_name(default) - self["default"] = default - # backwards compat; deprecate camelCase in dicts - self["defaultValue"] = default - elif ( is_fundamental(nameStack[-1]) or nameStack[-1] in [">", "<", ":", "."] @@ -2931,7 +2945,23 @@ def __init__( continue if parenDepth == 0 and tok.type == "{": - self.lastBraceReason = _BRACE_REASON_OTHER + if self.nameStack[0] in ( + "class", + "struct", + "union", + "namespace", + "enum", + "extern", + "typedef", + ) or (is_method_namestack(self.stack) or (not self.curClass)): + self.lastBraceReason = _BRACE_REASON_OTHER + else: + # Case : type variable {init}; + self.lastBraceReason = _BRACE_REASON_VARIABLE + self.braceDepth += 1 + self.braceReason.append(self.lastBraceReason) + self.nameStack.append(tok.value) + continue if len(self.nameStack) >= 2 and is_namespace( self.nameStack ): # namespace {} with no name used in boost, this sets default? @@ -2991,6 +3021,10 @@ def __init__( self.linkage_stack.pop() self.stack = [] # clear stack when linkage ends? self.stmtTokens = [] + # Case : type variable {init}; + elif reason == _BRACE_REASON_VARIABLE: + self.nameStack.append(tok.value) + continue else: self._evaluate_stack() self.braceDepth -= 1 diff --git a/test/test_CppHeaderParser.py b/test/test_CppHeaderParser.py index 5714e6c..4f949f5 100644 --- a/test/test_CppHeaderParser.py +++ b/test/test_CppHeaderParser.py @@ -2359,7 +2359,7 @@ def setUp(self): def test_array_size(self): self.assertEqual( self.cppHeader.classes["Picture"]["properties"]["public"][1]["array_size"], - 16384, + "16384", ) def test_multi_dimensional_array_size(self): @@ -4222,5 +4222,88 @@ def test_fn(self): self.assertEqual(fn["linkage"], "") +# Github PR 85 +class ContainerOfArray_TestCase(unittest.TestCase): + def setUp(self): + self.cppHeader = CppHeaderParser.CppHeader( + """ +class ContainerOfArray { +public: + std::unique_ptr variable; + std::unique_ptr function(std::unique_ptr param1); +}; +""", + "string", + ) + + def test_rtntype(self): + self.assertEqual( + self.cppHeader.classes["ContainerOfArray"]["methods"]["public"][0][ + "rtnType" + ], + "std::unique_ptr", + ) + + def test_parameters(self): + self.assertEqual( + filter_pameters( + self.cppHeader.classes["ContainerOfArray"]["methods"]["public"][0][ + "parameters" + ] + ), + [{"name": "param1", "desc": None, "type": "std::unique_ptr"}], + ) + + def test_member(self): + self.assertEqual( + self.cppHeader.classes["ContainerOfArray"]["properties"]["public"][0][ + "name" + ], + "variable", + ) + self.assertEqual( + self.cppHeader.classes["ContainerOfArray"]["properties"]["public"][0][ + "type" + ], + "std::unique_ptr", + ) + + +class InitBracket_TestCase(unittest.TestCase): + def setUp(self): + self.cppHeader = CppHeaderParser.CppHeader( + """ +class InitBracket { +public: + int variable{10}; + std::shared_ptr variable2 {std::make_shared(150)}; +}; +""", + "string", + ) + + def test_member(self): + self.assertEqual( + self.cppHeader.classes["InitBracket"]["properties"]["public"][0]["name"], + "variable", + ) + self.assertEqual( + self.cppHeader.classes["InitBracket"]["properties"]["public"][0][ + "defaultValue" + ], + "10", + ) + self.assertEqual( + self.cppHeader.classes["InitBracket"]["properties"]["public"][1]["name"], + "variable2", + ) + self.assertEqual( + self.cppHeader.classes["InitBracket"]["properties"]["public"][1][ + "defaultValue" + ], + "std::make_shared ( 150 )", + ) + + if __name__ == "__main__": unittest.main() From 75a599ae13d46a940cdf9e93761078ed39faef27 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Wed, 22 May 2024 17:18:41 -0400 Subject: [PATCH 23/24] Update github actions publish action --- .github/workflows/dist.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index ce3ac9a..198cb11 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -85,7 +85,7 @@ jobs: - name: Build packages run: python setup.py sdist bdist_wheel - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_PASSWORD }} From 0feb2a8d76a459130f98666be4191ab41fd29400 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Mon, 27 May 2024 23:06:12 -0400 Subject: [PATCH 24/24] Use trusted publishing --- .github/workflows/dist.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dist.yml b/.github/workflows/dist.yml index 198cb11..95df148 100644 --- a/.github/workflows/dist.yml +++ b/.github/workflows/dist.yml @@ -70,6 +70,8 @@ jobs: runs-on: ubuntu-latest needs: [check, check-doc, test] if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') + permissions: + id-token: write steps: - uses: actions/checkout@v4 @@ -86,6 +88,3 @@ jobs: run: python setup.py sdist bdist_wheel - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_PASSWORD }}