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.
1048 lines
40 KiB
1048 lines
40 KiB
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any, Callable
|
|
|
|
from sphinx.domains.c._ast import (
|
|
ASTAlignofExpr,
|
|
ASTArray,
|
|
ASTAssignmentExpr,
|
|
ASTBinOpExpr,
|
|
ASTBooleanLiteral,
|
|
ASTBracedInitList,
|
|
ASTCastExpr,
|
|
ASTCharLiteral,
|
|
ASTDeclaration,
|
|
ASTDeclarator,
|
|
ASTDeclaratorNameBitField,
|
|
ASTDeclaratorNameParam,
|
|
ASTDeclaratorParen,
|
|
ASTDeclaratorPtr,
|
|
ASTDeclSpecs,
|
|
ASTDeclSpecsSimple,
|
|
ASTEnum,
|
|
ASTEnumerator,
|
|
ASTExpression,
|
|
ASTFallbackExpr,
|
|
ASTFunctionParameter,
|
|
ASTIdentifier,
|
|
ASTIdExpression,
|
|
ASTInitializer,
|
|
ASTLiteral,
|
|
ASTMacro,
|
|
ASTMacroParameter,
|
|
ASTNestedName,
|
|
ASTNumberLiteral,
|
|
ASTParameters,
|
|
ASTParenExpr,
|
|
ASTParenExprList,
|
|
ASTPostfixArray,
|
|
ASTPostfixCallExpr,
|
|
ASTPostfixDec,
|
|
ASTPostfixExpr,
|
|
ASTPostfixInc,
|
|
ASTPostfixMemberOfPointer,
|
|
ASTPostfixOp,
|
|
ASTSizeofExpr,
|
|
ASTSizeofType,
|
|
ASTStringLiteral,
|
|
ASTStruct,
|
|
ASTTrailingTypeSpec,
|
|
ASTTrailingTypeSpecFundamental,
|
|
ASTTrailingTypeSpecName,
|
|
ASTType,
|
|
ASTTypeWithInit,
|
|
ASTUnaryOpExpr,
|
|
ASTUnion,
|
|
DeclarationType,
|
|
)
|
|
from sphinx.domains.c._ids import (
|
|
_expression_assignment_ops,
|
|
_expression_bin_ops,
|
|
_expression_unary_ops,
|
|
_keywords,
|
|
_simple_type_specifiers_re,
|
|
_string_re,
|
|
)
|
|
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
|
|
|
|
|
|
class DefinitionParser(BaseParser):
|
|
@property
|
|
def language(self) -> str:
|
|
return 'C'
|
|
|
|
@property
|
|
def id_attributes(self) -> Sequence[str]:
|
|
return self.config.c_id_attributes
|
|
|
|
@property
|
|
def paren_attributes(self) -> Sequence[str]:
|
|
return self.config.c_paren_attributes
|
|
|
|
def _parse_string(self) -> str | None:
|
|
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 | None:
|
|
# -> integer-literal
|
|
# | character-literal
|
|
# | floating-literal
|
|
# | string-literal
|
|
# | boolean-literal -> "false" | "true"
|
|
self.skip_ws()
|
|
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):
|
|
self.match(float_literal_suffix_re)
|
|
return ASTNumberLiteral(self.definition[pos:self.pos])
|
|
for regex in (binary_literal_re, hex_literal_re,
|
|
integer_literal_re, octal_literal_re):
|
|
if self.match(regex):
|
|
self.match(integers_literal_suffix_re)
|
|
return ASTNumberLiteral(self.definition[pos:self.pos])
|
|
|
|
string = self._parse_string()
|
|
if string is not None:
|
|
return 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:
|
|
return 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 None
|
|
|
|
def _parse_paren_expression(self) -> ASTExpression | None:
|
|
# "(" expression ")"
|
|
if self.current_char != '(':
|
|
return None
|
|
self.pos += 1
|
|
res = self._parse_expression()
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail("Expected ')' in end of parenthesized expression.")
|
|
return ASTParenExpr(res)
|
|
|
|
def _parse_primary_expression(self) -> ASTExpression | None:
|
|
# literal
|
|
# "(" expression ")"
|
|
# id-expression -> we parse this with _parse_nested_name
|
|
self.skip_ws()
|
|
res: ASTExpression | None = self._parse_literal()
|
|
if res is not None:
|
|
return res
|
|
res = self._parse_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] | None, bool | None]:
|
|
# Parse open and close with the actual initializer-list in between
|
|
# -> initializer-clause '...'[opt]
|
|
# | initializer-list ',' initializer-clause '...'[opt]
|
|
# TODO: designators
|
|
self.skip_ws()
|
|
if not self.skip_string_and_ws(open):
|
|
return None, None
|
|
if self.skip_string(close):
|
|
return [], False
|
|
|
|
exprs = []
|
|
trailingComma = False
|
|
while True:
|
|
self.skip_ws()
|
|
expr = self._parse_expression()
|
|
self.skip_ws()
|
|
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 | None:
|
|
# -> '(' 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_braced_init_list(self) -> ASTBracedInitList | None:
|
|
# -> '{' initializer-list ','[opt] '}'
|
|
# | '{' '}'
|
|
exprs, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}')
|
|
if exprs is None:
|
|
return None
|
|
return ASTBracedInitList(exprs, trailingComma)
|
|
|
|
def _parse_postfix_expression(self) -> ASTPostfixExpr:
|
|
# -> primary
|
|
# | postfix "[" expression "]"
|
|
# | postfix "[" braced-init-list [opt] "]"
|
|
# | postfix "(" expression-list [opt] ")"
|
|
# | postfix "." id-expression // taken care of in primary by nested name
|
|
# | postfix "->" id-expression
|
|
# | postfix "++"
|
|
# | postfix "--"
|
|
|
|
prefix = self._parse_primary_expression()
|
|
|
|
# and now parse postfixes
|
|
postFixes: list[ASTPostfixOp] = []
|
|
while True:
|
|
self.skip_ws()
|
|
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 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_paren_expression_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 ")"
|
|
# | "alignof" "(" type-id ")"
|
|
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('('):
|
|
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)
|
|
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) -> 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) -> ASTExpression:
|
|
if opId + 1 == len(_expression_bin_ops):
|
|
def parser() -> ASTExpression:
|
|
return self._parse_cast_expression()
|
|
else:
|
|
def parser() -> ASTExpression:
|
|
return _parse_bin_op_expr(self, opId + 1)
|
|
exprs = []
|
|
ops = []
|
|
exprs.append(parser())
|
|
while True:
|
|
self.skip_ws()
|
|
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()
|
|
exprs.append(expr)
|
|
ops.append(op)
|
|
oneMore = True
|
|
break
|
|
except DefinitionError:
|
|
self.pos = pos
|
|
if not oneMore:
|
|
break
|
|
return ASTBinOpExpr(exprs, ops) # type: ignore[return-value]
|
|
return _parse_bin_op_expr(self, 0)
|
|
|
|
def _parse_conditional_expression_tail(self, orExprHead: Any) -> ASTExpression | None:
|
|
# -> "?" expression ":" assignment-expression
|
|
return None
|
|
|
|
def _parse_assignment_expression(self) -> ASTExpression:
|
|
# -> conditional-expression
|
|
# | logical-or-expression assignment-operator initializer-clause
|
|
# -> conditional-expression ->
|
|
# logical-or-expression
|
|
# | logical-or-expression "?" expression ":" assignment-expression
|
|
# | logical-or-expression assignment-operator initializer-clause
|
|
exprs = []
|
|
ops = []
|
|
orExpr = self._parse_logical_or_expression()
|
|
exprs.append(orExpr)
|
|
# TODO: handle ternary with _parse_conditional_expression_tail
|
|
while True:
|
|
oneMore = False
|
|
self.skip_ws()
|
|
for op in _expression_assignment_ops:
|
|
if op[0] in 'abcnox':
|
|
if not self.skip_word(op):
|
|
continue
|
|
else:
|
|
if not self.skip_string(op):
|
|
continue
|
|
expr = self._parse_logical_or_expression()
|
|
exprs.append(expr)
|
|
ops.append(op)
|
|
oneMore = True
|
|
if not oneMore:
|
|
break
|
|
return ASTAssignmentExpr(exprs, ops)
|
|
|
|
def _parse_constant_expression(self) -> ASTExpression:
|
|
# -> conditional-expression
|
|
orExpr = self._parse_logical_or_expression()
|
|
# TODO: use _parse_conditional_expression_tail
|
|
return orExpr
|
|
|
|
def _parse_expression(self) -> ASTExpression:
|
|
# -> assignment-expression
|
|
# | expression "," assignment-expression
|
|
# TODO: actually parse the second production
|
|
return self._parse_assignment_expression()
|
|
|
|
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_nested_name(self) -> ASTNestedName:
|
|
names: list[Any] = []
|
|
|
|
self.skip_ws()
|
|
rooted = False
|
|
if self.skip_string('.'):
|
|
rooted = True
|
|
while 1:
|
|
self.skip_ws()
|
|
if not self.match(identifier_re):
|
|
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)
|
|
if self.matched_text in self.config.c_extra_keywords:
|
|
msg = "Expected identifier, got user-defined keyword: %s." \
|
|
+ " Remove it from c_extra_keywords to allow it as identifier.\n" \
|
|
+ "Currently c_extra_keywords is %s."
|
|
self.fail(msg % (self.matched_text,
|
|
str(self.config.c_extra_keywords)))
|
|
ident = ASTIdentifier(identifier)
|
|
names.append(ident)
|
|
|
|
self.skip_ws()
|
|
if not self.skip_string('.'):
|
|
break
|
|
return ASTNestedName(names, rooted)
|
|
|
|
def _parse_simple_type_specifier(self) -> str | None:
|
|
if self.match(_simple_type_specifiers_re):
|
|
return self.matched_text
|
|
for t in ('bool', 'complex', 'imaginary'):
|
|
if t in self.config.c_extra_keywords:
|
|
if self.skip_word(t):
|
|
return t
|
|
return None
|
|
|
|
def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental | None:
|
|
names: list[str] = []
|
|
|
|
self.skip_ws()
|
|
while True:
|
|
t = self._parse_simple_type_specifier()
|
|
if t is None:
|
|
break
|
|
names.append(t)
|
|
self.skip_ws()
|
|
if len(names) == 0:
|
|
return None
|
|
return ASTTrailingTypeSpecFundamental(names)
|
|
|
|
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
|
|
# fundamental types, https://en.cppreference.com/w/c/language/type
|
|
# and extensions
|
|
self.skip_ws()
|
|
res = self._parse_simple_type_specifiers()
|
|
if res is not None:
|
|
return res
|
|
|
|
# prefixed
|
|
prefix = None
|
|
self.skip_ws()
|
|
for k in ('struct', 'enum', 'union'):
|
|
if self.skip_word_and_ws(k):
|
|
prefix = k
|
|
break
|
|
|
|
nestedName = self._parse_nested_name()
|
|
return ASTTrailingTypeSpecName(prefix, nestedName)
|
|
|
|
def _parse_parameters(self, paramMode: str) -> ASTParameters | None:
|
|
self.skip_ws()
|
|
if not self.skip_string('('):
|
|
if paramMode == 'function':
|
|
self.fail('Expecting "(" in parameters.')
|
|
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.')
|
|
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(f'Expecting "," or ")" in parameters, got "{self.current_char}".')
|
|
|
|
attrs = self._parse_attribute_list()
|
|
return ASTParameters(args, attrs)
|
|
|
|
def _parse_decl_specs_simple(
|
|
self, outer: str | None, typed: bool,
|
|
) -> ASTDeclSpecsSimple:
|
|
"""Just parse the simple ones."""
|
|
storage = None
|
|
threadLocal = None
|
|
inline = None
|
|
restrict = None
|
|
volatile = None
|
|
const = None
|
|
attrs = []
|
|
while 1: # accept any permutation of a subset of some decl-specs
|
|
self.skip_ws()
|
|
if not storage:
|
|
if outer == 'member':
|
|
if self.skip_word('auto'):
|
|
storage = 'auto'
|
|
continue
|
|
if self.skip_word('register'):
|
|
storage = 'register'
|
|
continue
|
|
if outer in ('member', 'function'):
|
|
if self.skip_word('static'):
|
|
storage = 'static'
|
|
continue
|
|
if self.skip_word('extern'):
|
|
storage = 'extern'
|
|
continue
|
|
if outer == 'member' and not threadLocal:
|
|
if self.skip_word('thread_local'):
|
|
threadLocal = 'thread_local'
|
|
continue
|
|
if self.skip_word('_Thread_local'):
|
|
threadLocal = '_Thread_local'
|
|
continue
|
|
if outer == 'function' and not inline:
|
|
inline = self.skip_word('inline')
|
|
if inline:
|
|
continue
|
|
|
|
if not restrict and typed:
|
|
restrict = self.skip_word('restrict')
|
|
if restrict:
|
|
continue
|
|
if not volatile and typed:
|
|
volatile = self.skip_word('volatile')
|
|
if volatile:
|
|
continue
|
|
if not const and typed:
|
|
const = self.skip_word('const')
|
|
if const:
|
|
continue
|
|
attr = self._parse_attribute()
|
|
if attr:
|
|
attrs.append(attr)
|
|
continue
|
|
break
|
|
return ASTDeclSpecsSimple(storage, threadLocal, inline,
|
|
restrict, volatile, const, ASTAttributeList(attrs))
|
|
|
|
def _parse_decl_specs(self, outer: str | None, typed: bool = True) -> ASTDeclSpecs:
|
|
if outer:
|
|
if outer not in ('type', 'member', 'function'):
|
|
raise Exception('Internal error, unknown outer "%s".' % outer)
|
|
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,
|
|
) -> ASTDeclarator:
|
|
assert named in (True, False, 'single')
|
|
# now we should parse the name, and then suffixes
|
|
if named == 'single':
|
|
if self.match(identifier_re):
|
|
if self.matched_text in _keywords:
|
|
self.fail("Expected identifier, "
|
|
"got keyword: %s" % self.matched_text)
|
|
if self.matched_text in self.config.c_extra_keywords:
|
|
msg = "Expected identifier, got user-defined keyword: %s." \
|
|
+ " Remove it from c_extra_keywords to allow it as identifier.\n" \
|
|
+ "Currently c_extra_keywords is %s."
|
|
self.fail(msg % (self.matched_text,
|
|
str(self.config.c_extra_keywords)))
|
|
identifier = ASTIdentifier(self.matched_text)
|
|
declId = ASTNestedName([identifier], rooted=False)
|
|
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()
|
|
static = False
|
|
const = False
|
|
volatile = False
|
|
restrict = False
|
|
while True:
|
|
if not static:
|
|
if self.skip_word_and_ws('static'):
|
|
static = True
|
|
continue
|
|
if not const:
|
|
if self.skip_word_and_ws('const'):
|
|
const = True
|
|
continue
|
|
if not volatile:
|
|
if self.skip_word_and_ws('volatile'):
|
|
volatile = True
|
|
continue
|
|
if not restrict:
|
|
if self.skip_word_and_ws('restrict'):
|
|
restrict = True
|
|
continue
|
|
break
|
|
vla = False if static else self.skip_string_and_ws('*')
|
|
if vla:
|
|
if not self.skip_string(']'):
|
|
self.fail("Expected ']' in end of array operator.")
|
|
size = None
|
|
else:
|
|
if self.skip_string(']'):
|
|
size = None
|
|
else:
|
|
|
|
def parser() -> ASTExpression:
|
|
return self._parse_expression()
|
|
size = self._parse_expression_fallback([']'], parser)
|
|
self.skip_ws()
|
|
if not self.skip_string(']'):
|
|
self.fail("Expected ']' in end of array operator.")
|
|
arrayOps.append(ASTArray(static, const, volatile, restrict, vla, size))
|
|
else:
|
|
break
|
|
param = self._parse_parameters(paramMode)
|
|
if param 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()
|
|
return ASTDeclaratorNameBitField(declId=declId, size=size)
|
|
return ASTDeclaratorNameParam(declId=declId, arrayOps=arrayOps,
|
|
param=param)
|
|
|
|
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'):
|
|
raise Exception(
|
|
"Internal error, unknown paramMode '%s'." % paramMode)
|
|
prevErrors = []
|
|
self.skip_ws()
|
|
if typed and self.skip_string('*'):
|
|
self.skip_ws()
|
|
restrict = False
|
|
volatile = False
|
|
const = False
|
|
attrs = []
|
|
while 1:
|
|
if not restrict:
|
|
restrict = self.skip_word_and_ws('restrict')
|
|
if restrict:
|
|
continue
|
|
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:
|
|
attrs.append(attr)
|
|
continue
|
|
break
|
|
next = self._parse_declarator(named, paramMode, typed)
|
|
return ASTDeclaratorPtr(next=next,
|
|
restrict=restrict, volatile=volatile, const=const,
|
|
attrs=ASTAttributeList(attrs))
|
|
if typed and self.current_char == '(': # note: peeking, not skipping
|
|
# maybe this is the beginning of params, try that first,
|
|
# otherwise assume it's noptr->declarator > ( ptr-declarator )
|
|
pos = self.pos
|
|
try:
|
|
# assume this is params
|
|
res = self._parse_declarator_name_suffix(named, paramMode,
|
|
typed)
|
|
return res
|
|
except DefinitionError as exParamQual:
|
|
msg = "If declarator-id with parameters"
|
|
if paramMode == 'function':
|
|
msg += " (e.g., 'void f(int arg)')"
|
|
prevErrors.append((exParamQual, msg))
|
|
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 b
|
|
# 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
|
|
msg = "If parenthesis in noptr-declarator"
|
|
if paramMode == 'function':
|
|
msg += " (e.g., 'void (*f(int arg))(double)')"
|
|
prevErrors.append((exNoPtrParen, msg))
|
|
header = "Error in declarator"
|
|
raise self._make_multi_error(prevErrors, header) from exNoPtrParen
|
|
pos = self.pos
|
|
try:
|
|
return self._parse_declarator_name_suffix(named, paramMode, typed)
|
|
except DefinitionError as e:
|
|
self.pos = pos
|
|
prevErrors.append((e, "If declarator-id"))
|
|
header = "Error in declarator or parameters"
|
|
raise self._make_multi_error(prevErrors, header) from e
|
|
|
|
def _parse_initializer(self, outer: str | None = None, allowFallback: bool = True,
|
|
) -> ASTInitializer | None:
|
|
self.skip_ws()
|
|
if outer == 'member' and False: # NoQA: SIM223 # TODO
|
|
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 is None: # function parameter
|
|
fallbackEnd = [',', ')']
|
|
else:
|
|
self.fail("Internal error, initializer for outer '%s' not "
|
|
"implemented." % outer)
|
|
|
|
def parser() -> ASTExpression:
|
|
return self._parse_assignment_expression()
|
|
|
|
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|'single'|True: 'single' is e.g., for function objects which
|
|
doesn't need to name the arguments, but otherwise is a single name
|
|
"""
|
|
if outer: # always named
|
|
if outer not in ('type', 'member', 'function'):
|
|
raise Exception('Internal error, unknown outer "%s".' % outer)
|
|
assert named
|
|
|
|
if outer == 'type':
|
|
# We allow type objects to just be a name.
|
|
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)
|
|
self.assert_end(allowSemicolon=True)
|
|
except DefinitionError as exUntyped:
|
|
desc = "If just a name"
|
|
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
|
|
desc = "If typedef-like declaration"
|
|
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:
|
|
header = "Type must be either just a name or a "
|
|
header += "typedef-like declaration."
|
|
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)
|
|
elif outer == 'function':
|
|
declSpecs = self._parse_decl_specs(outer=outer)
|
|
decl = self._parse_declarator(named=True, paramMode=outer)
|
|
else:
|
|
paramMode = 'type'
|
|
if outer == 'member': # i.e., member
|
|
named = True
|
|
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 | None) -> ASTTypeWithInit:
|
|
if outer:
|
|
assert outer in ('type', 'member', 'function')
|
|
type = self._parse_type(outer=outer, named=named)
|
|
init = self._parse_initializer(outer=outer)
|
|
return ASTTypeWithInit(type, init)
|
|
|
|
def _parse_macro(self) -> ASTMacro:
|
|
self.skip_ws()
|
|
ident = self._parse_nested_name()
|
|
if ident is None:
|
|
self.fail("Expected identifier in macro definition.")
|
|
self.skip_ws()
|
|
if not self.skip_string_and_ws('('):
|
|
return ASTMacro(ident, None)
|
|
if self.skip_string(')'):
|
|
return ASTMacro(ident, [])
|
|
args = []
|
|
while 1:
|
|
self.skip_ws()
|
|
if self.skip_string('...'):
|
|
args.append(ASTMacroParameter(None, True))
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail('Expected ")" after "..." in macro parameters.')
|
|
break
|
|
if not self.match(identifier_re):
|
|
self.fail("Expected identifier in macro parameters.")
|
|
nn = ASTNestedName([ASTIdentifier(self.matched_text)], rooted=False)
|
|
# Allow named variadic args:
|
|
# https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
|
|
self.skip_ws()
|
|
if self.skip_string_and_ws('...'):
|
|
args.append(ASTMacroParameter(nn, False, True))
|
|
self.skip_ws()
|
|
if not self.skip_string(')'):
|
|
self.fail('Expected ")" after "..." in macro parameters.')
|
|
break
|
|
args.append(ASTMacroParameter(nn))
|
|
if self.skip_string_and_ws(','):
|
|
continue
|
|
if self.skip_string_and_ws(')'):
|
|
break
|
|
self.fail("Expected identifier, ')', or ',' in macro parameter list.")
|
|
return ASTMacro(ident, args)
|
|
|
|
def _parse_struct(self) -> ASTStruct:
|
|
name = self._parse_nested_name()
|
|
return ASTStruct(name)
|
|
|
|
def _parse_union(self) -> ASTUnion:
|
|
name = self._parse_nested_name()
|
|
return ASTUnion(name)
|
|
|
|
def _parse_enum(self) -> ASTEnum:
|
|
name = self._parse_nested_name()
|
|
return ASTEnum(name)
|
|
|
|
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()
|
|
|
|
initVal = self._parse_expression_fallback([], parser)
|
|
init = ASTInitializer(initVal)
|
|
return ASTEnumerator(name, init, attrs)
|
|
|
|
def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclaration:
|
|
if objectType not in ('function', 'member',
|
|
'macro', 'struct', 'union', 'enum', 'enumerator', 'type'):
|
|
raise Exception('Internal error, unknown objectType "%s".' % objectType)
|
|
if directiveType not in ('function', 'member', 'var',
|
|
'macro', 'struct', 'union', 'enum', 'enumerator', 'type'):
|
|
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
|
|
|
|
declaration: DeclarationType | None = None
|
|
if objectType == 'member':
|
|
declaration = self._parse_type_with_init(named=True, outer='member')
|
|
elif objectType == 'function':
|
|
declaration = self._parse_type(named=True, outer='function')
|
|
elif objectType == 'macro':
|
|
declaration = self._parse_macro()
|
|
elif objectType == 'struct':
|
|
declaration = self._parse_struct()
|
|
elif objectType == 'union':
|
|
declaration = self._parse_union()
|
|
elif objectType == 'enum':
|
|
declaration = self._parse_enum()
|
|
elif objectType == 'enumerator':
|
|
declaration = self._parse_enumerator()
|
|
elif objectType == 'type':
|
|
declaration = self._parse_type(named=True, outer='type')
|
|
else:
|
|
raise AssertionError
|
|
if objectType != 'macro':
|
|
self.skip_ws()
|
|
semicolon = self.skip_string(';')
|
|
else:
|
|
semicolon = False
|
|
return ASTDeclaration(objectType, directiveType, declaration, semicolon)
|
|
|
|
def parse_namespace_object(self) -> ASTNestedName:
|
|
return self._parse_nested_name()
|
|
|
|
def parse_xref_object(self) -> ASTNestedName:
|
|
name = self._parse_nested_name()
|
|
# if there are '()' left, just skip them
|
|
self.skip_ws()
|
|
self.skip_string('()')
|
|
self.assert_end()
|
|
return name
|
|
|
|
def parse_expression(self) -> ASTExpression | ASTType:
|
|
pos = self.pos
|
|
res: ASTExpression | ASTType | None = None
|
|
try:
|
|
res = self._parse_expression()
|
|
self.skip_ws()
|
|
self.assert_end()
|
|
except DefinitionError as exExpr:
|
|
self.pos = pos
|
|
try:
|
|
res = self._parse_type(False)
|
|
self.skip_ws()
|
|
self.assert_end()
|
|
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
|
|
return res
|
|
|