Skip to content

Commit f6ffb21

Browse files
committed
Add a grammar-snippet directive
1 parent 86a0ecc commit f6ffb21

File tree

3 files changed

+36
-31
lines changed

3 files changed

+36
-31
lines changed

Doc/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# Python specific content from Doc/Tools/extensions/pyspecific.py
2121
from pyspecific import SOURCE_URI
2222

23+
2324
# General configuration
2425
# ---------------------
2526

@@ -29,6 +30,7 @@
2930
'availability',
3031
'c_annotations',
3132
'glossary_search',
33+
'grammar_snippet',
3234
'lexers',
3335
'pyspecific',
3436
'sphinx.ext.coverage',

Doc/reference/toplevel_components.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ File input
6666

6767
All input read from non-interactive files has the same form:
6868

69-
.. productionlist:: python-grammar
69+
.. grammar-snippet::
70+
:group: python-grammar
71+
7072
file_input: (NEWLINE | `statement`)*
7173

7274
This syntax is used in the following situations:
@@ -85,7 +87,9 @@ Interactive input
8587

8688
Input in interactive mode is parsed using the following grammar:
8789

88-
.. productionlist:: python-grammar
90+
... grammar-snippet::
91+
:group: python-grammar
92+
8993
interactive_input: [`stmt_list`] NEWLINE | `compound_stmt` NEWLINE
9094

9195
Note that a (top-level) compound statement must be followed by a blank line in

Doc/tools/extensions/grammar_snippet.py

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
import re
2+
from docutils import nodes
3+
from docutils.parsers.rst import directives
4+
5+
from sphinx import addnodes
6+
from sphinx.util.docutils import SphinxDirective
7+
from sphinx.util.nodes import make_id
8+
19

210
class GrammarSnippetDirective(SphinxDirective):
311
"""Transform a grammar-snippet directive to a Sphinx productionlist
@@ -6,50 +14,45 @@ class GrammarSnippetDirective(SphinxDirective):
614
715
.. grammar-snippet:: file
816
:group: python-grammar
9-
:generated-by: Tools/peg_generator/docs_generator.py
1017
1118
file: (NEWLINE | statement)*
1219
13-
into something like:
14-
15-
.. productionlist:: python-grammar
16-
file: (NEWLINE | statement)*
20+
into something similar to Sphinx productionlist, but better suited
21+
for our needs:
22+
- Instead of `::=`, use a colon, as in `Grammar/python.gram`
23+
- Show the listing almost as is, with no auto-aligment.
24+
The only special character is the backtick, which marks tokens.
1725
18-
The custom directive is needed because Sphinx's `productionlist` does
19-
not support options.
26+
Unlike Sphinx's productionlist, this directive supports options.
27+
The "group" must be given as an option.
2028
"""
2129
has_content = True
2230
option_spec = {
2331
'group': directives.unchanged,
24-
'generated-by': directives.unchanged,
25-
'diagrams': directives.unchanged,
2632
}
2733

28-
# Arguments are used by the tool that generates grammar-snippet,
29-
# this Directive ignores them.
30-
required_arguments = 1
31-
optional_arguments = 0
34+
# We currently ignore arguments.
35+
required_arguments = 0
36+
optional_arguments = 1
3237
final_argument_whitespace = True
3338

3439
def run(self):
3540
group_name = self.options['group']
3641

37-
rawsource = '''
3842
# Docutils elements have a `rawsource` attribute that is supposed to be
3943
# set to the original ReST source.
4044
# Sphinx does the following with it:
4145
# - if it's empty, set it to `self.astext()`
4246
# - if it matches `self.astext()` when generating the output,
4347
# apply syntax highlighting (which is based on the plain-text content
4448
# and thus discards internal formatting, like references).
45-
# To get around this, we set it to this fake (and very non-empty)
46-
# string!
47-
'''
49+
# To get around this, we set it to this non-empty string:
50+
rawsource = 'You should not see this.'
4851

4952
literal = nodes.literal_block(
5053
rawsource,
5154
'',
52-
# TODO: Use a dedicated CSS class here and for strings,
55+
# TODO: Use a dedicated CSS class here and for strings.
5356
# and add it to the theme too
5457
classes=['highlight'],
5558
)
@@ -87,7 +90,9 @@ def run(self):
8790
name_node = addnodes.literal_strong()
8891

8992
# Cargo-culted magic to make `name_node` a link target
90-
# similar to Sphinx `production`:
93+
# similar to Sphinx `production`.
94+
# This needs to be the same as what Sphinx does
95+
# to avoid breaking existing links.
9196
domain = self.env.domains['std']
9297
obj_name = f"{group_name}:{name}"
9398
prefix = f'grammar-token-{group_name}'
@@ -116,19 +121,13 @@ def run(self):
116121
raise ValueError('unhandled match')
117122
literal += nodes.Text(line[last_pos:] + '\n')
118123

119-
120124
node = nodes.paragraph(
121125
'', '',
122126
literal,
123127
)
124128

125-
content = StringList()
126-
for rule_name in self.options['diagrams'].split():
127-
content.append('', source=__file__)
128-
content.append(f'``{rule_name}``:', source=__file__)
129-
content.append('', source=__file__)
130-
content.append(f'.. image:: diagrams/{rule_name}.svg', source=__file__)
131-
132-
self.state.nested_parse(content, 0, node)
133-
134129
return [node]
130+
131+
def setup(app):
132+
app.add_directive('grammar-snippet', GrammarSnippetDirective)
133+
return {'version': '1.0', 'parallel_read_safe': True}

0 commit comments

Comments
 (0)