From 2192a92003ddbc8bf89f3e1bdfcc79491a0a7fae Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 6 Dec 2022 07:46:16 -0500 Subject: [PATCH 1/3] Adjust testcase generator to remove unneeded whitespace --- cxxheaderparser/gentest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cxxheaderparser/gentest.py b/cxxheaderparser/gentest.py index d2d8f25..0eac123 100644 --- a/cxxheaderparser/gentest.py +++ b/cxxheaderparser/gentest.py @@ -60,14 +60,14 @@ def gentest(infile: str, name: str, outfile: str, verbose: bool) -> None: stmt = nondefault_repr(data) - content = content.replace("\n", "\n ") + content = ("\n" + content.strip()).replace("\n", "\n ") + content = "\n".join(l.rstrip() for l in content.splitlines()) stmt = inspect.cleandoc( f''' def test_{name}() -> None: - content = """ - {content} + content = """{content} """ data = parse_string(content, cleandoc=True) From f1c1f2e6af926c67eafb03c97ed578803926215e Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 6 Dec 2022 07:46:37 -0500 Subject: [PATCH 2/3] Add misc tests added to CppHeaderParser --- tests/test_class.py | 214 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_using.py | 168 ++++++++++++++++++++++++++++++++++ 2 files changed, 382 insertions(+) diff --git a/tests/test_class.py b/tests/test_class.py index 599afb0..ed60f26 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -2998,3 +2998,217 @@ def test_class_mutable() -> None: ] ) ) + + +def test_nested_class_access() -> None: + content = """ + class Outer { + struct Inner { + void fn(); + }; + + void ofn(); + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="Outer")], classkey="class" + ) + ), + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="Inner")], + classkey="struct", + ), + access="private", + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="fn")]), + parameters=[], + access="public", + ) + ], + ) + ], + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="ofn")]), + parameters=[], + access="private", + ) + ], + ) + ] + ) + ) + + +def test_class_with_typedef() -> None: + content = """ + template class A { + public: + typedef B C; + + A(); + + protected: + C aCInstance; + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="A")], classkey="class" + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="SomeType")] + ), + ), + fields=[ + Field( + access="protected", + type=Type( + typename=PQName(segments=[NameSpecifier(name="C")]) + ), + name="aCInstance", + ) + ], + methods=[ + Method( + return_type=None, + name=PQName(segments=[NameSpecifier(name="A")]), + parameters=[], + access="public", + constructor=True, + ) + ], + typedefs=[ + Typedef( + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="B", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="SomeType" + ) + ] + ) + ) + ) + ] + ), + ) + ] + ) + ), + name="C", + access="public", + ) + ], + ) + ] + ) + ) + + +def test_class_ref_qualifiers() -> None: + content = """ + struct X { + void fn0(); + void fn1() &; + void fn2() &&; + void fn3() && = 0; + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="X")], classkey="struct" + ) + ), + methods=[ + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="fn0")]), + parameters=[], + access="public", + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="fn1")]), + parameters=[], + access="public", + ref_qualifier="&", + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="fn2")]), + parameters=[], + access="public", + ref_qualifier="&&", + ), + Method( + return_type=Type( + typename=PQName( + segments=[FundamentalSpecifier(name="void")] + ) + ), + name=PQName(segments=[NameSpecifier(name="fn3")]), + parameters=[], + access="public", + ref_qualifier="&&", + pure_virtual=True, + ), + ], + ) + ] + ) + ) diff --git a/tests/test_using.py b/tests/test_using.py index ca1d76e..06a355f 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -548,3 +548,171 @@ def test_using_many_things() -> None: }, ) ) + + +def test_using_template_in_class() -> None: + content = """ + class X { + template + using TT = U; + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="X")], classkey="class" + ) + ), + using_alias=[ + UsingAlias( + alias="TT", + type=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="U", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="T" + ) + ] + ) + ) + ) + ] + ), + ) + ] + ) + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="typename", name="T")] + ), + access="private", + ) + ], + ) + ] + ) + ) + + +def test_using_typename_in_class() -> None: + content = """ + template class P { + using A = typename f::TP::A; + public: + using State = typename f::TP::S; + P(State st); + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + classes=[ + ClassScope( + class_decl=ClassDecl( + typename=PQName( + segments=[NameSpecifier(name="P")], classkey="class" + ), + template=TemplateDecl( + params=[TemplateTypeParam(typekey="class", name="D")] + ), + ), + methods=[ + Method( + return_type=None, + name=PQName(segments=[NameSpecifier(name="P")]), + parameters=[ + Parameter( + type=Type( + typename=PQName( + segments=[NameSpecifier(name="State")] + ) + ), + name="st", + ) + ], + access="public", + constructor=True, + ) + ], + using_alias=[ + UsingAlias( + alias="A", + type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="f"), + NameSpecifier( + name="TP", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="D" + ) + ] + ) + ) + ) + ] + ), + ), + NameSpecifier(name="A"), + ], + has_typename=True, + ) + ), + access="private", + ), + UsingAlias( + alias="State", + type=Type( + typename=PQName( + segments=[ + NameSpecifier(name="f"), + NameSpecifier( + name="TP", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Type( + typename=PQName( + segments=[ + NameSpecifier( + name="D" + ) + ] + ) + ) + ) + ] + ), + ), + NameSpecifier(name="S"), + ], + has_typename=True, + ) + ), + access="public", + ), + ], + ) + ] + ) + ) From ddad7cb6b1a37c42736f69ee7e7d49899c767bfe Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 6 Dec 2022 08:44:28 -0500 Subject: [PATCH 3/3] Add support for parsing explicit template instantiation --- cxxheaderparser/parser.py | 60 +++++++++++-- cxxheaderparser/simple.py | 8 ++ cxxheaderparser/types.py | 17 ++++ cxxheaderparser/visitor.py | 6 ++ tests/test_template.py | 173 +++++++++++++++++++++++++++++++++++++ 5 files changed, 256 insertions(+), 8 deletions(-) diff --git a/cxxheaderparser/parser.py b/cxxheaderparser/parser.py index 31b51ff..1703e00 100644 --- a/cxxheaderparser/parser.py +++ b/cxxheaderparser/parser.py @@ -43,6 +43,7 @@ from .types import ( Reference, TemplateArgument, TemplateDecl, + TemplateInst, TemplateNonTypeParam, TemplateParam, TemplateSpecialization, @@ -406,16 +407,21 @@ class CxxParser: def _parse_extern(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: - etok = self.lex.token_if("STRING_LITERAL") + etok = self.lex.token_if("STRING_LITERAL", "template") if etok: - if self.lex.token_if("{"): - state = self._push_state(ExternBlockState, etok.value) - state.location = tok.location - self.visitor.on_extern_block_start(state) - return + if etok.type == "STRING_LITERAL": + if self.lex.token_if("{"): + state = self._push_state(ExternBlockState, etok.value) + state.location = tok.location + self.visitor.on_extern_block_start(state) + return - # an extern variable/function with specific linkage - self.lex.return_token(etok) + # an extern variable/function with specific linkage + self.lex.return_token(etok) + else: + # must be an extern template instantitation + self._parse_template_instantiation(doxygen, True) + return self._parse_declarations(tok, doxygen) @@ -547,6 +553,9 @@ class CxxParser: return TemplateDecl(params) def _parse_template(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: + if not self.lex.token_peek_if("<"): + self._parse_template_instantiation(doxygen, False) + return template = self._parse_template_decl() @@ -624,6 +633,41 @@ class CxxParser: return TemplateSpecialization(args) + def _parse_template_instantiation( + self, doxygen: typing.Optional[str], extern: bool + ): + """ + explicit-instantiation: [extern] template declaration + """ + + # entry is right after template + + tok = self.lex.token_if("class", "struct") + if not tok: + raise self._parse_error(tok) + + atok = self.lex.token_if_in_set(self._attribute_start_tokens) + if atok: + self._consume_attribute(atok) + + typename, _ = self._parse_pqname(None) + + # the last segment must have a specialization + last_segment = typename.segments[-1] + if ( + not isinstance(last_segment, NameSpecifier) + or not last_segment.specialization + ): + raise self._parse_error( + None, "expected extern template to have specialization" + ) + + self._next_token_must_be(";") + + self.visitor.on_template_inst( + self.state, TemplateInst(typename, extern, doxygen) + ) + # # Attributes # diff --git a/cxxheaderparser/simple.py b/cxxheaderparser/simple.py index 1602654..35832c3 100644 --- a/cxxheaderparser/simple.py +++ b/cxxheaderparser/simple.py @@ -39,6 +39,7 @@ from .types import ( FriendDecl, Function, Method, + TemplateInst, Typedef, UsingAlias, UsingDecl, @@ -102,6 +103,9 @@ class NamespaceScope: using_ns: typing.List["UsingNamespace"] = field(default_factory=list) using_alias: typing.List[UsingAlias] = field(default_factory=list) + #: Explicit template instantiations + template_insts: typing.List[TemplateInst] = field(default_factory=list) + #: Child namespaces namespaces: typing.Dict[str, "NamespaceScope"] = field(default_factory=dict) @@ -248,6 +252,10 @@ class SimpleCxxVisitor: def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: self.block.forward_decls.append(fdecl) + def on_template_inst(self, state: State, inst: TemplateInst) -> None: + assert isinstance(self.block, NamespaceScope) + self.block.template_insts.append(inst) + def on_variable(self, state: State, v: Variable) -> None: assert isinstance(self.block, NamespaceScope) self.block.variables.append(v) diff --git a/cxxheaderparser/types.py b/cxxheaderparser/types.py index 2b6bdb4..03e107b 100644 --- a/cxxheaderparser/types.py +++ b/cxxheaderparser/types.py @@ -390,6 +390,23 @@ class TemplateDecl: params: typing.List[TemplateParam] = field(default_factory=list) +@dataclass +class TemplateInst: + """ + Explicit template instantiation + + .. code-block:: c++ + + template class MyClass<1,2>; + + extern template class MyClass<2,3>; + """ + + typename: PQName + extern: bool + doxygen: typing.Optional[str] = None + + @dataclass class ForwardDecl: """ diff --git a/cxxheaderparser/visitor.py b/cxxheaderparser/visitor.py index 828af5a..99ff169 100644 --- a/cxxheaderparser/visitor.py +++ b/cxxheaderparser/visitor.py @@ -14,6 +14,7 @@ from .types import ( FriendDecl, Function, Method, + TemplateInst, Typedef, UsingAlias, UsingDecl, @@ -94,6 +95,11 @@ class CxxVisitor(Protocol): Called when a forward declaration is encountered """ + def on_template_inst(self, state: State, inst: TemplateInst) -> None: + """ + Called when an explicit template instantiation is encountered + """ + def on_variable(self, state: State, v: Variable) -> None: ... diff --git a/tests/test_template.py b/tests/test_template.py index 6a9e246..4bea435 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -18,6 +18,7 @@ from cxxheaderparser.types import ( Reference, TemplateArgument, TemplateDecl, + TemplateInst, TemplateNonTypeParam, TemplateSpecialization, TemplateTypeParam, @@ -1814,3 +1815,175 @@ def test_template_specialized_fn_typename_template() -> None: ] ) ) + + +def test_template_instantiation() -> None: + content = """ + template class MyClass<1,2>; + template class __attribute__(("something")) MyClass<3,4>; + + namespace foo { + template class MyClass<5,6>; + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + template_insts=[ + TemplateInst( + typename=PQName( + segments=[ + NameSpecifier( + name="MyClass", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Value(tokens=[Token(value="1")]) + ), + TemplateArgument( + arg=Value(tokens=[Token(value="2")]) + ), + ] + ), + ) + ] + ), + extern=False, + ), + TemplateInst( + typename=PQName( + segments=[ + NameSpecifier( + name="MyClass", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Value(tokens=[Token(value="3")]) + ), + TemplateArgument( + arg=Value(tokens=[Token(value="4")]) + ), + ] + ), + ) + ] + ), + extern=False, + ), + ], + namespaces={ + "foo": NamespaceScope( + name="foo", + template_insts=[ + TemplateInst( + typename=PQName( + segments=[ + NameSpecifier( + name="MyClass", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Value(tokens=[Token(value="5")]) + ), + TemplateArgument( + arg=Value(tokens=[Token(value="6")]) + ), + ] + ), + ) + ] + ), + extern=False, + ) + ], + ) + }, + ) + ) + + +def test_extern_template() -> None: + content = """ + extern template class MyClass<1,2>; + extern template class __attribute__(("something")) MyClass<3,4>; + + namespace foo { + extern template class MyClass<5,6>; + }; + """ + data = parse_string(content, cleandoc=True) + + assert data == ParsedData( + namespace=NamespaceScope( + template_insts=[ + TemplateInst( + typename=PQName( + segments=[ + NameSpecifier( + name="MyClass", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Value(tokens=[Token(value="1")]) + ), + TemplateArgument( + arg=Value(tokens=[Token(value="2")]) + ), + ] + ), + ) + ] + ), + extern=True, + ), + TemplateInst( + typename=PQName( + segments=[ + NameSpecifier( + name="MyClass", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Value(tokens=[Token(value="3")]) + ), + TemplateArgument( + arg=Value(tokens=[Token(value="4")]) + ), + ] + ), + ) + ] + ), + extern=True, + ), + ], + namespaces={ + "foo": NamespaceScope( + name="foo", + template_insts=[ + TemplateInst( + typename=PQName( + segments=[ + NameSpecifier( + name="MyClass", + specialization=TemplateSpecialization( + args=[ + TemplateArgument( + arg=Value(tokens=[Token(value="5")]) + ), + TemplateArgument( + arg=Value(tokens=[Token(value="6")]) + ), + ] + ), + ) + ] + ), + extern=True, + ) + ], + ) + }, + ) + )