Skip to content

Commit 9883a4e

Browse files
authored
Merge pull request #80 from robotpy/cpp20
Add various C++20 features
2 parents e23c4db + 37cd3ab commit 9883a4e

File tree

7 files changed

+1431
-19
lines changed

7 files changed

+1431
-19
lines changed

cxxheaderparser/lexer.py

Lines changed: 4 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",
@@ -186,6 +188,7 @@ class PlyLexer:
186188
"DBL_RBRACKET",
187189
"DBL_COLON",
188190
"DBL_AMP",
191+
"DBL_PIPE",
189192
"ARROW",
190193
"SHIFT_LEFT",
191194
] + list(keywords)
@@ -471,6 +474,7 @@ def t_PP_DIRECTIVE(self, t: LexToken):
471474
t_DBL_RBRACKET = r"\]\]"
472475
t_DBL_COLON = r"::"
473476
t_DBL_AMP = r"&&"
477+
t_DBL_PIPE = r"\|\|"
474478
t_ARROW = r"->"
475479
t_SHIFT_LEFT = r"<<"
476480
# SHIFT_RIGHT introduces ambiguity

cxxheaderparser/parser.py

Lines changed: 203 additions & 19 deletions
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(
@@ -605,9 +606,13 @@ def _parse_template_decl(self) -> TemplateDecl:
605606
lex.return_token(ptok)
606607
param = self._parse_template_type_parameter(tok, None)
607608
else:
608-
param = self._parse_parameter(ptok, TemplateNonTypeParam, ">")
609+
param, _ = self._parse_parameter(
610+
ptok, TemplateNonTypeParam, False, ">"
611+
)
609612
else:
610-
param = self._parse_parameter(tok, TemplateNonTypeParam, ">")
613+
param, _ = self._parse_parameter(
614+
tok, TemplateNonTypeParam, concept_ok=False, end=">"
615+
)
611616

612617
params.append(param)
613618

@@ -640,6 +645,11 @@ def _parse_template(self, tok: LexToken, doxygen: typing.Optional[str]) -> None:
640645
self._parse_using(tok, doxygen, template)
641646
elif tok.type == "friend":
642647
self._parse_friend_decl(tok, doxygen, template)
648+
elif tok.type == "concept":
649+
self._parse_concept(tok, doxygen, template)
650+
elif tok.type == "requires":
651+
template.raw_requires_pre = self._parse_requires(tok)
652+
self._parse_declarations(self.lex.token(), doxygen, template)
643653
else:
644654
self._parse_declarations(tok, doxygen, template)
645655

@@ -750,6 +760,117 @@ def _parse_template_instantiation(
750760
self.state, TemplateInst(typename, extern, doxygen)
751761
)
752762

763+
def _parse_concept(
764+
self,
765+
tok: LexToken,
766+
doxygen: typing.Optional[str],
767+
template: TemplateDecl,
768+
) -> None:
769+
name = self._next_token_must_be("NAME")
770+
self._next_token_must_be("=")
771+
772+
# not trying to understand this for now
773+
raw_constraint = self._create_value(self._consume_value_until([], ",", ";"))
774+
775+
state = self.state
776+
if isinstance(state, ClassBlockState):
777+
raise CxxParseError("concept cannot be defined in a class")
778+
779+
self.visitor.on_concept(
780+
state,
781+
Concept(
782+
template=template,
783+
name=name.value,
784+
raw_constraint=raw_constraint,
785+
doxygen=doxygen,
786+
),
787+
)
788+
789+
# fmt: off
790+
_expr_operators = {
791+
"<", ">", "|", "%", "^", "!", "*", "-", "+", "&", "=",
792+
"&&", "||", "<<"
793+
}
794+
# fmt: on
795+
796+
def _parse_requires(
797+
self,
798+
tok: LexToken,
799+
) -> Value:
800+
tok = self.lex.token()
801+
802+
rawtoks: typing.List[LexToken] = []
803+
804+
# The easier case -- requires requires
805+
if tok.type == "requires":
806+
rawtoks.append(tok)
807+
for tt in ("(", "{"):
808+
tok = self._next_token_must_be(tt)
809+
rawtoks.extend(self._consume_balanced_tokens(tok))
810+
# .. and that's it?
811+
812+
# this is either a parenthesized expression or a primary clause
813+
elif tok.type == "(":
814+
rawtoks.extend(self._consume_balanced_tokens(tok))
815+
else:
816+
while True:
817+
if tok.type == "(":
818+
rawtoks.extend(self._consume_balanced_tokens(tok))
819+
else:
820+
tok = self._parse_requires_segment(tok, rawtoks)
821+
822+
# If this is not an operator of some kind, we don't know how
823+
# to proceed so let the next parser figure it out
824+
if tok.value not in self._expr_operators:
825+
break
826+
827+
rawtoks.append(tok)
828+
829+
# check once more for compound operator?
830+
tok = self.lex.token()
831+
if tok.value in self._expr_operators:
832+
rawtoks.append(tok)
833+
tok = self.lex.token()
834+
835+
self.lex.return_token(tok)
836+
837+
return self._create_value(rawtoks)
838+
839+
def _parse_requires_segment(
840+
self, tok: LexToken, rawtoks: typing.List[LexToken]
841+
) -> LexToken:
842+
# first token could be a name or ::
843+
if tok.type == "DBL_COLON":
844+
rawtoks.append(tok)
845+
tok = self.lex.token()
846+
847+
while True:
848+
# This token has to be a name or some other valid name-like thing
849+
if tok.value == "decltype":
850+
rawtoks.append(tok)
851+
tok = self._next_token_must_be("(")
852+
rawtoks.extend(self._consume_balanced_tokens(tok))
853+
elif tok.type == "NAME":
854+
rawtoks.append(tok)
855+
else:
856+
# not sure what I expected, but I didn't find it
857+
raise self._parse_error(tok)
858+
859+
tok = self.lex.token()
860+
861+
# Maybe there's a specialization
862+
if tok.value == "<":
863+
rawtoks.extend(self._consume_balanced_tokens(tok))
864+
tok = self.lex.token()
865+
866+
# Maybe we keep trying to parse this name
867+
if tok.type == "DBL_COLON":
868+
tok = self.lex.token()
869+
continue
870+
871+
# Let the caller decide
872+
return tok
873+
753874
#
754875
# Attributes
755876
#
@@ -1615,23 +1736,43 @@ def _parse_pqname(
16151736
#
16161737

16171738
def _parse_parameter(
1618-
self, tok: typing.Optional[LexToken], cls: typing.Type[PT], end: str = ")"
1619-
) -> PT:
1739+
self,
1740+
tok: typing.Optional[LexToken],
1741+
cls: typing.Type[PT],
1742+
concept_ok: bool,
1743+
end: str = ")",
1744+
) -> typing.Tuple[PT, typing.Optional[Type]]:
16201745
"""
16211746
Parses a single parameter (excluding vararg parameters). Also used
16221747
to parse template non-type parameters
1748+
1749+
Returns parameter type, abbreviated template type
16231750
"""
16241751

16251752
param_name = None
16261753
default = None
16271754
param_pack = False
1755+
parsed_type: typing.Optional[Type]
1756+
at_type: typing.Optional[Type] = None
16281757

1629-
# required typename + decorators
1630-
parsed_type, mods = self._parse_type(tok)
1631-
if parsed_type is None:
1632-
raise self._parse_error(None)
1758+
if not tok:
1759+
tok = self.lex.token()
1760+
1761+
# placeholder type, skip typename
1762+
if tok.type == "auto":
1763+
at_type = parsed_type = Type(PQName([AutoSpecifier()]))
1764+
else:
1765+
# required typename + decorators
1766+
parsed_type, mods = self._parse_type(tok)
1767+
if parsed_type is None:
1768+
raise self._parse_error(None)
1769+
1770+
mods.validate(var_ok=False, meth_ok=False, msg="parsing parameter")
16331771

1634-
mods.validate(var_ok=False, meth_ok=False, msg="parsing parameter")
1772+
# Could be a concept
1773+
if concept_ok and self.lex.token_if("auto"):
1774+
at_type = Type(parsed_type.typename)
1775+
parsed_type.typename = PQName([AutoSpecifier()])
16351776

16361777
dtype = self._parse_cv_ptr(parsed_type)
16371778

@@ -1659,32 +1800,50 @@ def _parse_parameter(
16591800
if self.lex.token_if("="):
16601801
default = self._create_value(self._consume_value_until([], ",", end))
16611802

1803+
# abbreviated template pack
1804+
if at_type and self.lex.token_if("ELLIPSIS"):
1805+
param_pack = True
1806+
16621807
param = cls(type=dtype, name=param_name, default=default, param_pack=param_pack)
16631808
self.debug_print("parameter: %s", param)
1664-
return param
1809+
return param, at_type
16651810

1666-
def _parse_parameters(self) -> typing.Tuple[typing.List[Parameter], bool]:
1811+
def _parse_parameters(
1812+
self, concept_ok: bool
1813+
) -> typing.Tuple[typing.List[Parameter], bool, typing.List[TemplateParam]]:
16671814
"""
1668-
Consumes function parameters and returns them, and vararg if found
1815+
Consumes function parameters and returns them, and vararg if found, and
1816+
promotes abbreviated template parameters to actual template parameters
1817+
if concept_ok is True
16691818
"""
16701819

16711820
# starting at a (
16721821

16731822
# special case: zero parameters
16741823
if self.lex.token_if(")"):
1675-
return [], False
1824+
return [], False, []
16761825

16771826
params: typing.List[Parameter] = []
16781827
vararg = False
1828+
at_params: typing.List[TemplateParam] = []
16791829

16801830
while True:
16811831
if self.lex.token_if("ELLIPSIS"):
16821832
vararg = True
16831833
self._next_token_must_be(")")
16841834
break
16851835

1686-
param = self._parse_parameter(None, Parameter)
1836+
param, at_type = self._parse_parameter(None, Parameter, concept_ok)
16871837
params.append(param)
1838+
if at_type:
1839+
at_params.append(
1840+
TemplateNonTypeParam(
1841+
type=at_type,
1842+
param_idx=len(params) - 1,
1843+
param_pack=param.param_pack,
1844+
)
1845+
)
1846+
16881847
tok = self._next_token_must_be(",", ")")
16891848
if tok.value == ")":
16901849
break
@@ -1699,7 +1858,7 @@ def _parse_parameters(self) -> typing.Tuple[typing.List[Parameter], bool]:
16991858
):
17001859
params = []
17011860

1702-
return params, vararg
1861+
return params, vararg, at_params
17031862

17041863
_auto_return_typename = PQName([AutoSpecifier()])
17051864

@@ -1745,6 +1904,15 @@ def _parse_fn_end(self, fn: Function) -> None:
17451904
if otok:
17461905
toks = self._consume_balanced_tokens(otok)[1:-1]
17471906
fn.noexcept = self._create_value(toks)
1907+
else:
1908+
rtok = self.lex.token_if("requires")
1909+
if rtok:
1910+
fn_template = fn.template
1911+
if fn_template is None:
1912+
raise self._parse_error(rtok)
1913+
elif isinstance(fn_template, list):
1914+
fn_template = fn_template[0]
1915+
fn_template.raw_requires_post = self._parse_requires(rtok)
17481916

17491917
if self.lex.token_if("{"):
17501918
self._discard_contents("{", "}")
@@ -1805,6 +1973,13 @@ def _parse_method_end(self, method: Method) -> None:
18051973
if otok:
18061974
toks = self._consume_balanced_tokens(otok)[1:-1]
18071975
method.noexcept = self._create_value(toks)
1976+
elif tok_value == "requires":
1977+
method_template = method.template
1978+
if method_template is None:
1979+
raise self._parse_error(tok)
1980+
elif isinstance(method_template, list):
1981+
method_template = method_template[0]
1982+
method_template.raw_requires_post = self._parse_requires(tok)
18081983
else:
18091984
self.lex.return_token(tok)
18101985
break
@@ -1846,7 +2021,16 @@ def _parse_function(
18462021
state.location = location
18472022
is_class_block = isinstance(state, ClassBlockState)
18482023

1849-
params, vararg = self._parse_parameters()
2024+
params, vararg, at_params = self._parse_parameters(True)
2025+
2026+
# Promote abbreviated template parameters
2027+
if at_params:
2028+
if template is None:
2029+
template = TemplateDecl(at_params)
2030+
elif isinstance(template, TemplateDecl):
2031+
template.params.extend(at_params)
2032+
else:
2033+
template[-1].params.extend(at_params)
18502034

18512035
# A method outside of a class has multiple name segments
18522036
multiple_name_segments = len(pqname.segments) > 1
@@ -2019,7 +2203,7 @@ def _parse_cv_ptr_or_fn(
20192203
toks = self._consume_balanced_tokens(gtok)
20202204
self.lex.return_tokens(toks[1:-1])
20212205

2022-
fn_params, vararg = self._parse_parameters()
2206+
fn_params, vararg, _ = self._parse_parameters(False)
20232207

20242208
assert not isinstance(dtype, FunctionType)
20252209
dtype = dtype_fn = FunctionType(dtype, fn_params, vararg)
@@ -2047,7 +2231,7 @@ def _parse_cv_ptr_or_fn(
20472231
assert not isinstance(dtype, FunctionType)
20482232
dtype = self._parse_array_type(aptok, dtype)
20492233
elif aptok.type == "(":
2050-
fn_params, vararg = self._parse_parameters()
2234+
fn_params, vararg, _ = self._parse_parameters(False)
20512235
# the type we already have is the return type of the function pointer
20522236

20532237
assert not isinstance(dtype, FunctionType)

0 commit comments

Comments
 (0)