You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2118 lines
86 KiB
2118 lines
86 KiB
8 months ago
|
from __future__ import annotations
|
||
|
|
||
|
import re
|
||
|
from typing import TYPE_CHECKING, Any, Callable
|
||
|
|
||
|
from sphinx.domains.cpp._ast import (
|
||
|
ASTAlignofExpr,
|
||
|
ASTArray,
|
||
|
ASTAssignmentExpr,
|
||
|
ASTBaseClass,
|
||
|
ASTBinOpExpr,
|
||
|
ASTBooleanLiteral,
|
||
|
ASTBracedInitList,
|
||
|
ASTCastExpr,
|
||
|
ASTCharLiteral,
|
||
|
ASTClass,
|
||
|
ASTCommaExpr,
|
||
|
ASTConcept,
|
||
|
ASTConditionalExpr,
|
||
|
ASTDeclaration,
|
||
|
ASTDeclarator,
|
||
|
ASTDeclaratorMemPtr,
|
||
|
ASTDeclaratorNameBitField,
|
||
|
ASTDeclaratorNameParamQual,
|
||
|
ASTDeclaratorParamPack,
|
||
|
ASTDeclaratorParen,
|
||
|
ASTDeclaratorPtr,
|
||
|
ASTDeclaratorRef,
|
||
|
ASTDeclSpecs,
|
||
|
ASTDeclSpecsSimple,
|
||
|
ASTDeleteExpr,
|
||
|
ASTEnum,
|
||
|
ASTEnumerator,
|
||
|
ASTExplicitCast,
|
||
|
ASTExplicitSpec,
|
||
|
ASTExpression,
|
||
|
ASTFallbackExpr,
|
||
|
ASTFoldExpr,
|
||
|
ASTFunctionParameter,
|
||
|
ASTIdentifier,
|
||
|
ASTIdExpression,
|
||
|
ASTInitializer,
|
||
|
ASTLiteral,
|
||
|
ASTNamespace,
|
||
|
ASTNestedName,
|
||
|
ASTNestedNameElement,
|
||
|
ASTNewExpr,
|
||
|
ASTNoexceptExpr,
|
||
|
ASTNoexceptSpec,
|
||
|
ASTNumberLiteral,
|
||
|
ASTOperator,
|
||
|
ASTOperatorBuildIn,
|
||
|
ASTOperatorLiteral,
|
||
|
ASTOperatorType,
|
||
|
ASTPackExpansionExpr,
|
||
|
ASTParametersQualifiers,
|
||
|
ASTParenExpr,
|
||
|
ASTParenExprList,
|
||
|
ASTPointerLiteral,
|
||
|
ASTPostfixArray,
|
||
|
ASTPostfixCallExpr,
|
||
|
ASTPostfixDec,
|
||
|
ASTPostfixExpr,
|
||
|
ASTPostfixInc,
|
||
|
ASTPostfixMember,
|
||
|
ASTPostfixMemberOfPointer,
|
||
|
ASTPostfixOp,
|
||
|
ASTRequiresClause,
|
||
|
ASTSizeofExpr,
|
||
|
ASTSizeofParamPack,
|
||
|
ASTSizeofType,
|
||
|
ASTStringLiteral,
|
||
|
ASTTemplateArgConstant,
|
||
|
ASTTemplateArgs,
|
||
|
ASTTemplateDeclarationPrefix,
|
||
|
ASTTemplateIntroduction,
|
||
|
ASTTemplateIntroductionParameter,
|
||
|
ASTTemplateKeyParamPackIdDefault,
|
||
|
ASTTemplateParam,
|
||
|
ASTTemplateParamConstrainedTypeWithInit,
|
||
|
ASTTemplateParamNonType,
|
||
|
ASTTemplateParams,
|
||
|
ASTTemplateParamTemplateType,
|
||
|
ASTTemplateParamType,
|
||
|
ASTThisLiteral,
|
||
|
ASTTrailingTypeSpec,
|
||
|
ASTTrailingTypeSpecDecltype,
|
||
|
ASTTrailingTypeSpecDecltypeAuto,
|
||
|
ASTTrailingTypeSpecFundamental,
|
||
|
ASTTrailingTypeSpecName,
|
||
|
ASTType,
|
||
|
ASTTypeId,
|
||
|
ASTTypeUsing,
|
||
|
ASTTypeWithInit,
|
||
|
ASTUnaryOpExpr,
|
||
|
ASTUnion,
|
||
|
ASTUserDefinedLiteral,
|
||
|
)
|
||
|
from sphinx.domains.cpp._ids import (
|
||
|
_expression_assignment_ops,
|
||
|
_expression_bin_ops,
|
||
|
_expression_unary_ops,
|
||
|
_fold_operator_re,
|
||
|
_id_explicit_cast,
|
||
|
_keywords,
|
||
|
_operator_re,
|
||
|
_simple_type_specifiers_re,
|
||
|
_string_re,
|
||
|
_visibility_re,
|
||
|
udl_identifier_re,
|
||
|
)
|
||
|
from sphinx.util import logging
|
||
|
from sphinx.util.cfamily import (
|
||
|
ASTAttributeList,
|
||
|
BaseParser,
|
||
|
DefinitionError,
|
||
|
UnsupportedMultiCharacterCharLiteral,
|
||
|
binary_literal_re,
|
||
|
char_literal_re,
|
||
|
float_literal_re,
|
||
|
float_literal_suffix_re,
|
||
|
hex_literal_re,
|
||
|
identifier_re,
|
||
|
integer_literal_re,
|
||
|
integers_literal_suffix_re,
|
||
|
octal_literal_re,
|
||
|
)
|
||
|
|
||
|
if TYPE_CHECKING:
|
||
|
from collections.abc import Sequence
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
class DefinitionParser(BaseParser):
|
||
|
@property
|
||
|
def language(self) -> str:
|
||
|
return 'C++'
|
||
|
|
||
|
@property
|
||
|
def id_attributes(self) -> Sequence[str]:
|
||
|
return self.config.cpp_id_attributes
|
||
|
|
||
|
@property
|
||
|
def paren_attributes(self) -> Sequence[str]:
|
||
|
return self.config.cpp_paren_attributes
|
||
|
|
||
|
def _parse_string(self) -> str:
|
||
|
if self.current_char != '"':
|
||
|
return None
|
||
|
startPos = self.pos
|
||
|
self.pos += 1
|
||
|
escape = False
|
||
|
while True:
|
||
|
if self.eof:
|
||
|
self.fail("Unexpected end during inside string.")
|
||
|
elif self.current_char == '"' and not escape:
|
||
|
self.pos += 1
|
||
|
break
|
||
|
elif self.current_char == '\\':
|
||
|
escape = True
|
||
|
else:
|
||
|
escape = False
|
||
|
self.pos += 1
|
||
|
return self.definition[startPos:self.pos]
|
||
|
|
||
|
def _parse_literal(self) -> ASTLiteral:
|
||
|
# -> integer-literal
|
||
|
# | character-literal
|
||
|
# | floating-literal
|
||
|
# | string-literal
|
||
|
# | boolean-literal -> "false" | "true"
|
||
|
# | pointer-literal -> "nullptr"
|
||
|
# | user-defined-literal
|
||
|
|
||
|
def _udl(literal: ASTLiteral) -> ASTLiteral:
|
||
|
if not self.match(udl_identifier_re):
|
||
|
return literal
|
||
|
# hmm, should we care if it's a keyword?
|
||
|
# it looks like GCC does not disallow keywords
|
||
|
ident = ASTIdentifier(self.matched_text)
|
||
|
return ASTUserDefinedLiteral(literal, ident)
|
||
|
|
||
|
self.skip_ws()
|
||
|
if self.skip_word('nullptr'):
|
||
|
return ASTPointerLiteral()
|
||
|
if self.skip_word('true'):
|
||
|
return ASTBooleanLiteral(True)
|
||
|
if self.skip_word('false'):
|
||
|
return ASTBooleanLiteral(False)
|
||
|
pos = self.pos
|
||
|
if self.match(float_literal_re):
|
||
|
hasSuffix = self.match(float_literal_suffix_re)
|
||
|
floatLit = ASTNumberLiteral(self.definition[pos:self.pos])
|
||
|
if hasSuffix:
|
||
|
return floatLit
|
||
|
else:
|
||
|
return _udl(floatLit)
|
||
|
for regex in (binary_literal_re, hex_literal_re,
|
||
|
integer_literal_re, octal_literal_re):
|
||
|
if self.match(regex):
|
||
|
hasSuffix = self.match(integers_literal_suffix_re)
|
||
|
intLit = ASTNumberLiteral(self.definition[pos:self.pos])
|
||
|
if hasSuffix:
|
||
|
return intLit
|
||
|
else:
|
||
|
return _udl(intLit)
|
||
|
|
||
|
string = self._parse_string()
|
||
|
if string is not None:
|
||
|
return _udl(ASTStringLiteral(string))
|
||
|
|
||
|
# character-literal
|
||
|
if self.match(char_literal_re):
|
||
|
prefix = self.last_match.group(1) # may be None when no prefix
|
||
|
data = self.last_match.group(2)
|
||
|
try:
|
||
|
charLit = ASTCharLiteral(prefix, data)
|
||
|
except UnicodeDecodeError as e:
|
||
|
self.fail("Can not handle character literal. Internal error was: %s" % e)
|
||
|
except UnsupportedMultiCharacterCharLiteral:
|
||
|
self.fail("Can not handle character literal"
|
||
|
" resulting in multiple decoded characters.")
|
||
|
return _udl(charLit)
|
||
|
return None
|
||
|
|
||
|
def _parse_fold_or_paren_expression(self) -> ASTExpression | None:
|
||
|
# "(" expression ")"
|
||
|
# fold-expression
|
||
|
# -> ( cast-expression fold-operator ... )
|
||
|
# | ( ... fold-operator cast-expression )
|
||
|
# | ( cast-expression fold-operator ... fold-operator cast-expression
|
||
|
if self.current_char != '(':
|
||
|
return None
|
||
|
self.pos += 1
|
||
|
self.skip_ws()
|
||
|
if self.skip_string_and_ws("..."):
|
||
|
# ( ... fold-operator cast-expression )
|
||
|
if not self.match(_fold_operator_re):
|
||
|
self.fail("Expected fold operator after '...' in fold expression.")
|
||
|
op = self.matched_text
|
||
|
rightExpr = self._parse_cast_expression()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' in end of fold expression.")
|
||
|
return ASTFoldExpr(None, op, rightExpr)
|
||
|
# try first parsing a unary right fold, or a binary fold
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
self.skip_ws()
|
||
|
leftExpr = self._parse_cast_expression()
|
||
|
self.skip_ws()
|
||
|
if not self.match(_fold_operator_re):
|
||
|
self.fail("Expected fold operator after left expression in fold expression.")
|
||
|
op = self.matched_text
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string_and_ws('...'):
|
||
|
self.fail("Expected '...' after fold operator in fold expression.")
|
||
|
except DefinitionError as eFold:
|
||
|
self.pos = pos
|
||
|
# fall back to a paren expression
|
||
|
try:
|
||
|
res = self._parse_expression()
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' in end of parenthesized expression.")
|
||
|
except DefinitionError as eExpr:
|
||
|
raise self._make_multi_error([
|
||
|
(eFold, "If fold expression"),
|
||
|
(eExpr, "If parenthesized expression"),
|
||
|
], "Error in fold expression or parenthesized expression.") from eExpr
|
||
|
return ASTParenExpr(res)
|
||
|
# now it definitely is a fold expression
|
||
|
if self.skip_string(')'):
|
||
|
return ASTFoldExpr(leftExpr, op, None)
|
||
|
if not self.match(_fold_operator_re):
|
||
|
self.fail("Expected fold operator or ')' after '...' in fold expression.")
|
||
|
if op != self.matched_text:
|
||
|
self.fail("Operators are different in binary fold: '%s' and '%s'."
|
||
|
% (op, self.matched_text))
|
||
|
rightExpr = self._parse_cast_expression()
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' to end binary fold expression.")
|
||
|
return ASTFoldExpr(leftExpr, op, rightExpr)
|
||
|
|
||
|
def _parse_primary_expression(self) -> ASTExpression:
|
||
|
# literal
|
||
|
# "this"
|
||
|
# lambda-expression
|
||
|
# "(" expression ")"
|
||
|
# fold-expression
|
||
|
# id-expression -> we parse this with _parse_nested_name
|
||
|
self.skip_ws()
|
||
|
res: ASTExpression = self._parse_literal()
|
||
|
if res is not None:
|
||
|
return res
|
||
|
self.skip_ws()
|
||
|
if self.skip_word("this"):
|
||
|
return ASTThisLiteral()
|
||
|
# TODO: try lambda expression
|
||
|
res = self._parse_fold_or_paren_expression()
|
||
|
if res is not None:
|
||
|
return res
|
||
|
nn = self._parse_nested_name()
|
||
|
if nn is not None:
|
||
|
return ASTIdExpression(nn)
|
||
|
return None
|
||
|
|
||
|
def _parse_initializer_list(self, name: str, open: str, close: str,
|
||
|
) -> tuple[list[ASTExpression | ASTBracedInitList],
|
||
|
bool]:
|
||
|
# Parse open and close with the actual initializer-list in between
|
||
|
# -> initializer-clause '...'[opt]
|
||
|
# | initializer-list ',' initializer-clause '...'[opt]
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string_and_ws(open):
|
||
|
return None, None
|
||
|
if self.skip_string(close):
|
||
|
return [], False
|
||
|
|
||
|
exprs: list[ASTExpression | ASTBracedInitList] = []
|
||
|
trailingComma = False
|
||
|
while True:
|
||
|
self.skip_ws()
|
||
|
expr = self._parse_initializer_clause()
|
||
|
self.skip_ws()
|
||
|
if self.skip_string('...'):
|
||
|
exprs.append(ASTPackExpansionExpr(expr))
|
||
|
else:
|
||
|
exprs.append(expr)
|
||
|
self.skip_ws()
|
||
|
if self.skip_string(close):
|
||
|
break
|
||
|
if not self.skip_string_and_ws(','):
|
||
|
self.fail(f"Error in {name}, expected ',' or '{close}'.")
|
||
|
if self.current_char == close == '}':
|
||
|
self.pos += 1
|
||
|
trailingComma = True
|
||
|
break
|
||
|
return exprs, trailingComma
|
||
|
|
||
|
def _parse_paren_expression_list(self) -> ASTParenExprList:
|
||
|
# -> '(' expression-list ')'
|
||
|
# though, we relax it to also allow empty parens
|
||
|
# as it's needed in some cases
|
||
|
#
|
||
|
# expression-list
|
||
|
# -> initializer-list
|
||
|
exprs, trailingComma = self._parse_initializer_list("parenthesized expression-list",
|
||
|
'(', ')')
|
||
|
if exprs is None:
|
||
|
return None
|
||
|
return ASTParenExprList(exprs)
|
||
|
|
||
|
def _parse_initializer_clause(self) -> ASTExpression | ASTBracedInitList:
|
||
|
bracedInitList = self._parse_braced_init_list()
|
||
|
if bracedInitList is not None:
|
||
|
return bracedInitList
|
||
|
return self._parse_assignment_expression(inTemplate=False)
|
||
|
|
||
|
def _parse_braced_init_list(self) -> ASTBracedInitList:
|
||
|
# -> '{' initializer-list ','[opt] '}'
|
||
|
# | '{' '}'
|
||
|
exprs, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}')
|
||
|
if exprs is None:
|
||
|
return None
|
||
|
return ASTBracedInitList(exprs, trailingComma)
|
||
|
|
||
|
def _parse_expression_list_or_braced_init_list(
|
||
|
self,
|
||
|
) -> ASTParenExprList | ASTBracedInitList:
|
||
|
paren = self._parse_paren_expression_list()
|
||
|
if paren is not None:
|
||
|
return paren
|
||
|
return self._parse_braced_init_list()
|
||
|
|
||
|
def _parse_postfix_expression(self) -> ASTPostfixExpr:
|
||
|
# -> primary
|
||
|
# | postfix "[" expression "]"
|
||
|
# | postfix "[" braced-init-list [opt] "]"
|
||
|
# | postfix "(" expression-list [opt] ")"
|
||
|
# | postfix "." "template" [opt] id-expression
|
||
|
# | postfix "->" "template" [opt] id-expression
|
||
|
# | postfix "." pseudo-destructor-name
|
||
|
# | postfix "->" pseudo-destructor-name
|
||
|
# | postfix "++"
|
||
|
# | postfix "--"
|
||
|
# | simple-type-specifier "(" expression-list [opt] ")"
|
||
|
# | simple-type-specifier braced-init-list
|
||
|
# | typename-specifier "(" expression-list [opt] ")"
|
||
|
# | typename-specifier braced-init-list
|
||
|
# | "dynamic_cast" "<" type-id ">" "(" expression ")"
|
||
|
# | "static_cast" "<" type-id ">" "(" expression ")"
|
||
|
# | "reinterpret_cast" "<" type-id ">" "(" expression ")"
|
||
|
# | "const_cast" "<" type-id ">" "(" expression ")"
|
||
|
# | "typeid" "(" expression ")"
|
||
|
# | "typeid" "(" type-id ")"
|
||
|
|
||
|
prefixType = None
|
||
|
prefix: Any = None
|
||
|
self.skip_ws()
|
||
|
|
||
|
cast = None
|
||
|
for c in _id_explicit_cast:
|
||
|
if self.skip_word_and_ws(c):
|
||
|
cast = c
|
||
|
break
|
||
|
if cast is not None:
|
||
|
prefixType = "cast"
|
||
|
if not self.skip_string("<"):
|
||
|
self.fail("Expected '<' after '%s'." % cast)
|
||
|
typ = self._parse_type(False)
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string_and_ws(">"):
|
||
|
self.fail("Expected '>' after type in '%s'." % cast)
|
||
|
if not self.skip_string("("):
|
||
|
self.fail("Expected '(' in '%s'." % cast)
|
||
|
|
||
|
def parser() -> ASTExpression:
|
||
|
return self._parse_expression()
|
||
|
expr = self._parse_expression_fallback([')'], parser)
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(")"):
|
||
|
self.fail("Expected ')' to end '%s'." % cast)
|
||
|
prefix = ASTExplicitCast(cast, typ, expr)
|
||
|
elif self.skip_word_and_ws("typeid"):
|
||
|
prefixType = "typeid"
|
||
|
if not self.skip_string_and_ws('('):
|
||
|
self.fail("Expected '(' after 'typeid'.")
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
typ = self._parse_type(False)
|
||
|
prefix = ASTTypeId(typ, isType=True)
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' to end 'typeid' of type.")
|
||
|
except DefinitionError as eType:
|
||
|
self.pos = pos
|
||
|
try:
|
||
|
|
||
|
def parser() -> ASTExpression:
|
||
|
return self._parse_expression()
|
||
|
expr = self._parse_expression_fallback([')'], parser)
|
||
|
prefix = ASTTypeId(expr, isType=False)
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' to end 'typeid' of expression.")
|
||
|
except DefinitionError as eExpr:
|
||
|
self.pos = pos
|
||
|
header = "Error in 'typeid(...)'."
|
||
|
header += " Expected type or expression."
|
||
|
errors = []
|
||
|
errors.append((eType, "If type"))
|
||
|
errors.append((eExpr, "If expression"))
|
||
|
raise self._make_multi_error(errors, header) from eExpr
|
||
|
else: # a primary expression or a type
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
prefix = self._parse_primary_expression()
|
||
|
prefixType = 'expr'
|
||
|
except DefinitionError as eOuter:
|
||
|
self.pos = pos
|
||
|
try:
|
||
|
# we are potentially casting, so save parens for us
|
||
|
# TODO: hmm, would we need to try both with operatorCast and with None?
|
||
|
prefix = self._parse_type(False, 'operatorCast')
|
||
|
prefixType = 'typeOperatorCast'
|
||
|
# | simple-type-specifier "(" expression-list [opt] ")"
|
||
|
# | simple-type-specifier braced-init-list
|
||
|
# | typename-specifier "(" expression-list [opt] ")"
|
||
|
# | typename-specifier braced-init-list
|
||
|
self.skip_ws()
|
||
|
if self.current_char != '(' and self.current_char != '{':
|
||
|
self.fail("Expecting '(' or '{' after type in cast expression.")
|
||
|
except DefinitionError as eInner:
|
||
|
self.pos = pos
|
||
|
header = "Error in postfix expression,"
|
||
|
header += " expected primary expression or type."
|
||
|
errors = []
|
||
|
errors.append((eOuter, "If primary expression"))
|
||
|
errors.append((eInner, "If type"))
|
||
|
raise self._make_multi_error(errors, header) from eInner
|
||
|
|
||
|
# and now parse postfixes
|
||
|
postFixes: list[ASTPostfixOp] = []
|
||
|
while True:
|
||
|
self.skip_ws()
|
||
|
if prefixType in ('expr', 'cast', 'typeid'):
|
||
|
if self.skip_string_and_ws('['):
|
||
|
expr = self._parse_expression()
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(']'):
|
||
|
self.fail("Expected ']' in end of postfix expression.")
|
||
|
postFixes.append(ASTPostfixArray(expr))
|
||
|
continue
|
||
|
if self.skip_string('.'):
|
||
|
if self.skip_string('*'):
|
||
|
# don't steal the dot
|
||
|
self.pos -= 2
|
||
|
elif self.skip_string('..'):
|
||
|
# don't steal the dot
|
||
|
self.pos -= 3
|
||
|
else:
|
||
|
name = self._parse_nested_name()
|
||
|
postFixes.append(ASTPostfixMember(name))
|
||
|
continue
|
||
|
if self.skip_string('->'):
|
||
|
if self.skip_string('*'):
|
||
|
# don't steal the arrow
|
||
|
self.pos -= 3
|
||
|
else:
|
||
|
name = self._parse_nested_name()
|
||
|
postFixes.append(ASTPostfixMemberOfPointer(name))
|
||
|
continue
|
||
|
if self.skip_string('++'):
|
||
|
postFixes.append(ASTPostfixInc())
|
||
|
continue
|
||
|
if self.skip_string('--'):
|
||
|
postFixes.append(ASTPostfixDec())
|
||
|
continue
|
||
|
lst = self._parse_expression_list_or_braced_init_list()
|
||
|
if lst is not None:
|
||
|
postFixes.append(ASTPostfixCallExpr(lst))
|
||
|
continue
|
||
|
break
|
||
|
return ASTPostfixExpr(prefix, postFixes)
|
||
|
|
||
|
def _parse_unary_expression(self) -> ASTExpression:
|
||
|
# -> postfix
|
||
|
# | "++" cast
|
||
|
# | "--" cast
|
||
|
# | unary-operator cast -> (* | & | + | - | ! | ~) cast
|
||
|
# The rest:
|
||
|
# | "sizeof" unary
|
||
|
# | "sizeof" "(" type-id ")"
|
||
|
# | "sizeof" "..." "(" identifier ")"
|
||
|
# | "alignof" "(" type-id ")"
|
||
|
# | noexcept-expression -> noexcept "(" expression ")"
|
||
|
# | new-expression
|
||
|
# | delete-expression
|
||
|
self.skip_ws()
|
||
|
for op in _expression_unary_ops:
|
||
|
# TODO: hmm, should we be able to backtrack here?
|
||
|
if op[0] in 'cn':
|
||
|
res = self.skip_word(op)
|
||
|
else:
|
||
|
res = self.skip_string(op)
|
||
|
if res:
|
||
|
expr = self._parse_cast_expression()
|
||
|
return ASTUnaryOpExpr(op, expr)
|
||
|
if self.skip_word_and_ws('sizeof'):
|
||
|
if self.skip_string_and_ws('...'):
|
||
|
if not self.skip_string_and_ws('('):
|
||
|
self.fail("Expecting '(' after 'sizeof...'.")
|
||
|
if not self.match(identifier_re):
|
||
|
self.fail("Expecting identifier for 'sizeof...'.")
|
||
|
ident = ASTIdentifier(self.matched_text)
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(")"):
|
||
|
self.fail("Expecting ')' to end 'sizeof...'.")
|
||
|
return ASTSizeofParamPack(ident)
|
||
|
if self.skip_string_and_ws('('):
|
||
|
typ = self._parse_type(named=False)
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expecting ')' to end 'sizeof'.")
|
||
|
return ASTSizeofType(typ)
|
||
|
expr = self._parse_unary_expression()
|
||
|
return ASTSizeofExpr(expr)
|
||
|
if self.skip_word_and_ws('alignof'):
|
||
|
if not self.skip_string_and_ws('('):
|
||
|
self.fail("Expecting '(' after 'alignof'.")
|
||
|
typ = self._parse_type(named=False)
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expecting ')' to end 'alignof'.")
|
||
|
return ASTAlignofExpr(typ)
|
||
|
if self.skip_word_and_ws('noexcept'):
|
||
|
if not self.skip_string_and_ws('('):
|
||
|
self.fail("Expecting '(' after 'noexcept'.")
|
||
|
expr = self._parse_expression()
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expecting ')' to end 'noexcept'.")
|
||
|
return ASTNoexceptExpr(expr)
|
||
|
# new-expression
|
||
|
pos = self.pos
|
||
|
rooted = self.skip_string('::')
|
||
|
self.skip_ws()
|
||
|
if not self.skip_word_and_ws('new'):
|
||
|
self.pos = pos
|
||
|
else:
|
||
|
# new-placement[opt] new-type-id new-initializer[opt]
|
||
|
# new-placement[opt] ( type-id ) new-initializer[opt]
|
||
|
isNewTypeId = True
|
||
|
if self.skip_string_and_ws('('):
|
||
|
# either this is a new-placement or it's the second production
|
||
|
# without placement, and it's actually the ( type-id ) part
|
||
|
self.fail("Sorry, neither new-placement nor parenthesised type-id "
|
||
|
"in new-epression is supported yet.")
|
||
|
# set isNewTypeId = False if it's (type-id)
|
||
|
if isNewTypeId:
|
||
|
declSpecs = self._parse_decl_specs(outer=None)
|
||
|
decl = self._parse_declarator(named=False, paramMode="new")
|
||
|
else:
|
||
|
self.fail("Sorry, parenthesised type-id in new expression not yet supported.")
|
||
|
lst = self._parse_expression_list_or_braced_init_list()
|
||
|
return ASTNewExpr(rooted, isNewTypeId, ASTType(declSpecs, decl), lst)
|
||
|
# delete-expression
|
||
|
pos = self.pos
|
||
|
rooted = self.skip_string('::')
|
||
|
self.skip_ws()
|
||
|
if not self.skip_word_and_ws('delete'):
|
||
|
self.pos = pos
|
||
|
else:
|
||
|
array = self.skip_string_and_ws('[')
|
||
|
if array and not self.skip_string_and_ws(']'):
|
||
|
self.fail("Expected ']' in array delete-expression.")
|
||
|
expr = self._parse_cast_expression()
|
||
|
return ASTDeleteExpr(rooted, array, expr)
|
||
|
return self._parse_postfix_expression()
|
||
|
|
||
|
def _parse_cast_expression(self) -> ASTExpression:
|
||
|
# -> unary | "(" type-id ")" cast
|
||
|
pos = self.pos
|
||
|
self.skip_ws()
|
||
|
if self.skip_string('('):
|
||
|
try:
|
||
|
typ = self._parse_type(False)
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' in cast expression.")
|
||
|
expr = self._parse_cast_expression()
|
||
|
return ASTCastExpr(typ, expr)
|
||
|
except DefinitionError as exCast:
|
||
|
self.pos = pos
|
||
|
try:
|
||
|
return self._parse_unary_expression()
|
||
|
except DefinitionError as exUnary:
|
||
|
errs = []
|
||
|
errs.append((exCast, "If type cast expression"))
|
||
|
errs.append((exUnary, "If unary expression"))
|
||
|
raise self._make_multi_error(errs,
|
||
|
"Error in cast expression.") from exUnary
|
||
|
else:
|
||
|
return self._parse_unary_expression()
|
||
|
|
||
|
def _parse_logical_or_expression(self, inTemplate: bool) -> ASTExpression:
|
||
|
# logical-or = logical-and ||
|
||
|
# logical-and = inclusive-or &&
|
||
|
# inclusive-or = exclusive-or |
|
||
|
# exclusive-or = and ^
|
||
|
# and = equality &
|
||
|
# equality = relational ==, !=
|
||
|
# relational = shift <, >, <=, >=, <=>
|
||
|
# shift = additive <<, >>
|
||
|
# additive = multiplicative +, -
|
||
|
# multiplicative = pm *, /, %
|
||
|
# pm = cast .*, ->*
|
||
|
def _parse_bin_op_expr(self: DefinitionParser,
|
||
|
opId: int, inTemplate: bool) -> ASTExpression:
|
||
|
if opId + 1 == len(_expression_bin_ops):
|
||
|
def parser(inTemplate: bool) -> ASTExpression:
|
||
|
return self._parse_cast_expression()
|
||
|
else:
|
||
|
def parser(inTemplate: bool) -> ASTExpression:
|
||
|
return _parse_bin_op_expr(self, opId + 1, inTemplate=inTemplate)
|
||
|
exprs = []
|
||
|
ops = []
|
||
|
exprs.append(parser(inTemplate=inTemplate))
|
||
|
while True:
|
||
|
self.skip_ws()
|
||
|
if inTemplate and self.current_char == '>':
|
||
|
break
|
||
|
pos = self.pos
|
||
|
oneMore = False
|
||
|
for op in _expression_bin_ops[opId]:
|
||
|
if op[0] in 'abcnox':
|
||
|
if not self.skip_word(op):
|
||
|
continue
|
||
|
else:
|
||
|
if not self.skip_string(op):
|
||
|
continue
|
||
|
if op == self.current_char == '&':
|
||
|
# don't split the && 'token'
|
||
|
self.pos -= 1
|
||
|
# and btw. && has lower precedence, so we are done
|
||
|
break
|
||
|
try:
|
||
|
expr = parser(inTemplate=inTemplate)
|
||
|
exprs.append(expr)
|
||
|
ops.append(op)
|
||
|
oneMore = True
|
||
|
break
|
||
|
except DefinitionError:
|
||
|
self.pos = pos
|
||
|
if not oneMore:
|
||
|
break
|
||
|
return ASTBinOpExpr(exprs, ops)
|
||
|
return _parse_bin_op_expr(self, 0, inTemplate=inTemplate)
|
||
|
|
||
|
def _parse_conditional_expression_tail(self, orExprHead: ASTExpression,
|
||
|
inTemplate: bool) -> ASTConditionalExpr | None:
|
||
|
# Consumes the orExprHead on success.
|
||
|
|
||
|
# -> "?" expression ":" assignment-expression
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string("?"):
|
||
|
return None
|
||
|
thenExpr = self._parse_expression()
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(":"):
|
||
|
self.fail('Expected ":" after then-expression in conditional expression.')
|
||
|
elseExpr = self._parse_assignment_expression(inTemplate)
|
||
|
return ASTConditionalExpr(orExprHead, thenExpr, elseExpr)
|
||
|
|
||
|
def _parse_assignment_expression(self, inTemplate: bool) -> ASTExpression:
|
||
|
# -> conditional-expression
|
||
|
# | logical-or-expression assignment-operator initializer-clause
|
||
|
# | yield-expression -> "co_yield" assignment-expression
|
||
|
# | "co_yield" braced-init-list
|
||
|
# | throw-expression -> "throw" assignment-expression[opt]
|
||
|
# TODO: yield-expression
|
||
|
# TODO: throw-expression
|
||
|
|
||
|
# Now we have (after expanding conditional-expression:
|
||
|
# logical-or-expression
|
||
|
# | logical-or-expression "?" expression ":" assignment-expression
|
||
|
# | logical-or-expression assignment-operator initializer-clause
|
||
|
leftExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
|
||
|
# the ternary operator
|
||
|
condExpr = self._parse_conditional_expression_tail(leftExpr, inTemplate)
|
||
|
if condExpr is not None:
|
||
|
return condExpr
|
||
|
# and actual assignment
|
||
|
for op in _expression_assignment_ops:
|
||
|
if op[0] in 'anox':
|
||
|
if not self.skip_word(op):
|
||
|
continue
|
||
|
else:
|
||
|
if not self.skip_string(op):
|
||
|
continue
|
||
|
rightExpr = self._parse_initializer_clause()
|
||
|
return ASTAssignmentExpr(leftExpr, op, rightExpr)
|
||
|
# just a logical-or-expression
|
||
|
return leftExpr
|
||
|
|
||
|
def _parse_constant_expression(self, inTemplate: bool) -> ASTExpression:
|
||
|
# -> conditional-expression ->
|
||
|
# logical-or-expression
|
||
|
# | logical-or-expression "?" expression ":" assignment-expression
|
||
|
orExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
|
||
|
condExpr = self._parse_conditional_expression_tail(orExpr, inTemplate)
|
||
|
if condExpr is not None:
|
||
|
return condExpr
|
||
|
return orExpr
|
||
|
|
||
|
def _parse_expression(self) -> ASTExpression:
|
||
|
# -> assignment-expression
|
||
|
# | expression "," assignment-expression
|
||
|
exprs = [self._parse_assignment_expression(inTemplate=False)]
|
||
|
while True:
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(','):
|
||
|
break
|
||
|
exprs.append(self._parse_assignment_expression(inTemplate=False))
|
||
|
if len(exprs) == 1:
|
||
|
return exprs[0]
|
||
|
else:
|
||
|
return ASTCommaExpr(exprs)
|
||
|
|
||
|
def _parse_expression_fallback(self, end: list[str],
|
||
|
parser: Callable[[], ASTExpression],
|
||
|
allow: bool = True) -> ASTExpression:
|
||
|
# Stupidly "parse" an expression.
|
||
|
# 'end' should be a list of characters which ends the expression.
|
||
|
|
||
|
# first try to use the provided parser
|
||
|
prevPos = self.pos
|
||
|
try:
|
||
|
return parser()
|
||
|
except DefinitionError as e:
|
||
|
# some places (e.g., template parameters) we really don't want to use fallback,
|
||
|
# and for testing we may want to globally disable it
|
||
|
if not allow or not self.allowFallbackExpressionParsing:
|
||
|
raise
|
||
|
self.warn("Parsing of expression failed. Using fallback parser."
|
||
|
" Error was:\n%s" % e)
|
||
|
self.pos = prevPos
|
||
|
# and then the fallback scanning
|
||
|
assert end is not None
|
||
|
self.skip_ws()
|
||
|
startPos = self.pos
|
||
|
if self.match(_string_re):
|
||
|
value = self.matched_text
|
||
|
else:
|
||
|
# TODO: add handling of more bracket-like things, and quote handling
|
||
|
brackets = {'(': ')', '{': '}', '[': ']', '<': '>'}
|
||
|
symbols: list[str] = []
|
||
|
while not self.eof:
|
||
|
if (len(symbols) == 0 and self.current_char in end):
|
||
|
break
|
||
|
if self.current_char in brackets:
|
||
|
symbols.append(brackets[self.current_char])
|
||
|
elif len(symbols) > 0 and self.current_char == symbols[-1]:
|
||
|
symbols.pop()
|
||
|
self.pos += 1
|
||
|
if len(end) > 0 and self.eof:
|
||
|
self.fail("Could not find end of expression starting at %d."
|
||
|
% startPos)
|
||
|
value = self.definition[startPos:self.pos].strip()
|
||
|
return ASTFallbackExpr(value.strip())
|
||
|
|
||
|
# ==========================================================================
|
||
|
|
||
|
def _parse_operator(self) -> ASTOperator:
|
||
|
self.skip_ws()
|
||
|
# adapted from the old code
|
||
|
# yay, a regular operator definition
|
||
|
if self.match(_operator_re):
|
||
|
return ASTOperatorBuildIn(self.matched_text)
|
||
|
|
||
|
# new/delete operator?
|
||
|
for op in 'new', 'delete':
|
||
|
if not self.skip_word(op):
|
||
|
continue
|
||
|
self.skip_ws()
|
||
|
if self.skip_string('['):
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(']'):
|
||
|
self.fail('Expected "]" after "operator ' + op + '["')
|
||
|
op += '[]'
|
||
|
return ASTOperatorBuildIn(op)
|
||
|
|
||
|
# user-defined literal?
|
||
|
if self.skip_string('""'):
|
||
|
self.skip_ws()
|
||
|
if not self.match(identifier_re):
|
||
|
self.fail("Expected user-defined literal suffix.")
|
||
|
identifier = ASTIdentifier(self.matched_text)
|
||
|
return ASTOperatorLiteral(identifier)
|
||
|
|
||
|
# oh well, looks like a cast operator definition.
|
||
|
# In that case, eat another type.
|
||
|
type = self._parse_type(named=False, outer="operatorCast")
|
||
|
return ASTOperatorType(type)
|
||
|
|
||
|
def _parse_template_argument_list(self) -> ASTTemplateArgs:
|
||
|
# template-argument-list: (but we include the < and > here
|
||
|
# template-argument ...[opt]
|
||
|
# template-argument-list, template-argument ...[opt]
|
||
|
# template-argument:
|
||
|
# constant-expression
|
||
|
# type-id
|
||
|
# id-expression
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string_and_ws('<'):
|
||
|
return None
|
||
|
if self.skip_string('>'):
|
||
|
return ASTTemplateArgs([], False)
|
||
|
prevErrors = []
|
||
|
templateArgs: list[ASTType | ASTTemplateArgConstant] = []
|
||
|
packExpansion = False
|
||
|
while 1:
|
||
|
pos = self.pos
|
||
|
parsedComma = False
|
||
|
parsedEnd = False
|
||
|
try:
|
||
|
type = self._parse_type(named=False)
|
||
|
self.skip_ws()
|
||
|
if self.skip_string_and_ws('...'):
|
||
|
packExpansion = True
|
||
|
parsedEnd = True
|
||
|
if not self.skip_string('>'):
|
||
|
self.fail('Expected ">" after "..." in template argument list.')
|
||
|
elif self.skip_string('>'):
|
||
|
parsedEnd = True
|
||
|
elif self.skip_string(','):
|
||
|
parsedComma = True
|
||
|
else:
|
||
|
self.fail('Expected "...>", ">" or "," in template argument list.')
|
||
|
templateArgs.append(type)
|
||
|
except DefinitionError as e:
|
||
|
prevErrors.append((e, "If type argument"))
|
||
|
self.pos = pos
|
||
|
try:
|
||
|
value = self._parse_constant_expression(inTemplate=True)
|
||
|
self.skip_ws()
|
||
|
if self.skip_string_and_ws('...'):
|
||
|
packExpansion = True
|
||
|
parsedEnd = True
|
||
|
if not self.skip_string('>'):
|
||
|
self.fail('Expected ">" after "..." in template argument list.')
|
||
|
elif self.skip_string('>'):
|
||
|
parsedEnd = True
|
||
|
elif self.skip_string(','):
|
||
|
parsedComma = True
|
||
|
else:
|
||
|
self.fail('Expected "...>", ">" or "," in template argument list.')
|
||
|
templateArgs.append(ASTTemplateArgConstant(value))
|
||
|
except DefinitionError as e:
|
||
|
self.pos = pos
|
||
|
prevErrors.append((e, "If non-type argument"))
|
||
|
header = "Error in parsing template argument list."
|
||
|
raise self._make_multi_error(prevErrors, header) from e
|
||
|
if parsedEnd:
|
||
|
assert not parsedComma
|
||
|
break
|
||
|
assert not packExpansion
|
||
|
return ASTTemplateArgs(templateArgs, packExpansion)
|
||
|
|
||
|
def _parse_nested_name(self, memberPointer: bool = False) -> ASTNestedName:
|
||
|
names: list[ASTNestedNameElement] = []
|
||
|
templates: list[bool] = []
|
||
|
|
||
|
self.skip_ws()
|
||
|
rooted = False
|
||
|
if self.skip_string('::'):
|
||
|
rooted = True
|
||
|
while 1:
|
||
|
self.skip_ws()
|
||
|
if len(names) > 0:
|
||
|
template = self.skip_word_and_ws('template')
|
||
|
else:
|
||
|
template = False
|
||
|
templates.append(template)
|
||
|
identOrOp: ASTIdentifier | ASTOperator | None = None
|
||
|
if self.skip_word_and_ws('operator'):
|
||
|
identOrOp = self._parse_operator()
|
||
|
else:
|
||
|
if not self.match(identifier_re):
|
||
|
if memberPointer and len(names) > 0:
|
||
|
templates.pop()
|
||
|
break
|
||
|
self.fail("Expected identifier in nested name.")
|
||
|
identifier = self.matched_text
|
||
|
# make sure there isn't a keyword
|
||
|
if identifier in _keywords:
|
||
|
self.fail("Expected identifier in nested name, "
|
||
|
"got keyword: %s" % identifier)
|
||
|
identOrOp = ASTIdentifier(identifier)
|
||
|
# try greedily to get template arguments,
|
||
|
# but otherwise a < might be because we are in an expression
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
templateArgs = self._parse_template_argument_list()
|
||
|
except DefinitionError as ex:
|
||
|
self.pos = pos
|
||
|
templateArgs = None
|
||
|
self.otherErrors.append(ex)
|
||
|
names.append(ASTNestedNameElement(identOrOp, templateArgs))
|
||
|
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string('::'):
|
||
|
if memberPointer:
|
||
|
self.fail("Expected '::' in pointer to member (function).")
|
||
|
break
|
||
|
return ASTNestedName(names, templates, rooted)
|
||
|
|
||
|
# ==========================================================================
|
||
|
|
||
|
def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental:
|
||
|
modifier: str | None = None
|
||
|
signedness: str | None = None
|
||
|
width: list[str] = []
|
||
|
typ: str | None = None
|
||
|
names: list[str] = [] # the parsed sequence
|
||
|
|
||
|
self.skip_ws()
|
||
|
while self.match(_simple_type_specifiers_re):
|
||
|
t = self.matched_text
|
||
|
names.append(t)
|
||
|
if t in ('auto', 'void', 'bool',
|
||
|
'char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
|
||
|
'int', '__int64', '__int128',
|
||
|
'float', 'double',
|
||
|
'__float80', '_Float64x', '__float128', '_Float128'):
|
||
|
if typ is not None:
|
||
|
self.fail(f"Can not have both {t} and {typ}.")
|
||
|
typ = t
|
||
|
elif t in ('signed', 'unsigned'):
|
||
|
if signedness is not None:
|
||
|
self.fail(f"Can not have both {t} and {signedness}.")
|
||
|
signedness = t
|
||
|
elif t == 'short':
|
||
|
if len(width) != 0:
|
||
|
self.fail(f"Can not have both {t} and {width[0]}.")
|
||
|
width.append(t)
|
||
|
elif t == 'long':
|
||
|
if len(width) != 0 and width[0] != 'long':
|
||
|
self.fail(f"Can not have both {t} and {width[0]}.")
|
||
|
width.append(t)
|
||
|
elif t in ('_Imaginary', '_Complex'):
|
||
|
if modifier is not None:
|
||
|
self.fail(f"Can not have both {t} and {modifier}.")
|
||
|
modifier = t
|
||
|
self.skip_ws()
|
||
|
if len(names) == 0:
|
||
|
return None
|
||
|
|
||
|
if typ in ('auto', 'void', 'bool',
|
||
|
'wchar_t', 'char8_t', 'char16_t', 'char32_t',
|
||
|
'__float80', '_Float64x', '__float128', '_Float128'):
|
||
|
if modifier is not None:
|
||
|
self.fail(f"Can not have both {typ} and {modifier}.")
|
||
|
if signedness is not None:
|
||
|
self.fail(f"Can not have both {typ} and {signedness}.")
|
||
|
if len(width) != 0:
|
||
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
||
|
elif typ == 'char':
|
||
|
if modifier is not None:
|
||
|
self.fail(f"Can not have both {typ} and {modifier}.")
|
||
|
if len(width) != 0:
|
||
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
||
|
elif typ == 'int':
|
||
|
if modifier is not None:
|
||
|
self.fail(f"Can not have both {typ} and {modifier}.")
|
||
|
elif typ in ('__int64', '__int128'):
|
||
|
if modifier is not None:
|
||
|
self.fail(f"Can not have both {typ} and {modifier}.")
|
||
|
if len(width) != 0:
|
||
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
||
|
elif typ == 'float':
|
||
|
if signedness is not None:
|
||
|
self.fail(f"Can not have both {typ} and {signedness}.")
|
||
|
if len(width) != 0:
|
||
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
||
|
elif typ == 'double':
|
||
|
if signedness is not None:
|
||
|
self.fail(f"Can not have both {typ} and {signedness}.")
|
||
|
if len(width) > 1:
|
||
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
||
|
if len(width) == 1 and width[0] != 'long':
|
||
|
self.fail(f"Can not have both {typ} and {' '.join(width)}.")
|
||
|
elif typ is None:
|
||
|
if modifier is not None:
|
||
|
self.fail(f"Can not have {modifier} without a floating point type.")
|
||
|
else:
|
||
|
msg = f'Unhandled type {typ}'
|
||
|
raise AssertionError(msg)
|
||
|
|
||
|
canonNames: list[str] = []
|
||
|
if modifier is not None:
|
||
|
canonNames.append(modifier)
|
||
|
if signedness is not None:
|
||
|
canonNames.append(signedness)
|
||
|
canonNames.extend(width)
|
||
|
if typ is not None:
|
||
|
canonNames.append(typ)
|
||
|
return ASTTrailingTypeSpecFundamental(names, canonNames)
|
||
|
|
||
|
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
|
||
|
# fundamental types, https://en.cppreference.com/w/cpp/language/type
|
||
|
# and extensions
|
||
|
self.skip_ws()
|
||
|
res = self._parse_simple_type_specifiers()
|
||
|
if res is not None:
|
||
|
return res
|
||
|
|
||
|
# decltype
|
||
|
self.skip_ws()
|
||
|
if self.skip_word_and_ws('decltype'):
|
||
|
if not self.skip_string_and_ws('('):
|
||
|
self.fail("Expected '(' after 'decltype'.")
|
||
|
if self.skip_word_and_ws('auto'):
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' after 'decltype(auto'.")
|
||
|
return ASTTrailingTypeSpecDecltypeAuto()
|
||
|
expr = self._parse_expression()
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' after 'decltype(<expr>'.")
|
||
|
return ASTTrailingTypeSpecDecltype(expr)
|
||
|
|
||
|
# prefixed
|
||
|
prefix = None
|
||
|
self.skip_ws()
|
||
|
for k in ('class', 'struct', 'enum', 'union', 'typename'):
|
||
|
if self.skip_word_and_ws(k):
|
||
|
prefix = k
|
||
|
break
|
||
|
nestedName = self._parse_nested_name()
|
||
|
self.skip_ws()
|
||
|
placeholderType = None
|
||
|
if self.skip_word('auto'):
|
||
|
placeholderType = 'auto'
|
||
|
elif self.skip_word_and_ws('decltype'):
|
||
|
if not self.skip_string_and_ws('('):
|
||
|
self.fail("Expected '(' after 'decltype' in placeholder type specifier.")
|
||
|
if not self.skip_word_and_ws('auto'):
|
||
|
self.fail("Expected 'auto' after 'decltype(' in placeholder type specifier.")
|
||
|
if not self.skip_string_and_ws(')'):
|
||
|
self.fail("Expected ')' after 'decltype(auto' in placeholder type specifier.")
|
||
|
placeholderType = 'decltype(auto)'
|
||
|
return ASTTrailingTypeSpecName(prefix, nestedName, placeholderType)
|
||
|
|
||
|
def _parse_parameters_and_qualifiers(
|
||
|
self, paramMode: str,
|
||
|
) -> ASTParametersQualifiers | None:
|
||
|
if paramMode == 'new':
|
||
|
return None
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string('('):
|
||
|
if paramMode == 'function':
|
||
|
self.fail('Expecting "(" in parameters-and-qualifiers.')
|
||
|
else:
|
||
|
return None
|
||
|
args = []
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
while 1:
|
||
|
self.skip_ws()
|
||
|
if self.skip_string('...'):
|
||
|
args.append(ASTFunctionParameter(None, True))
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail('Expected ")" after "..." in '
|
||
|
'parameters-and-qualifiers.')
|
||
|
break
|
||
|
# note: it seems that function arguments can always be named,
|
||
|
# even in function pointers and similar.
|
||
|
arg = self._parse_type_with_init(outer=None, named='single')
|
||
|
# TODO: parse default parameters # TODO: didn't we just do that?
|
||
|
args.append(ASTFunctionParameter(arg))
|
||
|
|
||
|
self.skip_ws()
|
||
|
if self.skip_string(','):
|
||
|
continue
|
||
|
if self.skip_string(')'):
|
||
|
break
|
||
|
self.fail('Expecting "," or ")" in parameters-and-qualifiers, '
|
||
|
f'got "{self.current_char}".')
|
||
|
|
||
|
self.skip_ws()
|
||
|
const = self.skip_word_and_ws('const')
|
||
|
volatile = self.skip_word_and_ws('volatile')
|
||
|
if not const: # the can be permuted
|
||
|
const = self.skip_word_and_ws('const')
|
||
|
|
||
|
refQual = None
|
||
|
if self.skip_string('&&'):
|
||
|
refQual = '&&'
|
||
|
if not refQual and self.skip_string('&'):
|
||
|
refQual = '&'
|
||
|
|
||
|
exceptionSpec = None
|
||
|
self.skip_ws()
|
||
|
if self.skip_string('noexcept'):
|
||
|
if self.skip_string_and_ws('('):
|
||
|
expr = self._parse_constant_expression(False)
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expecting ')' to end 'noexcept'.")
|
||
|
exceptionSpec = ASTNoexceptSpec(expr)
|
||
|
else:
|
||
|
exceptionSpec = ASTNoexceptSpec(None)
|
||
|
|
||
|
self.skip_ws()
|
||
|
if self.skip_string('->'):
|
||
|
trailingReturn = self._parse_type(named=False)
|
||
|
else:
|
||
|
trailingReturn = None
|
||
|
|
||
|
self.skip_ws()
|
||
|
override = self.skip_word_and_ws('override')
|
||
|
final = self.skip_word_and_ws('final')
|
||
|
if not override:
|
||
|
override = self.skip_word_and_ws(
|
||
|
'override') # they can be permuted
|
||
|
|
||
|
attrs = self._parse_attribute_list()
|
||
|
|
||
|
self.skip_ws()
|
||
|
initializer = None
|
||
|
# if this is a function pointer we should not swallow an initializer
|
||
|
if paramMode == 'function' and self.skip_string('='):
|
||
|
self.skip_ws()
|
||
|
valid = ('0', 'delete', 'default')
|
||
|
for w in valid:
|
||
|
if self.skip_word_and_ws(w):
|
||
|
initializer = w
|
||
|
break
|
||
|
if not initializer:
|
||
|
self.fail(
|
||
|
'Expected "%s" in initializer-specifier.'
|
||
|
% '" or "'.join(valid))
|
||
|
|
||
|
return ASTParametersQualifiers(
|
||
|
args, volatile, const, refQual, exceptionSpec, trailingReturn,
|
||
|
override, final, attrs, initializer)
|
||
|
|
||
|
def _parse_decl_specs_simple(self, outer: str, typed: bool) -> ASTDeclSpecsSimple:
|
||
|
"""Just parse the simple ones."""
|
||
|
storage = None
|
||
|
threadLocal = None
|
||
|
inline = None
|
||
|
virtual = None
|
||
|
explicitSpec = None
|
||
|
consteval = None
|
||
|
constexpr = None
|
||
|
constinit = None
|
||
|
volatile = None
|
||
|
const = None
|
||
|
friend = None
|
||
|
attrs = []
|
||
|
while 1: # accept any permutation of a subset of some decl-specs
|
||
|
self.skip_ws()
|
||
|
if not const and typed:
|
||
|
const = self.skip_word('const')
|
||
|
if const:
|
||
|
continue
|
||
|
if not volatile and typed:
|
||
|
volatile = self.skip_word('volatile')
|
||
|
if volatile:
|
||
|
continue
|
||
|
if not storage:
|
||
|
if outer in ('member', 'function'):
|
||
|
if self.skip_word('static'):
|
||
|
storage = 'static'
|
||
|
continue
|
||
|
if self.skip_word('extern'):
|
||
|
storage = 'extern'
|
||
|
continue
|
||
|
if outer == 'member':
|
||
|
if self.skip_word('mutable'):
|
||
|
storage = 'mutable'
|
||
|
continue
|
||
|
if self.skip_word('register'):
|
||
|
storage = 'register'
|
||
|
continue
|
||
|
if not inline and outer in ('function', 'member'):
|
||
|
inline = self.skip_word('inline')
|
||
|
if inline:
|
||
|
continue
|
||
|
if not constexpr and outer in ('member', 'function'):
|
||
|
constexpr = self.skip_word("constexpr")
|
||
|
if constexpr:
|
||
|
continue
|
||
|
|
||
|
if outer == 'member':
|
||
|
if not constinit:
|
||
|
constinit = self.skip_word('constinit')
|
||
|
if constinit:
|
||
|
continue
|
||
|
if not threadLocal:
|
||
|
threadLocal = self.skip_word('thread_local')
|
||
|
if threadLocal:
|
||
|
continue
|
||
|
if outer == 'function':
|
||
|
if not consteval:
|
||
|
consteval = self.skip_word('consteval')
|
||
|
if consteval:
|
||
|
continue
|
||
|
if not friend:
|
||
|
friend = self.skip_word('friend')
|
||
|
if friend:
|
||
|
continue
|
||
|
if not virtual:
|
||
|
virtual = self.skip_word('virtual')
|
||
|
if virtual:
|
||
|
continue
|
||
|
if not explicitSpec:
|
||
|
explicit = self.skip_word_and_ws('explicit')
|
||
|
if explicit:
|
||
|
expr: ASTExpression = None
|
||
|
if self.skip_string('('):
|
||
|
expr = self._parse_constant_expression(inTemplate=False)
|
||
|
if not expr:
|
||
|
self.fail("Expected constant expression after '('" +
|
||
|
" in explicit specifier.")
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' to end explicit specifier.")
|
||
|
explicitSpec = ASTExplicitSpec(expr)
|
||
|
continue
|
||
|
attr = self._parse_attribute()
|
||
|
if attr:
|
||
|
attrs.append(attr)
|
||
|
continue
|
||
|
break
|
||
|
return ASTDeclSpecsSimple(storage, threadLocal, inline, virtual,
|
||
|
explicitSpec, consteval, constexpr, constinit,
|
||
|
volatile, const, friend, ASTAttributeList(attrs))
|
||
|
|
||
|
def _parse_decl_specs(self, outer: str, typed: bool = True) -> ASTDeclSpecs:
|
||
|
if outer:
|
||
|
if outer not in ('type', 'member', 'function', 'templateParam'):
|
||
|
raise Exception('Internal error, unknown outer "%s".' % outer)
|
||
|
"""
|
||
|
storage-class-specifier function-specifier "constexpr"
|
||
|
"volatile" "const" trailing-type-specifier
|
||
|
|
||
|
storage-class-specifier ->
|
||
|
"static" (only for member_object and function_object)
|
||
|
| "register"
|
||
|
|
||
|
function-specifier -> "inline" | "virtual" | "explicit" (only for
|
||
|
function_object)
|
||
|
|
||
|
"constexpr" (only for member_object and function_object)
|
||
|
"""
|
||
|
leftSpecs = self._parse_decl_specs_simple(outer, typed)
|
||
|
rightSpecs = None
|
||
|
|
||
|
if typed:
|
||
|
trailing = self._parse_trailing_type_spec()
|
||
|
rightSpecs = self._parse_decl_specs_simple(outer, typed)
|
||
|
else:
|
||
|
trailing = None
|
||
|
return ASTDeclSpecs(outer, leftSpecs, rightSpecs, trailing)
|
||
|
|
||
|
def _parse_declarator_name_suffix(
|
||
|
self, named: bool | str, paramMode: str, typed: bool,
|
||
|
) -> ASTDeclaratorNameParamQual | ASTDeclaratorNameBitField:
|
||
|
# now we should parse the name, and then suffixes
|
||
|
if named == 'maybe':
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
declId = self._parse_nested_name()
|
||
|
except DefinitionError:
|
||
|
self.pos = pos
|
||
|
declId = None
|
||
|
elif named == 'single':
|
||
|
if self.match(identifier_re):
|
||
|
identifier = ASTIdentifier(self.matched_text)
|
||
|
nne = ASTNestedNameElement(identifier, None)
|
||
|
declId = ASTNestedName([nne], [False], rooted=False)
|
||
|
# if it's a member pointer, we may have '::', which should be an error
|
||
|
self.skip_ws()
|
||
|
if self.current_char == ':':
|
||
|
self.fail("Unexpected ':' after identifier.")
|
||
|
else:
|
||
|
declId = None
|
||
|
elif named:
|
||
|
declId = self._parse_nested_name()
|
||
|
else:
|
||
|
declId = None
|
||
|
arrayOps = []
|
||
|
while 1:
|
||
|
self.skip_ws()
|
||
|
if typed and self.skip_string('['):
|
||
|
self.skip_ws()
|
||
|
if self.skip_string(']'):
|
||
|
arrayOps.append(ASTArray(None))
|
||
|
continue
|
||
|
|
||
|
def parser() -> ASTExpression:
|
||
|
return self._parse_expression()
|
||
|
value = self._parse_expression_fallback([']'], parser)
|
||
|
if not self.skip_string(']'):
|
||
|
self.fail("Expected ']' in end of array operator.")
|
||
|
arrayOps.append(ASTArray(value))
|
||
|
continue
|
||
|
break
|
||
|
paramQual = self._parse_parameters_and_qualifiers(paramMode)
|
||
|
if paramQual is None and len(arrayOps) == 0:
|
||
|
# perhaps a bit-field
|
||
|
if named and paramMode == 'type' and typed:
|
||
|
self.skip_ws()
|
||
|
if self.skip_string(':'):
|
||
|
size = self._parse_constant_expression(inTemplate=False)
|
||
|
return ASTDeclaratorNameBitField(declId=declId, size=size)
|
||
|
return ASTDeclaratorNameParamQual(declId=declId, arrayOps=arrayOps,
|
||
|
paramQual=paramQual)
|
||
|
|
||
|
def _parse_declarator(self, named: bool | str, paramMode: str,
|
||
|
typed: bool = True,
|
||
|
) -> ASTDeclarator:
|
||
|
# 'typed' here means 'parse return type stuff'
|
||
|
if paramMode not in ('type', 'function', 'operatorCast', 'new'):
|
||
|
raise Exception(
|
||
|
"Internal error, unknown paramMode '%s'." % paramMode)
|
||
|
prevErrors = []
|
||
|
self.skip_ws()
|
||
|
if typed and self.skip_string('*'):
|
||
|
self.skip_ws()
|
||
|
volatile = False
|
||
|
const = False
|
||
|
attrList = []
|
||
|
while 1:
|
||
|
if not volatile:
|
||
|
volatile = self.skip_word_and_ws('volatile')
|
||
|
if volatile:
|
||
|
continue
|
||
|
if not const:
|
||
|
const = self.skip_word_and_ws('const')
|
||
|
if const:
|
||
|
continue
|
||
|
attr = self._parse_attribute()
|
||
|
if attr is not None:
|
||
|
attrList.append(attr)
|
||
|
continue
|
||
|
break
|
||
|
next = self._parse_declarator(named, paramMode, typed)
|
||
|
return ASTDeclaratorPtr(next=next, volatile=volatile, const=const,
|
||
|
attrs=ASTAttributeList(attrList))
|
||
|
# TODO: shouldn't we parse an R-value ref here first?
|
||
|
if typed and self.skip_string("&"):
|
||
|
attrs = self._parse_attribute_list()
|
||
|
next = self._parse_declarator(named, paramMode, typed)
|
||
|
return ASTDeclaratorRef(next=next, attrs=attrs)
|
||
|
if typed and self.skip_string("..."):
|
||
|
next = self._parse_declarator(named, paramMode, False)
|
||
|
return ASTDeclaratorParamPack(next=next)
|
||
|
if typed and self.current_char == '(': # note: peeking, not skipping
|
||
|
if paramMode == "operatorCast":
|
||
|
# TODO: we should be able to parse cast operators which return
|
||
|
# function pointers. For now, just hax it and ignore.
|
||
|
return ASTDeclaratorNameParamQual(declId=None, arrayOps=[],
|
||
|
paramQual=None)
|
||
|
# maybe this is the beginning of params and quals,try that first,
|
||
|
# otherwise assume it's noptr->declarator > ( ptr-declarator )
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
# assume this is params and quals
|
||
|
res = self._parse_declarator_name_suffix(named, paramMode,
|
||
|
typed)
|
||
|
return res
|
||
|
except DefinitionError as exParamQual:
|
||
|
prevErrors.append((exParamQual,
|
||
|
"If declarator-id with parameters-and-qualifiers"))
|
||
|
self.pos = pos
|
||
|
try:
|
||
|
assert self.current_char == '('
|
||
|
self.skip_string('(')
|
||
|
# TODO: hmm, if there is a name, it must be in inner, right?
|
||
|
# TODO: hmm, if there must be parameters, they must be
|
||
|
# inside, right?
|
||
|
inner = self._parse_declarator(named, paramMode, typed)
|
||
|
if not self.skip_string(')'):
|
||
|
self.fail("Expected ')' in \"( ptr-declarator )\"")
|
||
|
next = self._parse_declarator(named=False,
|
||
|
paramMode="type",
|
||
|
typed=typed)
|
||
|
return ASTDeclaratorParen(inner=inner, next=next)
|
||
|
except DefinitionError as exNoPtrParen:
|
||
|
self.pos = pos
|
||
|
prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
|
||
|
header = "Error in declarator"
|
||
|
raise self._make_multi_error(prevErrors, header) from exNoPtrParen
|
||
|
if typed: # pointer to member
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
name = self._parse_nested_name(memberPointer=True)
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string('*'):
|
||
|
self.fail("Expected '*' in pointer to member declarator.")
|
||
|
self.skip_ws()
|
||
|
except DefinitionError as e:
|
||
|
self.pos = pos
|
||
|
prevErrors.append((e, "If pointer to member declarator"))
|
||
|
else:
|
||
|
volatile = False
|
||
|
const = False
|
||
|
while 1:
|
||
|
if not volatile:
|
||
|
volatile = self.skip_word_and_ws('volatile')
|
||
|
if volatile:
|
||
|
continue
|
||
|
if not const:
|
||
|
const = self.skip_word_and_ws('const')
|
||
|
if const:
|
||
|
continue
|
||
|
break
|
||
|
next = self._parse_declarator(named, paramMode, typed)
|
||
|
return ASTDeclaratorMemPtr(name, const, volatile, next=next)
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
res = self._parse_declarator_name_suffix(named, paramMode, typed)
|
||
|
# this is a heuristic for error messages, for when there is a < after a
|
||
|
# nested name, but it was not a successful template argument list
|
||
|
if self.current_char == '<':
|
||
|
self.otherErrors.append(self._make_multi_error(prevErrors, ""))
|
||
|
return res
|
||
|
except DefinitionError as e:
|
||
|
self.pos = pos
|
||
|
prevErrors.append((e, "If declarator-id"))
|
||
|
header = "Error in declarator or parameters-and-qualifiers"
|
||
|
raise self._make_multi_error(prevErrors, header) from e
|
||
|
|
||
|
def _parse_initializer(self, outer: str | None = None, allowFallback: bool = True,
|
||
|
) -> ASTInitializer | None:
|
||
|
# initializer # global vars
|
||
|
# -> brace-or-equal-initializer
|
||
|
# | '(' expression-list ')'
|
||
|
#
|
||
|
# brace-or-equal-initializer # member vars
|
||
|
# -> '=' initializer-clause
|
||
|
# | braced-init-list
|
||
|
#
|
||
|
# initializer-clause # function params, non-type template params (with '=' in front)
|
||
|
# -> assignment-expression
|
||
|
# | braced-init-list
|
||
|
#
|
||
|
# we don't distinguish between global and member vars, so disallow paren:
|
||
|
#
|
||
|
# -> braced-init-list # var only
|
||
|
# | '=' assignment-expression
|
||
|
# | '=' braced-init-list
|
||
|
self.skip_ws()
|
||
|
if outer == 'member':
|
||
|
bracedInit = self._parse_braced_init_list()
|
||
|
if bracedInit is not None:
|
||
|
return ASTInitializer(bracedInit, hasAssign=False)
|
||
|
|
||
|
if not self.skip_string('='):
|
||
|
return None
|
||
|
|
||
|
bracedInit = self._parse_braced_init_list()
|
||
|
if bracedInit is not None:
|
||
|
return ASTInitializer(bracedInit)
|
||
|
|
||
|
if outer == 'member':
|
||
|
fallbackEnd: list[str] = []
|
||
|
elif outer == 'templateParam':
|
||
|
fallbackEnd = [',', '>']
|
||
|
elif outer is None: # function parameter
|
||
|
fallbackEnd = [',', ')']
|
||
|
else:
|
||
|
self.fail("Internal error, initializer for outer '%s' not "
|
||
|
"implemented." % outer)
|
||
|
|
||
|
inTemplate = outer == 'templateParam'
|
||
|
|
||
|
def parser() -> ASTExpression:
|
||
|
return self._parse_assignment_expression(inTemplate=inTemplate)
|
||
|
value = self._parse_expression_fallback(fallbackEnd, parser, allow=allowFallback)
|
||
|
return ASTInitializer(value)
|
||
|
|
||
|
def _parse_type(self, named: bool | str, outer: str | None = None) -> ASTType:
|
||
|
"""
|
||
|
named=False|'maybe'|True: 'maybe' is e.g., for function objects which
|
||
|
doesn't need to name the arguments
|
||
|
|
||
|
outer == operatorCast: annoying case, we should not take the params
|
||
|
"""
|
||
|
if outer: # always named
|
||
|
if outer not in ('type', 'member', 'function',
|
||
|
'operatorCast', 'templateParam'):
|
||
|
raise Exception('Internal error, unknown outer "%s".' % outer)
|
||
|
if outer != 'operatorCast':
|
||
|
assert named
|
||
|
if outer in ('type', 'function'):
|
||
|
# We allow type objects to just be a name.
|
||
|
# Some functions don't have normal return types: constructors,
|
||
|
# destructors, cast operators
|
||
|
prevErrors = []
|
||
|
startPos = self.pos
|
||
|
# first try without the type
|
||
|
try:
|
||
|
declSpecs = self._parse_decl_specs(outer=outer, typed=False)
|
||
|
decl = self._parse_declarator(named=True, paramMode=outer,
|
||
|
typed=False)
|
||
|
mustEnd = True
|
||
|
if outer == 'function':
|
||
|
# Allow trailing requires on functions.
|
||
|
self.skip_ws()
|
||
|
if re.compile(r'requires\b').match(self.definition, self.pos):
|
||
|
mustEnd = False
|
||
|
if mustEnd:
|
||
|
self.assert_end(allowSemicolon=True)
|
||
|
except DefinitionError as exUntyped:
|
||
|
if outer == 'type':
|
||
|
desc = "If just a name"
|
||
|
elif outer == 'function':
|
||
|
desc = "If the function has no return type"
|
||
|
else:
|
||
|
raise AssertionError from exUntyped
|
||
|
prevErrors.append((exUntyped, desc))
|
||
|
self.pos = startPos
|
||
|
try:
|
||
|
declSpecs = self._parse_decl_specs(outer=outer)
|
||
|
decl = self._parse_declarator(named=True, paramMode=outer)
|
||
|
except DefinitionError as exTyped:
|
||
|
self.pos = startPos
|
||
|
if outer == 'type':
|
||
|
desc = "If typedef-like declaration"
|
||
|
elif outer == 'function':
|
||
|
desc = "If the function has a return type"
|
||
|
else:
|
||
|
raise AssertionError from exUntyped
|
||
|
prevErrors.append((exTyped, desc))
|
||
|
# Retain the else branch for easier debugging.
|
||
|
# TODO: it would be nice to save the previous stacktrace
|
||
|
# and output it here.
|
||
|
if True:
|
||
|
if outer == 'type':
|
||
|
header = "Type must be either just a name or a "
|
||
|
header += "typedef-like declaration."
|
||
|
elif outer == 'function':
|
||
|
header = "Error when parsing function declaration."
|
||
|
else:
|
||
|
raise AssertionError from exUntyped
|
||
|
raise self._make_multi_error(prevErrors, header) from exTyped
|
||
|
else: # NoQA: RET506
|
||
|
# For testing purposes.
|
||
|
# do it again to get the proper traceback (how do you
|
||
|
# reliably save a traceback when an exception is
|
||
|
# constructed?)
|
||
|
self.pos = startPos
|
||
|
typed = True
|
||
|
declSpecs = self._parse_decl_specs(outer=outer, typed=typed)
|
||
|
decl = self._parse_declarator(named=True, paramMode=outer,
|
||
|
typed=typed)
|
||
|
else:
|
||
|
paramMode = 'type'
|
||
|
if outer == 'member':
|
||
|
named = True
|
||
|
elif outer == 'operatorCast':
|
||
|
paramMode = 'operatorCast'
|
||
|
outer = None
|
||
|
elif outer == 'templateParam':
|
||
|
named = 'single'
|
||
|
declSpecs = self._parse_decl_specs(outer=outer)
|
||
|
decl = self._parse_declarator(named=named, paramMode=paramMode)
|
||
|
return ASTType(declSpecs, decl)
|
||
|
|
||
|
def _parse_type_with_init(
|
||
|
self, named: bool | str,
|
||
|
outer: str) -> ASTTypeWithInit | ASTTemplateParamConstrainedTypeWithInit:
|
||
|
if outer:
|
||
|
assert outer in ('type', 'member', 'function', 'templateParam')
|
||
|
type = self._parse_type(outer=outer, named=named)
|
||
|
if outer != 'templateParam':
|
||
|
init = self._parse_initializer(outer=outer)
|
||
|
return ASTTypeWithInit(type, init)
|
||
|
# it could also be a constrained type parameter, e.g., C T = int&
|
||
|
pos = self.pos
|
||
|
eExpr = None
|
||
|
try:
|
||
|
init = self._parse_initializer(outer=outer, allowFallback=False)
|
||
|
# note: init may be None if there is no =
|
||
|
if init is None:
|
||
|
return ASTTypeWithInit(type, None)
|
||
|
# we parsed an expression, so we must have a , or a >,
|
||
|
# otherwise the expression didn't get everything
|
||
|
self.skip_ws()
|
||
|
if self.current_char != ',' and self.current_char != '>':
|
||
|
# pretend it didn't happen
|
||
|
self.pos = pos
|
||
|
init = None
|
||
|
else:
|
||
|
# we assume that it was indeed an expression
|
||
|
return ASTTypeWithInit(type, init)
|
||
|
except DefinitionError as e:
|
||
|
self.pos = pos
|
||
|
eExpr = e
|
||
|
if not self.skip_string("="):
|
||
|
return ASTTypeWithInit(type, None)
|
||
|
try:
|
||
|
typeInit = self._parse_type(named=False, outer=None)
|
||
|
return ASTTemplateParamConstrainedTypeWithInit(type, typeInit)
|
||
|
except DefinitionError as eType:
|
||
|
if eExpr is None:
|
||
|
raise
|
||
|
errs = []
|
||
|
errs.append((eExpr, "If default template argument is an expression"))
|
||
|
errs.append((eType, "If default template argument is a type"))
|
||
|
msg = "Error in non-type template parameter"
|
||
|
msg += " or constrained template parameter."
|
||
|
raise self._make_multi_error(errs, msg) from eType
|
||
|
|
||
|
def _parse_type_using(self) -> ASTTypeUsing:
|
||
|
name = self._parse_nested_name()
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string('='):
|
||
|
return ASTTypeUsing(name, None)
|
||
|
type = self._parse_type(False, None)
|
||
|
return ASTTypeUsing(name, type)
|
||
|
|
||
|
def _parse_concept(self) -> ASTConcept:
|
||
|
nestedName = self._parse_nested_name()
|
||
|
self.skip_ws()
|
||
|
initializer = self._parse_initializer('member')
|
||
|
return ASTConcept(nestedName, initializer)
|
||
|
|
||
|
def _parse_class(self) -> ASTClass:
|
||
|
attrs = self._parse_attribute_list()
|
||
|
name = self._parse_nested_name()
|
||
|
self.skip_ws()
|
||
|
final = self.skip_word_and_ws('final')
|
||
|
bases = []
|
||
|
self.skip_ws()
|
||
|
if self.skip_string(':'):
|
||
|
while 1:
|
||
|
self.skip_ws()
|
||
|
visibility = None
|
||
|
virtual = False
|
||
|
pack = False
|
||
|
if self.skip_word_and_ws('virtual'):
|
||
|
virtual = True
|
||
|
if self.match(_visibility_re):
|
||
|
visibility = self.matched_text
|
||
|
self.skip_ws()
|
||
|
if not virtual and self.skip_word_and_ws('virtual'):
|
||
|
virtual = True
|
||
|
baseName = self._parse_nested_name()
|
||
|
self.skip_ws()
|
||
|
pack = self.skip_string('...')
|
||
|
bases.append(ASTBaseClass(baseName, visibility, virtual, pack))
|
||
|
self.skip_ws()
|
||
|
if self.skip_string(','):
|
||
|
continue
|
||
|
break
|
||
|
return ASTClass(name, final, bases, attrs)
|
||
|
|
||
|
def _parse_union(self) -> ASTUnion:
|
||
|
attrs = self._parse_attribute_list()
|
||
|
name = self._parse_nested_name()
|
||
|
return ASTUnion(name, attrs)
|
||
|
|
||
|
def _parse_enum(self) -> ASTEnum:
|
||
|
scoped = None # is set by CPPEnumObject
|
||
|
attrs = self._parse_attribute_list()
|
||
|
name = self._parse_nested_name()
|
||
|
self.skip_ws()
|
||
|
underlyingType = None
|
||
|
if self.skip_string(':'):
|
||
|
underlyingType = self._parse_type(named=False)
|
||
|
return ASTEnum(name, scoped, underlyingType, attrs)
|
||
|
|
||
|
def _parse_enumerator(self) -> ASTEnumerator:
|
||
|
name = self._parse_nested_name()
|
||
|
attrs = self._parse_attribute_list()
|
||
|
self.skip_ws()
|
||
|
init = None
|
||
|
if self.skip_string('='):
|
||
|
self.skip_ws()
|
||
|
|
||
|
def parser() -> ASTExpression:
|
||
|
return self._parse_constant_expression(inTemplate=False)
|
||
|
initVal = self._parse_expression_fallback([], parser)
|
||
|
init = ASTInitializer(initVal)
|
||
|
return ASTEnumerator(name, init, attrs)
|
||
|
|
||
|
# ==========================================================================
|
||
|
|
||
|
def _parse_template_parameter(self) -> ASTTemplateParam:
|
||
|
self.skip_ws()
|
||
|
if self.skip_word('template'):
|
||
|
# declare a template template parameter
|
||
|
nestedParams = self._parse_template_parameter_list()
|
||
|
else:
|
||
|
nestedParams = None
|
||
|
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
# Unconstrained type parameter or template type parameter
|
||
|
key = None
|
||
|
self.skip_ws()
|
||
|
if self.skip_word_and_ws('typename'):
|
||
|
key = 'typename'
|
||
|
elif self.skip_word_and_ws('class'):
|
||
|
key = 'class'
|
||
|
elif nestedParams:
|
||
|
self.fail("Expected 'typename' or 'class' after "
|
||
|
"template template parameter list.")
|
||
|
else:
|
||
|
self.fail("Expected 'typename' or 'class' in the "
|
||
|
"beginning of template type parameter.")
|
||
|
self.skip_ws()
|
||
|
parameterPack = self.skip_string('...')
|
||
|
self.skip_ws()
|
||
|
if self.match(identifier_re):
|
||
|
identifier = ASTIdentifier(self.matched_text)
|
||
|
else:
|
||
|
identifier = None
|
||
|
self.skip_ws()
|
||
|
if not parameterPack and self.skip_string('='):
|
||
|
default = self._parse_type(named=False, outer=None)
|
||
|
else:
|
||
|
default = None
|
||
|
if self.current_char not in ',>':
|
||
|
self.fail('Expected "," or ">" after (template) type parameter.')
|
||
|
data = ASTTemplateKeyParamPackIdDefault(key, identifier,
|
||
|
parameterPack, default)
|
||
|
if nestedParams:
|
||
|
return ASTTemplateParamTemplateType(nestedParams, data)
|
||
|
else:
|
||
|
return ASTTemplateParamType(data)
|
||
|
except DefinitionError as eType:
|
||
|
if nestedParams:
|
||
|
raise
|
||
|
try:
|
||
|
# non-type parameter or constrained type parameter
|
||
|
self.pos = pos
|
||
|
param = self._parse_type_with_init('maybe', 'templateParam')
|
||
|
self.skip_ws()
|
||
|
parameterPack = self.skip_string('...')
|
||
|
return ASTTemplateParamNonType(param, parameterPack)
|
||
|
except DefinitionError as eNonType:
|
||
|
self.pos = pos
|
||
|
header = "Error when parsing template parameter."
|
||
|
errs = []
|
||
|
errs.append(
|
||
|
(eType, "If unconstrained type parameter or template type parameter"))
|
||
|
errs.append(
|
||
|
(eNonType, "If constrained type parameter or non-type parameter"))
|
||
|
raise self._make_multi_error(errs, header) from None
|
||
|
|
||
|
def _parse_template_parameter_list(self) -> ASTTemplateParams:
|
||
|
# only: '<' parameter-list '>'
|
||
|
# we assume that 'template' has just been parsed
|
||
|
templateParams: list[ASTTemplateParam] = []
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string("<"):
|
||
|
self.fail("Expected '<' after 'template'")
|
||
|
while 1:
|
||
|
pos = self.pos
|
||
|
err = None
|
||
|
try:
|
||
|
param = self._parse_template_parameter()
|
||
|
templateParams.append(param)
|
||
|
except DefinitionError as eParam:
|
||
|
self.pos = pos
|
||
|
err = eParam
|
||
|
self.skip_ws()
|
||
|
if self.skip_string('>'):
|
||
|
requiresClause = self._parse_requires_clause()
|
||
|
return ASTTemplateParams(templateParams, requiresClause)
|
||
|
elif self.skip_string(','):
|
||
|
continue
|
||
|
else:
|
||
|
header = "Error in template parameter list."
|
||
|
errs = []
|
||
|
if err:
|
||
|
errs.append((err, "If parameter"))
|
||
|
try:
|
||
|
self.fail('Expected "," or ">".')
|
||
|
except DefinitionError as e:
|
||
|
errs.append((e, "If no parameter"))
|
||
|
logger.debug(errs)
|
||
|
raise self._make_multi_error(errs, header)
|
||
|
|
||
|
def _parse_template_introduction(self) -> ASTTemplateIntroduction | None:
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
concept = self._parse_nested_name()
|
||
|
except Exception:
|
||
|
self.pos = pos
|
||
|
return None
|
||
|
self.skip_ws()
|
||
|
if not self.skip_string('{'):
|
||
|
self.pos = pos
|
||
|
return None
|
||
|
|
||
|
# for sure it must be a template introduction now
|
||
|
params = []
|
||
|
while 1:
|
||
|
self.skip_ws()
|
||
|
parameterPack = self.skip_string('...')
|
||
|
self.skip_ws()
|
||
|
if not self.match(identifier_re):
|
||
|
self.fail("Expected identifier in template introduction list.")
|
||
|
txt_identifier = self.matched_text
|
||
|
# make sure there isn't a keyword
|
||
|
if txt_identifier in _keywords:
|
||
|
self.fail("Expected identifier in template introduction list, "
|
||
|
"got keyword: %s" % txt_identifier)
|
||
|
identifier = ASTIdentifier(txt_identifier)
|
||
|
params.append(ASTTemplateIntroductionParameter(identifier, parameterPack))
|
||
|
|
||
|
self.skip_ws()
|
||
|
if self.skip_string('}'):
|
||
|
break
|
||
|
if self.skip_string(','):
|
||
|
continue
|
||
|
self.fail('Error in template introduction list. Expected ",", or "}".')
|
||
|
return ASTTemplateIntroduction(concept, params)
|
||
|
|
||
|
def _parse_requires_clause(self) -> ASTRequiresClause | None:
|
||
|
# requires-clause -> 'requires' constraint-logical-or-expression
|
||
|
# constraint-logical-or-expression
|
||
|
# -> constraint-logical-and-expression
|
||
|
# | constraint-logical-or-expression '||' constraint-logical-and-expression
|
||
|
# constraint-logical-and-expression
|
||
|
# -> primary-expression
|
||
|
# | constraint-logical-and-expression '&&' primary-expression
|
||
|
self.skip_ws()
|
||
|
if not self.skip_word('requires'):
|
||
|
return None
|
||
|
|
||
|
def parse_and_expr(self: DefinitionParser) -> ASTExpression:
|
||
|
andExprs = []
|
||
|
ops = []
|
||
|
andExprs.append(self._parse_primary_expression())
|
||
|
while True:
|
||
|
self.skip_ws()
|
||
|
oneMore = False
|
||
|
if self.skip_string('&&'):
|
||
|
oneMore = True
|
||
|
ops.append('&&')
|
||
|
elif self.skip_word('and'):
|
||
|
oneMore = True
|
||
|
ops.append('and')
|
||
|
if not oneMore:
|
||
|
break
|
||
|
andExprs.append(self._parse_primary_expression())
|
||
|
if len(andExprs) == 1:
|
||
|
return andExprs[0]
|
||
|
else:
|
||
|
return ASTBinOpExpr(andExprs, ops)
|
||
|
|
||
|
orExprs = []
|
||
|
ops = []
|
||
|
orExprs.append(parse_and_expr(self))
|
||
|
while True:
|
||
|
self.skip_ws()
|
||
|
oneMore = False
|
||
|
if self.skip_string('||'):
|
||
|
oneMore = True
|
||
|
ops.append('||')
|
||
|
elif self.skip_word('or'):
|
||
|
oneMore = True
|
||
|
ops.append('or')
|
||
|
if not oneMore:
|
||
|
break
|
||
|
orExprs.append(parse_and_expr(self))
|
||
|
if len(orExprs) == 1:
|
||
|
return ASTRequiresClause(orExprs[0])
|
||
|
else:
|
||
|
return ASTRequiresClause(ASTBinOpExpr(orExprs, ops))
|
||
|
|
||
|
def _parse_template_declaration_prefix(self, objectType: str,
|
||
|
) -> ASTTemplateDeclarationPrefix | None:
|
||
|
templates: list[ASTTemplateParams | ASTTemplateIntroduction] = []
|
||
|
while 1:
|
||
|
self.skip_ws()
|
||
|
# the saved position is only used to provide a better error message
|
||
|
params: ASTTemplateParams | ASTTemplateIntroduction | None = None
|
||
|
pos = self.pos
|
||
|
if self.skip_word("template"):
|
||
|
try:
|
||
|
params = self._parse_template_parameter_list()
|
||
|
except DefinitionError as e:
|
||
|
if objectType == 'member' and len(templates) == 0:
|
||
|
return ASTTemplateDeclarationPrefix(None)
|
||
|
else:
|
||
|
raise e
|
||
|
if objectType == 'concept' and params.requiresClause is not None:
|
||
|
self.fail('requires-clause not allowed for concept')
|
||
|
else:
|
||
|
params = self._parse_template_introduction()
|
||
|
if not params:
|
||
|
break
|
||
|
if objectType == 'concept' and len(templates) > 0:
|
||
|
self.pos = pos
|
||
|
self.fail("More than 1 template parameter list for concept.")
|
||
|
templates.append(params)
|
||
|
if len(templates) == 0 and objectType == 'concept':
|
||
|
self.fail('Missing template parameter list for concept.')
|
||
|
if len(templates) == 0:
|
||
|
return None
|
||
|
else:
|
||
|
return ASTTemplateDeclarationPrefix(templates)
|
||
|
|
||
|
def _check_template_consistency(self, nestedName: ASTNestedName,
|
||
|
templatePrefix: ASTTemplateDeclarationPrefix,
|
||
|
fullSpecShorthand: bool, isMember: bool = False,
|
||
|
) -> ASTTemplateDeclarationPrefix:
|
||
|
numArgs = nestedName.num_templates()
|
||
|
isMemberInstantiation = False
|
||
|
if not templatePrefix:
|
||
|
numParams = 0
|
||
|
else:
|
||
|
if isMember and templatePrefix.templates is None:
|
||
|
numParams = 0
|
||
|
isMemberInstantiation = True
|
||
|
else:
|
||
|
numParams = len(templatePrefix.templates)
|
||
|
if numArgs + 1 < numParams:
|
||
|
self.fail("Too few template argument lists compared to parameter"
|
||
|
" lists. Argument lists: %d, Parameter lists: %d."
|
||
|
% (numArgs, numParams))
|
||
|
if numArgs > numParams:
|
||
|
numExtra = numArgs - numParams
|
||
|
if not fullSpecShorthand and not isMemberInstantiation:
|
||
|
msg = "Too many template argument lists compared to parameter" \
|
||
|
" lists. Argument lists: %d, Parameter lists: %d," \
|
||
|
" Extra empty parameters lists prepended: %d." \
|
||
|
% (numArgs, numParams, numExtra)
|
||
|
msg += " Declaration:\n\t"
|
||
|
if templatePrefix:
|
||
|
msg += "%s\n\t" % templatePrefix
|
||
|
msg += str(nestedName)
|
||
|
self.warn(msg)
|
||
|
|
||
|
newTemplates: list[ASTTemplateParams | ASTTemplateIntroduction] = [
|
||
|
ASTTemplateParams([], requiresClause=None)
|
||
|
for _i in range(numExtra)
|
||
|
]
|
||
|
if templatePrefix and not isMemberInstantiation:
|
||
|
newTemplates.extend(templatePrefix.templates)
|
||
|
templatePrefix = ASTTemplateDeclarationPrefix(newTemplates)
|
||
|
return templatePrefix
|
||
|
|
||
|
def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclaration:
|
||
|
if objectType not in ('class', 'union', 'function', 'member', 'type',
|
||
|
'concept', 'enum', 'enumerator'):
|
||
|
raise Exception('Internal error, unknown objectType "%s".' % objectType)
|
||
|
if directiveType not in ('class', 'struct', 'union', 'function', 'member', 'var',
|
||
|
'type', 'concept',
|
||
|
'enum', 'enum-struct', 'enum-class', 'enumerator'):
|
||
|
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
|
||
|
visibility = None
|
||
|
templatePrefix = None
|
||
|
trailingRequiresClause = None
|
||
|
declaration: Any = None
|
||
|
|
||
|
self.skip_ws()
|
||
|
if self.match(_visibility_re):
|
||
|
visibility = self.matched_text
|
||
|
|
||
|
if objectType in ('type', 'concept', 'member', 'function', 'class', 'union'):
|
||
|
templatePrefix = self._parse_template_declaration_prefix(objectType)
|
||
|
|
||
|
if objectType == 'type':
|
||
|
prevErrors = []
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
if not templatePrefix:
|
||
|
declaration = self._parse_type(named=True, outer='type')
|
||
|
except DefinitionError as e:
|
||
|
prevErrors.append((e, "If typedef-like declaration"))
|
||
|
self.pos = pos
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
if not declaration:
|
||
|
declaration = self._parse_type_using()
|
||
|
except DefinitionError as e:
|
||
|
self.pos = pos
|
||
|
prevErrors.append((e, "If type alias or template alias"))
|
||
|
header = "Error in type declaration."
|
||
|
raise self._make_multi_error(prevErrors, header) from e
|
||
|
elif objectType == 'concept':
|
||
|
declaration = self._parse_concept()
|
||
|
elif objectType == 'member':
|
||
|
declaration = self._parse_type_with_init(named=True, outer='member')
|
||
|
elif objectType == 'function':
|
||
|
declaration = self._parse_type(named=True, outer='function')
|
||
|
trailingRequiresClause = self._parse_requires_clause()
|
||
|
elif objectType == 'class':
|
||
|
declaration = self._parse_class()
|
||
|
elif objectType == 'union':
|
||
|
declaration = self._parse_union()
|
||
|
elif objectType == 'enum':
|
||
|
declaration = self._parse_enum()
|
||
|
elif objectType == 'enumerator':
|
||
|
declaration = self._parse_enumerator()
|
||
|
else:
|
||
|
raise AssertionError
|
||
|
templatePrefix = self._check_template_consistency(declaration.name,
|
||
|
templatePrefix,
|
||
|
fullSpecShorthand=False,
|
||
|
isMember=objectType == 'member')
|
||
|
self.skip_ws()
|
||
|
semicolon = self.skip_string(';')
|
||
|
return ASTDeclaration(objectType, directiveType, visibility,
|
||
|
templatePrefix, declaration,
|
||
|
trailingRequiresClause, semicolon)
|
||
|
|
||
|
def parse_namespace_object(self) -> ASTNamespace:
|
||
|
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
|
||
|
name = self._parse_nested_name()
|
||
|
templatePrefix = self._check_template_consistency(name, templatePrefix,
|
||
|
fullSpecShorthand=False)
|
||
|
res = ASTNamespace(name, templatePrefix)
|
||
|
res.objectType = 'namespace' # type: ignore[attr-defined]
|
||
|
return res
|
||
|
|
||
|
def parse_xref_object(self) -> tuple[ASTNamespace | ASTDeclaration, bool]:
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
templatePrefix = self._parse_template_declaration_prefix(objectType="xref")
|
||
|
name = self._parse_nested_name()
|
||
|
# if there are '()' left, just skip them
|
||
|
self.skip_ws()
|
||
|
self.skip_string('()')
|
||
|
self.assert_end()
|
||
|
templatePrefix = self._check_template_consistency(name, templatePrefix,
|
||
|
fullSpecShorthand=True)
|
||
|
res1 = ASTNamespace(name, templatePrefix)
|
||
|
res1.objectType = 'xref' # type: ignore[attr-defined]
|
||
|
return res1, True
|
||
|
except DefinitionError as e1:
|
||
|
try:
|
||
|
self.pos = pos
|
||
|
res2 = self.parse_declaration('function', 'function')
|
||
|
# if there are '()' left, just skip them
|
||
|
self.skip_ws()
|
||
|
self.skip_string('()')
|
||
|
self.assert_end()
|
||
|
return res2, False
|
||
|
except DefinitionError as e2:
|
||
|
errs = []
|
||
|
errs.append((e1, "If shorthand ref"))
|
||
|
errs.append((e2, "If full function ref"))
|
||
|
msg = "Error in cross-reference."
|
||
|
raise self._make_multi_error(errs, msg) from e2
|
||
|
|
||
|
def parse_expression(self) -> ASTExpression | ASTType:
|
||
|
pos = self.pos
|
||
|
try:
|
||
|
expr = self._parse_expression()
|
||
|
self.skip_ws()
|
||
|
self.assert_end()
|
||
|
return expr
|
||
|
except DefinitionError as exExpr:
|
||
|
self.pos = pos
|
||
|
try:
|
||
|
typ = self._parse_type(False)
|
||
|
self.skip_ws()
|
||
|
self.assert_end()
|
||
|
return typ
|
||
|
except DefinitionError as exType:
|
||
|
header = "Error when parsing (type) expression."
|
||
|
errs = []
|
||
|
errs.append((exExpr, "If expression"))
|
||
|
errs.append((exType, "If type"))
|
||
|
raise self._make_multi_error(errs, header) from exType
|