bpo-41762: Fix usage of productionlist markup in the doc (GH-22281)

Use an unique identifier for the different grammars documented using
the Sphinx productionlist markup.

productionlist markups of the same grammar, like "expressions" or
"compound statements", use the same identifier "python-grammar".
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index d381d43..7543fc4 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -596,7 +596,7 @@
    input must conform to the following grammar after leading and trailing
    whitespace characters are removed:
 
-   .. productionlist::
+   .. productionlist:: float
       sign: "+" | "-"
       infinity: "Infinity" | "inf"
       nan: "nan"
diff --git a/Doc/library/string.rst b/Doc/library/string.rst
index 62e86d6..91f43e9 100644
--- a/Doc/library/string.rst
+++ b/Doc/library/string.rst
@@ -205,7 +205,7 @@
 
 The grammar for a replacement field is as follows:
 
-   .. productionlist:: sf
+   .. productionlist:: format-string
       replacement_field: "{" [`field_name`] ["!" `conversion`] [":" `format_spec`] "}"
       field_name: arg_name ("." `attribute_name` | "[" `element_index` "]")*
       arg_name: [`identifier` | `digit`+]
@@ -308,7 +308,7 @@
 
 The general form of a *standard format specifier* is:
 
-.. productionlist::
+.. productionlist:: format-spec
    format_spec: [[`fill`]`align`][`sign`][#][0][`width`][`grouping_option`][.`precision`][`type`]
    fill: <any character>
    align: "<" | ">" | "=" | "^"
diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index df720f6..158d6a8 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -44,7 +44,8 @@
 
 Summarizing:
 
-.. productionlist::
+
+.. productionlist:: python-grammar
    compound_stmt: `if_stmt`
                 : | `while_stmt`
                 : | `for_stmt`
@@ -89,7 +90,7 @@
 
 The :keyword:`if` statement is used for conditional execution:
 
-.. productionlist::
+.. productionlist:: python-grammar
    if_stmt: "if" `assignment_expression` ":" `suite`
           : ("elif" `assignment_expression` ":" `suite`)*
           : ["else" ":" `suite`]
@@ -115,7 +116,7 @@
 The :keyword:`while` statement is used for repeated execution as long as an
 expression is true:
 
-.. productionlist::
+.. productionlist:: python-grammar
    while_stmt: "while" `assignment_expression` ":" `suite`
              : ["else" ":" `suite`]
 
@@ -151,7 +152,7 @@
 The :keyword:`for` statement is used to iterate over the elements of a sequence
 (such as a string, tuple or list) or other iterable object:
 
-.. productionlist::
+.. productionlist:: python-grammar
    for_stmt: "for" `target_list` "in" `expression_list` ":" `suite`
            : ["else" ":" `suite`]
 
@@ -234,7 +235,7 @@
 The :keyword:`try` statement specifies exception handlers and/or cleanup code
 for a group of statements:
 
-.. productionlist::
+.. productionlist:: python-grammar
    try_stmt: `try1_stmt` | `try2_stmt`
    try1_stmt: "try" ":" `suite`
             : ("except" [`expression` ["as" `identifier`]] ":" `suite`)+
@@ -390,7 +391,7 @@
 This allows common :keyword:`try`...\ :keyword:`except`...\ :keyword:`finally`
 usage patterns to be encapsulated for convenient reuse.
 
-.. productionlist::
+.. productionlist:: python-grammar
    with_stmt: "with" `with_item` ("," `with_item`)* ":" `suite`
    with_item: `expression` ["as" `target`]
 
@@ -503,7 +504,7 @@
 A function definition defines a user-defined function object (see section
 :ref:`types`):
 
-.. productionlist::
+.. productionlist:: python-grammar
    funcdef: [`decorators`] "def" `funcname` "(" [`parameter_list`] ")"
           : ["->" `expression`] ":" `suite`
    decorators: `decorator`+
@@ -670,7 +671,7 @@
 
 A class definition defines a class object (see section :ref:`types`):
 
-.. productionlist::
+.. productionlist:: python-grammar
    classdef: [`decorators`] "class" `classname` [`inheritance`] ":" `suite`
    inheritance: "(" [`argument_list`] ")"
    classname: `identifier`
@@ -762,7 +763,7 @@
 Coroutine function definition
 -----------------------------
 
-.. productionlist::
+.. productionlist:: python-grammar
    async_funcdef: [`decorators`] "async" "def" `funcname` "(" [`parameter_list`] ")"
                 : ["->" `expression`] ":" `suite`
 
@@ -795,7 +796,7 @@
 The :keyword:`!async for` statement
 -----------------------------------
 
-.. productionlist::
+.. productionlist:: python-grammar
    async_for_stmt: "async" `for_stmt`
 
 An :term:`asynchronous iterable` is able to call asynchronous code in its
@@ -840,7 +841,7 @@
 The :keyword:`!async with` statement
 ------------------------------------
 
-.. productionlist::
+.. productionlist:: python-grammar
    async_with_stmt: "async" `with_stmt`
 
 An :term:`asynchronous context manager` is a :term:`context manager` that is
diff --git a/Doc/reference/expressions.rst b/Doc/reference/expressions.rst
index 18abce3..b68c298 100644
--- a/Doc/reference/expressions.rst
+++ b/Doc/reference/expressions.rst
@@ -13,7 +13,7 @@
 be used to describe syntax, not lexical analysis.  When (one alternative of) a
 syntax rule has the form
 
-.. productionlist:: *
+.. productionlist:: python-grammar
    name: `othername`
 
 and no semantics are given, the semantics of this form of ``name`` are the same
@@ -54,7 +54,7 @@
 identifiers or literals.  Forms enclosed in parentheses, brackets or braces are
 also categorized syntactically as atoms.  The syntax for atoms is:
 
-.. productionlist::
+.. productionlist:: python-grammar
    atom: `identifier` | `literal` | `enclosure`
    enclosure: `parenth_form` | `list_display` | `dict_display` | `set_display`
             : | `generator_expression` | `yield_atom`
@@ -103,7 +103,7 @@
 
 Python supports string and bytes literals and various numeric literals:
 
-.. productionlist::
+.. productionlist:: python-grammar
    literal: `stringliteral` | `bytesliteral`
           : | `integer` | `floatnumber` | `imagnumber`
 
@@ -134,7 +134,7 @@
 
 A parenthesized form is an optional expression list enclosed in parentheses:
 
-.. productionlist::
+.. productionlist:: python-grammar
    parenth_form: "(" [`starred_expression`] ")"
 
 A parenthesized expression list yields whatever that expression list yields: if
@@ -177,7 +177,7 @@
 
 Common syntax elements for comprehensions are:
 
-.. productionlist::
+.. productionlist:: python-grammar
    comprehension: `assignment_expression` `comp_for`
    comp_for: ["async"] "for" `target_list` "in" `or_test` [`comp_iter`]
    comp_iter: `comp_for` | `comp_if`
@@ -243,7 +243,7 @@
 A list display is a possibly empty series of expressions enclosed in square
 brackets:
 
-.. productionlist::
+.. productionlist:: python-grammar
    list_display: "[" [`starred_list` | `comprehension`] "]"
 
 A list display yields a new list object, the contents being specified by either
@@ -267,7 +267,7 @@
 A set display is denoted by curly braces and distinguishable from dictionary
 displays by the lack of colons separating keys and values:
 
-.. productionlist::
+.. productionlist:: python-grammar
    set_display: "{" (`starred_list` | `comprehension`) "}"
 
 A set display yields a new mutable set object, the contents being specified by
@@ -296,7 +296,7 @@
 A dictionary display is a possibly empty series of key/datum pairs enclosed in
 curly braces:
 
-.. productionlist::
+.. productionlist:: python-grammar
    dict_display: "{" [`key_datum_list` | `dict_comprehension`] "}"
    key_datum_list: `key_datum` ("," `key_datum`)* [","]
    key_datum: `expression` ":" `expression` | "**" `or_expr`
@@ -355,7 +355,7 @@
 
 A generator expression is a compact generator notation in parentheses:
 
-.. productionlist::
+.. productionlist:: python-grammar
    generator_expression: "(" `expression` `comp_for` ")"
 
 A generator expression yields a new generator object.  Its syntax is the same as
@@ -409,7 +409,7 @@
    pair: yield; expression
    pair: generator; function
 
-.. productionlist::
+.. productionlist:: python-grammar
    yield_atom: "(" `yield_expression` ")"
    yield_expression: "yield" [`expression_list` | "from" `expression`]
 
@@ -746,7 +746,7 @@
 Primaries represent the most tightly bound operations of the language. Their
 syntax is:
 
-.. productionlist::
+.. productionlist:: python-grammar
    primary: `atom` | `attributeref` | `subscription` | `slicing` | `call`
 
 
@@ -761,7 +761,7 @@
 
 An attribute reference is a primary followed by a period and a name:
 
-.. productionlist::
+.. productionlist:: python-grammar
    attributeref: `primary` "." `identifier`
 
 .. index::
@@ -799,7 +799,7 @@
 A subscription selects an item of a sequence (string, tuple or list) or mapping
 (dictionary) object:
 
-.. productionlist::
+.. productionlist:: python-grammar
    subscription: `primary` "[" `expression_list` "]"
 
 The primary must evaluate to an object that supports subscription (lists or
@@ -855,7 +855,7 @@
 or list).  Slicings may be used as expressions or as targets in assignment or
 :keyword:`del` statements.  The syntax for a slicing:
 
-.. productionlist::
+.. productionlist:: python-grammar
    slicing: `primary` "[" `slice_list` "]"
    slice_list: `slice_item` ("," `slice_item`)* [","]
    slice_item: `expression` | `proper_slice`
@@ -905,7 +905,7 @@
 A call calls a callable object (e.g., a :term:`function`) with a possibly empty
 series of :term:`arguments <argument>`:
 
-.. productionlist::
+.. productionlist:: python-grammar
    call: `primary` "(" [`argument_list` [","] | `comprehension`] ")"
    argument_list: `positional_arguments` ["," `starred_and_keywords`]
                 :   ["," `keywords_arguments`]
@@ -1088,7 +1088,7 @@
 Suspend the execution of :term:`coroutine` on an :term:`awaitable` object.
 Can only be used inside a :term:`coroutine function`.
 
-.. productionlist::
+.. productionlist:: python-grammar
    await_expr: "await" `primary`
 
 .. versionadded:: 3.5
@@ -1106,7 +1106,7 @@
 The power operator binds more tightly than unary operators on its left; it binds
 less tightly than unary operators on its right.  The syntax is:
 
-.. productionlist::
+.. productionlist:: python-grammar
    power: (`await_expr` | `primary`) ["**" `u_expr`]
 
 Thus, in an unparenthesized sequence of power and unary operators, the operators
@@ -1139,7 +1139,7 @@
 
 All unary arithmetic and bitwise operations have the same priority:
 
-.. productionlist::
+.. productionlist:: python-grammar
    u_expr: `power` | "-" `u_expr` | "+" `u_expr` | "~" `u_expr`
 
 .. index::
@@ -1183,7 +1183,7 @@
 from the power operator, there are only two levels, one for multiplicative
 operators and one for additive operators:
 
-.. productionlist::
+.. productionlist:: python-grammar
    m_expr: `u_expr` | `m_expr` "*" `u_expr` | `m_expr` "@" `m_expr` |
          : `m_expr` "//" `u_expr` | `m_expr` "/" `u_expr` |
          : `m_expr` "%" `u_expr`
@@ -1279,7 +1279,7 @@
 
 The shifting operations have lower priority than the arithmetic operations:
 
-.. productionlist::
+.. productionlist:: python-grammar
    shift_expr: `a_expr` | `shift_expr` ("<<" | ">>") `a_expr`
 
 These operators accept integers as arguments.  They shift the first argument to
@@ -1300,7 +1300,7 @@
 
 Each of the three bitwise operations has a different priority level:
 
-.. productionlist::
+.. productionlist:: python-grammar
    and_expr: `shift_expr` | `and_expr` "&" `shift_expr`
    xor_expr: `and_expr` | `xor_expr` "^" `and_expr`
    or_expr: `xor_expr` | `or_expr` "|" `xor_expr`
@@ -1349,7 +1349,7 @@
 C, expressions like ``a < b < c`` have the interpretation that is conventional
 in mathematics:
 
-.. productionlist::
+.. productionlist:: python-grammar
    comparison: `or_expr` (`comp_operator` `or_expr`)*
    comp_operator: "<" | ">" | "==" | ">=" | "<=" | "!="
                 : | "is" ["not"] | ["not"] "in"
@@ -1608,7 +1608,7 @@
    pair: Conditional; expression
    pair: Boolean; operation
 
-.. productionlist::
+.. productionlist:: python-grammar
    or_test: `and_test` | `or_test` "or" `and_test`
    and_test: `not_test` | `and_test` "and" `not_test`
    not_test: `comparison` | "not" `not_test`
@@ -1647,7 +1647,7 @@
 Assignment expressions
 ======================
 
-.. productionlist::
+.. productionlist:: python-grammar
    assignment_expression: [`identifier` ":="] `expression`
 
 An assignment expression (sometimes also called a "named expression" or
@@ -1683,7 +1683,7 @@
    single: if; conditional expression
    single: else; conditional expression
 
-.. productionlist::
+.. productionlist:: python-grammar
    conditional_expression: `or_test` ["if" `or_test` "else" `expression`]
    expression: `conditional_expression` | `lambda_expr`
    expression_nocond: `or_test` | `lambda_expr_nocond`
@@ -1710,7 +1710,7 @@
    pair: anonymous; function
    single: : (colon); lambda expression
 
-.. productionlist::
+.. productionlist:: python-grammar
    lambda_expr: "lambda" [`parameter_list`] ":" `expression`
    lambda_expr_nocond: "lambda" [`parameter_list`] ":" `expression_nocond`
 
@@ -1737,7 +1737,7 @@
    pair: expression; list
    single: , (comma); expression list
 
-.. productionlist::
+.. productionlist:: python-grammar
    expression_list: `expression` ("," `expression`)* [","]
    starred_list: `starred_item` ("," `starred_item`)* [","]
    starred_expression: `expression` | (`starred_item` ",")* [`starred_item`]
diff --git a/Doc/reference/introduction.rst b/Doc/reference/introduction.rst
index 62480bd..72e874e 100644
--- a/Doc/reference/introduction.rst
+++ b/Doc/reference/introduction.rst
@@ -93,7 +93,7 @@
 The descriptions of lexical analysis and syntax use a modified BNF grammar
 notation.  This uses the following style of definition:
 
-.. productionlist::
+.. productionlist:: notation
    name: `lc_letter` (`lc_letter` | "_")*
    lc_letter: "a"..."z"
 
diff --git a/Doc/reference/lexical_analysis.rst b/Doc/reference/lexical_analysis.rst
index 19ba83a..77e0578 100644
--- a/Doc/reference/lexical_analysis.rst
+++ b/Doc/reference/lexical_analysis.rst
@@ -296,7 +296,7 @@
 
 Identifiers are unlimited in length.  Case is significant.
 
-.. productionlist::
+.. productionlist:: python-grammar
    identifier: `xid_start` `xid_continue`*
    id_start: <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
    id_continue: <all characters in `id_start`, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
@@ -412,7 +412,7 @@
 
 String literals are described by the following lexical definitions:
 
-.. productionlist::
+.. productionlist:: python-grammar
    stringliteral: [`stringprefix`](`shortstring` | `longstring`)
    stringprefix: "r" | "u" | "R" | "U" | "f" | "F"
                : | "fr" | "Fr" | "fR" | "FR" | "rf" | "rF" | "Rf" | "RF"
@@ -424,7 +424,7 @@
    longstringchar: <any source character except "\">
    stringescapeseq: "\" <any source character>
 
-.. productionlist::
+.. productionlist:: python-grammar
    bytesliteral: `bytesprefix`(`shortbytes` | `longbytes`)
    bytesprefix: "b" | "B" | "br" | "Br" | "bR" | "BR" | "rb" | "rB" | "Rb" | "RB"
    shortbytes: "'" `shortbytesitem`* "'" | '"' `shortbytesitem`* '"'
@@ -659,7 +659,7 @@
 a literal is also marked as a raw string).  After decoding, the grammar
 for the contents of the string is:
 
-.. productionlist::
+.. productionlist:: python-grammar
    f_string: (`literal_char` | "{{" | "}}" | `replacement_field`)*
    replacement_field: "{" `f_expression` ["="] ["!" `conversion`] [":" `format_spec`] "}"
    f_expression: (`conditional_expression` | "*" `or_expr`)
@@ -820,7 +820,7 @@
 
 Integer literals are described by the following lexical definitions:
 
-.. productionlist::
+.. productionlist:: python-grammar
    integer: `decinteger` | `bininteger` | `octinteger` | `hexinteger`
    decinteger: `nonzerodigit` (["_"] `digit`)* | "0"+ (["_"] "0")*
    bininteger: "0" ("b" | "B") (["_"] `bindigit`)+
@@ -864,7 +864,7 @@
 
 Floating point literals are described by the following lexical definitions:
 
-.. productionlist::
+.. productionlist:: python-grammar
    floatnumber: `pointfloat` | `exponentfloat`
    pointfloat: [`digitpart`] `fraction` | `digitpart` "."
    exponentfloat: (`digitpart` | `pointfloat`) `exponent`
@@ -894,7 +894,7 @@
 
 Imaginary literals are described by the following lexical definitions:
 
-.. productionlist::
+.. productionlist:: python-grammar
    imagnumber: (`floatnumber` | `digitpart`) ("j" | "J")
 
 An imaginary literal yields a complex number with a real part of 0.0.  Complex
diff --git a/Doc/reference/simple_stmts.rst b/Doc/reference/simple_stmts.rst
index a8ec0fb..93be327 100644
--- a/Doc/reference/simple_stmts.rst
+++ b/Doc/reference/simple_stmts.rst
@@ -11,7 +11,7 @@
 statements may occur on a single line separated by semicolons.  The syntax for
 simple statements is:
 
-.. productionlist::
+.. productionlist:: python-grammar
    simple_stmt: `expression_stmt`
               : | `assert_stmt`
               : | `assignment_stmt`
@@ -46,7 +46,7 @@
 expression statements are allowed and occasionally useful.  The syntax for an
 expression statement is:
 
-.. productionlist::
+.. productionlist:: python-grammar
    expression_stmt: `starred_expression`
 
 An expression statement evaluates the expression list (which may be a single
@@ -82,7 +82,7 @@
 Assignment statements are used to (re)bind names to values and to modify
 attributes or items of mutable objects:
 
-.. productionlist::
+.. productionlist:: python-grammar
    assignment_stmt: (`target_list` "=")+ (`starred_expression` | `yield_expression`)
    target_list: `target` ("," `target`)* [","]
    target: `identifier`
@@ -280,7 +280,7 @@
 Augmented assignment is the combination, in a single statement, of a binary
 operation and an assignment statement:
 
-.. productionlist::
+.. productionlist:: python-grammar
    augmented_assignment_stmt: `augtarget` `augop` (`expression_list` | `yield_expression`)
    augtarget: `identifier` | `attributeref` | `subscription` | `slicing`
    augop: "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**="
@@ -328,7 +328,7 @@
 :term:`Annotation <variable annotation>` assignment is the combination, in a single
 statement, of a variable or attribute annotation and an optional assignment statement:
 
-.. productionlist::
+.. productionlist:: python-grammar
    annotated_assignment_stmt: `augtarget` ":" `expression`
                             : ["=" (`starred_expression` | `yield_expression`)]
 
@@ -385,7 +385,7 @@
 Assert statements are a convenient way to insert debugging assertions into a
 program:
 
-.. productionlist::
+.. productionlist:: python-grammar
    assert_stmt: "assert" `expression` ["," `expression`]
 
 The simple form, ``assert expression``, is equivalent to ::
@@ -425,7 +425,7 @@
    pair: null; operation
            pair: null; operation
 
-.. productionlist::
+.. productionlist:: python-grammar
    pass_stmt: "pass"
 
 :keyword:`pass` is a null operation --- when it is executed, nothing happens.
@@ -447,7 +447,7 @@
    pair: deletion; target
    triple: deletion; target; list
 
-.. productionlist::
+.. productionlist:: python-grammar
    del_stmt: "del" `target_list`
 
 Deletion is recursively defined very similar to the way assignment is defined.
@@ -486,7 +486,7 @@
    pair: function; definition
    pair: class; definition
 
-.. productionlist::
+.. productionlist:: python-grammar
    return_stmt: "return" [`expression_list`]
 
 :keyword:`return` may only occur syntactically nested in a function definition,
@@ -525,7 +525,7 @@
    single: function; generator
    exception: StopIteration
 
-.. productionlist::
+.. productionlist:: python-grammar
    yield_stmt: `yield_expression`
 
 A :keyword:`yield` statement is semantically equivalent to a :ref:`yield
@@ -560,7 +560,7 @@
    pair: raising; exception
    single: __traceback__ (exception attribute)
 
-.. productionlist::
+.. productionlist:: python-grammar
    raise_stmt: "raise" [`expression` ["from" `expression`]]
 
 If no expressions are present, :keyword:`raise` re-raises the last exception
@@ -663,7 +663,7 @@
    statement: while
    pair: loop; statement
 
-.. productionlist::
+.. productionlist:: python-grammar
    break_stmt: "break"
 
 :keyword:`break` may only occur syntactically nested in a :keyword:`for` or
@@ -698,7 +698,7 @@
    pair: loop; statement
    keyword: finally
 
-.. productionlist::
+.. productionlist:: python-grammar
    continue_stmt: "continue"
 
 :keyword:`continue` may only occur syntactically nested in a :keyword:`for` or
@@ -725,7 +725,7 @@
    exception: ImportError
    single: , (comma); import statement
 
-.. productionlist::
+.. productionlist:: python-grammar
    import_stmt: "import" `module` ["as" `identifier`] ("," `module` ["as" `identifier`])*
               : | "from" `relative_module` "import" `identifier` ["as" `identifier`]
               : ("," `identifier` ["as" `identifier`])*
@@ -859,7 +859,7 @@
 features on a per-module basis before the release in which the feature becomes
 standard.
 
-.. productionlist:: *
+.. productionlist:: python-grammar
    future_stmt: "from" "__future__" "import" `feature` ["as" `identifier`]
               : ("," `feature` ["as" `identifier`])*
               : | "from" "__future__" "import" "(" `feature` ["as" `identifier`]
@@ -937,7 +937,7 @@
    triple: global; name; binding
    single: , (comma); identifier list
 
-.. productionlist::
+.. productionlist:: python-grammar
    global_stmt: "global" `identifier` ("," `identifier`)*
 
 The :keyword:`global` statement is a declaration which holds for the entire
@@ -982,7 +982,7 @@
 .. index:: statement: nonlocal
    single: , (comma); identifier list
 
-.. productionlist::
+.. productionlist:: python-grammar
    nonlocal_stmt: "nonlocal" `identifier` ("," `identifier`)*
 
 .. XXX add when implemented
diff --git a/Doc/reference/toplevel_components.rst b/Doc/reference/toplevel_components.rst
index d5ffb37..319c9de 100644
--- a/Doc/reference/toplevel_components.rst
+++ b/Doc/reference/toplevel_components.rst
@@ -66,7 +66,7 @@
 
 All input read from non-interactive files has the same form:
 
-.. productionlist::
+.. productionlist:: python-grammar
    file_input: (NEWLINE | `statement`)*
 
 This syntax is used in the following situations:
@@ -85,7 +85,7 @@
 
 Input in interactive mode is parsed using the following grammar:
 
-.. productionlist::
+.. productionlist:: python-grammar
    interactive_input: [`stmt_list`] NEWLINE | `compound_stmt` NEWLINE
 
 Note that a (top-level) compound statement must be followed by a blank line in
@@ -103,5 +103,5 @@
 :func:`eval` is used for expression input.  It ignores leading whitespace. The
 string argument to :func:`eval` must have the following form:
 
-.. productionlist::
+.. productionlist:: python-grammar
    eval_input: `expression_list` NEWLINE*