From 8d506e570027093c7efa2b131ace9ab7ca8fdad2 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 26 Nov 2021 10:03:26 -0500 Subject: [PATCH] Support __declspec (and other attributes) when parsing a name - Sometimes this will lead to accepting invalid code, but "dont do that" - Fixes #12 --- cxxheaderparser/parser.py | 34 ++++++++++++++++-------- tests/test_attributes.py | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index f626301..2930cc7 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -614,6 +614,23 @@ class CxxParser: # Attributes # + _attribute_specifier_seq_start_types = {"DBL_LBRACKET", "alignas"} + _attribute_start_tokens = { + "__attribute__", + "__declspec", + } + _attribute_start_tokens |= _attribute_specifier_seq_start_types + + def _consume_attribute(self, tok: LexToken) -> None: + if tok.type == "__attribute__": + self._consume_gcc_attribute(tok) + elif tok.type == "__declspec": + self._consume_declspec(tok) + elif tok.type in self._attribute_specifier_seq_start_types: + self._consume_attribute_specifier_seq(tok) + else: + raise CxxParseError("internal error") + def _consume_gcc_attribute( self, tok: LexToken, doxygen: typing.Optional[str] = None ) -> None: @@ -627,8 +644,6 @@ class CxxParser: tok = self._next_token_must_be("(") self._consume_balanced_tokens(tok) - _attribute_specifier_seq_start_types = ("DBL_LBRACKET", "alignas") - def _consume_attribute_specifier_seq( self, tok: LexToken, doxygen: typing.Optional[str] = None ) -> None: @@ -1361,16 +1376,10 @@ class CxxParser: if tok: classkey = f"{classkey} {tok.value}" - tok = self.lex.token_if( - "alignas", "__attribute__", "__declspec", "DBL_LBRACKET" - ) + # Sometimes there's an embedded attribute + tok = self.lex.token_if(*self._attribute_start_tokens) if tok: - if tok.type == "__attribute__": - self._consume_gcc_attribute(tok) - elif tok.type == "__declspec": - self._consume_declspec(tok) - else: - self._consume_attribute_specifier_seq(tok) + self._consume_attribute(tok) tok = self.lex.token_if("NAME", "DBL_COLON") if not tok: @@ -1898,6 +1907,7 @@ class CxxParser: pqname: typing.Optional[PQName] = None _pqname_start_tokens = self._pqname_start_tokens + _attribute_start = self._attribute_start_tokens # This loop parses until it finds two pqname or ptr/ref while True: @@ -1926,6 +1936,8 @@ class CxxParser: vars["mutable"] = tok elif tok_type == "volatile": volatile = True + elif tok_type in _attribute_start: + self._consume_attribute(tok) else: break diff --git a/tests/test_attributes.py b/tests/test_attributes.py index a84fd47..f1d62ce 100644 --- a/tests/test_attributes.py +++ b/tests/test_attributes.py @@ -5,8 +5,10 @@ from cxxheaderparser.types import ( EnumDecl, Enumerator, Field, + FriendDecl, Function, FundamentalSpecifier, + Method, NameSpecifier, PQName, Pointer, @@ -146,3 +148,56 @@ def test_attributes_gcc_enum_packed(): ] ) ) + + +def test_friendly_declspec(): + content = """ + struct D { + friend __declspec(dllexport) void my_friend(); + static __declspec(dllexport) void static_declspec(); + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="D")], classkey="struct" + ) + ), + friends=[ + FriendDecl( + fn=Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="my_friend")]), + parameters=[], + access="public", + ) + ) + ], + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName( + segments=[NameSpecifier(name="static_declspec")] + ), + parameters=[], + static=True, + access="public", + ) + ], + ) + ] + ) + )