Skip to content

Commit 6b4ec3d

Browse files
committed
Extract production lines logic into production_definitions()
1 parent e591450 commit 6b4ec3d

File tree

1 file changed

+59
-40
lines changed

1 file changed

+59
-40
lines changed

Doc/tools/extensions/grammar_snippet.py

Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from sphinx.util.nodes import make_id
1414

1515
if TYPE_CHECKING:
16-
from collections.abc import Sequence
16+
from collections.abc import Iterable, Iterator, Sequence
1717
from typing import Any, Final
1818

1919
from docutils.nodes import Node
@@ -62,28 +62,15 @@ def make_grammar_snippet(
6262

6363
group_name = options['group']
6464
node_location = self.get_location()
65-
current_lines = []
66-
production_node = addnodes.production()
67-
production_nodes = [production_node]
68-
for source_line in content:
69-
# Start a new production_node if there's text in the first
70-
# column of the line. (That means a new rule is starting.)
71-
if not source_line[:1].isspace():
72-
# set the raw source of the previous production
73-
production_node.rawsource = '\n'.join(current_lines)
74-
current_lines.clear()
75-
# start a new production_node
76-
production_node = addnodes.production()
77-
production_nodes.append(production_node)
78-
current_lines.append(source_line)
79-
self.add_production_line(
80-
production_node,
81-
source_line,
65+
production_nodes = []
66+
for rawsource, production_defs in self.production_definitions(content):
67+
production = self.make_production(
68+
rawsource,
69+
production_defs,
8270
group_name=group_name,
8371
location=node_location,
8472
)
85-
# set the raw source of the final production
86-
production_node.rawsource = '\n'.join(current_lines)
73+
production_nodes.append(production)
8774

8875
node = addnodes.productionlist(
8976
'',
@@ -94,29 +81,59 @@ def make_grammar_snippet(
9481
self.set_source_info(node)
9582
return [node]
9683

97-
def add_production_line(
84+
def production_definitions(
85+
self, lines: Iterable[str], /
86+
) -> Iterator[tuple[str, list[dict[str, str]]]]:
87+
"""Yield pairs of rawsource and production content dicts."""
88+
production_lines: list[str] = []
89+
production_content: list[dict[str, str]] = []
90+
for line in lines:
91+
# If this line is the start of a new rule (text in the column 1),
92+
# emit the current production and start a new one.
93+
if not line[:1].isspace():
94+
rawsource = '\n'.join(production_lines)
95+
production_lines.clear()
96+
if production_content:
97+
yield rawsource, production_content
98+
production_content = []
99+
100+
# Append the current line for the raw source
101+
production_lines.append(line)
102+
103+
# Parse the line into constituent parts
104+
last_pos = 0
105+
for match in self.grammar_re.finditer(line):
106+
# Handle text between matches
107+
if match.start() > last_pos:
108+
unmatched_text = line[last_pos : match.start()]
109+
production_content.append({'text': unmatched_text})
110+
last_pos = match.end()
111+
112+
# Handle matches
113+
group_dict = {
114+
name: content
115+
for name, content in match.groupdict().items()
116+
if content is not None
117+
}
118+
production_content.append(group_dict)
119+
production_content.append({'text': line[last_pos:] + '\n'})
120+
121+
# Emit the final production
122+
if production_content:
123+
rawsource = '\n'.join(production_lines)
124+
yield rawsource, production_content
125+
126+
def make_production(
98127
self,
99-
production_node: addnodes.production,
100-
source_line: str,
128+
rawsource: str,
129+
production_defs: list[dict[str, str]],
101130
*,
102131
group_name: str,
103132
location: str,
104-
) -> None:
105-
"""Add one line of content to the given production_node"""
106-
last_pos = 0
107-
for match in self.grammar_re.finditer(source_line):
108-
# Handle text between matches
109-
if match.start() > last_pos:
110-
unmatched_text = source_line[last_pos : match.start()]
111-
production_node += nodes.Text(unmatched_text)
112-
last_pos = match.end()
113-
114-
# Handle matches
115-
group_dict = {
116-
name: content
117-
for name, content in match.groupdict().items()
118-
if content is not None
119-
}
133+
) -> addnodes.production:
134+
"""Create a production node from a list of parts."""
135+
production_node = addnodes.production(rawsource)
136+
for group_dict in production_defs:
120137
match group_dict:
121138
case {'rule_name': name}:
122139
production_node += self.make_name_target(
@@ -128,9 +145,11 @@ def add_production_line(
128145
production_node += token_xrefs(ref_text, group_name)
129146
case {'single_quoted': name} | {'double_quoted': name}:
130147
production_node += snippet_string_node('', name)
148+
case {'text': text}:
149+
production_node += nodes.Text(text)
131150
case _:
132151
raise ValueError('unhandled match')
133-
production_node += nodes.Text(source_line[last_pos:] + '\n')
152+
return production_node
134153

135154
def make_name_target(
136155
self,

0 commit comments

Comments
 (0)