This repository provides User Manual for setting up a Docker environment tailored for testing DGTD code.
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.

1049 lines
40 KiB

8 months ago
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