C, initial rewrite

This commit is contained in:
Jakob Lykke Andersen 2020-03-12 16:59:30 +01:00
parent 77d84713a1
commit 0f49e30c51
10 changed files with 2881 additions and 469 deletions

View File

@ -556,43 +556,29 @@ The C domain (name **c**) is suited for documentation of C API.
Describes a C function. The signature should be given as in C, e.g.::
.. c:function:: PyObject* PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
This is also used to describe function-like preprocessor macros. The names
of the arguments should be given so they may be used in the description.
.. c:function:: PyObject *PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)
Note that you don't have to backslash-escape asterisks in the signature, as
it is not parsed by the reST inliner.
.. rst:directive:: .. c:member:: declaration
.. c:var:: declaration
Describes a C struct member. Example signature::
Describes a C struct member or variable. Example signature::
.. c:member:: PyObject* PyTypeObject.tp_bases
The text of the description should include the range of values allowed, how
the value should be interpreted, and whether the value can be changed.
References to structure members in text should use the ``member`` role.
.. c:member:: PyObject *PyTypeObject.tp_bases
.. rst:directive:: .. c:macro:: name
.. c:macro:: name(arg list)
Describes a "simple" C macro. Simple macros are macros which are used for
code expansion, but which do not take arguments so cannot be described as
functions. This is a simple C-language ``#define``. Examples of its use in
the Python documentation include :c:macro:`PyObject_HEAD` and
:c:macro:`Py_BEGIN_ALLOW_THREADS`.
Describes a C macro, i.e., a C-language ``#define``, without the replacement
text.
.. rst:directive:: .. c:type:: name
.. rst:directive:: .. c:type:: typedef-like declaration
.. c:type:: name
Describes a C type (whether defined by a typedef or struct). The signature
should just be the type name.
.. rst:directive:: .. c:var:: declaration
Describes a global C variable. The signature should include the type, such
as::
.. c:var:: PyObject* PyClass_Type
Describes a C type, either as a typedef, or the alias for an unspecified
type.
.. _c-roles:
@ -607,21 +593,18 @@ are defined in the documentation:
Reference a C-language function. Should include trailing parentheses.
.. rst:role:: c:member
c:data
Reference a C-language member of a struct.
Reference a C-language member of a struct or variable.
.. rst:role:: c:macro
Reference a "simple" C macro, as defined above.
Reference a simple C macro, as defined above.
.. rst:role:: c:type
Reference a C-language type.
.. rst:role:: c:data
Reference a C-language variable.
.. _cpp-domain:

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,6 @@
import re
import warnings
from copy import deepcopy
from typing import (
Any, Callable, Dict, Iterator, List, Match, Pattern, Tuple, Type, TypeVar, Union
)
@ -35,13 +34,16 @@ from sphinx.roles import XRefRole
from sphinx.transforms import SphinxTransform
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
)
from sphinx.util.docfields import Field, GroupedField
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import make_refnode
logger = logging.getLogger(__name__)
StringifyTransform = Callable[[Any], str]
T = TypeVar('T')
"""
@ -325,16 +327,6 @@ _char_literal_re = re.compile(r'''(?x)
)'
''')
_anon_identifier_re = re.compile(r'(@[a-zA-Z0-9_])[a-zA-Z0-9_]*\b')
_identifier_re = re.compile(r'''(?x)
( # This 'extends' _anon_identifier_re with the ordinary identifiers,
# make sure they are in sync.
(~?\b[a-zA-Z_]) # ordinary identifiers
| (@[a-zA-Z0-9_]) # our extension for names of anonymous entities
)
[a-zA-Z0-9_]*\b
''')
_whitespace_re = re.compile(r'(?u)\s+')
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
_visibility_re = re.compile(r'\b(public|private|protected)\b')
@ -582,25 +574,6 @@ _id_explicit_cast = {
}
class NoOldIdError(Exception):
# Used to avoid implementing unneeded id generation for old id schemes.
@property
def description(self) -> str:
warnings.warn('%s.description 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:
warnings.warn('%s.description is deprecated. '
'Coerce the instance to a string instead.' % self.__class__.__name__,
RemovedInSphinx40Warning, stacklevel=2)
return str(self)
class _DuplicateSymbolError(Exception):
def __init__(self, symbol: "Symbol", declaration: Any) -> None:
assert symbol
@ -612,42 +585,6 @@ class _DuplicateSymbolError(Exception):
return "Internal C++ duplicate symbol error:\n%s" % self.symbol.dump(0)
class ASTBase:
def __eq__(self, other: Any) -> bool:
if type(self) is not type(other):
return False
try:
for key, value in self.__dict__.items():
if value != getattr(other, key):
return False
except AttributeError:
return False
return True
__hash__ = None # type: Callable[[], int]
def clone(self) -> Any:
"""Clone a definition expression node."""
return deepcopy(self)
def _stringify(self, transform: StringifyTransform) -> str:
raise NotImplementedError(repr(self))
def __str__(self) -> str:
return self._stringify(lambda ast: str(ast))
def get_display_string(self) -> str:
return self._stringify(lambda ast: ast.get_display_string())
def __repr__(self) -> str:
return '<%s>' % self.__class__.__name__
def _verify_description_mode(mode: str) -> None:
if mode not in ('lastIsName', 'noneIsName', 'markType', 'markName', 'param'):
raise Exception("Description mode '%s' is invalid." % mode)
################################################################################
# Attributes
################################################################################
@ -1441,7 +1378,7 @@ class ASTIdentifier(ASTBase):
def describe_signature(self, signode: Any, mode: str, env: "BuildEnvironment",
prefix: str, templateArgs: str, symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
if mode == 'markType':
targetText = prefix + self.identifier + templateArgs
pnode = addnodes.pending_xref('', refdomain='cpp',
@ -1847,7 +1784,7 @@ class ASTTemplateDeclarationPrefix(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol", lineSpec: bool) -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
for t in self.templates:
t.describe_signature(signode, 'lastIsName', env, symbol, lineSpec)
@ -1868,7 +1805,7 @@ class ASTOperator(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", prefix: str, templateArgs: str,
symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
identifier = str(self)
if mode == 'lastIsName':
signode += addnodes.desc_name(identifier, identifier)
@ -1947,7 +1884,7 @@ class ASTTemplateArgConstant(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
self.value.describe_signature(signode, mode, env, symbol)
@ -1977,7 +1914,7 @@ class ASTTemplateArgs(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
signode += nodes.Text('<')
first = True
for a in self.args:
@ -2072,7 +2009,7 @@ class ASTNestedName(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
# just print the name part, with template args, not template params
if mode == 'noneIsName':
signode += nodes.Text(str(self))
@ -2243,7 +2180,7 @@ class ASTFunctionParameter(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
if self.ellipsis:
signode += nodes.Text('...')
else:
@ -2323,7 +2260,7 @@ class ASTParametersQualifiers(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
paramlist = addnodes.desc_parameterlist()
for arg in self.args:
param = addnodes.desc_parameter('', '', noemph=True)
@ -2489,7 +2426,7 @@ class ASTDeclSpecs(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
modifiers = [] # type: List[Node]
def _add(modifiers: List[Node], text: str) -> None:
@ -2539,7 +2476,7 @@ class ASTArray(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
signode.append(nodes.Text("["))
if self.size:
self.size.describe_signature(signode, mode, env, symbol)
@ -2623,7 +2560,7 @@ class ASTDeclaratorPtr(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
signode += nodes.Text("*")
for a in self.attrs:
a.describe_signature(signode)
@ -2696,7 +2633,7 @@ class ASTDeclaratorRef(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
signode += nodes.Text("&")
for a in self.attrs:
a.describe_signature(signode)
@ -2749,7 +2686,7 @@ class ASTDeclaratorParamPack(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
signode += nodes.Text("...")
if self.next.name:
signode += nodes.Text(' ')
@ -2826,7 +2763,7 @@ class ASTDeclaratorMemPtr(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
self.className.describe_signature(signode, mode, env, symbol)
signode += nodes.Text('::*')
@ -2896,7 +2833,7 @@ class ASTDeclaratorParen(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
signode += nodes.Text('(')
self.inner.describe_signature(signode, mode, env, symbol)
signode += nodes.Text(')')
@ -2972,7 +2909,7 @@ class ASTDeclaratorNameParamQual(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
if self.declId:
self.declId.describe_signature(signode, mode, env, symbol)
for op in self.arrayOps:
@ -3014,7 +2951,7 @@ class ASTDeclaratorNameBitField(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
if self.declId:
self.declId.describe_signature(signode, mode, env, symbol)
signode.append(nodes.Text(' : ', ' : '))
@ -3034,7 +2971,7 @@ class ASTParenExprList(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
signode.append(nodes.Text('('))
first = True
for e in self.exprs:
@ -3061,7 +2998,7 @@ class ASTBracedInitList(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
signode.append(nodes.Text('{'))
first = True
for e in self.exprs:
@ -3089,7 +3026,7 @@ class ASTInitializer(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
if self.hasAssign:
signode.append(nodes.Text(' = '))
self.value.describe_signature(signode, 'markType', env, symbol)
@ -3184,7 +3121,7 @@ class ASTType(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
self.declSpecs.describe_signature(signode, 'markType', env, symbol)
if (self.decl.require_space_after_declSpecs() and
len(str(self.declSpecs)) > 0):
@ -3227,7 +3164,7 @@ class ASTTypeWithInit(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
self.type.describe_signature(signode, mode, env, symbol)
if self.init:
self.init.describe_signature(signode, mode, env, symbol)
@ -3257,7 +3194,7 @@ class ASTTypeUsing(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
self.name.describe_signature(signode, mode, env, symbol=symbol)
if self.type:
signode += nodes.Text(' = ')
@ -3315,7 +3252,7 @@ class ASTBaseClass(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
if self.visibility is not None:
signode += addnodes.desc_annotation(self.visibility,
self.visibility)
@ -3354,7 +3291,7 @@ class ASTClass(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
self.name.describe_signature(signode, mode, env, symbol=symbol)
if self.final:
signode += nodes.Text(' ')
@ -3381,7 +3318,7 @@ class ASTUnion(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
self.name.describe_signature(signode, mode, env, symbol=symbol)
@ -3409,7 +3346,7 @@ class ASTEnum(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
# self.scoped has been done by the CPPEnumObject
self.name.describe_signature(signode, mode, env, symbol=symbol)
if self.underlyingType:
@ -3437,7 +3374,7 @@ class ASTEnumerator(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
self.name.describe_signature(signode, mode, env, symbol)
if self.init:
self.init.describe_signature(signode, 'markType', env, symbol)
@ -3509,7 +3446,7 @@ class ASTDeclaration(ASTBase):
def describe_signature(self, signode: desc_signature, mode: str,
env: "BuildEnvironment", options: Dict) -> None:
_verify_description_mode(mode)
verify_description_mode(mode)
assert self.symbol
# The caller of the domain added a desc_signature node.
# Always enable multiline:
@ -4497,7 +4434,7 @@ class Symbol:
return ''.join(res)
class DefinitionParser:
class DefinitionParser(BaseParser):
# those without signedness and size modifiers
# see https://en.cppreference.com/w/cpp/language/types
_simple_fundemental_types = (
@ -4508,128 +4445,9 @@ class DefinitionParser:
_prefix_keys = ('class', 'struct', 'enum', 'union', 'typename')
def __init__(self, definition: Any, warnEnv: Any, config: "Config") -> None:
self.definition = definition.strip()
self.pos = 0
self.end = len(self.definition)
self.last_match = None # type: Match
self._previous_state = (0, None) # type: Tuple[int, Match]
self.otherErrors = [] # type: List[DefinitionError]
# in our tests the following is set to False to capture bad parsing
self.allowFallbackExpressionParsing = True
self.warnEnv = warnEnv
super().__init__(definition, warnEnv)
self.config = config
def _make_multi_error(self, errors: List[Any], header: str) -> DefinitionError:
if len(errors) == 1:
if len(header) > 0:
return DefinitionError(header + '\n' + str(errors[0][0]))
else:
return DefinitionError(str(errors[0][0]))
result = [header, '\n']
for e in errors:
if len(e[1]) > 0:
ident = ' '
result.append(e[1])
result.append(':\n')
for line in str(e[0]).split('\n'):
if len(line) == 0:
continue
result.append(ident)
result.append(line)
result.append('\n')
else:
result.append(str(e[0]))
return DefinitionError(''.join(result))
def status(self, msg: str) -> None:
# for debugging
indicator = '-' * self.pos + '^'
print("%s\n%s\n%s" % (msg, self.definition, indicator))
def fail(self, msg: str) -> None:
errors = []
indicator = '-' * self.pos + '^'
exMain = DefinitionError(
'Invalid definition: %s [error at %d]\n %s\n %s' %
(msg, self.pos, self.definition, indicator))
errors.append((exMain, "Main error"))
for err in self.otherErrors:
errors.append((err, "Potential other error"))
self.otherErrors = []
raise self._make_multi_error(errors, '')
def warn(self, msg: str) -> None:
if self.warnEnv:
self.warnEnv.warn(msg)
else:
print("Warning: %s" % msg)
def match(self, regex: Pattern) -> bool:
match = regex.match(self.definition, self.pos)
if match is not None:
self._previous_state = (self.pos, self.last_match)
self.pos = match.end()
self.last_match = match
return True
return False
def backout(self) -> None:
self.pos, self.last_match = self._previous_state
def skip_string(self, string: str) -> bool:
strlen = len(string)
if self.definition[self.pos:self.pos + strlen] == string:
self.pos += strlen
return True
return False
def skip_word(self, word: str) -> bool:
return self.match(re.compile(r'\b%s\b' % re.escape(word)))
def skip_ws(self) -> bool:
return self.match(_whitespace_re)
def skip_word_and_ws(self, word: str) -> bool:
if self.skip_word(word):
self.skip_ws()
return True
return False
def skip_string_and_ws(self, string: str) -> bool:
if self.skip_string(string):
self.skip_ws()
return True
return False
@property
def eof(self) -> bool:
return self.pos >= self.end
@property
def current_char(self) -> str:
try:
return self.definition[self.pos]
except IndexError:
return 'EOF'
@property
def matched_text(self) -> str:
if self.last_match is not None:
return self.last_match.group()
else:
return None
def read_rest(self) -> str:
rv = self.definition[self.pos:]
self.pos = self.end
return rv
def assert_end(self) -> None:
self.skip_ws()
if not self.eof:
self.fail('Expected end of definition.')
def _parse_string(self):
if self.current_char != '"':
return None
@ -4693,7 +4511,7 @@ class DefinitionParser:
self.fail("Expected '(' after '__attribute__('.")
attrs = []
while 1:
if self.match(_identifier_re):
if self.match(identifier_re):
name = self.matched_text
self.skip_ws()
if self.skip_string_and_ws('('):
@ -5085,7 +4903,7 @@ class DefinitionParser:
if self.skip_string_and_ws('...'):
if not self.skip_string_and_ws('('):
self.fail("Expecting '(' after 'sizeof...'.")
if not self.match(_identifier_re):
if not self.match(identifier_re):
self.fail("Expecting identifier for 'sizeof...'.")
ident = ASTIdentifier(self.matched_text)
self.skip_ws()
@ -5335,7 +5153,7 @@ class DefinitionParser:
# user-defined literal?
if self.skip_string('""'):
self.skip_ws()
if not self.match(_identifier_re):
if not self.match(identifier_re):
self.fail("Expected user-defined literal suffix.")
identifier = ASTIdentifier(self.matched_text)
return ASTOperatorLiteral(identifier)
@ -5411,7 +5229,7 @@ class DefinitionParser:
if self.skip_word_and_ws('operator'):
identOrOp = self._parse_operator()
else:
if not self.match(_identifier_re):
if not self.match(identifier_re):
if memberPointer and len(names) > 0:
templates.pop()
break
@ -5699,7 +5517,7 @@ class DefinitionParser:
self.pos = pos
declId = None
elif named == 'single':
if self.match(_identifier_re):
if self.match(identifier_re):
identifier = ASTIdentifier(self.matched_text)
nne = ASTNestedNameElement(identifier, None)
declId = ASTNestedName([nne], [False], rooted=False)
@ -6129,7 +5947,7 @@ class DefinitionParser:
self.skip_ws()
parameterPack = self.skip_string('...')
self.skip_ws()
if self.match(_identifier_re):
if self.match(identifier_re):
identifier = ASTIdentifier(self.matched_text)
else:
identifier = None
@ -6188,7 +6006,7 @@ class DefinitionParser:
self.skip_ws()
parameterPack = self.skip_string('...')
self.skip_ws()
if not self.match(_identifier_re):
if not self.match(identifier_re):
self.fail("Expected identifier in template introduction list.")
txt_identifier = self.matched_text
# make sure there isn't a keyword
@ -6907,7 +6725,7 @@ class CPPXRefRole(XRefRole):
if not has_explicit_title:
# major hax: replace anon names via simple string manipulation.
# Can this actually fail?
title = _anon_identifier_re.sub("[anonymous]", str(title))
title = anon_identifier_re.sub("[anonymous]", str(title))
if refnode['reftype'] == 'any':
# Assume the removal part of fix_parens for :any: refs.

209
sphinx/util/cfamily.py Normal file
View File

@ -0,0 +1,209 @@
"""
sphinx.util.cfamily
~~~~~~~~~~~~~~~~
Utility functions common to the C and C++ domains.
:copyright: Copyright 2020-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import warnings
from copy import deepcopy
from typing import (
Any, Callable, Dict, Iterator, List, Match, Pattern, Tuple, Type, TypeVar, Union
)
from sphinx.deprecation import RemovedInSphinx40Warning
StringifyTransform = Callable[[Any], str]
_whitespace_re = re.compile(r'(?u)\s+')
anon_identifier_re = re.compile(r'(@[a-zA-Z0-9_])[a-zA-Z0-9_]*\b')
identifier_re = re.compile(r'''(?x)
( # This 'extends' _anon_identifier_re with the ordinary identifiers,
# make sure they are in sync.
(~?\b[a-zA-Z_]) # ordinary identifiers
| (@[a-zA-Z0-9_]) # our extension for names of anonymous entities
)
[a-zA-Z0-9_]*\b
''')
def verify_description_mode(mode: str) -> None:
if mode not in ('lastIsName', 'noneIsName', 'markType', 'markName', 'param'):
raise Exception("Description mode '%s' is invalid." % mode)
class NoOldIdError(Exception):
# Used to avoid implementing unneeded id generation for old id schemes.
@property
def description(self) -> str:
warnings.warn('%s.description is deprecated. '
'Coerce the instance to a string instead.' % self.__class__.__name__,
RemovedInSphinx40Warning, stacklevel=2)
return str(self)
class ASTBase:
def __eq__(self, other: Any) -> bool:
if type(self) is not type(other):
return False
try:
for key, value in self.__dict__.items():
if value != getattr(other, key):
return False
except AttributeError:
return False
return True
__hash__ = None # type: Callable[[], int]
def clone(self) -> Any:
"""Clone a definition expression node."""
return deepcopy(self)
def _stringify(self, transform: StringifyTransform) -> str:
raise NotImplementedError(repr(self))
def __str__(self) -> str:
return self._stringify(lambda ast: str(ast))
def get_display_string(self) -> str:
return self._stringify(lambda ast: ast.get_display_string())
def __repr__(self) -> str:
return '<%s>' % self.__class__.__name__
class DefinitionError(Exception):
@property
def description(self) -> str:
warnings.warn('%s.description is deprecated. '
'Coerce the instance to a string instead.' % self.__class__.__name__,
RemovedInSphinx40Warning, stacklevel=2)
return str(self)
class BaseParser:
def __init__(self, definition: Any, warnEnv: Any) -> None:
self.warnEnv = warnEnv
self.definition = definition.strip()
self.pos = 0
self.end = len(self.definition)
self.last_match = None # type: Match
self._previous_state = (0, None) # type: Tuple[int, Match]
self.otherErrors = [] # type: List[DefinitionError]
# in our tests the following is set to False to capture bad parsing
self.allowFallbackExpressionParsing = True
def _make_multi_error(self, errors: List[Any], header: str) -> DefinitionError:
if len(errors) == 1:
if len(header) > 0:
return DefinitionError(header + '\n' + str(errors[0][0]))
else:
return DefinitionError(str(errors[0][0]))
result = [header, '\n']
for e in errors:
if len(e[1]) > 0:
ident = ' '
result.append(e[1])
result.append(':\n')
for line in str(e[0]).split('\n'):
if len(line) == 0:
continue
result.append(ident)
result.append(line)
result.append('\n')
else:
result.append(str(e[0]))
return DefinitionError(''.join(result))
def status(self, msg: str) -> None:
# for debugging
indicator = '-' * self.pos + '^'
print("%s\n%s\n%s" % (msg, self.definition, indicator))
def fail(self, msg: str) -> None:
errors = []
indicator = '-' * self.pos + '^'
exMain = DefinitionError(
'Invalid definition: %s [error at %d]\n %s\n %s' %
(msg, self.pos, self.definition, indicator))
errors.append((exMain, "Main error"))
for err in self.otherErrors:
errors.append((err, "Potential other error"))
self.otherErrors = []
raise self._make_multi_error(errors, '')
def warn(self, msg: str) -> None:
if self.warnEnv:
self.warnEnv.warn(msg)
else:
print("Warning: %s" % msg)
def match(self, regex: Pattern) -> bool:
match = regex.match(self.definition, self.pos)
if match is not None:
self._previous_state = (self.pos, self.last_match)
self.pos = match.end()
self.last_match = match
return True
return False
def skip_string(self, string: str) -> bool:
strlen = len(string)
if self.definition[self.pos:self.pos + strlen] == string:
self.pos += strlen
return True
return False
def skip_word(self, word: str) -> bool:
return self.match(re.compile(r'\b%s\b' % re.escape(word)))
def skip_ws(self) -> bool:
return self.match(_whitespace_re)
def skip_word_and_ws(self, word: str) -> bool:
if self.skip_word(word):
self.skip_ws()
return True
return False
def skip_string_and_ws(self, string: str) -> bool:
if self.skip_string(string):
self.skip_ws()
return True
return False
@property
def eof(self) -> bool:
return self.pos >= self.end
@property
def current_char(self) -> str:
try:
return self.definition[self.pos]
except IndexError:
return 'EOF'
@property
def matched_text(self) -> str:
if self.last_match is not None:
return self.last_match.group()
else:
return None
def read_rest(self) -> str:
rv = self.definition[self.pos:]
self.pos = self.end
return rv
def assert_end(self) -> None:
self.skip_ws()
if not self.eof:
self.fail('Expected end of definition.')

View File

@ -0,0 +1 @@
exclude_patterns = ['_build']

View File

@ -0,0 +1,17 @@
test-domain-c
=============
directives
----------
.. c:function:: int hello(char *name)
.. c:member:: float Sphinx.version
.. c:macro:: IS_SPHINX
.. c:macro:: SPHINX(arg1, arg2)
.. c:type:: Sphinx
.. c:var:: int version

View File

@ -107,15 +107,15 @@ Referring to :func:`nothing <>`.
C items
=======
.. c:function:: Sphinx_DoSomething()
.. c:function:: void Sphinx_DoSomething()
.. c:member:: SphinxStruct.member
.. c:member:: int SphinxStruct.member
.. c:macro:: SPHINX_USE_PYTHON
.. c:type:: SphinxType
.. c:var:: sphinx_global
.. c:var:: int sphinx_global
Javascript items

View File

@ -28,7 +28,7 @@ def test_build(app):
assert path_html in htmltext
malloc_html = (
'<b>Test_Malloc</b>: <i>changed:</i> Changed in version 0.6:'
'<b>void *Test_Malloc(size_t n)</b>: <i>changed:</i> Changed in version 0.6:'
' Can now be replaced with a different allocator.</a>')
assert malloc_html in htmltext

View File

@ -289,11 +289,11 @@ def test_html4_output(app, status, warning):
(".//a[@class='reference internal'][@href='#errmod-error']/strong", 'Error'),
# C references
(".//span[@class='pre']", 'CFunction()'),
(".//a[@href='#c-sphinx-dosomething']", ''),
(".//a[@href='#c-sphinxstruct-member']", ''),
(".//a[@href='#c-sphinx-use-python']", ''),
(".//a[@href='#c-sphinxtype']", ''),
(".//a[@href='#c-sphinx-global']", ''),
(".//a[@href='#c.Sphinx_DoSomething']", ''),
(".//a[@href='#c.SphinxStruct.member']", ''),
(".//a[@href='#c.SPHINX_USE_PYTHON']", ''),
(".//a[@href='#c.SphinxType']", ''),
(".//a[@href='#c.sphinx_global']", ''),
# test global TOC created by toctree()
(".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='#']",
'Testing object descriptions'),

View File

@ -8,73 +8,549 @@
:license: BSD, see LICENSE for details.
"""
from docutils import nodes
import re
import pytest
from docutils import nodes
import sphinx.domains.c as cDomain
from sphinx import addnodes
from sphinx.addnodes import (
desc, desc_addname, desc_annotation, desc_content, desc_name, desc_optional,
desc_parameter, desc_parameterlist, desc_returns, desc_signature, desc_type,
pending_xref
)
from sphinx.domains.c import DefinitionParser, DefinitionError
from sphinx.domains.c import _max_id, _id_prefix, Symbol
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node
from sphinx.util import docutils
def parse(name, string):
parser = DefinitionParser(string, None)
parser.allowFallbackExpressionParsing = False
ast = parser.parse_declaration(name, name)
parser.assert_end()
return ast
def check(name, input, idDict, output=None):
# first a simple check of the AST
if output is None:
output = input
ast = parse(name, input)
res = str(ast)
if res != output:
print("")
print("Input: ", input)
print("Result: ", res)
print("Expected: ", output)
raise DefinitionError("")
rootSymbol = Symbol(None, None, None, None)
symbol = rootSymbol.add_declaration(ast, docname="TestDoc")
parentNode = addnodes.desc()
signode = addnodes.desc_signature(input, '')
parentNode += signode
ast.describe_signature(signode, 'lastIsName', symbol, options={})
idExpected = [None]
for i in range(1, _max_id + 1):
if i in idDict:
idExpected.append(idDict[i])
else:
idExpected.append(idExpected[i - 1])
idActual = [None]
for i in range(1, _max_id + 1):
#try:
id = ast.get_id(version=i)
assert id is not None
idActual.append(id[len(_id_prefix[i]):])
#except NoOldIdError:
# idActual.append(None)
res = [True]
for i in range(1, _max_id + 1):
res.append(idExpected[i] == idActual[i])
if not all(res):
print("input: %s" % input.rjust(20))
for i in range(1, _max_id + 1):
if res[i]:
continue
print("Error in id version %d." % i)
print("result: %s" % idActual[i])
print("expected: %s" % idExpected[i])
#print(rootSymbol.dump(0))
raise DefinitionError("")
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)
# primary
exprCheck('nullptr', 'LDnE')
exprCheck('true', 'L1E')
exprCheck('false', 'L0E')
ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1']
unsignedSuffix = ['', 'u', 'U']
longSuffix = ['', 'l', 'L', 'll', 'LL']
for i in ints:
for u in unsignedSuffix:
for l in longSuffix:
expr = i + u + l
exprCheck(expr, 'L' + expr + 'E')
expr = i + l + u
exprCheck(expr, 'L' + expr + 'E')
for suffix in ['', 'f', 'F', 'l', 'L']:
for e in [
'5e42', '5e+42', '5e-42',
'5.', '5.e42', '5.e+42', '5.e-42',
'.5', '.5e42', '.5e+42', '.5e-42',
'5.0', '5.0e42', '5.0e+42', '5.0e-42']:
expr = e + suffix
exprCheck(expr, 'L' + expr + 'E')
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')
# 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")
# 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')
# 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')
# 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')
# cast
exprCheck('(int)2', 'cviL2E')
# 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', 'eqL5EL42E')
exprCheck('5 != 42', 'neL5EL42E')
# ['<=', '>=', '<', '>']
exprCheck('5 <= 42', 'leL5EL42E')
exprCheck('5 >= 42', 'geL5EL42E')
exprCheck('5 < 42', 'ltL5EL42E')
exprCheck('5 > 42', 'gtL5EL42E')
# ['<<', '>>']
exprCheck('5 << 42', 'lsL5EL42E')
exprCheck('5 >> 42', 'rsL5EL42E')
# ['+', '-']
exprCheck('5 + 42', 'plL5EL42E')
exprCheck('5 - 42', 'miL5EL42E')
# ['*', '/', '%']
exprCheck('5 * 42', 'mlL5EL42E')
exprCheck('5 / 42', 'dvL5EL42E')
exprCheck('5 % 42', 'rmL5EL42E')
# ['.*', '->*']
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')
def test_type_definitions():
check('type', "T", {1: "T"})
check('type', "struct T", {1: 'T'})
check('type', "enum T", {1: 'T'})
check('type', "union T", {1: 'T'})
check('type', "bool *b", {1: 'b'})
check('type', "bool *const 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'})
check('type', "bool b[]", {1: 'b'})
check('type', "long long int foo", {1: 'foo'})
# test decl specs on right
check('type', "bool const b", {1: 'b'})
# from breathe#267 (named function parameters for function pointers
check('type', 'void (*gpio_callback_t)(struct device *port, uint32_t pin)',
{1: 'gpio_callback_t'})
def test_macro_definitions():
check('macro', 'M', {1: 'M'})
check('macro', 'M()', {1: 'M'})
check('macro', 'M(arg)', {1: 'M'})
check('macro', 'M(arg1, arg2)', {1: 'M'})
check('macro', 'M(arg1, arg2, arg3)', {1: 'M'})
check('macro', 'M(...)', {1: 'M'})
check('macro', 'M(arg, ...)', {1: 'M'})
check('macro', 'M(arg1, arg2, ...)', {1: 'M'})
check('macro', 'M(arg1, arg2, arg3, ...)', {1: 'M'})
def test_member_definitions():
check('member', 'void a', {1: 'a'})
check('member', '_Bool a', {1: 'a'})
check('member', 'bool a', {1: 'a'})
check('member', 'char a', {1: 'a'})
check('member', 'int a', {1: 'a'})
check('member', 'float a', {1: 'a'})
check('member', 'double a', {1: 'a'})
check('member', 'unsigned long a', {1: 'a'})
check('member', 'int .a', {1: 'a'})
check('member', 'int *a', {1: 'a'})
check('member', 'int **a', {1: 'a'})
check('member', 'const int a', {1: 'a'})
check('member', 'volatile int a', {1: 'a'})
check('member', 'restrict int a', {1: 'a'})
check('member', 'volatile const int a', {1: 'a'})
check('member', 'restrict const int a', {1: 'a'})
check('member', 'restrict volatile int a', {1: 'a'})
check('member', 'restrict volatile const int a', {1: 'a'})
check('member', 'T t', {1: 't'})
check('member', 'struct T t', {1: 't'})
check('member', 'enum T t', {1: 't'})
check('member', 'union T t', {1: 't'})
check('member', 'int a[]', {1: 'a'})
check('member', 'int (*p)[]', {1: 'p'})
check('member', 'int a[42]', {1: 'a'})
check('member', 'int a = 42', {1: 'a'})
check('member', 'T a = {}', {1: 'a'})
check('member', 'T a = {1}', {1: 'a'})
check('member', 'T a = {1, 2}', {1: 'a'})
check('member', 'T a = {1, 2, 3}', {1: 'a'})
# test from issue #1539
check('member', 'CK_UTF8CHAR model[16]', {1: 'model'})
check('member', 'auto int a', {1: 'a'})
check('member', 'register int a', {1: 'a'})
check('member', 'extern int a', {1: 'a'})
check('member', 'static int a', {1: 'a'})
check('member', 'thread_local int a', {1: 'a'})
check('member', '_Thread_local int a', {1: 'a'})
check('member', 'extern thread_local int a', {1: 'a'})
check('member', 'thread_local extern int a', {1: 'a'},
'extern thread_local int a')
check('member', 'static thread_local int a', {1: 'a'})
check('member', 'thread_local static int a', {1: 'a'},
'static thread_local int a')
check('member', 'int b : 3', {1: 'b'})
def test_function_definitions():
check('function', 'void f()', {1: 'f'})
check('function', 'void f(int)', {1: 'f'})
check('function', 'void f(int i)', {1: 'f'})
check('function', 'void f(int i, int j)', {1: 'f'})
check('function', 'void f(...)', {1: 'f'})
check('function', 'void f(int i, ...)', {1: 'f'})
check('function', 'void f(struct T)', {1: 'f'})
check('function', 'void f(struct T t)', {1: 'f'})
check('function', 'void f(union T)', {1: 'f'})
check('function', 'void f(union T t)', {1: 'f'})
check('function', 'void f(enum T)', {1: 'f'})
check('function', 'void f(enum T t)', {1: 'f'})
# test from issue #1539
check('function', 'void f(A x[])', {1: 'f'})
# test from issue #2377
check('function', 'void (*signal(int sig, void (*func)(int)))(int)', {1: 'signal'})
check('function', 'extern void f()', {1: 'f'})
check('function', 'static void f()', {1: 'f'})
check('function', 'inline void f()', {1: 'f'})
# tests derived from issue #1753 (skip to keep sanity)
check('function', "void f(float *q(double))", {1: 'f'})
check('function', "void f(float *(*q)(double))", {1: 'f'})
check('function', "void f(float (*q)(double))", {1: 'f'})
check('function', "int (*f(double d))(float)", {1: 'f'})
check('function', "int (*f(bool b))[5]", {1: 'f'})
check('function', "void f(int *const p)", {1: 'f'})
check('function', "void f(int *volatile const p)", {1: 'f'})
# from breathe#223
check('function', 'void f(struct E e)', {1: 'f'})
check('function', 'void f(enum E e)', {1: 'f'})
check('function', 'void f(union E e)', {1: 'f'})
def test_union_definitions():
return # TODO
check('union', 'A', {2: "1A"})
def test_enum_definitions():
return # TODO
check('enum', 'A', {2: "1A"})
check('enum', 'A : std::underlying_type<B>::type', {2: "1A"})
check('enum', 'A : unsigned int', {2: "1A"})
check('enum', 'public A', {2: "1A"}, output='A')
check('enum', 'private A', {2: "1A"})
check('enumerator', 'A', {2: "1A"})
check('enumerator', 'A = std::numeric_limits<unsigned long>::max()', {2: "1A"})
def test_anon_definitions():
return # TODO
check('class', '@a', {3: "Ut1_a"})
check('union', '@a', {3: "Ut1_a"})
check('enum', '@a', {3: "Ut1_a"})
check('class', '@1', {3: "Ut1_1"})
check('class', '@a::A', {3: "NUt1_a1AE"})
def test_initializers():
return # TODO
idsMember = {1: 'v__T', 2: '1v'}
idsFunction = {1: 'f__T', 2: '1f1T'}
idsTemplate = {2: 'I_1TE1fv', 4: 'I_1TE1fvv'}
# 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)
def test_attributes():
return # TODO
# style: C++
check('member', '[[]] int f', {1: 'f__i', 2: '1f'})
check('member', '[ [ ] ] int f', {1: 'f__i', 2: '1f'},
# this will fail when the proper grammar is implemented
output='[[ ]] int f')
check('member', '[[a]] int f', {1: 'f__i', 2: '1f'})
# style: GNU
check('member', '__attribute__(()) int f', {1: 'f__i', 2: '1f'})
check('member', '__attribute__((a)) int f', {1: 'f__i', 2: '1f'})
check('member', '__attribute__((a, b)) int f', {1: 'f__i', 2: '1f'})
# style: user-defined id
check('member', 'id_attr int f', {1: 'f__i', 2: '1f'})
# style: user-defined paren
check('member', 'paren_attr() int f', {1: 'f__i', 2: '1f'})
check('member', 'paren_attr(a) int f', {1: 'f__i', 2: '1f'})
check('member', 'paren_attr("") int f', {1: 'f__i', 2: '1f'})
check('member', 'paren_attr(()[{}][]{}) int f', {1: 'f__i', 2: '1f'})
with pytest.raises(DefinitionError):
parse('member', 'paren_attr(() int f')
with pytest.raises(DefinitionError):
parse('member', 'paren_attr([) int f')
with pytest.raises(DefinitionError):
parse('member', 'paren_attr({) int f')
with pytest.raises(DefinitionError):
parse('member', 'paren_attr([)]) int f')
with pytest.raises(DefinitionError):
parse('member', 'paren_attr((])) int f')
with pytest.raises(DefinitionError):
parse('member', 'paren_attr({]}) int f')
# position: decl specs
check('function', 'static inline __attribute__(()) void f()',
{1: 'f', 2: '1fv'},
output='__attribute__(()) static inline void f()')
check('function', '[[attr1]] [[attr2]] void f()',
{1: 'f', 2: '1fv'},
output='[[attr1]] [[attr2]] void f()')
# position: declarator
check('member', 'int *[[attr]] i', {1: 'i__iP', 2: '1i'})
check('member', 'int *const [[attr]] volatile i', {1: 'i__iPVC', 2: '1i'},
output='int *[[attr]] volatile const i')
check('member', 'int &[[attr]] i', {1: 'i__iR', 2: '1i'})
check('member', 'int *[[attr]] *i', {1: 'i__iPP', 2: '1i'})
def test_xref_parsing():
return # TODO
def check(target):
class Config:
cpp_id_attributes = ["id_attr"]
cpp_paren_attributes = ["paren_attr"]
parser = DefinitionParser(target, None, Config())
ast, isShorthand = parser.parse_xref_object()
parser.assert_end()
check('f')
check('f()')
check('void f()')
check('T f()')
# def test_print():
# # used for getting all the ids out for checking
# for a in ids:
# print(a)
# raise DefinitionError("")
def filter_warnings(warning, file):
lines = warning.getvalue().split("\n");
res = [l for l in lines if "domain-c" in l and "{}.rst".format(file) in l and
"WARNING: document isn't included in any toctree" not in l]
print("Filtered warnings for file '{}':".format(file))
for w in res:
print(w)
return res
@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
def test_build_domain_c(app, status, warning):
app.builder.build_all()
ws = filter_warnings(warning, "index")
assert len(ws) == 0
def test_cfunction(app):
text = (".. c:function:: PyObject* "
"PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)")
doctree = restructuredtext.parse(app, text)
assert_node(doctree,
(addnodes.index,
[desc, ([desc_signature, ([desc_type, ([pending_xref, "PyObject"],
"* ")],
[desc_name, "PyType_GenericAlloc"],
[desc_parameterlist, (desc_parameter,
desc_parameter)])],
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="function",
domain="c", objtype="function", noindex=False)
assert_node(doctree[1][0][2][0],
[desc_parameter, ([pending_xref, "PyTypeObject"],
[nodes.emphasis, "\xa0*type"])])
assert_node(doctree[1][0][2][1],
[desc_parameter, ([pending_xref, "Py_ssize_t"],
[nodes.emphasis, "\xa0nitems"])])
domain = app.env.get_domain('c')
entry = domain.objects.get('PyType_GenericAlloc')
assert entry == ('index', 'c-pytype-genericalloc', 'function')
assert entry == ('index', 'c.PyType_GenericAlloc', 'function')
def test_cmember(app):
text = ".. c:member:: PyObject* PyTypeObject.tp_bases"
doctree = restructuredtext.parse(app, text)
assert_node(doctree,
(addnodes.index,
[desc, ([desc_signature, ([desc_type, ([pending_xref, "PyObject"],
"* ")],
[desc_name, "PyTypeObject.tp_bases"])],
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="member",
domain="c", objtype="member", noindex=False)
domain = app.env.get_domain('c')
entry = domain.objects.get('PyTypeObject.tp_bases')
assert entry == ('index', 'c-pytypeobject-tp-bases', 'member')
assert entry == ('index', 'c.PyTypeObject.tp_bases', 'member')
def test_cvar(app):
text = ".. c:var:: PyObject* PyClass_Type"
doctree = restructuredtext.parse(app, text)
assert_node(doctree,
(addnodes.index,
[desc, ([desc_signature, ([desc_type, ([pending_xref, "PyObject"],
"* ")],
[desc_name, "PyClass_Type"])],
desc_content)]))
assert_node(doctree[1], addnodes.desc, desctype="var",
domain="c", objtype="var", noindex=False)
domain = app.env.get_domain('c')
entry = domain.objects.get('PyClass_Type')
assert entry == ('index', 'c-pyclass-type', 'var')
assert entry == ('index', 'c.PyClass_Type', 'var')