From 83a0fb805dc04a1e12fd5bbb4c3fe56871a13cca Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Fri, 5 Jan 2024 14:24:47 +0100 Subject: [PATCH] Added basic parsing of pointer-to-member types. --- cxxheaderparser/parser.py | 82 ++++++++++---- cxxheaderparser/types.py | 43 +++++++- tests/test_pointer_to_member.py | 188 ++++++++++++++++++++++++++++++++ 3 files changed, 288 insertions(+), 25 deletions(-) create mode 100644 tests/test_pointer_to_member.py diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 5bef044..2cfdce5 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -43,6 +43,7 @@ from .types import ( Parameter, PQName, Pointer, + PointerToMember, Reference, TemplateArgument, TemplateDecl, @@ -1598,6 +1599,7 @@ class CxxParser: fn_ok: bool = False, compound_ok: bool = False, fund_ok: bool = False, + ptr_to_member_ok: bool = False, ) -> typing.Tuple[PQName, typing.Optional[str]]: """ Parses a possibly qualified function name or a type name, returns when @@ -1725,7 +1727,12 @@ class CxxParser: if not self.lex.token_if("DBL_COLON"): break - tok = self._next_token_must_be("NAME", "operator", "template", "decltype") + tok = self._next_token_must_be("NAME", "operator", "template", "decltype", "*") + + if tok.value == '*': + if not ptr_to_member_ok: + raise self._parse_error(tok) + return name, '*' pqname = PQName(segments, classkey, has_typename) @@ -1792,10 +1799,31 @@ class CxxParser: toks = self._consume_balanced_tokens(tok) self.lex.return_tokens(toks[1:-1]) + # optional name - tok = self.lex.token_if("NAME", "final") + tok = self.lex.token_if("NAME", "final", "DBL_COLON") if tok: - param_name = tok.value + pqname, op = self._parse_pqname(tok, fn_ok=True, ptr_to_member_ok=True) + while op == '*': + dtype = PointerToMember(base_type=Type(typename=pqname), ptr_to=dtype, const=dtype.const, volatile=dtype.volatile) + # dtype = self._parse_cv_ptr(dtype) + tok = self.lex.token_if("NAME", "final", "DBL_COLON") + if tok: + pqname, op = self._parse_pqname(tok, fn_ok=True, ptr_to_member_ok=True) + else: + pqname = None + op = None + if pqname: + if len(pqname.segments) != 1: + raise self._parse_error(None) + param_name = pqname.segments[0].name + + if self.lex.token_if("("): + if isinstance(dtype, PointerToMember): + params, vararg, at_params = self._parse_parameters(False) + dtype.ptr_to = FunctionType(return_type=dtype.ptr_to, parameters=params, vararg=vararg) + else: + assert(False) # TODO # optional array parameter tok = self.lex.token_if("[") @@ -2477,7 +2505,17 @@ class CxxParser: tok = self.lex.token_if_in_set(self._pqname_start_tokens) if tok: - pqname, op = self._parse_pqname(tok, fn_ok=True) + pqname, op = self._parse_pqname(tok, fn_ok=True, ptr_to_member_ok=True) + while op == '*': + dtype = PointerToMember(base_type=Type(typename=pqname), ptr_to=dtype, const=dtype.const, volatile=dtype.volatile) + # dtype = self._parse_cv_ptr(dtype) + tok = self.lex.token_if_in_set(self._pqname_start_tokens) + if tok: + pqname, op = self._parse_pqname(tok, fn_ok=True, ptr_to_member_ok=True) + else: + pqname = None + op = None + # TODO: "type fn(x);" is ambiguous here. Because this is a header # parser, we assume it's a function, not a variable declaration @@ -2487,22 +2525,26 @@ class CxxParser: if self.lex.token_if("("): if not pqname: raise self._parse_error(None) - - return self._parse_function( - mods, - dtype, - pqname, - op, - template, - doxygen, - location, - constructor, - destructor, - is_friend, - is_typedef, - msvc_convention, - is_guide, - ) + + if isinstance(dtype, PointerToMember): + params, vararg, at_params = self._parse_parameters(False) + dtype.ptr_to = FunctionType(return_type=dtype.ptr_to, parameters=params, vararg=vararg) + else: + return self._parse_function( + mods, + dtype, + pqname, + op, + template, + doxygen, + location, + constructor, + destructor, + is_friend, + is_typedef, + msvc_convention, + is_guide, + ) elif msvc_convention: raise self._parse_error(msvc_convention) diff --git a/cxxheaderparser/types.py b/cxxheaderparser/types.py index 1aa0b99..5cd2ffe 100644 --- a/cxxheaderparser/types.py +++ b/cxxheaderparser/types.py @@ -306,7 +306,7 @@ class Array: """ #: The type that this is an array of - array_of: typing.Union["Array", "Pointer", Type] + array_of: typing.Union["Array", "Pointer", "PointerToMember", Type] #: Size of the array #: @@ -332,7 +332,7 @@ class Pointer: """ #: Thing that this points to - ptr_to: typing.Union[Array, FunctionType, "Pointer", Type] + ptr_to: typing.Union[Array, FunctionType, "Pointer", "PointerToMember", Type] const: bool = False volatile: bool = False @@ -356,6 +356,39 @@ class Pointer: else: return f"{ptr_to.format()}*{c}{v} {name}" +@dataclass +class PointerToMember: + """ + Pointer to a class member. (``Class::* int``) + """ + + #: Thing that this points to + base_type: Type + ptr_to: typing.Union[Array, FunctionType, "Pointer", "PointerToMember", Type] + + const: bool = False + volatile: bool = False + + def format(self) -> str: + c = " const" if self.const else "" + v = " volatile" if self.volatile else "" + ptr_to = self.ptr_to + if isinstance(ptr_to, (Array, FunctionType)): + return ptr_to.format_decl(f"({base_type.format()}::*{c}{v})") + else: + return f"{ptr_to.format()} {base_type.format()}::*{c}{v}" + + def format_decl(self, name: str): + """Format as a named declaration""" + c = " const" if self.const else "" + v = " volatile" if self.volatile else "" + ptr_to = self.ptr_to + if isinstance(ptr_to, (Array, FunctionType)): + return ptr_to.format_decl(f"({base_type.format()}::*{c}{v} {name})") + else: + return f"{ptr_to.format()} {base_type.format()}::*{c}{v} {name}" + + @dataclass class Reference: @@ -363,7 +396,7 @@ class Reference: A lvalue (``&``) reference """ - ref_to: typing.Union[Array, FunctionType, Pointer, Type] + ref_to: typing.Union[Array, FunctionType, Pointer, PointerToMember, Type] def format(self) -> str: ref_to = self.ref_to @@ -388,7 +421,7 @@ class MoveReference: An rvalue (``&&``) reference """ - moveref_to: typing.Union[Array, FunctionType, Pointer, Type] + moveref_to: typing.Union[Array, FunctionType, Pointer, PointerToMember, Type] def format(self) -> str: return f"{self.moveref_to.format()}&&" @@ -402,7 +435,7 @@ class MoveReference: #: #: .. note:: There can only be one of FunctionType or Type in a DecoratedType #: chain -DecoratedType = typing.Union[Array, Pointer, MoveReference, Reference, Type] +DecoratedType = typing.Union[Array, Pointer, PointerToMember, MoveReference, Reference, Type] @dataclass diff --git a/tests/test_pointer_to_member.py b/tests/test_pointer_to_member.py new file mode 100644 index 0000000..a259bc9 --- /dev/null +++ b/tests/test_pointer_to_member.py @@ -0,0 +1,188 @@ +def test_pointer_to_member() -> None: + content = """ + class Class + { + + }; + + int Class::* intPtr; + int (Class::* intReturnFuncPtr)(); + void (Class::* intParamFuncPtr)(int); + void (Class::* varargFuncPtr)(...); + + template + int takesFunc(void (*func)(TArgs...)); + + template + int takesMemberFunc(TObject& object, void (TObject::* func)(TArgs...)); + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="Class")], classkey="class" + ) + ) + ) + ], + functions=[ + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="takesFunc")]), + parameters=[ + Parameter( + type=Pointer( + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="TArgs") + ] + ) + ), + param_pack=True, + ) + ], + ) + ), + name="func", + ) + ], + template=TemplateDecl( + params=[ + TemplateTypeParam( + typekey="typename", name="TArgs", param_pack=True + ) + ] + ), + ), + Function( + return_type=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + name=PQName(segments=[NameSpecifier(name="takesMemberFunc")]), + parameters=[ + Parameter( + type=Reference( + ref_to=Type( + typename=PQName( + segments=[NameSpecifier(name="TObject")] + ) + ) + ), + name="object", + ), + Parameter( + type=PointerToMember( + base_type=Type(typename=NameSpecifier(name="TObject")), + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="TArgs") + ] + ) + ), + param_pack=True, + ) + ], + ), + ), + name="func", + ), + ], + template=TemplateDecl( + params=[ + TemplateTypeParam(typekey="typename", name="TObject"), + TemplateTypeParam( + typekey="typename", name="TArgs", param_pack=True + ), + ] + ), + ), + ], + variables=[ + Variable( + name=PQName(segments=[NameSpecifier(name="intPtr")]), + type=PointerToMember( + base_type=Type(typename=NameSpecifier(name="Class")), + ptr_to=Type( + typename=PQName(segments=[FundamentalSpecifier(name="int")]) + ), + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="intReturnFuncPtr")]), + type=PointerToMember( + base_type=Type(typename=NameSpecifier(name="Class")), + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ), + parameters=[], + ), + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="intParamFuncPtr")]), + type=PointerToMember( + base_type=Type(typename=NameSpecifier(name="Class")), + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="int")] + ) + ) + ) + ], + ), + ), + ), + Variable( + name=PQName(segments=[NameSpecifier(name="varargFuncPtr")]), + type=PointerToMember( + base_type=Type(typename=NameSpecifier(name="Class")), + ptr_to=FunctionType( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + parameters=[], + vararg=True, + ), + ), + ), + ], + ) + ) +