From ddad7cb6b1a37c42736f69ee7e7d49899c767bfe Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Tue, 6 Dec 2022 08:44:28 -0500 Subject: [PATCH] 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, + ) + ], + ) + }, + ) + )