Skip to content

Commit 0bc2f51

Browse files
authored
Merge pull request #149 from robotpy/member-fn-ptrs
Add support for parsing member function pointers
2 parents 1056119 + 5fd6d52 commit 0bc2f51

6 files changed

Lines changed: 167 additions & 9 deletions

File tree

cxxheaderparser/parser.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,9 +2526,21 @@ def _parse_cv_ptr_or_fn(
25262526

25272527
# this might be a grouping paren, so consume it and inspect it
25282528
toks, inner_toks = self._consume_balanced_tokens_with_inner(tok)
2529+
member_ptr_idx = next(
2530+
(
2531+
i
2532+
for i, itok in enumerate(inner_toks)
2533+
if itok.type == "*"
2534+
and i > 0
2535+
and inner_toks[i - 1].type == "DBL_COLON"
2536+
),
2537+
None,
2538+
)
25292539

25302540
# Check to see if this is a grouping paren or something else
2531-
if not (inner_toks and inner_toks[0].type in ("*", "&")):
2541+
if not inner_toks or (
2542+
inner_toks[0].type not in ("*", "&") and member_ptr_idx is None
2543+
):
25322544
self.lex.return_tokens(toks)
25332545
break
25342546

@@ -2548,9 +2560,25 @@ def _parse_cv_ptr_or_fn(
25482560
dtype, fn_params, vararg, msvc_convention=msvc_convention
25492561
)
25502562

2551-
# the inner tokens must either be a * or a pqname that ends
2552-
# with ::* (member function pointer)
2553-
# ... TODO member function pointer :(
2563+
if isinstance(dtype, FunctionType) and member_ptr_idx is not None:
2564+
# Keep the * and declarator name for the normal pointer/name
2565+
# parsing below, and store the qualified class name on the
2566+
# function type.
2567+
class_toks = inner_toks[: member_ptr_idx - 1] + [PhonyEnding]
2568+
old_lex = self.lex
2569+
try:
2570+
self.lex = lexer.BoundedTokenStream(class_toks)
2571+
classname, _ = self._parse_pqname(
2572+
None, compound_ok=False, fn_ok=False, fund_ok=False
2573+
)
2574+
self._next_token_must_be(PhonyEnding.type)
2575+
finally:
2576+
self.lex = old_lex
2577+
2578+
dtype.classname = classname
2579+
inner_toks = [inner_toks[member_ptr_idx]] + inner_toks[
2580+
member_ptr_idx + 1 :
2581+
]
25542582

25552583
# return the inner toks and recurse
25562584
# -> this could return some weird results for invalid code, but

cxxheaderparser/types.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,6 @@ class FunctionType:
272272
return_type: "DecoratedType"
273273
parameters: typing.List["Parameter"]
274274

275-
#: If a member function pointer
276-
# TODO classname: typing.Optional[PQName]
277-
278275
#: Set to True if ends with ``...``
279276
vararg: bool = False
280277

@@ -293,6 +290,9 @@ class FunctionType:
293290
#: calling convention
294291
msvc_convention: typing.Optional[str] = None
295292

293+
#: If a member function pointer, the class that owns the member function.
294+
classname: typing.Optional[PQName] = None
295+
296296
def format(self) -> str:
297297
vararg = "..." if self.vararg else ""
298298
params = ", ".join(p.format() for p in self.parameters)
@@ -379,7 +379,9 @@ def format(self) -> str:
379379
v = " volatile" if self.volatile else ""
380380
r = " __restrict__" if self.restrict else ""
381381
ptr_to = self.ptr_to
382-
if isinstance(ptr_to, (Array, FunctionType)):
382+
if isinstance(ptr_to, FunctionType) and ptr_to.classname:
383+
return ptr_to.format_decl(f"({ptr_to.classname.format()}::*{r}{c}{v})")
384+
elif isinstance(ptr_to, (Array, FunctionType)):
383385
return ptr_to.format_decl(f"(*{r}{c}{v})")
384386
else:
385387
return f"{ptr_to.format()}*{r}{c}{v}"
@@ -390,7 +392,11 @@ def format_decl(self, name: str):
390392
v = " volatile" if self.volatile else ""
391393
r = " __restrict__" if self.restrict else ""
392394
ptr_to = self.ptr_to
393-
if isinstance(ptr_to, (Array, FunctionType)):
395+
if isinstance(ptr_to, FunctionType) and ptr_to.classname:
396+
return ptr_to.format_decl(
397+
f"({ptr_to.classname.format()}::*{r}{c}{v} {name})"
398+
)
399+
elif isinstance(ptr_to, (Array, FunctionType)):
394400
return ptr_to.format_decl(f"(*{r}{c}{v} {name})")
395401
else:
396402
return f"{ptr_to.format()}*{r}{c}{v} {name}"

tests/test_tokfmt.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"void * (*)()",
1919
"void (*)(void * buf, int buflen)",
2020
"void (* fnType)(void * buf, int buflen)",
21+
"int (Fred::* FredMemFn)(char x, float y)",
2122
"TypeName<int, void>& x",
2223
"vector<string>&",
2324
"std::vector<Pointer *> *",

tests/test_typedef.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,53 @@ def test_typedef_fnptr() -> None:
606606
)
607607

608608

609+
def test_typedef_member_fnptr() -> None:
610+
content = """
611+
typedef int (Fred::*FredMemFn)(char x, float y);
612+
"""
613+
data = parse_string(content, cleandoc=True)
614+
615+
assert data == ParsedData(
616+
namespace=NamespaceScope(
617+
typedefs=[
618+
Typedef(
619+
type=Pointer(
620+
ptr_to=FunctionType(
621+
return_type=Type(
622+
typename=PQName(
623+
segments=[FundamentalSpecifier(name="int")]
624+
)
625+
),
626+
parameters=[
627+
Parameter(
628+
type=Type(
629+
typename=PQName(
630+
segments=[FundamentalSpecifier(name="char")]
631+
)
632+
),
633+
name="x",
634+
),
635+
Parameter(
636+
type=Type(
637+
typename=PQName(
638+
segments=[
639+
FundamentalSpecifier(name="float")
640+
]
641+
)
642+
),
643+
name="y",
644+
),
645+
],
646+
classname=PQName(segments=[NameSpecifier(name="Fred")]),
647+
)
648+
),
649+
name="FredMemFn",
650+
)
651+
]
652+
)
653+
)
654+
655+
609656
def test_typedef_const() -> None:
610657
content = """
611658
typedef int theint, *const ptheint;

tests/test_typefmt.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,36 @@
204204
"int (*)(int)",
205205
"int (* name)(int)",
206206
),
207+
(
208+
Pointer(
209+
ptr_to=FunctionType(
210+
return_type=Type(
211+
typename=PQName(segments=[FundamentalSpecifier(name="int")])
212+
),
213+
parameters=[
214+
Parameter(
215+
type=Type(
216+
typename=PQName(
217+
segments=[FundamentalSpecifier(name="char")]
218+
)
219+
),
220+
name="x",
221+
),
222+
Parameter(
223+
type=Type(
224+
typename=PQName(
225+
segments=[FundamentalSpecifier(name="float")]
226+
)
227+
),
228+
name="y",
229+
),
230+
],
231+
classname=PQName(segments=[NameSpecifier(name="Fred")]),
232+
)
233+
),
234+
"int (Fred::*)(char x, float y)",
235+
"int (Fred::* name)(char x, float y)",
236+
),
207237
(
208238
Type(
209239
typename=PQName(

tests/test_var.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,52 @@ def test_var_ref_to_array_grouped_declarator() -> None:
418418
)
419419

420420

421+
def test_var_member_fnptr_with_initializer() -> None:
422+
content = """
423+
int (Calculator::*funcPtr)(int) = &Calculator::multiply;
424+
"""
425+
data = parse_string(content, cleandoc=True)
426+
427+
assert data == ParsedData(
428+
namespace=NamespaceScope(
429+
variables=[
430+
Variable(
431+
name=PQName(segments=[NameSpecifier(name="funcPtr")]),
432+
type=Pointer(
433+
ptr_to=FunctionType(
434+
return_type=Type(
435+
typename=PQName(
436+
segments=[FundamentalSpecifier(name="int")]
437+
)
438+
),
439+
parameters=[
440+
Parameter(
441+
type=Type(
442+
typename=PQName(
443+
segments=[FundamentalSpecifier(name="int")]
444+
)
445+
)
446+
)
447+
],
448+
classname=PQName(
449+
segments=[NameSpecifier(name="Calculator")]
450+
),
451+
)
452+
),
453+
value=Value(
454+
tokens=[
455+
Token(value="&"),
456+
Token(value="Calculator"),
457+
Token(value="::"),
458+
Token(value="multiply"),
459+
]
460+
),
461+
)
462+
]
463+
)
464+
)
465+
466+
421467
def test_var_fnptr_moreparens() -> None:
422468
content = """
423469
void (*x)(int(p1), int);

0 commit comments

Comments
 (0)