Skip to content

Commit 143f842

Browse files
committed
C, initial introduction of tags
1 parent b8c5884 commit 143f842

File tree

4 files changed

+182
-106
lines changed

4 files changed

+182
-106
lines changed

sphinx/domains/c.py

Lines changed: 92 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"""
1010

1111
import re
12-
from typing import (Any, Callable, Dict, Generator, Iterator, List, Tuple, Type, TypeVar,
13-
Union, cast)
12+
from typing import (Any, Callable, Dict, Generator, Iterator, List, Optional, Tuple, Type,
13+
TypeVar, Union, cast)
1414

1515
from docutils import nodes
1616
from docutils.nodes import Element, Node, TextElement, system_message
@@ -78,8 +78,8 @@
7878
_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
7979
">>=", "<<=", "&=", "and_eq", "^=", "xor_eq", "|=", "or_eq"]
8080

81-
_max_id = 1
82-
_id_prefix = [None, 'c.', 'Cv2.']
81+
_max_id = 2
82+
_id_prefix = [None, 'c.', 'C2-']
8383
# Ids are used in lookup keys which are used across pickled files,
8484
# so when _max_id changes, make sure to update the ENV_VERSION.
8585

@@ -108,31 +108,54 @@ def describe_signature(self, signode: TextElement, mode: str,
108108
################################################################################
109109

110110
class ASTIdentifier(ASTBaseBase):
111-
def __init__(self, identifier: str) -> None:
111+
def __init__(self, identifier: str, tag: Optional[str]) -> None:
112+
# tag:
113+
# - len > 0: it's a struct/union/enum
114+
# - len == 0: it's not a struct/union/enum
115+
# - None: for matching, can be either
112116
assert identifier is not None
113117
assert len(identifier) != 0
114118
self.identifier = identifier
119+
self.tag = tag
115120

116121
def __eq__(self, other: Any) -> bool:
117-
return type(other) is ASTIdentifier and self.identifier == other.identifier
122+
return type(other) is ASTIdentifier \
123+
and self.identifier == other.identifier \
124+
and self.tag == other.tag
118125

119126
def is_anon(self) -> bool:
120127
return self.identifier[0] == '@'
121128

129+
def get_id(self, version: int) -> str:
130+
if version <= 1:
131+
return self.identifier
132+
if self.tag:
133+
assert len(self.tag) != 0
134+
return '-' + self.identifier
135+
else:
136+
return self.identifier
137+
122138
# and this is where we finally make a difference between __str__ and the display string
123139

124140
def __str__(self) -> str:
125-
return self.identifier
141+
return self.tag + " " + self.identifier if self.tag else self.identifier
126142

127143
def get_display_string(self) -> str:
128-
return "[anonymous]" if self.is_anon() else self.identifier
144+
id = "[anonymous]" if self.is_anon() else self.identifier
145+
return self.tag + " " + id if self.tag else id
129146

130147
def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment",
131148
prefix: str, symbol: "Symbol") -> None:
132149
# note: slightly different signature of describe_signature due to the prefix
133150
verify_description_mode(mode)
151+
if self.tag:
152+
signode += nodes.Text(self.tag)
153+
signode += nodes.Text(' ')
134154
if mode == 'markType':
135-
targetText = prefix + self.identifier
155+
if self.tag:
156+
targetText = prefix + self.tag + ' ' + self.identifier
157+
else:
158+
targetText = prefix + self.identifier
136159
pnode = addnodes.pending_xref('', refdomain='c',
137160
reftype='identifier',
138161
reftarget=targetText, modname=None,
@@ -164,12 +187,17 @@ def __init__(self, names: List[ASTIdentifier], rooted: bool) -> None:
164187
self.names = names
165188
self.rooted = rooted
166189

190+
def setTagsToPattern(self) -> None:
191+
for n in self.names:
192+
if n.tag == '':
193+
n.tag = None
194+
167195
@property
168196
def name(self) -> "ASTNestedName":
169197
return self
170198

171199
def get_id(self, version: int) -> str:
172-
return '.'.join(str(n) for n in self.names)
200+
return '.'.join(n.get_id(version) for n in self.names)
173201

174202
def _stringify(self, transform: StringifyTransform) -> str:
175203
res = '.'.join(transform(n) for n in self.names)
@@ -595,27 +623,18 @@ def describe_signature(self, signode: TextElement, mode: str,
595623

596624

597625
class ASTTrailingTypeSpecName(ASTTrailingTypeSpec):
598-
def __init__(self, prefix: str, nestedName: ASTNestedName) -> None:
599-
self.prefix = prefix
626+
def __init__(self, nestedName: ASTNestedName) -> None:
600627
self.nestedName = nestedName
601628

602629
@property
603630
def name(self) -> ASTNestedName:
604631
return self.nestedName
605632

606633
def _stringify(self, transform: StringifyTransform) -> str:
607-
res = []
608-
if self.prefix:
609-
res.append(self.prefix)
610-
res.append(' ')
611-
res.append(transform(self.nestedName))
612-
return ''.join(res)
634+
return transform(self.nestedName)
613635

614636
def describe_signature(self, signode: TextElement, mode: str,
615637
env: "BuildEnvironment", symbol: "Symbol") -> None:
616-
if self.prefix:
617-
signode += addnodes.desc_annotation(self.prefix, self.prefix)
618-
signode += nodes.Text(' ')
619638
self.nestedName.describe_signature(signode, mode, env, symbol=symbol)
620639

621640

@@ -1863,23 +1882,9 @@ def handleDuplicateDeclaration(symbol: "Symbol", candSymbol: "Symbol") -> None:
18631882
candSymbol.isRedeclaration = True
18641883
raise _DuplicateSymbolError(symbol, declaration)
18651884

1866-
if declaration.objectType != "function":
1867-
assert len(withDecl) <= 1
1868-
handleDuplicateDeclaration(withDecl[0], candSymbol)
1869-
# (not reachable)
1870-
1871-
# a function, so compare IDs
1872-
candId = declaration.get_newest_id()
1873-
if Symbol.debug_lookup:
1874-
Symbol.debug_print("candId:", candId)
1875-
for symbol in withDecl:
1876-
oldId = symbol.declaration.get_newest_id()
1877-
if Symbol.debug_lookup:
1878-
Symbol.debug_print("oldId: ", oldId)
1879-
if candId == oldId:
1880-
handleDuplicateDeclaration(symbol, candSymbol)
1881-
# (not reachable)
1882-
# no candidate symbol found with matching ID
1885+
assert len(withDecl) <= 1
1886+
handleDuplicateDeclaration(withDecl[0], candSymbol)
1887+
# (not reachable)
18831888
# if there is an empty symbol, fill that one
18841889
if len(noDecl) == 0:
18851890
if Symbol.debug_lookup:
@@ -1961,8 +1966,12 @@ def add_declaration(self, declaration: ASTDeclaration,
19611966
Symbol.debug_print("add_declaration:")
19621967
assert declaration is not None
19631968
assert docname is not None
1964-
assert line is not None
1965-
nestedName = declaration.name
1969+
assert declaration.name.names[-1].tag == ''
1970+
if declaration.objectType in ('struct', 'union', 'enum'):
1971+
nestedName = declaration.name.clone()
1972+
nestedName.names[-1].tag = declaration.objectType
1973+
else:
1974+
nestedName = declaration.name
19661975
res = self._add_symbols(nestedName, declaration, docname, line)
19671976
if Symbol.debug_lookup:
19681977
Symbol.debug_indent -= 1
@@ -2090,8 +2099,6 @@ class DefinitionParser(BaseParser):
20902099
'__int64',
20912100
)
20922101

2093-
_prefix_keys = ('struct', 'enum', 'union')
2094-
20952102
@property
20962103
def language(self) -> str:
20972104
return 'C'
@@ -2183,10 +2190,9 @@ def _parse_primary_expression(self) -> ASTExpression:
21832190
res = self._parse_paren_expression()
21842191
if res is not None:
21852192
return res
2186-
nn = self._parse_nested_name()
2187-
if nn is not None:
2188-
return ASTIdExpression(nn)
2189-
return None
2193+
nn = self._parse_nested_name(allowTags=None)
2194+
assert nn is not None
2195+
return ASTIdExpression(nn)
21902196

21912197
def _parse_initializer_list(self, name: str, open: str, close: str
21922198
) -> Tuple[List[ASTExpression], bool]:
@@ -2267,7 +2273,7 @@ def _parse_postfix_expression(self) -> ASTPostfixExpr:
22672273
# don't steal the arrow
22682274
self.pos -= 3
22692275
else:
2270-
name = self._parse_nested_name()
2276+
name = self._parse_nested_name(allowTags=None)
22712277
postFixes.append(ASTPostfixMemberOfPointer(name))
22722278
continue
22732279
if self.skip_string('++'):
@@ -2485,29 +2491,46 @@ def _parse_expression_fallback(
24852491
value = self.definition[startPos:self.pos].strip()
24862492
return ASTFallbackExpr(value.strip())
24872493

2488-
def _parse_nested_name(self) -> ASTNestedName:
2489-
names = [] # type: List[Any]
2494+
def _parse_nested_name(self, *, allowTags: Optional[str] = 'not last') -> ASTNestedName:
2495+
# A dot-separated list of identifiers, each with an optional struct/union/enum tag.
2496+
# A leading dot makes the name rooted at global scope.
2497+
2498+
assert allowTags in (None, 'all', 'not last')
2499+
2500+
names = [] # type: List[ASTIdentifier]
24902501

2491-
self.skip_ws()
24922502
rooted = False
24932503
if self.skip_string('.'):
24942504
rooted = True
24952505
while 1:
24962506
self.skip_ws()
2507+
tag = ''
2508+
if allowTags is not None:
2509+
for k in ('struct', 'union', 'enum'):
2510+
if self.skip_word_and_ws(k):
2511+
tag = k
2512+
break
2513+
afterTagPos = self.pos
24972514
if not self.match(identifier_re):
24982515
self.fail("Expected identifier in nested name.")
24992516
identifier = self.matched_text
25002517
# make sure there isn't a keyword
25012518
if identifier in _keywords:
25022519
self.fail("Expected identifier in nested name, "
25032520
"got keyword: %s" % identifier)
2504-
ident = ASTIdentifier(identifier)
2521+
ident = ASTIdentifier(identifier, tag)
25052522
names.append(ident)
25062523

25072524
self.skip_ws()
25082525
if not self.skip_string('.'):
25092526
break
2510-
return ASTNestedName(names, rooted)
2527+
2528+
# post hax to explicitly forbid the last one to have a tag
2529+
if allowTags == 'not last' and names[-1].tag != '':
2530+
self.pos = afterTagPos
2531+
self.fail("Expected identifier in nested name, "
2532+
"got keyword: %s" % names[-1].tag)
2533+
return ASTNestedName(names, rooted=rooted)
25112534

25122535
def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
25132536
# fundamental types
@@ -2540,16 +2563,8 @@ def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
25402563
if len(elements) > 0:
25412564
return ASTTrailingTypeSpecFundamental(' '.join(elements))
25422565

2543-
# prefixed
2544-
prefix = None
2545-
self.skip_ws()
2546-
for k in self._prefix_keys:
2547-
if self.skip_word_and_ws(k):
2548-
prefix = k
2549-
break
2550-
2551-
nestedName = self._parse_nested_name()
2552-
return ASTTrailingTypeSpecName(prefix, nestedName)
2566+
nestedName = self._parse_nested_name(allowTags='all')
2567+
return ASTTrailingTypeSpecName(nestedName)
25532568

25542569
def _parse_parameters(self, paramMode: str) -> ASTParameters:
25552570
self.skip_ws()
@@ -2677,7 +2692,7 @@ def _parse_declarator_name_suffix(
26772692
if self.matched_text in _keywords:
26782693
self.fail("Expected identifier, "
26792694
"got keyword: %s" % self.matched_text)
2680-
identifier = ASTIdentifier(self.matched_text)
2695+
identifier = ASTIdentifier(self.matched_text, '')
26812696
declId = ASTNestedName([identifier], rooted=False)
26822697
else:
26832698
declId = None
@@ -2939,7 +2954,7 @@ def _parse_macro(self) -> ASTMacro:
29392954
break
29402955
if not self.match(identifier_re):
29412956
self.fail("Expected identifier in macro parameters.")
2942-
nn = ASTNestedName([ASTIdentifier(self.matched_text)], rooted=False)
2957+
nn = ASTNestedName([ASTIdentifier(self.matched_text, '')], rooted=False)
29432958
# Allow named variadic args:
29442959
# https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
29452960
self.skip_ws()
@@ -3036,10 +3051,10 @@ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclarati
30363051
return ASTDeclaration(objectType, directiveType, declaration, semicolon)
30373052

30383053
def parse_namespace_object(self) -> ASTNestedName:
3039-
return self._parse_nested_name()
3054+
return self._parse_nested_name(allowTags='all')
30403055

30413056
def parse_xref_object(self) -> ASTNestedName:
3042-
name = self._parse_nested_name()
3057+
name = self._parse_nested_name(allowTags='all')
30433058
# if there are '()' left, just skip them
30443059
self.skip_ws()
30453060
self.skip_string('()')
@@ -3069,7 +3084,7 @@ def parse_expression(self) -> Union[ASTExpression, ASTType]:
30693084

30703085

30713086
def _make_phony_error_name() -> ASTNestedName:
3072-
return ASTNestedName([ASTIdentifier("PhonyNameDueToError")], rooted=False)
3087+
return ASTNestedName([ASTIdentifier("PhonyNameDueToError", None)], rooted=False)
30733088

30743089

30753090
class CObject(ObjectDescription[ASTDeclaration]):
@@ -3800,9 +3815,18 @@ def setup(app: Sphinx) -> Dict[str, Any]:
38003815
app.add_config_value("c_allow_pre_v3", False, 'env')
38013816
app.add_config_value("c_warn_on_allowed_pre_v3", True, 'env')
38023817

3818+
# debug stuff
3819+
app.add_config_value("c_debug_lookup", False, '')
3820+
app.add_config_value("c_debug_show_tree", False, '')
3821+
3822+
def setDebugFlags(app):
3823+
Symbol.debug_lookup = app.config.c_debug_lookup
3824+
Symbol.debug_show_tree = app.config.c_debug_show_tree
3825+
app.connect("builder-inited", setDebugFlags)
3826+
38033827
return {
38043828
'version': 'builtin',
3805-
'env_version': 2,
3829+
'env_version': 3,
38063830
'parallel_read_safe': True,
38073831
'parallel_write_safe': True,
38083832
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.. c:struct:: f_struct
2+
.. c:type:: struct f_struct f_struct
3+
.. c:union:: f_union
4+
.. c:type:: struct f_union f_union
5+
.. c:enum:: f_enum
6+
.. c:type:: struct f_enum f_enum
7+
8+
- :c:struct:`f_struct`, :c:type:`f_struct`
9+
- :c:union:`f_union`, :c:type:`f_union`
10+
- :c:enum:`f_enum`, :c:type:`f_enum`

tests/test_build_html.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,11 +289,11 @@ def test_html4_output(app, status, warning):
289289
(".//a[@class='reference internal'][@href='#errmod.Error']/strong", 'Error'),
290290
# C references
291291
(".//span[@class='pre']", 'CFunction()'),
292-
(".//a[@href='#c.Sphinx_DoSomething']", ''),
293-
(".//a[@href='#c.SphinxStruct.member']", ''),
294-
(".//a[@href='#c.SPHINX_USE_PYTHON']", ''),
295-
(".//a[@href='#c.SphinxType']", ''),
296-
(".//a[@href='#c.sphinx_global']", ''),
292+
(".//a[@href='#C2-Sphinx_DoSomething']", ''),
293+
(".//a[@href='#C2-SphinxStruct.member']", ''),
294+
(".//a[@href='#C2-SPHINX_USE_PYTHON']", ''),
295+
(".//a[@href='#C2-SphinxType']", ''),
296+
(".//a[@href='#C2-sphinx_global']", ''),
297297
# test global TOC created by toctree()
298298
(".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='#']",
299299
'Testing object descriptions'),

0 commit comments

Comments
 (0)