summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Lykke Andersen <jakobandersen@users.noreply.github.com>2021-12-05 11:32:56 +0100
committerGitHub <noreply@github.com>2021-12-05 11:32:56 +0100
commit3ae124f8ac038a3ca79aa266d4558fd85c6e6032 (patch)
tree09266fc2cad7bb9bcd3b5d02d8e36c2961306ded
parent95969a47e5b303c187cb7f2f0505d37d6d739687 (diff)
parentf8f26286ad74cd33981c07b59bdaa50b7f4cc7f9 (diff)
downloadsphinx-git-3ae124f8ac038a3ca79aa266d4558fd85c6e6032.tar.gz
Merge pull request #9918 from jakobandersen/c-cpp-fundamental-type-permutation
C, C++: fundamental type parsing
-rw-r--r--CHANGES3
-rw-r--r--sphinx/domains/c.py66
-rw-r--r--sphinx/domains/cpp.py146
-rw-r--r--tests/test_domain_c.py13
-rw-r--r--tests/test_domain_cpp.py11
5 files changed, 178 insertions, 61 deletions
diff --git a/CHANGES b/CHANGES
index c00765178..f642906c5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,9 @@ Features added
Bugs fixed
----------
+* #9917: C and C++, parse fundamental types no matter the order of simple type
+ specifiers.
+
Testing
--------
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index 84878cd05..dff8bef00 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -92,31 +92,22 @@ _id_prefix = [None, 'c.', 'Cv2.']
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
+# bool, complex, and imaginary are macro "keywords", so they are handled seperately
_simple_type_specifiers_re = re.compile(r"""(?x)
\b(
- void|_Bool|bool
- # Integer
- # -------
- |((signed|unsigned)\s+)?(char|(
- ((long\s+long|long|short)\s+)?int
- ))
+ void|_Bool
+ |signed|unsigned
+ |short|long
+ |char
+ |int
|__uint128|__int128
- # extensions
- |((signed|unsigned)\s+)?__int(8|16|32|64|128)
- # Floating-point
- # --------------
- |(float|double|long\s+double)(\s+(_Complex|complex|_Imaginary|imaginary))?
- |(_Complex|complex|_Imaginary|imaginary)\s+(float|double|long\s+double)
+ |__int(8|16|32|64|128) # extension
+ |float|double
|_Decimal(32|64|128)
- # extensions
- |__float80|_Float64x|__float128|_Float128|__ibm128
- |__fp16
- # Fixed-point, extension
- |(_Sat\s+)?((signed|unsigned)\s+)?((short|long|long\s+long)\s+)?(_Fract|fract|_Accum|accum)
- # Integer types that could be prefixes of the previous ones
- # ---------------------------------------------------------
- |((signed|unsigned)\s+)?(long\s+long|long|short)
- |signed|unsigned
+ |_Complex|_Imaginary
+ |__float80|_Float64x|__float128|_Float128|__ibm128 # extension
+ |__fp16 # extension
+ |_Sat|_Fract|fract|_Accum|accum # extension
)\b
""")
@@ -636,8 +627,9 @@ class ASTTrailingTypeSpec(ASTBase):
class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
- def __init__(self, name: str) -> None:
- self.names = name.split()
+ def __init__(self, names: List[str]) -> None:
+ assert len(names) != 0
+ self.names = names
def _stringify(self, transform: StringifyTransform) -> str:
return ' '.join(self.names)
@@ -2580,12 +2572,36 @@ class DefinitionParser(BaseParser):
break
return ASTNestedName(names, rooted)
+ def _parse_simple_type_specifier(self) -> Optional[str]:
+ 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:
+ 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()
- if self.match(_simple_type_specifiers_re):
- return ASTTrailingTypeSpecFundamental(self.matched_text)
+ res = self._parse_simple_type_specifiers()
+ if res is not None:
+ return res
# prefixed
prefix = None
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index b332ae422..b22ee0b13 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -338,24 +338,14 @@ _keywords = [
_simple_type_specifiers_re = re.compile(r"""(?x)
\b(
auto|void|bool
- # Integer
- # -------
- |((signed|unsigned)\s+)?(char|__int128|(
- ((long\s+long|long|short)\s+)?int
- ))
- |wchar_t|char(8|16|32)_t
- # extensions
- |((signed|unsigned)\s+)?__int(64|128)
- # Floating-point
- # --------------
- |(float|double|long\s+double)(\s+(_Complex|_Imaginary))?
- |(_Complex|_Imaginary)\s+(float|double|long\s+double)
- # extensions
- |__float80|_Float64x|__float128|_Float128
- # Integer types that could be prefixes of the previous ones
- # ---------------------------------------------------------
- |((signed|unsigned)\s+)?(long\s+long|long|short)
|signed|unsigned
+ |short|long
+ |char|wchar_t|char(8|16|32)_t
+ |int
+ |__int(64|128) # extension
+ |float|double
+ |__float80|_Float64x|__float128|_Float128 # extension
+ |_Complex|_Imaginary # extension
)\b
""")
@@ -485,12 +475,12 @@ _id_fundamental_v2 = {
'long double': 'e',
'__float80': 'e', '_Float64x': 'e',
'__float128': 'g', '_Float128': 'g',
- 'float _Complex': 'Cf', '_Complex float': 'Cf',
- 'double _Complex': 'Cd', '_Complex double': 'Cd',
- 'long double _Complex': 'Ce', '_Complex long double': 'Ce',
- 'float _Imaginary': 'f', '_Imaginary float': 'f',
- 'double _Imaginary': 'd', '_Imaginary double': 'd',
- 'long double _Imaginary': 'e', '_Imaginary long double': 'e',
+ '_Complex float': 'Cf',
+ '_Complex double': 'Cd',
+ '_Complex long double': 'Ce',
+ '_Imaginary float': 'f',
+ '_Imaginary double': 'd',
+ '_Imaginary long double': 'e',
'auto': 'Da',
'decltype(auto)': 'Dc',
'std::nullptr_t': 'Dn'
@@ -1853,8 +1843,12 @@ class ASTTrailingTypeSpec(ASTBase):
class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
- def __init__(self, name: str) -> None:
- self.names = name.split()
+ def __init__(self, names: List[str], canonNames: List[str]) -> None:
+ assert len(names) != 0
+ assert len(names) == len(canonNames), (names, canonNames)
+ self.names = names
+ # the canonical name list is for ID lookup
+ self.canonNames = canonNames
def _stringify(self, transform: StringifyTransform) -> str:
return ' '.join(self.names)
@@ -1862,14 +1856,14 @@ class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
def get_id(self, version: int) -> str:
if version == 1:
res = []
- for a in self.names:
+ for a in self.canonNames:
if a in _id_fundamental_v1:
res.append(_id_fundamental_v1[a])
else:
res.append(a)
return '-'.join(res)
- txt = str(self)
+ txt = ' '.join(self.canonNames)
if txt not in _id_fundamental_v2:
raise Exception(
'Semi-internal error: Fundamental type "%s" can not be mapped '
@@ -5855,12 +5849,102 @@ class DefinitionParser(BaseParser):
# ==========================================================================
+ def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental:
+ modifier: Optional[str] = None
+ signedness: Optional[str] = None
+ width: List[str] = []
+ typ: Optional[str] = None
+ names: List[str] = [] # the parsed sequence
+
+ self.skip_ws()
+ while self.match(_simple_type_specifiers_re):
+ t = self.matched_text
+ names.append(t)
+ if t in ('auto', 'void', 'bool',
+ 'char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
+ 'int', '__int64', '__int128',
+ 'float', 'double',
+ '__float80', '_Float64x', '__float128', '_Float128'):
+ if typ is not None:
+ self.fail("Can not have both {} and {}.".format(t, typ))
+ typ = t
+ elif t in ('signed', 'unsigned'):
+ if signedness is not None:
+ self.fail("Can not have both {} and {}.".format(t, signedness))
+ signedness = t
+ elif t == 'short':
+ if len(width) != 0:
+ self.fail("Can not have both {} and {}.".format(t, width[0]))
+ width.append(t)
+ elif t == 'long':
+ if len(width) != 0 and width[0] != 'long':
+ self.fail("Can not have both {} and {}.".format(t, width[0]))
+ width.append(t)
+ elif t in ('_Imaginary', '_Complex'):
+ if modifier is not None:
+ self.fail("Can not have both {} and {}.".format(t, modifier))
+ modifier = t
+ self.skip_ws()
+ if len(names) == 0:
+ return None
+
+ if typ in ('auto', 'void', 'bool',
+ 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
+ '__float80', '_Float64x', '__float128', '_Float128'):
+ if modifier is not None:
+ self.fail("Can not have both {} and {}.".format(typ, modifier))
+ if signedness is not None:
+ self.fail("Can not have both {} and {}.".format(typ, signedness))
+ if len(width) != 0:
+ self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
+ elif typ == 'char':
+ if modifier is not None:
+ self.fail("Can not have both {} and {}.".format(typ, modifier))
+ if len(width) != 0:
+ self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
+ elif typ == 'int':
+ if modifier is not None:
+ self.fail("Can not have both {} and {}.".format(typ, modifier))
+ elif typ in ('__int64', '__int128'):
+ if modifier is not None:
+ self.fail("Can not have both {} and {}.".format(typ, modifier))
+ if len(width) != 0:
+ self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
+ elif typ == 'float':
+ if signedness is not None:
+ self.fail("Can not have both {} and {}.".format(typ, signedness))
+ if len(width) != 0:
+ self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
+ elif typ == 'double':
+ if signedness is not None:
+ self.fail("Can not have both {} and {}.".format(typ, signedness))
+ if len(width) > 1:
+ self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
+ if len(width) == 1 and width[0] != 'long':
+ self.fail("Can not have both {} and {}.".format(typ, ' '.join(width)))
+ elif typ is None:
+ if modifier is not None:
+ self.fail("Can not have {} without a floating point type.".format(modifier))
+ else:
+ assert False, "Unhandled type {}".format(typ)
+
+ canonNames: List[str] = []
+ if modifier is not None:
+ canonNames.append(modifier)
+ if signedness is not None:
+ canonNames.append(signedness)
+ canonNames.extend(width)
+ if typ is not None:
+ canonNames.append(typ)
+ return ASTTrailingTypeSpecFundamental(names, canonNames)
+
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
# fundamental types, https://en.cppreference.com/w/cpp/language/type
# and extensions
self.skip_ws()
- if self.match(_simple_type_specifiers_re):
- return ASTTrailingTypeSpecFundamental(self.matched_text)
+ res = self._parse_simple_type_specifiers()
+ if res is not None:
+ return res
# decltype
self.skip_ws()
@@ -6541,7 +6625,7 @@ class DefinitionParser(BaseParser):
# ==========================================================================
- def _parse_template_paramter(self) -> ASTTemplateParam:
+ def _parse_template_parameter(self) -> ASTTemplateParam:
self.skip_ws()
if self.skip_word('template'):
# declare a tenplate template parameter
@@ -6613,7 +6697,7 @@ class DefinitionParser(BaseParser):
pos = self.pos
err = None
try:
- param = self._parse_template_paramter()
+ param = self._parse_template_parameter()
templateParams.append(param)
except DefinitionError as eParam:
self.pos = pos
diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py
index 0800c5059..d3198dcc0 100644
--- a/tests/test_domain_c.py
+++ b/tests/test_domain_c.py
@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""
+import itertools
import zlib
from xml.etree import ElementTree
@@ -329,6 +330,13 @@ def test_domain_c_ast_fundamental_types():
input = "{key}%s foo" % t
output = ' '.join(input.split())
check('type', input, {1: 'foo'}, key='typedef', output=output)
+ if ' ' in t:
+ # try permutations of all components
+ tcs = t.split()
+ for p in itertools.permutations(tcs):
+ input = "{key}%s foo" % ' '.join(p)
+ output = ' '.join(input.split())
+ check("type", input, {1: 'foo'}, key='typedef', output=output)
def test_domain_c_ast_type_definitions():
@@ -587,10 +595,7 @@ def test_domain_c_ast_attributes():
def test_extra_keywords():
with pytest.raises(DefinitionError,
- match='Expected identifier, got user-defined keyword: complex.'):
- parse('function', 'void f(int complex)')
- with pytest.raises(DefinitionError,
- match='Expected identifier, got user-defined keyword: complex.'):
+ match='Expected identifier in nested name'):
parse('function', 'void complex(void)')
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index ede4a2531..5ff8f8e12 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""
+import itertools
import re
import zlib
@@ -137,9 +138,17 @@ def test_domain_cpp_ast_fundamental_types():
if t == "std::nullptr_t":
id = "NSt9nullptr_tE"
return "1f%s" % id
+ id1 = makeIdV1()
+ id2 = makeIdV2()
input = "void f(%s arg)" % t.replace(' ', ' ')
output = "void f(%s arg)" % t
- check("function", input, {1: makeIdV1(), 2: makeIdV2()}, output=output)
+ check("function", input, {1: id1, 2: id2}, output=output)
+ if ' ' in t:
+ # try permutations of all components
+ tcs = t.split()
+ for p in itertools.permutations(tcs):
+ input = "void f(%s arg)" % ' '.join(p)
+ check("function", input, {1: id1, 2: id2})
def test_domain_cpp_ast_expressions():