C, add expression parsing and expr role

This commit is contained in:
Jakob Lykke Andersen 2020-03-14 18:18:10 +01:00
parent 815a9c657d
commit b2ca906830
6 changed files with 976 additions and 213 deletions

View File

@ -674,6 +674,38 @@ Explicit ref: :c:var:`Data.@data.a`. Short-hand ref: :c:var:`Data.a`.
.. versionadded:: 3.0
Inline Expressions and Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. rst:role:: c:expr
c:texpr
Insert a C expression or type either as inline code (``cpp:expr``)
or inline text (``cpp:texpr``). For example::
.. c:var:: int a = 42
.. c:function:: int f(int i)
An expression: :c:expr:`a * f(a)` (or as text: :c:texpr:`a * f(a)`).
A type: :c:expr:`const Data*`
(or as text :c:texpr:`const Data*`).
will be rendered as follows:
.. c:var:: int a = 42
.. c:function:: int f(int i)
An expression: :c:expr:`a * f(a)` (or as text: :c:texpr:`a * f(a)`).
A type: :c:expr:`const Data*`
(or as text :c:texpr:`const Data*`).
.. versionadded:: 3.0
.. _cpp-domain:
The C++ Domain

View File

@ -14,8 +14,9 @@ from typing import (
)
from typing import cast
from docutils import nodes
from docutils.nodes import Element, Node
from docutils import nodes, utils
from docutils.nodes import Element, Node, TextElement, system_message
from docutils.parsers.rst.states import Inliner
from sphinx import addnodes
from sphinx.addnodes import pending_xref, desc_signature
@ -30,7 +31,10 @@ from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.cfamily import (
NoOldIdError, ASTBase, verify_description_mode, StringifyTransform,
BaseParser, DefinitionError, identifier_re, anon_identifier_re
BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral,
identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re,
hex_literal_re, binary_literal_re, float_literal_re,
char_literal_re
)
from sphinx.util.docfields import Field, TypedField
from sphinx.util.nodes import make_refnode
@ -49,6 +53,24 @@ _keywords = [
'_Thread_local', 'thread_local',
]
# these are ordered by preceedence
_expression_bin_ops = [
['||'],
['&&'],
['|'],
['^'],
['&'],
['==', '!='],
['<=', '>=', '<', '>'],
['<<', '>>'],
['+', '-'],
['*', '/', '%'],
['.*', '->*']
]
_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "~"]
_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
">>=", "<<=", "&=", "^=", "|="]
_max_id = 1
_id_prefix = [None, 'c.', 'Cv2.']
# Ids are used in lookup keys which are used across pickled files,
@ -69,7 +91,307 @@ class _DuplicateSymbolError(Exception):
return "Internal C duplicate symbol error:\n%s" % self.symbol.dump(0)
##############################################################################################
################################################################################
# Expressions and Literals
################################################################################
class ASTBooleanLiteral(ASTBase):
def __init__(self, value: bool) -> None:
self.value = value
def _stringify(self, transform: StringifyTransform) -> str:
if self.value:
return 'true'
else:
return 'false'
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(str(self)))
class ASTNumberLiteral(ASTBase):
def __init__(self, data: str) -> None:
self.data = data
def _stringify(self, transform: StringifyTransform) -> str:
return self.data
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
class ASTCharLiteral(ASTBase):
def __init__(self, prefix: str, data: str) -> None:
self.prefix = prefix # may be None when no prefix
self.data = data
decoded = data.encode().decode('unicode-escape')
if len(decoded) == 1:
self.value = ord(decoded)
else:
raise UnsupportedMultiCharacterCharLiteral(decoded)
def _stringify(self, transform: StringifyTransform) -> str:
if self.prefix is None:
return "'" + self.data + "'"
else:
return self.prefix + "'" + self.data + "'"
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
class ASTStringLiteral(ASTBase):
def __init__(self, data: str) -> None:
self.data = data
def _stringify(self, transform: StringifyTransform) -> str:
return self.data
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
class ASTParenExpr(ASTBase):
def __init__(self, expr):
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
return '(' + transform(self.expr) + ')'
def get_id(self, version: int) -> str:
return self.expr.get_id(version)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('(', '('))
self.expr.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')', ')'))
class ASTBinOpExpr(ASTBase):
def __init__(self, exprs, ops):
assert len(exprs) > 0
assert len(exprs) == len(ops) + 1
self.exprs = exprs
self.ops = ops
def _stringify(self, transform: StringifyTransform) -> str:
res = []
res.append(transform(self.exprs[0]))
for i in range(1, len(self.exprs)):
res.append(' ')
res.append(self.ops[i - 1])
res.append(' ')
res.append(transform(self.exprs[i]))
return ''.join(res)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.exprs[0].describe_signature(signode, mode, env, symbol)
for i in range(1, len(self.exprs)):
signode.append(nodes.Text(' '))
signode.append(nodes.Text(self.ops[i - 1]))
signode.append(nodes.Text(' '))
self.exprs[i].describe_signature(signode, mode, env, symbol)
class ASTAssignmentExpr(ASTBase):
def __init__(self, exprs, ops):
assert len(exprs) > 0
assert len(exprs) == len(ops) + 1
self.exprs = exprs
self.ops = ops
def _stringify(self, transform: StringifyTransform) -> str:
res = []
res.append(transform(self.exprs[0]))
for i in range(1, len(self.exprs)):
res.append(' ')
res.append(self.ops[i - 1])
res.append(' ')
res.append(transform(self.exprs[i]))
return ''.join(res)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.exprs[0].describe_signature(signode, mode, env, symbol)
for i in range(1, len(self.exprs)):
signode.append(nodes.Text(' '))
signode.append(nodes.Text(self.ops[i - 1]))
signode.append(nodes.Text(' '))
self.exprs[i].describe_signature(signode, mode, env, symbol)
class ASTCastExpr(ASTBase):
def __init__(self, typ, expr):
self.typ = typ
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
res = ['(']
res.append(transform(self.typ))
res.append(')')
res.append(transform(self.expr))
return ''.join(res)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('('))
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
self.expr.describe_signature(signode, mode, env, symbol)
class ASTUnaryOpExpr(ASTBase):
def __init__(self, op, expr):
self.op = op
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
return transform(self.op) + transform(self.expr)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(self.op))
self.expr.describe_signature(signode, mode, env, symbol)
class ASTSizeofType(ASTBase):
def __init__(self, typ):
self.typ = typ
def _stringify(self, transform: StringifyTransform) -> str:
return "sizeof(" + transform(self.typ) + ")"
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('sizeof('))
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
class ASTSizeofExpr(ASTBase):
def __init__(self, expr):
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
return "sizeof " + transform(self.expr)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('sizeof '))
self.expr.describe_signature(signode, mode, env, symbol)
class ASTAlignofExpr(ASTBase):
def __init__(self, typ):
self.typ = typ
def _stringify(self, transform: StringifyTransform) -> str:
return "alignof(" + transform(self.typ) + ")"
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('alignof('))
self.typ.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
class ASTPostfixCallExpr(ASTBase):
def __init__(self, lst: Union["ASTParenExprList", "ASTBracedInitList"]) -> None:
self.lst = lst
def _stringify(self, transform: StringifyTransform) -> str:
return transform(self.lst)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.lst.describe_signature(signode, mode, env, symbol)
class ASTPostfixArray(ASTBase):
def __init__(self, expr):
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
return '[' + transform(self.expr) + ']'
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('['))
self.expr.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(']'))
class ASTPostfixInc(ASTBase):
def _stringify(self, transform: StringifyTransform) -> str:
return '++'
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('++'))
class ASTPostfixDec(ASTBase):
def _stringify(self, transform: StringifyTransform) -> str:
return '--'
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('--'))
class ASTPostfixMember(ASTBase):
def __init__(self, name):
self.name = name
def _stringify(self, transform: StringifyTransform) -> str:
return '.' + transform(self.name)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('.'))
self.name.describe_signature(signode, 'noneIsName', env, symbol)
class ASTPostfixMemberOfPointer(ASTBase):
def __init__(self, name):
self.name = name
def _stringify(self, transform: StringifyTransform) -> str:
return '->' + transform(self.name)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text('->'))
self.name.describe_signature(signode, 'noneIsName', env, symbol)
class ASTPostfixExpr(ASTBase):
def __init__(self, prefix, postFixes):
assert len(postFixes) > 0
self.prefix = prefix
self.postFixes = postFixes
def _stringify(self, transform: StringifyTransform) -> str:
res = [transform(self.prefix)]
for p in self.postFixes:
res.append(transform(p))
return ''.join(res)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
self.prefix.describe_signature(signode, mode, env, symbol)
for p in self.postFixes:
p.describe_signature(signode, mode, env, symbol)
class ASTFallbackExpr(ASTBase):
def __init__(self, expr):
@ -452,7 +774,9 @@ class ASTDeclaratorPtr(ASTBase):
return self.next.function_params
def require_space_after_declSpecs(self) -> bool:
return True
return self.const or self.volatile or self.restrict or \
len(self.attrs) > 0 or \
self.next.require_space_after_declSpecs()
def _stringify(self, transform: StringifyTransform) -> str:
res = ['*']
@ -598,9 +922,7 @@ class ASTDeclaratorNameBitField(ASTBase):
if self.declId:
res.append(transform(self.declId))
res.append(" : ")
# TODO: when size is properly parsed
# res.append(transform(self.size))
res.append(self.size)
res.append(transform(self.size))
return ''.join(res)
def describe_signature(self, signode: desc_signature, mode: str,
@ -609,11 +931,58 @@ class ASTDeclaratorNameBitField(ASTBase):
if self.declId:
self.declId.describe_signature(signode, mode, env, symbol)
signode += nodes.Text(' : ', ' : ')
# TODO: when size is properly parsed
# self.size.describe_signature(signode, mode, env, symbol)
self.size.describe_signature(signode, mode, env, symbol)
signode += nodes.Text(self.size, self.size)
class ASTParenExprList(ASTBase):
def __init__(self, exprs: List[Any]) -> None:
self.exprs = exprs
def _stringify(self, transform: StringifyTransform) -> str:
exprs = [transform(e) for e in self.exprs]
return '(%s)' % ', '.join(exprs)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
signode.append(nodes.Text('('))
first = True
for e in self.exprs:
if not first:
signode.append(nodes.Text(', '))
else:
first = False
e.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(')'))
class ASTBracedInitList(ASTBase):
def __init__(self, exprs: List[Any], trailingComma: bool) -> None:
self.exprs = exprs
self.trailingComma = trailingComma
def _stringify(self, transform: StringifyTransform) -> str:
exprs = [transform(e) for e in self.exprs]
trailingComma = ',' if self.trailingComma else ''
return '{%s%s}' % (', '.join(exprs), trailingComma)
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
signode.append(nodes.Text('{'))
first = True
for e in self.exprs:
if not first:
signode.append(nodes.Text(', '))
else:
first = False
e.describe_signature(signode, mode, env, symbol)
if self.trailingComma:
signode.append(nodes.Text(','))
signode.append(nodes.Text('}'))
class ASTInitializer(ASTBase):
def __init__(self, value: Any, hasAssign: bool = True) -> None:
self.value = value
@ -1526,6 +1895,25 @@ class DefinitionParser(BaseParser):
_prefix_keys = ('struct', 'enum', 'union')
def _parse_string(self):
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_attribute(self) -> Any:
return None
# self.skip_ws()
@ -1586,23 +1974,340 @@ class DefinitionParser(BaseParser):
return None
def _parse_literal(self):
# -> 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)
for regex in [float_literal_re, binary_literal_re, hex_literal_re,
integer_literal_re, octal_literal_re]:
pos = self.pos
if self.match(regex):
while self.current_char in 'uUlLfF':
self.pos += 1
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):
# "(" 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):
# literal
# "(" expression ")"
# id-expression -> we parse this with _parse_nested_name
self.skip_ws()
res = self._parse_literal()
if res is not None:
return res
res = self._parse_paren_expression()
if res is not None:
return res
return self._parse_nested_name()
def _parse_initializer_list(self, name: str, open: str, close: str
) -> Tuple[List[Any], bool]:
# Parse open and close with the actual initializer-list inbetween
# -> 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("Error in %s, expected ',' or '%s'." % (name, close))
if self.current_char == close and 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_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_postfix_expression(self):
# -> primary
# | postfix "[" expression "]"
# | postfix "[" braced-init-list [opt] "]"
# | postfix "(" expression-list [opt] ")"
# | postfix "." id-expression
# | postfix "->" id-expression
# | postfix "++"
# | postfix "--"
prefix = self._parse_primary_expression()
# and now parse postfixes
postFixes = [] # type: List[Any]
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 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_paren_expression_list()
if lst is not None:
postFixes.append(ASTPostfixCallExpr(lst))
continue
break
if len(postFixes) == 0:
return prefix
else:
return ASTPostfixExpr(prefix, postFixes)
def _parse_unary_expression(self):
# -> 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 self.skip_string(op):
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):
# -> 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.")
else:
return self._parse_unary_expression()
def _parse_logical_or_expression(self):
# 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, opId):
if opId + 1 == len(_expression_bin_ops):
def parser():
return self._parse_cast_expression()
else:
def parser():
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 not self.skip_string(op):
continue
if op == '&' and 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)
return _parse_bin_op_expr(self, 0)
def _parse_conditional_expression_tail(self, orExprHead):
# -> "?" expression ":" assignment-expression
return None
def _parse_assignment_expression(self):
# -> 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 not self.skip_string(op):
continue
expr = self._parse_logical_or_expression()
exprs.append(expr)
ops.append(op)
oneMore = True
if not oneMore:
break
if len(ops) == 0:
return orExpr
else:
return ASTAssignmentExpr(exprs, ops)
def _parse_constant_expression(self):
# -> conditional-expression
orExpr = self._parse_logical_or_expression()
# TODO: use _parse_conditional_expression_tail
return orExpr
def _parse_expression(self):
# -> assignment-expression
# | expression "," assignment-expresion
# TODO: actually parse the second production
return self._parse_assignment_expression()
def _parse_expression_fallback(self, end, parser, allow=True):
# Stupidly "parse" an expression.
# 'end' should be a list of characters which ends the expression.
# first try to use the provided parser
# TODO: copy-paste and adapt real expression 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
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()
@ -1831,7 +2536,7 @@ class DefinitionParser(BaseParser):
continue
def parser():
return self._parse_expression(inTemplate=False)
return self._parse_expression()
value = self._parse_expression_fallback([']'], parser)
if not self.skip_string(']'):
@ -1846,9 +2551,7 @@ class DefinitionParser(BaseParser):
if named and paramMode == 'type' and typed:
self.skip_ws()
if self.skip_string(':'):
# TODO: copy-paste and adapt proper parser
# size = self._parse_constant_expression(inTemplate=False)
size = self.read_rest().strip()
size = self._parse_constant_expression()
return ASTDeclaratorNameBitField(declId=declId, size=size)
return ASTDeclaratorNameParam(declId=declId, arrayOps=arrayOps,
param=param)
@ -1939,10 +2642,9 @@ class DefinitionParser(BaseParser):
if not self.skip_string('='):
return None
# TODO: implement
# bracedInit = self._parse_braced_init_list()
# if bracedInit is not None:
# return ASTInitializer(bracedInit)
bracedInit = self._parse_braced_init_list()
if bracedInit is not None:
return ASTInitializer(bracedInit)
if outer == 'member':
fallbackEnd = [] # type: List[str]
@ -2077,7 +2779,7 @@ class DefinitionParser(BaseParser):
self.skip_ws()
def parser():
return self._parse_constant_expression(inTemplate=False)
return self._parse_constant_expression()
initVal = self._parse_expression_fallback([], parser)
init = ASTInitializer(initVal)
@ -2119,6 +2821,26 @@ class DefinitionParser(BaseParser):
self.assert_end()
return name
def parse_expression(self):
pos = self.pos
try:
expr = self._parse_expression()
self.skip_ws()
self.assert_end()
except DefinitionError as exExpr:
self.pos = pos
try:
expr = 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)
return expr
def _make_phony_error_name() -> ASTNestedName:
return ASTNestedName([ASTIdentifier("PhonyNameDueToError")], rooted=False)
@ -2365,6 +3087,44 @@ class CXRefRole(XRefRole):
return title, target
class CExprRole:
def __init__(self, asCode: bool) -> None:
if asCode:
# render the expression as inline code
self.class_type = 'c-expr'
self.node_type = nodes.literal # type: Type[TextElement]
else:
# render the expression as inline text
self.class_type = 'c-texpr'
self.node_type = nodes.inline
def __call__(self, typ: str, rawtext: str, text: str, lineno: int,
inliner: Inliner, options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
class Warner:
def warn(self, msg: str) -> None:
inliner.reporter.warning(msg, line=lineno)
text = utils.unescape(text).replace('\n', ' ')
env = inliner.document.settings.env
parser = DefinitionParser(text, Warner())
# attempt to mimic XRefRole classes, except that...
classes = ['xref', 'c', self.class_type]
try:
ast = parser.parse_expression()
except DefinitionError as ex:
Warner().warn('Unparseable C expression: %r\n%s' % (text, ex))
# see below
return [self.node_type(text, text, classes=classes)], []
parentSymbol = env.temp_data.get('cpp:parent_symbol', None)
if parentSymbol is None:
parentSymbol = env.domaindata['c']['root_symbol']
# ...most if not all of these classes should really apply to the individual references,
# not the container node
signode = self.node_type(classes=classes)
ast.describe_signature(signode, 'markType', env, parentSymbol)
return [signode], []
class CDomain(Domain):
"""C language domain."""
name = 'c'
@ -2399,6 +3159,8 @@ class CDomain(Domain):
'enum': CXRefRole(),
'enumerator': CXRefRole(),
'type': CXRefRole(),
'expr': CExprRole(asCode=True),
'texpr': CExprRole(asCode=False)
}
initial_data = {
'root_symbol': Symbol(None, None, None, None),

View File

@ -36,7 +36,10 @@ from sphinx.transforms.post_transforms import ReferencesResolver
from sphinx.util import logging
from sphinx.util.cfamily import (
NoOldIdError, ASTBase, verify_description_mode, StringifyTransform,
BaseParser, DefinitionError, identifier_re, anon_identifier_re
BaseParser, DefinitionError, UnsupportedMultiCharacterCharLiteral,
identifier_re, anon_identifier_re, integer_literal_re, octal_literal_re,
hex_literal_re, binary_literal_re, float_literal_re,
char_literal_re
)
from sphinx.util.docfields import Field, GroupedField
from sphinx.util.docutils import SphinxDirective
@ -296,37 +299,6 @@ T = TypeVar('T')
nested-name
"""
_integer_literal_re = re.compile(r'[1-9][0-9]*')
_octal_literal_re = re.compile(r'0[0-7]*')
_hex_literal_re = re.compile(r'0[xX][0-9a-fA-F][0-9a-fA-F]*')
_binary_literal_re = re.compile(r'0[bB][01][01]*')
_integer_suffix_re = re.compile(r'')
_float_literal_re = re.compile(r'''(?x)
[+-]?(
# decimal
([0-9]+[eE][+-]?[0-9]+)
| ([0-9]*\.[0-9]+([eE][+-]?[0-9]+)?)
| ([0-9]+\.([eE][+-]?[0-9]+)?)
# hex
| (0[xX][0-9a-fA-F]+[pP][+-]?[0-9a-fA-F]+)
| (0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+([pP][+-]?[0-9a-fA-F]+)?)
| (0[xX][0-9a-fA-F]+\.([pP][+-]?[0-9a-fA-F]+)?)
)
''')
_char_literal_re = re.compile(r'''(?x)
((?:u8)|u|U|L)?
'(
(?:[^\\'])
| (\\(
(?:['"?\\abfnrtv])
| (?:[0-7]{1,3})
| (?:x[0-9a-fA-F]{2})
| (?:u[0-9a-fA-F]{4})
| (?:U[0-9a-fA-F]{8})
))
)'
''')
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
_visibility_re = re.compile(r'\b(public|private|protected)\b')
@ -716,15 +688,6 @@ class ASTNumberLiteral(ASTBase):
signode.append(nodes.Text(txt, txt))
class UnsupportedMultiCharacterCharLiteral(Exception):
@property
def decoded(self) -> str:
warnings.warn('%s.decoded is deprecated. '
'Coerce the instance to a string instead.' % self.__class__.__name__,
RemovedInSphinx40Warning, stacklevel=2)
return str(self)
class ASTCharLiteral(ASTBase):
def __init__(self, prefix: str, data: str) -> None:
self.prefix = prefix # may be None when no prefix
@ -4561,8 +4524,8 @@ class DefinitionParser(BaseParser):
return ASTBooleanLiteral(True)
if self.skip_word('false'):
return ASTBooleanLiteral(False)
for regex in [_float_literal_re, _binary_literal_re, _hex_literal_re,
_integer_literal_re, _octal_literal_re]:
for regex in [float_literal_re, binary_literal_re, hex_literal_re,
integer_literal_re, octal_literal_re]:
pos = self.pos
if self.match(regex):
while self.current_char in 'uUlLfF':
@ -4574,7 +4537,7 @@ class DefinitionParser(BaseParser):
return ASTStringLiteral(string)
# character-literal
if self.match(_char_literal_re):
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:

View File

@ -31,6 +31,35 @@ identifier_re = re.compile(r'''(?x)
)
[a-zA-Z0-9_]*\b
''')
integer_literal_re = re.compile(r'[1-9][0-9]*')
octal_literal_re = re.compile(r'0[0-7]*')
hex_literal_re = re.compile(r'0[xX][0-9a-fA-F][0-9a-fA-F]*')
binary_literal_re = re.compile(r'0[bB][01][01]*')
float_literal_re = re.compile(r'''(?x)
[+-]?(
# decimal
([0-9]+[eE][+-]?[0-9]+)
| ([0-9]*\.[0-9]+([eE][+-]?[0-9]+)?)
| ([0-9]+\.([eE][+-]?[0-9]+)?)
# hex
| (0[xX][0-9a-fA-F]+[pP][+-]?[0-9a-fA-F]+)
| (0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+([pP][+-]?[0-9a-fA-F]+)?)
| (0[xX][0-9a-fA-F]+\.([pP][+-]?[0-9a-fA-F]+)?)
)
''')
char_literal_re = re.compile(r'''(?x)
((?:u8)|u|U|L)?
'(
(?:[^\\'])
| (\\(
(?:['"?\\abfnrtv])
| (?:[0-7]{1,3})
| (?:x[0-9a-fA-F]{2})
| (?:u[0-9a-fA-F]{4})
| (?:U[0-9a-fA-F]{8})
))
)'
''')
def verify_description_mode(mode: str) -> None:
@ -79,6 +108,15 @@ class ASTBase:
return '<%s>' % self.__class__.__name__
class UnsupportedMultiCharacterCharLiteral(Exception):
@property
def decoded(self) -> str:
warnings.warn('%s.decoded is deprecated. '
'Coerce the instance to a string instead.' % self.__class__.__name__,
RemovedInSphinx40Warning, stacklevel=2)
return str(self)
class DefinitionError(Exception):
@property
def description(self) -> str:

View File

@ -36,3 +36,11 @@ directives
- :c:member:`A.@data.a`
- :c:member:`A.a`
- :c:expr:`unsigned int`
- :c:texpr:`unsigned int`
.. c:var:: A a
- :c:expr:`a->b`
- :c:texpr:`a->b`

View File

@ -86,17 +86,34 @@ def check(name, input, idDict, output=None):
def test_expressions():
return # TODO
def exprCheck(expr, id, id4=None):
ids = 'IE1CIA%s_1aE'
idDict = {2: ids % expr, 3: ids % id}
if id4 is not None:
idDict[4] = ids % id4
check('class', 'template<> C<a[%s]>' % expr, idDict)
def exprCheck(expr, output=None):
parser = DefinitionParser(expr, None)
parser.allowFallbackExpressionParsing = False
ast = parser.parse_expression()
parser.assert_end()
# first a simple check of the AST
if output is None:
output = expr
res = str(ast)
if res != output:
print("")
print("Input: ", input)
print("Result: ", res)
print("Expected: ", output)
raise DefinitionError("")
# type expressions
exprCheck('int*')
exprCheck('int *const*')
exprCheck('int *volatile*')
exprCheck('int *restrict*')
exprCheck('int *(*)(double)')
exprCheck('const int*')
# actual expressions
# primary
exprCheck('nullptr', 'LDnE')
exprCheck('true', 'L1E')
exprCheck('false', 'L0E')
exprCheck('true')
exprCheck('false')
ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1']
unsignedSuffix = ['', 'u', 'U']
longSuffix = ['', 'l', 'L', 'll', 'LL']
@ -104,9 +121,9 @@ def test_expressions():
for u in unsignedSuffix:
for l in longSuffix:
expr = i + u + l
exprCheck(expr, 'L' + expr + 'E')
exprCheck(expr)
expr = i + l + u
exprCheck(expr, 'L' + expr + 'E')
exprCheck(expr)
for suffix in ['', 'f', 'F', 'l', 'L']:
for e in [
'5e42', '5e+42', '5e-42',
@ -114,134 +131,90 @@ def test_expressions():
'.5', '.5e42', '.5e+42', '.5e-42',
'5.0', '5.0e42', '5.0e+42', '5.0e-42']:
expr = e + suffix
exprCheck(expr, 'L' + expr + 'E')
exprCheck(expr)
for e in [
'ApF', 'Ap+F', 'Ap-F',
'A.', 'A.pF', 'A.p+F', 'A.p-F',
'.A', '.ApF', '.Ap+F', '.Ap-F',
'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F']:
expr = "0x" + e + suffix
exprCheck(expr, 'L' + expr + 'E')
exprCheck('"abc\\"cba"', 'LA8_KcE') # string
exprCheck('this', 'fpT')
exprCheck(expr)
exprCheck('"abc\\"cba"') # string
# character literals
for p, t in [('', 'c'), ('u8', 'c'), ('u', 'Ds'), ('U', 'Di'), ('L', 'w')]:
exprCheck(p + "'a'", t + "97")
exprCheck(p + "'\\n'", t + "10")
exprCheck(p + "'\\012'", t + "10")
exprCheck(p + "'\\0'", t + "0")
exprCheck(p + "'\\x0a'", t + "10")
exprCheck(p + "'\\x0A'", t + "10")
exprCheck(p + "'\\u0a42'", t + "2626")
exprCheck(p + "'\\u0A42'", t + "2626")
exprCheck(p + "'\\U0001f34c'", t + "127820")
exprCheck(p + "'\\U0001F34C'", t + "127820")
for p in ['', 'u8', 'u', 'U', 'L']:
exprCheck(p + "'a'")
exprCheck(p + "'\\n'")
exprCheck(p + "'\\012'")
exprCheck(p + "'\\0'")
exprCheck(p + "'\\x0a'")
exprCheck(p + "'\\x0A'")
exprCheck(p + "'\\u0a42'")
exprCheck(p + "'\\u0A42'")
exprCheck(p + "'\\U0001f34c'")
exprCheck(p + "'\\U0001F34C'")
# TODO: user-defined lit
exprCheck('(... + Ns)', '(... + Ns)', id4='flpl2Ns')
exprCheck('(Ns + ...)', '(Ns + ...)', id4='frpl2Ns')
exprCheck('(Ns + ... + 0)', '(Ns + ... + 0)', id4='fLpl2NsL0E')
exprCheck('(5)', 'L5E')
exprCheck('C', '1C')
exprCheck('(5)')
exprCheck('C')
# postfix
exprCheck('A(2)', 'cl1AL2EE')
exprCheck('A[2]', 'ix1AL2E')
exprCheck('a.b.c', 'dtdt1a1b1c')
exprCheck('a->b->c', 'ptpt1a1b1c')
exprCheck('i++', 'pp1i')
exprCheck('i--', 'mm1i')
exprCheck('dynamic_cast<T&>(i)++', 'ppdcR1T1i')
exprCheck('static_cast<T&>(i)++', 'ppscR1T1i')
exprCheck('reinterpret_cast<T&>(i)++', 'pprcR1T1i')
exprCheck('const_cast<T&>(i)++', 'ppccR1T1i')
exprCheck('typeid(T).name', 'dtti1T4name')
exprCheck('typeid(a + b).name', 'dttepl1a1b4name')
exprCheck('A(2)')
exprCheck('A[2]')
exprCheck('a.b.c')
exprCheck('a->b->c')
exprCheck('i++')
exprCheck('i--')
# unary
exprCheck('++5', 'pp_L5E')
exprCheck('--5', 'mm_L5E')
exprCheck('*5', 'deL5E')
exprCheck('&5', 'adL5E')
exprCheck('+5', 'psL5E')
exprCheck('-5', 'ngL5E')
exprCheck('!5', 'ntL5E')
exprCheck('~5', 'coL5E')
exprCheck('sizeof...(a)', 'sZ1a')
exprCheck('sizeof(T)', 'st1T')
exprCheck('sizeof -42', 'szngL42E')
exprCheck('alignof(T)', 'at1T')
exprCheck('noexcept(-42)', 'nxngL42E')
# new-expression
exprCheck('new int', 'nw_iE')
exprCheck('new volatile int', 'nw_ViE')
exprCheck('new int[42]', 'nw_AL42E_iE')
exprCheck('new int()', 'nw_ipiE')
exprCheck('new int(5, 42)', 'nw_ipiL5EL42EE')
exprCheck('::new int', 'nw_iE')
exprCheck('new int{}', 'nw_iilE')
exprCheck('new int{5, 42}', 'nw_iilL5EL42EE')
# delete-expression
exprCheck('delete p', 'dl1p')
exprCheck('delete [] p', 'da1p')
exprCheck('::delete p', 'dl1p')
exprCheck('::delete [] p', 'da1p')
exprCheck('++5')
exprCheck('--5')
exprCheck('*5')
exprCheck('&5')
exprCheck('+5')
exprCheck('-5')
exprCheck('!5')
exprCheck('~5')
exprCheck('sizeof(T)')
exprCheck('sizeof -42')
exprCheck('alignof(T)')
# cast
exprCheck('(int)2', 'cviL2E')
exprCheck('(int)2')
# binary op
exprCheck('5 || 42', 'ooL5EL42E')
exprCheck('5 && 42', 'aaL5EL42E')
exprCheck('5 | 42', 'orL5EL42E')
exprCheck('5 ^ 42', 'eoL5EL42E')
exprCheck('5 & 42', 'anL5EL42E')
exprCheck('5 || 42')
exprCheck('5 && 42')
exprCheck('5 | 42')
exprCheck('5 ^ 42')
exprCheck('5 & 42')
# ['==', '!=']
exprCheck('5 == 42', 'eqL5EL42E')
exprCheck('5 != 42', 'neL5EL42E')
exprCheck('5 == 42')
exprCheck('5 != 42')
# ['<=', '>=', '<', '>']
exprCheck('5 <= 42', 'leL5EL42E')
exprCheck('5 >= 42', 'geL5EL42E')
exprCheck('5 < 42', 'ltL5EL42E')
exprCheck('5 > 42', 'gtL5EL42E')
exprCheck('5 <= 42')
exprCheck('5 >= 42')
exprCheck('5 < 42')
exprCheck('5 > 42')
# ['<<', '>>']
exprCheck('5 << 42', 'lsL5EL42E')
exprCheck('5 >> 42', 'rsL5EL42E')
exprCheck('5 << 42')
exprCheck('5 >> 42')
# ['+', '-']
exprCheck('5 + 42', 'plL5EL42E')
exprCheck('5 - 42', 'miL5EL42E')
exprCheck('5 + 42')
exprCheck('5 - 42')
# ['*', '/', '%']
exprCheck('5 * 42', 'mlL5EL42E')
exprCheck('5 / 42', 'dvL5EL42E')
exprCheck('5 % 42', 'rmL5EL42E')
exprCheck('5 * 42')
exprCheck('5 / 42')
exprCheck('5 % 42')
# ['.*', '->*']
exprCheck('5 .* 42', 'dsL5EL42E')
exprCheck('5 ->* 42', 'pmL5EL42E')
# conditional
# TODO
# assignment
exprCheck('a = 5', 'aS1aL5E')
exprCheck('a *= 5', 'mL1aL5E')
exprCheck('a /= 5', 'dV1aL5E')
exprCheck('a %= 5', 'rM1aL5E')
exprCheck('a += 5', 'pL1aL5E')
exprCheck('a -= 5', 'mI1aL5E')
exprCheck('a >>= 5', 'rS1aL5E')
exprCheck('a <<= 5', 'lS1aL5E')
exprCheck('a &= 5', 'aN1aL5E')
exprCheck('a ^= 5', 'eO1aL5E')
exprCheck('a |= 5', 'oR1aL5E')
# Additional tests
# a < expression that starts with something that could be a template
exprCheck('A < 42', 'lt1AL42E')
check('function', 'template<> void f(A<B, 2> &v)',
{2: "IE1fR1AI1BX2EE", 3: "IE1fR1AI1BXL2EEE", 4: "IE1fvR1AI1BXL2EEE"})
exprCheck('A<1>::value', 'N1AIXL1EEE5valueE')
check('class', "template<int T = 42> A", {2: "I_iE1A"})
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()', {2: "1A"})
exprCheck('operator()()', 'clclE')
exprCheck('operator()<int>()', 'clclIiEE')
# pack expansion
exprCheck('a(b(c, 1 + d...)..., e(f..., g))', 'cl1aspcl1b1cspplL1E1dEcl1esp1f1gEE')
exprCheck('a = 5')
exprCheck('a *= 5')
exprCheck('a /= 5')
exprCheck('a %= 5')
exprCheck('a += 5')
exprCheck('a -= 5')
exprCheck('a >>= 5')
exprCheck('a <<= 5')
exprCheck('a &= 5')
exprCheck('a ^= 5')
exprCheck('a |= 5')
def test_type_definitions():
@ -249,6 +222,9 @@ def test_type_definitions():
check('type', "bool *b", {1: 'b'})
check('type', "bool *const b", {1: 'b'})
check('type', "bool *const *b", {1: 'b'})
check('type', "bool *volatile *b", {1: 'b'})
check('type', "bool *restrict *b", {1: 'b'})
check('type', "bool *volatile const b", {1: 'b'})
check('type', "bool *volatile const b", {1: 'b'})
check('type', "bool *volatile const *b", {1: 'b'})
@ -394,38 +370,22 @@ def test_anon_definitions():
def test_initializers():
return # TODO
idsMember = {1: 'v__T', 2: '1v'}
idsFunction = {1: 'f__T', 2: '1f1T'}
idsTemplate = {2: 'I_1TE1fv', 4: 'I_1TE1fvv'}
idsMember = {1: 'v'}
idsFunction = {1: 'f'}
# no init
check('member', 'T v', idsMember)
check('function', 'void f(T v)', idsFunction)
check('function', 'template<T v> void f()', idsTemplate)
# with '=', assignment-expression
check('member', 'T v = 42', idsMember)
check('function', 'void f(T v = 42)', idsFunction)
check('function', 'template<T v = 42> void f()', idsTemplate)
# with '=', braced-init
check('member', 'T v = {}', idsMember)
check('function', 'void f(T v = {})', idsFunction)
check('function', 'template<T v = {}> void f()', idsTemplate)
check('member', 'T v = {42, 42, 42}', idsMember)
check('function', 'void f(T v = {42, 42, 42})', idsFunction)
check('function', 'template<T v = {42, 42, 42}> void f()', idsTemplate)
check('member', 'T v = {42, 42, 42,}', idsMember)
check('function', 'void f(T v = {42, 42, 42,})', idsFunction)
check('function', 'template<T v = {42, 42, 42,}> void f()', idsTemplate)
check('member', 'T v = {42, 42, args...}', idsMember)
check('function', 'void f(T v = {42, 42, args...})', idsFunction)
check('function', 'template<T v = {42, 42, args...}> void f()', idsTemplate)
# without '=', braced-init
check('member', 'T v{}', idsMember)
check('member', 'T v{42, 42, 42}', idsMember)
check('member', 'T v{42, 42, 42,}', idsMember)
check('member', 'T v{42, 42, args...}', idsMember)
# other
check('member', 'T v = T{}', idsMember)
# TODO: designator-list
def test_attributes():