Skip to content

Commit e935959

Browse files
committed
Implement basic concept parsing
- requires clauses are collected into a Value and not interpreted at this time
1 parent e23c4db commit e935959

File tree

6 files changed

+479
-1
lines changed

6 files changed

+479
-1
lines changed

cxxheaderparser/lexer.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ class PlyLexer:
8383
"char16_t",
8484
"char32_t",
8585
"class",
86+
"concept",
8687
"const",
8788
"constexpr",
8889
"const_cast",
@@ -121,6 +122,7 @@ class PlyLexer:
121122
"public",
122123
"register",
123124
"reinterpret_cast",
125+
"requires",
124126
"return",
125127
"short",
126128
"signed",

cxxheaderparser/parser.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
AutoSpecifier,
2323
BaseClass,
2424
ClassDecl,
25+
Concept,
2526
DecltypeSpecifier,
2627
DecoratedType,
2728
EnumDecl,
@@ -537,7 +538,7 @@ def _on_block_end(self, tok: LexToken, doxygen: typing.Optional[str]) -> None:
537538
self._finish_class_decl(old_state)
538539

539540
#
540-
# Template parsing
541+
# Template and concept parsing
541542
#
542543

543544
def _parse_template_type_parameter(
@@ -640,6 +641,8 @@ def _parse_template(self, tok: LexToken, doxygen: typing.Optional[str]) -> None:
640641
self._parse_using(tok, doxygen, template)
641642
elif tok.type == "friend":
642643
self._parse_friend_decl(tok, doxygen, template)
644+
elif tok.type == "concept":
645+
self._parse_concept(tok, doxygen, template)
643646
else:
644647
self._parse_declarations(tok, doxygen, template)
645648

@@ -750,6 +753,32 @@ def _parse_template_instantiation(
750753
self.state, TemplateInst(typename, extern, doxygen)
751754
)
752755

756+
def _parse_concept(
757+
self,
758+
tok: LexToken,
759+
doxygen: typing.Optional[str],
760+
template: TemplateDecl,
761+
) -> None:
762+
name = self._next_token_must_be("NAME")
763+
self._next_token_must_be("=")
764+
765+
# not trying to understand this for now
766+
raw_constraint = self._create_value(self._consume_value_until([], ",", ";"))
767+
768+
state = self.state
769+
if isinstance(state, ClassBlockState):
770+
raise CxxParseError("concept cannot be defined in a class")
771+
772+
self.visitor.on_concept(
773+
state,
774+
Concept(
775+
template=template,
776+
name=name.value,
777+
raw_constraint=raw_constraint,
778+
doxygen=doxygen,
779+
),
780+
)
781+
753782
#
754783
# Attributes
755784
#

cxxheaderparser/simple.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
from .types import (
3636
ClassDecl,
37+
Concept,
3738
EnumDecl,
3839
Field,
3940
ForwardDecl,
@@ -113,6 +114,9 @@ class NamespaceScope:
113114
using_alias: typing.List[UsingAlias] = field(default_factory=list)
114115
ns_alias: typing.List[NamespaceAlias] = field(default_factory=list)
115116

117+
#: Concepts
118+
concepts: typing.List[Concept] = field(default_factory=list)
119+
116120
#: Explicit template instantiations
117121
template_insts: typing.List[TemplateInst] = field(default_factory=list)
118122

@@ -243,6 +247,9 @@ def on_namespace_start(self, state: SNamespaceBlockState) -> typing.Optional[boo
243247
def on_namespace_end(self, state: SNamespaceBlockState) -> None:
244248
pass
245249

250+
def on_concept(self, state: SNonClassBlockState, concept: Concept) -> None:
251+
state.user_data.concepts.append(concept)
252+
246253
def on_namespace_alias(
247254
self, state: SNonClassBlockState, alias: NamespaceAlias
248255
) -> None:

cxxheaderparser/types.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,31 @@ class TemplateInst:
551551
doxygen: typing.Optional[str] = None
552552

553553

554+
@dataclass
555+
class Concept:
556+
"""
557+
Preliminary support for consuming headers that contain concepts, but
558+
not trying to actually make sense of them at this time. If this is
559+
something you care about, pull requests are welcomed!
560+
561+
.. code-block:: c++
562+
563+
template <class T>
564+
concept Meowable = is_meowable<T>;
565+
566+
template<typename T>
567+
concept Addable = requires (T x) { x + x; };
568+
"""
569+
570+
template: TemplateDecl
571+
name: str
572+
573+
#: In the future this will be removed if we fully parse the expression
574+
raw_constraint: Value
575+
576+
doxygen: typing.Optional[str] = None
577+
578+
554579
@dataclass
555580
class ForwardDecl:
556581
"""

cxxheaderparser/visitor.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99

1010
from .types import (
11+
Concept,
1112
EnumDecl,
1213
Field,
1314
ForwardDecl,
@@ -89,6 +90,14 @@ def on_namespace_alias(
8990
Called when a ``namespace`` alias is encountered
9091
"""
9192

93+
def on_concept(self, state: NonClassBlockState, concept: Concept) -> None:
94+
"""
95+
.. code-block:: c++
96+
97+
template <class T>
98+
concept Meowable = is_meowable<T>;
99+
"""
100+
92101
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
93102
"""
94103
Called when a forward declaration is encountered
@@ -254,6 +263,9 @@ def on_namespace_start(self, state: NamespaceBlockState) -> typing.Optional[bool
254263
def on_namespace_end(self, state: NamespaceBlockState) -> None:
255264
return None
256265

266+
def on_concept(self, state: NonClassBlockState, concept: Concept) -> None:
267+
return None
268+
257269
def on_namespace_alias(
258270
self, state: NonClassBlockState, alias: NamespaceAlias
259271
) -> None:

0 commit comments

Comments
 (0)